美文网首页codeER.tec
深入理解synchronized

深入理解synchronized

作者: 贪挽懒月 | 来源:发表于2021-06-17 17:52 被阅读0次

    面试官:请谈谈你对synchronized的理解。

    小白:这是一个java的关键字,用来控制并发的,被它锁住的代码同一时刻只能有一个线程访问。

    面试官:还有吗?

    小白:没有了……

    面试官:那你先回去等通知吧!


    synchronized,相信学过java的都知道它,但是面试一被问到这个,又总是答不出多少东西来。下面我就将synchronized的知识点列举出来,深入理解(要深入它,才能征服它)。


    1. 用来干嘛的?

    这是一个同步关键字,保证同一时刻只能有一个线程执行被其修饰的方法或代码块,可以保证线程安全。

    2. 怎么用呢?

    这是一个关键字,可以用来修饰静态方法、实例方法、代码块。注意这里的代码块不是类中的静态代码块和构造代码块,而是方法中的代码块。

    • 修饰静态方法:
      介绍它修饰静态方法之前,先来回忆一下静态方法的特点。静态是该类所有实例共享的,JVM加载该类时就会对其进行初始化,因为不属于任何一个实例,所以静态方法里面不能用this关键字。如果synchronized修饰静态方法,那么锁对象是啥呢?首先排除this,因为调用静态方法的时候可能该类都还没有实例。所以修饰静态方法的时候,锁对象其实是当前class。
    // 静态方法
    public synchronized static void staticFun(){
        System.out.println("synchronized修饰静态方法,锁对象是当前class");
        // 业务代码……
    }
    
    • 修饰实例方法:
      既然都说了是实例方法,那么锁对象就是当前类的实例。
    // 实例方法
    public synchronized void instanceFun(){
        System.out.println("synchronized修饰实例方法,锁对象是类实例");
        // 业务代码……
    }
    
    • 修饰代码块:
      修饰代码块,锁对象可以是class,也可以是给定的对象。如果是class,那就是不管该类new几个实例,都是属于这个类的,都会被锁住;如果是对象,那么不同对象去访问时是可以获取到锁的,所以class作为锁其实粒度更粗。
    public void fun(){
        synchronized (TestSync.class){ // 锁对象是当前class
    //    synchronized (this){ // 锁对象是实例
            System.out.println("synchronized修饰代码块,锁对象可以是实例,可以是类");
        }
    }
    

    3. 线程A调用类的同步实例方法,线程B可以同时调用类的同步静态方法吗?为什么?

    我们先用代码看结果,再解释为什么。

     // 静态方法
     public synchronized static void staticFun(){
         System.out.println("synchronized修饰静态方法,锁对象是当前class");
         System.out.println(Thread.currentThread().getName() + "进入同步静态方法");
         System.out.println(Thread.currentThread().getName() + "执行结束");
     }
    
     // 实例方法
     public synchronized void instanceFun(){
         System.out.println("synchronized修饰实例方法,锁对象是类实例");
         System.out.println(Thread.currentThread().getName() + "进入同步实例方法");
         try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
         System.out.println(Thread.currentThread().getName() + "执行结束");
     }
    
     public static void main(String[] args){
         TestSync testSync = new TestSync();
         new Thread(() -> {
             testSync.instanceFun();
         }, "线程A").start();
    
         new Thread(() -> {
             staticFun();
         }, "线程B").start();
    
         new Thread(() -> {
             testSync.instanceFun();
         }, "线程C").start();
     }
    

    运行结果:

    运行结果

    上面的代码,线程A调用实例方法,并且进入方法后线程睡了5秒钟;线程B调用静态方法,还没等线程A结束,线程B已经执行结束了,线程B不需要等线程A释放锁也可以执行。而线程C,因为是同一个对象去调用的同步实例方法,所以得等线程A释放了锁,线程C才能拿到执行权。假如线程C是另外再new一个对象去调用的,那么也不需要等待线程A释放锁。

    从结果可以得出答案:线程A调用类的同步实例方法,线程B可以同时调用类的同步静态方法。原因就是同步实例方法的锁是对象锁,而同步静态方法的锁是类锁,锁对象不同,所以可以同时调用

    4. 可以用String字符串来做锁对象吗?

    可以,但没必要。代码块的锁对象其实可以是任意对象,不过一般都用class或者this,并不建议用string做锁对象,因为用string很容易造成死锁。为什么容易造成死锁呢?因为JVM中有个常量池,比如你定义两个字符串:

    String str1 = "haha";
    String str2 = "haha";
    

    这里明明是两个字符串,但其实是同一个对象,因为这样赋值的String,首先会看常量池中有没有,没有就往常量池中添加一个,并指向它,有的话,就直接指向。所以str1和str2都是指向常量池中同一个对象。

    5. synchronized可以修饰构造方法吗?为什么?

    不能修饰构造方法,构造方法只能有权限修饰符,比如public、private之类的,它本身就是线程安全的。

    6. jdk1.6开始对synchronized做了哪些优化?

    jdk1.6之前,synchronized是很重的锁,jdk1.6开始,做了大量的优化,比如用偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销(当你这么回答的时候,估计面试官紧接着就问什么是偏向锁、自旋锁……有什么区别,这些后续再详细地说)。

    7. 你知道synchronized的底层原理吗?

    • 同步代码块:方法里面的同步代码块,synchronized底层是通过监视器monitor来实现的。通过指令
    javap -c -s -v -l Xxx.class
    

    可以发现monitorenter指令指向同步代码块开始的位置,同时会尝试获取锁,锁的计数器为0表示可以获取锁,获取后计数器变为1;monitorexit指令指向同步代码块结束的位置,同时释放锁,将锁的计数器置为0。所以获取锁就是获取Monitor的执行权。Monitor是基于C++,由ObjectMonitor实现的,每个对象都内置了ObjectMonitor。另外,wait/notify方法也是基于monitor来实现的。

    • 同步方法:执行上述的javap指令查看同步方法,可以发现并没有monitorenter和monitorexit指令,但是在方法开头有个名为ACC_SYNCHRONIZED的flag标识,同步方法就是通过这个标识来控制同步操作的。

    8. synchronized和ReentrantLock有何异同?

    相同点:

    • 两者都是可重入锁;

    • 都可实现选择性通知;

    不同点:

    • synchronized是JVM层面的,ReentrantLock是API层面的;

    • synchronized是非公平锁,ReentrantLock可以指定为公平锁或者非公平锁;

    • synchronized无需手动释放锁,ReentrantLock需要手动释放锁;

    • synchronized等待不能中断,ReentrantLock等待可通过lock.lockInterruptibly()中断等待;

    相关文章

      网友评论

        本文标题:深入理解synchronized

        本文链接:https://www.haomeiwen.com/subject/uhizcltx.html