Synchronized原理
目录
一.Synchronized锁的状态
二.偏向锁
1.加锁过程
2.解锁过程
三.轻量级锁
1.加锁过程
2.解锁过程
四.重量级锁
五.总结
一.Synchronized锁的状态
四种锁状态:
1.无锁
2.偏向锁
3.轻量级锁
4.重量级锁
锁的级别由低到高分别是:无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态。这几个状态会随着锁的竞争情况逐渐升级(只能升级,不能降级),也叫锁的膨胀。
二.偏向锁
作用:减少同一线程获取锁的代价
偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。
1.加锁过程
当锁对象第一次被线程获取的时候,虚拟机把对象头中的锁标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,直接检查markword中的ThreadId是否和 自身线程Id一致,如果一致,则认为当前线程已经获取了锁,JVM就可以不再进行任何同步操作。
当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定状态或 轻量级锁定(标志位为“00”)的状态。
2.解锁过程
一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象是偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象(锁)的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象(锁)回复成无锁状态,然后重新偏向。
三.轻量级锁
当有别的线程参与到偏向锁的竞争中时,会先判断 markword 中的线程ID与这个线程是否一致,如果不一致,则会立即撤销偏向锁,升级为轻量级锁。
每个线程都会在自己的栈中维护一个 LockRecord(LR),然后每个线程在竞争锁时,都试图将 锁对象头 中的markword 设置为指向自己LR的指针,哪个线程设置成功,则意味着哪个线程成功获取到锁。
1.加锁过程
在代码进入同步块前,如果该同步块没有被锁定(即锁的标志位为“01”),那么JVM在将当前线程的栈帧中,创建一个 LockRecord(锁记录LR),并将锁对象头中的 markWord 信息复制到锁记录(LR)中,这个官方称为 Displaced Mard Word。
然后线程尝试使用 CAS 将对象头中的 MarkWord 替换为指向锁记录(LR)的指针。
如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为“00”,即表示此对象处于轻量级锁定状态。
2.解锁过程
四.重量级锁
五.总结
由于轻量级锁会自旋,即不会放弃CPU,那么对一些执行时间短的任务而言,用轻量级锁可以减少线程切换的时间(对比重量级锁)。
(1)偏向锁通过对比 Mark Word 解决加锁问题,避免执行CAS操作。
(2)轻量级锁是通过用 CAS 操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。
(3)重量级锁是将除了拥有锁的线程以外的线程都阻塞。