在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧(即同步块进入的地方,这个需要大家理解基于栈的编程的思想)中建立一个名为锁记录(Lock Record)的空间,用于存储 Displaced Mark Word(锁对象目前的Mark Word的拷贝)。这时候线程堆栈与对象头的状态如图:
(上图中的Object就是锁对象。)
拷贝对象头中的Mark Word复制到锁记录中,作为Displaced Mark Word;
拷贝成功后,JVM会通过CAS操作(旧值为Displaced Mark Word,新值为Lock Record Adderss,即当前线程的锁对象地址)将锁对象的Mark Word更新为指向Lock Record的指针(就是修改锁对象的引用变量Mark Word的指向,直接指向锁的竞争者线程的Lock Record的Displaced Mark Word),并将Lock record里的owner指针指向锁对象的Mark Word。如果更新成功,则执行步骤4,否则执行步骤5。
如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图所示。
(上图中的Object就是锁对象。)
如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行(这点是Synchronized为可重入锁的佐证,起码说明在轻量级锁状态下,Synchronized锁为可重入锁。)。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁(其实是CAS自旋失败一定次数后,才进行锁升级),锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
适用的场景为线程交替执行同步块的场景。
那么轻量级锁在什么情况下会升级为重量级锁呢?
重量级锁:重量级锁是由轻量级锁升级而来的。那么升级的方式有两个。
第一,线程1与线程2拷贝了锁对象的Mark Word,然后通过CAS抢锁,其中一个线程(如线程1)抢锁成功,另一个线程只有不断自旋,等待线程1释放锁。自旋达到一定次数(即等待时间较长)后,轻量级锁将会升级为重量级锁。
第二,如果线程1拷贝了锁对象的Mark Word,并通过CAS将锁对象的Mark Word修改为了线程1的Lock Record Adderss。这时候线程2过来后,将无法直接进行Mark Word的拷贝工作,那么轻量级锁将会升级为重量级锁。
无论是同步方法,还是同步代码块,无论是ACC_SYNCHRONIZED(类的同步指令,可通过javap反汇编查看)还是monitorenter,monitorexit(这两个用于实现同步代码块)都是基于Monitor实现的。
所以,要想继续在JVM层次学习重量级锁,我们需要先学习一些概念,如Monitor。
Monitor互斥同步时一种常见的并发保障手段。
同步:确保同一时刻共享数据被一个线程(也可以通过信号量实现多个线程)使用。
互斥:实现同步的一种手段
关系:互斥是因,同步是果。互斥是方法,同步是目的
主要的互斥实现手段有临界区(Critical Section),互斥量(Mutex),信号量(Semaphore)(信号量又可以分为二进制,整型,记录型。这里不再深入)。其中后两者属于同步原语。
在Mutex和Semaphore基础上,提出更高层次的同步原语Monitor。操作系统不支持Monitor机制,部分语言(如Java)支持Monitor机制。
这里贴上作者的一页笔记,帮助大家更好理解(主要图片展示效果,比文字好)。
(请不要在意字迹问题,以后一定改正)