锁优化
概述
本文主要论述jvm的各种锁优化技术,主要分为适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁。这些技术都是为了在线程之间更高效共享数据,解决竞争问题,提高并发效率。、
自旋锁和适应性自旋
互斥同步对性能最大的影响是阻塞的实现,因为挂起线程和恢复线程都需要转入内核态完成,所以对系统的并发性能带了很大压力。
产生原因
在很多应用中,共享数据的锁定状态只会持续很短时间,所以为了这段时间挂起线程不值得。
实现方式
假设一个物理机有一个以上的处理器,让两条以上线程并发执行,后面请求锁的线程等待一下,不放弃cpu处理时间(让线程执行一个忙循环),看持有锁的线程是否很快释放锁。
自旋时间:在jdk1.6前面的版本默认自旋次数是10次(可以通过-XX:PreBlockSpin设定),jdk1.6引入了自适应的自旋锁,自旋的时间不再固定,由前一次在同一个锁上的自旋时间和锁的拥有者状态决定。如果在同一个锁上,自旋等待刚刚成功过,并且持有锁的线程正在运行,认为这次自旋可能成功,允许更长的自选时间;如果某个锁,自旋很少获得通过,则省略自旋等待。
缺点
自旋锁适用于锁被占用的时间比较短的场景,如果锁占用的时间很长,自旋的线程就会浪费处理器的资源,带来性能的浪费
锁消除
锁消除是虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
实现方式
使用逃逸分析的数据支持,堆上的所有数据都不会逃逸出去被其他线程访问,就把它们当成栈上数据对待,同步加锁无需进行。
锁粗化
虚拟机检测到一连串零碎的操作都对同一个对象加锁,将会将加锁的范围扩大到整个操作序列外部。
轻量级锁
“轻量级”是相对与使用系统互斥量实现的传统锁而言,传统锁机制称为“重量级”锁。
基础知识-markword
markword是java对象头信息,在32位和64位虚拟机分别为32bit和64bit
对象头.png
markword.png
实现方式
获得锁
1.在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图:
步骤一.png
2.拷贝对象头中的Mark Word复制到锁记录中
3.拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。
4.如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图所示。
步骤4.png
5.如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
释放锁
释放锁线程视角
由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。
因为重量级锁被修改了,所有display mark word和原来的markword不一样了。
怎么补救,就是进入mutex前,compare一下obj的markword状态。确认该markword是否被其他线程持有。
此时如果线程已经释放了markword,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。
尝试获取锁线程视角
如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改markword,修改重量级锁,表示该进入重量锁了。
还有一个注意点:等待轻量锁的线程不会阻塞,它会一直自旋等待锁,并如上所说修改markword。
这就是自旋锁,尝试获取锁的线程,在没有获得锁的时候,不被挂起,而转而去执行一个空循环,即自旋。在若干个自旋后,如果还没有获得锁,则才被挂起,获得锁,则执行代码。
偏向锁
产生原因
消除数据在无竞争状态下的同步原语,提高性能。
实现方式
当锁对象第一次被线程获取时,虚拟机将对象头的标志位设为“01”,同时CAS把获得锁的线程id记录在对象Mark Word中,成功后持有偏向锁的线程进入这个锁同步块,虚拟机不进行同步操作。但是当另外线程尝试获得锁时,偏向锁会升级为轻量级锁。
总结
偏向锁、轻量级锁的状态转换.pngsynchronized的执行过程:
- 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
- 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
- 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
- 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
- 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
- 如果自旋成功则依然处于轻量级状态。
- 如果自旋失败,则升级为重量级锁。
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗轻量级锁 | 适用于只有一个线程访问同步块场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度。 | 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 | 追求响应时间。同步块执行速度非常快。 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU。 | 线程阻塞,响应时间缓慢。 | 追求吞吐量。同步块执行速度较长 |