Optimus_7

博客园 首页 新随笔 联系 订阅 管理

  1. Synchronized的各种使用方式 以及 锁的都是什么:

        Synchronized方法,且为非静态方法:锁的是当前类的实例对象(this)。

        Synchronized方法,但是为静态方法:锁的是当前类对象。

        Synchronized块(Object):锁的是Object对象。

        Synchronized块(this):锁的是当前类的实例对象。

        Synchronized块(类.class):锁的是当前类对象。

  2. 从字节码看Synchronized的实现原理:monitor

    2.1 synchronized方法经过编译生成的字节码:

      可以看到synchronized方法的flags属性为ACC_SYNCHRONIZED

synchronized void hy () {

}

 

synchronized void hy();
    descriptor: ()V
    flags: ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   LTest27JavapSycnhronized;

 

    2.2 synchronized块经过编译生成的字节码:

      可以看到synchronized块所在的方法的Code区多了一对monitorenter和monitorexit命令:

 void hy6 (Hy h) {
        synchronized (h) {
            doSomething();
    }

 

 1  void hy6(Hy);
 2     descriptor: (LHy;)V
 3     flags:
 4     Code:
 5      stack=2, locals=4, args_size=2
 6      0: aload_1                //将对象h从局部变量表入操作数栈(局部变量表的_1=对象h)
 8      1: dup                    // 将栈顶的对象h复制并再入栈
 9      2: astore_2                // 将对象h(新复制来的)加载入局部变量表,意味这是新元素(栈中的新h ——>成为 局部变量表的_2)
12     3: monitorenter            // 锁住当前栈顶元素(即老对象h)的monitor,
13     4: aload_0                 // 将局部变量表中的this入栈,目的是下一步执行dosomething方法时可以从栈顶取到this,因为this是dosomething方法 的入参。(局部变量表的_0=this18     5: invokevirtual #4         // 调用常量池的#4 ,即dosomething方法
19     8: aload_2                 //  将局部变量表中的新h入栈(局部变量表的_2=新h)
21     9: monitorexit            // 退出(即老对象h)的monitor,
22     10: goto          18       //  既然已经退出,说明是正常结束,取到#18行return掉。
24     13: astore_3            // 走到这里13行说明已经是异常了,因为查看异常表 Exception table得知,4~10发生的异常会走到13行(栈中的异常对象 ——>成为 局部变量表的_3)
28    14: aload_2                //  将局部变量表中的新h入栈(局部变量表的_2=新h)
30    15: monitorexit            // 退出(即老对象h)的monitor,
31    16: aload_3                // 将局部变量表中的异常对象入栈(局部变量表的_3=异常对象)
33    17: athrow                // 抛出异常
34     18: return                // 返回
35 
36  Exception table:
37          from    to    target   type
38              4     10     13       any
39             13    16     13       any                                                                                                         

 

    2.3 总结一下

        分别写一个Synchronized方法和Synchronized块,观察javap反编译后的字节码:

          Synchronized方法(不论静态非静态)利用的机制是:

            在所在方法的flags属性会多了一个参数:ACC_SYNCHRONIZED。

          Synchronized块(锁的不论对象还是class)利用的机制是:

            在所在方法的Code域中多了两个动作:monitorenter和monitorexit。

    2.4 ACC_SYNCHRONIZED 和 monitorenter&monitorexit 有什么区别:

          其实两者的底层实现是一样的,只不过ACC_SYNCHRONIZED是隐式的。

          1. ACC_SYNCHRONIZED:

            当虚拟机执行该方法时,看到该方法的flags之一是ACC_SYNCHRONIZED,

            就会要求当前线程尝试获取当前操作数栈顶元素的monitor(如果flags还有ACC_STATIC,就获取当前调用者类对象的monitor),然后才能执行方法。

          2. monitorenter&monitorexit:

            执行monitorenter字节码指令时,当前线程尝试获取当前操作数栈顶元素的monitor,

            执行monitorexit字节码指令时,退出那个元素的monitor。

  3. Monitor

    3.1 Monitor简介

        每个对象有一个monitor监视器。

          monitor可以实现Synchronized、wait / notify。

          这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

        执行monitorenter时,当前线程尝试获取当前操作数栈顶元素的monitor:

          1、如果发现此时monitor的进入数(_count字段)为0,则该线程成功进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

          2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

          3、如果发现此时其他线程已经占用了monitor,则该线程进入阻塞状态。

        执行monitorexit时,当前线程尝试退出那个元素的monitor,(前提是当前线程必须是那个元素的monitor所有者)

          1、monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。

            其他被这个monitor阻塞的线程(monitor的_EntryList字段)可以尝试去获取这个 monitor 的所有权。

    3.2 Monitor详细介绍

          ObjectMonitor中有几个关键属性:

            _owner:指向持有ObjectMonitor对象的线程

            _recursions:锁的重入次数

            _count:用来记录该线程获取锁的次数

            _WaitSet:如果一个线程在同步块中调用了wait方法,会将该线程加入到WaitSet中,然后释放锁。当wait的线程被notify之后,会将对应的线程从WaitSet移动到EntryList中

            _cxq(ContentionList):当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程插入到cxq队列的队首

            _EntryList:当持有锁的线程释放锁前,会将cxq中的所有元素移动到EntryList中去,并唤醒EntryList的队首线程

       3.2.1 以Synchronized场景举例说明monitor的线程阻塞顺序和策略

          当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程插入到cxq队列的队首

       3.2.2 以Synchronized场景举例说明monitor的线程唤醒顺序和策略

          当一个线程执行到monitorexit时,会执行唤醒策略:

            如果QMode = 2的操作最特殊:取_cxq队列首元素唤醒;

            如果QMode = 3,把_cxq队列的首元素放入_EntryList尾部(相当于顺序),然后执行步骤5;

            如果QMode = 4,把_cxq队列的首元素放入_EntryList头部(相当于倒序),然后执行步骤5;

            如果QMode = 0(默认),不做什么,执行步骤下一步:

              如果_EntryList非空,就取首元素+调用ExitEpilog方法,该方法会唤醒该元素对应的线程,然后立即返回;

              如果_EntryList为空,会将cxq中的所有元素移动到EntryList中去+调用ExitEpilog方法,唤醒EntryList的队首线程,该方法会唤醒该元素对应的线程,然后立即返回;

          假设线程A、B、C的执行顺序为 A->B->C,并且A线程首先抢到了锁:

          因为锁被A线程占用,则会将B、C线程插入到cxq队列中,由于每次都是插入的队首,所以cxq队列中的元素顺序为C->B,

          又因为EntryList为空,所以当A线程执行完毕后,则会取出C线程进行执行。

       3.2.3 monitor实现wait/notify应该和实现Synchronized的方式差不多,只不过cxq队列换成_WaitSet队列而已

          略。

  

 

posted on 2020-08-12 00:29  Optimus_7  阅读(148)  评论(0编辑  收藏  举报