【Java并发】偏向锁、轻量级锁、重量级锁的区别

Synchronized一直是多线程并发编程中的重要角色,但是在Java1.6中,为了减少获得锁带来的性能消耗,引入了偏向锁和轻量级锁。

目录

锁的状态:

偏向锁

轻量级锁

重量级锁

偏向锁、轻量级锁、重量级锁应用场景


锁的状态:

  • 无锁状态
  • 偏向锁状态
  • 轻量级锁状态
  • 重量级锁状态

四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

要注意的是,这四种状态都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。 
首先通过一个小例子来解释一下三种锁的区别:

假如家里只有一个碗,当我自己在家时,没有人会和我争碗,这时即为偏向锁状态 
当我和女朋友都在家吃饭时,如果女朋友不是很饿,则她会等我吃完再用我的碗去吃饭,这就是轻量级锁状态 
当我和女朋友都很饿的时候,这时候就会去争抢这唯一的一个碗(贫穷的我)吃饭,这就是重量级锁状态

偏向锁

偏向锁的使用原因与作用: 
大部分时候,并不是存在多个多个线程的竞争,而是单个线程多次获得,为了更少的代价所以引入偏向锁,偏向锁被获得后会记录访问的线程ID,如果下一次还是这个线程申请获得,则直接执行代码块,省去了大量的加锁解锁时间。

偏向锁获取过程:

  1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。 
    (Mark Word介绍)

  2. 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。

  3. 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark 
    Word中线程ID设置为当前线程ID,然后执行5;如果竞争失败,执行4。

  4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起, 偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。(撤销偏向锁的时候会导致stop the word)

  5. 执行同步代码。

CAS介绍

 

释放偏向锁的过程: 
1. 当其他线程尝试竞争偏向锁时,就会释放锁,锁的撤销,需要等待全局安全点,分为以下几个步骤: 
2. 暂停拥有偏向锁的线程,检查线程是否存活 
3. 处于非活动状态,则设置为无锁状态 
4. 存活,则重新偏向于其他线程或者恢复到无锁状态或者标记对象不适合作为偏向锁唤醒线程

偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

偏向锁的适用场景: 
始终只有一个线程在执行同步块,在它没有执行完释放锁之前,没有其它线程去执行同步块,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the world操作; 
在有锁的竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用;

轻量级锁

轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

轻量级锁的加锁过程: 
在代码进入同步块的时候,JVM首先将在当前线程的栈帧中建立一个用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称之为 Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,则当前线程获得锁,如果失败,表明其他线程获得竞争锁,当前线程开始自旋。 
(自旋介绍)

轻量级锁的释放过程: 
释放锁时,先使用CAS操作将Dispalyedlaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。 
如果失败,表示当前锁存在竞争,锁就会膨胀为重量级锁。

重量级锁

重量级锁特点:

其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程,进行竞争。

偏向锁、轻量级锁、重量级锁应用场景

  • 偏向锁:只有一个线程进入临界区;
  • 轻量级锁:多个线程交替进入临界区
  • 重量级锁:多个线程同时进入临界区。

对于偏向锁的升级场景,我们可以理解为,临界区A只有线程1在执行,线程2进入后若发现偏向锁偏向线程1,但是线程1不存在了或者不再使用该对象锁了,则线程2将自己的线程ID写上去,获得该偏向锁,然后执行,此时该锁仍为偏向锁。若发现偏向锁偏向线程1,但是线程1存在并且还在使用该对象锁,说明开始存在两个线程的竞争了,此时,暂停线程1,然后该锁升级为轻量级锁,线程1继续执行,线程2同时开始自旋。

对于轻量级锁的升级场景,我们可以理解为,临界区A只有线程1在执行,线程2进入后发现线程1在执行,线程2开始自旋等待,若自旋很久达到一个阈值后还没有得到锁,则升级为重量级锁。

(以上观点为个人总结分析得到,有可能存在不对的地方,请指正!)

 

参考资料:《Java并发编程的艺术》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章