轻量级锁与偏向锁

锁级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁。只能升级不能降级。

锁级别越高,获得锁和释放锁就越耗费性能。

轻量级

轻量级锁是一种多线程优化,通过 CAS 来避免进入开销较大的互斥操作。

有线程竞争锁就会导致轻量级锁膨胀为重量级锁。

【加锁】

线程访问同步块,JVM 会先在当前线程的栈桢中创建一个空间用于存储锁记录,并将对象头中的 Mark Word 复制于此。

然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针。

如果成功,当前线程获得锁,对象头中锁标志位设置为00。

如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁,自旋一定次数后若还是失败,就会膨胀为重量级锁,因为重量级锁的互斥性,线程阻塞。

PS:自旋虽然避免了线程切换的开销,但会消耗 CPU,因此要控制次数。

【解锁】

CAS 操作来将 Mark Word 从锁记录替换回到对象头。

如果成功,则表示没有竞争发生。

如果失败,表示当前锁存在竞争,锁已膨胀成重量级锁,那就要在释放锁的同时,唤醒被挂起的线程。

【轻量级锁及膨胀流程图】

偏向锁

偏向锁只能在单线程下起作用。

偏向锁,偏向于第一个访问锁的线程,如果接下来该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。

本质上偏向锁就是为了消除 CAS,使获得锁的代价更低。

【加锁】

当一个线程访问同步块时,会在对象头中存储锁偏向的线程 ID(通过最开始的一次CAS),以后这个线程在进入这个同步块时就不需要进行 CAS 操作来加锁和解锁,只需要简单测试 Mark Word 中是否存储着指向当前线程的偏向锁。

如果测试成功,则说明线程已经获取了锁。

如果测试失败,则需再测试 Mark Word 中偏向锁的标识是否设置为 1(是否还是偏向锁)。

如果没有设置,表示当前可能已经升级为轻量锁或重量锁了,则使用 CAS 竞争锁。

如果设置了,表示当前还处于偏向锁层次只是此时没有线程占有锁,尝试使用 CAS 将对象头的偏向锁指向该线程(释放锁时,会将 Mark Word 中线程 ID 的这个位置置空,以便其他线程获取该偏向锁)。

【解锁(锁撤销)】

线程不会主动去释放偏向锁:只有其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

遇到其他线程竞争锁,持有偏向锁的线程会被挂起,若持有偏向锁的线程处于同步块内,将锁升级为轻量级锁,若持有偏向锁的线程已退出同步块,将锁置为无锁状态,最后唤醒暂停的线程。

【偏向锁的获得和撤销流程】

锁的优缺点对比 锁 优点 缺点 适用场景
偏向锁   加锁解锁不需要额外消耗   如果线程间存在锁竞争,会带来额外的锁撤销消耗   适用于单线程  
轻量级锁   竞争的线程不会阻塞,提高了程序的相应速度   如果始终得不到锁竞争的线程,使用自旋会消耗 CPU   追求相应时间,同步块执行速度非常快  
重量级锁   线程竞争不使用自旋,不会消耗 CPU   线程阻塞,响应时间缓慢   追求吞吐量,同步块执行速度较长  

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zydzjf.html