当前位置 博文首页 > 是琳琳呀!的博客:面试中常问的锁你都知道吗?

    是琳琳呀!的博客:面试中常问的锁你都知道吗?

    作者:[db:作者] 时间:2021-08-16 10:01

    (1)乐观锁VS悲观锁
    1》乐观锁

    他认为一般情况下不会出现问题,所有他在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有则会返回失败信息给用户自行处理。
    乐观锁的经典事项:CAS(Compare And Swap)对比并且替换

    • 1)CAS实现

    (V【内存中的值】,A【预期的旧值】,B【新值】)
    每次把V和A进行对比,如果V==A则将V修改为B,如果不相等,则不修改。进行自旋对比和替换。

    • 2)CAS实现原理是什么?
    public final native boolean compareAndSwapObject(Object var1,long var2,Object var3)
    

    CAS在java中是通过UnSafe实现,UnSafe是本地类和本地方法,它是C/C++实现的原生方法,通过调用操作系统的Atomic::cmpxchg(原子指令)来实现的。

    • 3)CAS的应用:Atomic*
    private  static AtomicInteger count = new AtomicInteger(0);
    
    • 4)在多线程中实现i++,i–保证线程安全的方法:

    1.加锁
    2.ThreadLocal
    3.AtomicInteger

    • 5)CAS(乐观锁)存在的问题:ABA问题
    • ABA问题:

    (1)在我给别人转账100元的情况下点击了两次提交的按钮(V=100,A=100,B=0)
    第一次提交时V=100,A=100,V=A则V=B=0; 第二次提交时V=0,A=100 ,V!=A,停止执行,这种情况下是正常的。
    (2)但是当我在第二次提交的时候,有人在我第二次提交前给我也转了100元。 第一次提交V=100,A=100,V=A则V=B=0;
    在中间是别人给我转钱V=100 第二次提交V=100,A=100,则V==A,V=B=0,此时又进行了一次转账。
    这个就是ABA问题在经历了一段时间一定事件后A又变成A。

    • 6)如何解决ABA问题?

    使用版本号(AtomicStampedReference),每次修改的时候判断预期的旧值和版本号,每次成功修改之后更换版本号,这样即使预期的值和V值相等,但是因为版本号不同,也不能进行修改,从而解决了ABA问题。

    private  static AtomicStampedReference money = new AtomicStampedReference(100,1);
        public static void main(String[] args) throws InterruptedException {
            //两次转账
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                   boolean resule1= money.compareAndSet(100,0,1,2);//旧值,新值,旧版本号,新版本号
                    System.out.println(resule1);//true
                }
            });
            thread.start();
            //增加100
            Thread thread3= new Thread(new Runnable() {
                @Override
                public void run() {
                    boolean resule2= money.compareAndSet(0,100,2,3);
                    System.out.println(resule2);//true
                }
            });
            thread3.start();
            thread.join();
            thread3.join();
            Thread thread2= new Thread(new Runnable() {
                @Override
                public void run() {
                    boolean resule2= money.compareAndSet(100,0,1,2);
                    System.out.println(resule2);//false
                }
            });
            thread2.start();
            thread2.join();
    
        }
    

    注意事项:AtomicReference有ABA问题AtomicStampedReference是解决ABA问题的.AtomicStampedReference它对比里面的旧值对比的是引用。
    这里就有一个高速缓存机制,当取的值是范围里面的时,会直接按值对比,超过此范围就会对比地址。Integer(-128–127)

    2》悲观锁

    悲观锁是认为只要执行多线程就会出现问题,所以在进入方法后都会进行加锁。 悲观锁的实现就是synchronize。

    2)公平锁VS非公平锁:

    公平锁:1;一个线程释放锁,2;主动唤醒需要得到锁的线程。
    非公平锁:抢占式执行,当一个线程释放锁,另一个线程刚好执行到获取锁的代码就可以获取到锁。 使用new
    ReentrantLock(true)来设置公平锁,默认是false,非公平锁,synchronize是非公平锁

    3)独占锁VS共享锁

    独占锁:指的是这一把锁只能被这一个线程拥有。(synchronize)
    共享锁:指的是这把锁可以被多个线程同时拥有。(ReadWriteLock中的读锁就是共享锁)将锁的粒度更加细化,从而提高锁的性能。

    (4)可重入锁

    一个线程在拥有了一把锁后,可以重复的进入(synchronize,ReentrantLock)

    private  static  Object object = new Object();
    
        public static void main(String[] args) {
            synchronized (object){
                System.out.println("进入主方法");
                synchronized (object){
                    System.out.println("重复进入方法");
                }
            }
        }
    

    5)自旋锁

    相当于死循环,一直循环尝试获取锁(synchronize)

    6)偏向锁

    在线程初次访问的时候,将线程的ID放入对象头偏向锁ID的字段中,每次线程访问的时候先判断线程的ID是否等于对象投中的ID,如果相等则说明这个线程用于此锁就可以正常执行代码,否则表明线程不拥有此锁,只能通过自旋的方式尝试

    cs