JVM锁优化

synchronized重量级锁

synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK 1.6 后对synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用,如适应性自旋,锁清除,锁粗化,轻量级锁,偏向锁。


自旋锁

首先是一种锁,与互斥锁相似,基本作用是用于线程(进程)之间的同步。与普通锁不同的是,一个线程A在获得普通锁后,如果再有线程B试图获取锁,那么这个线程B将会挂起(阻塞);试想下,如果两个线程资源竞争不是特别激烈,而处理器阻塞一个线程引起的线程上下文的切换的代价高于等待资源的代价的时候(锁的已保持者保持锁时间比较短),那么线程B可以不放弃CPU时间片,而是在“原地”忙等,直到锁的持有者释放了该锁,这就是自旋锁的原理,可见自旋锁是一种非阻塞锁(JDK 1.6 中是默认开启的)。用户可以通过参数更改:-XX:-UseSpinning来关闭。

自旋锁可能引起的问题:自旋锁虽然避免了线程之间切换的开销,但它是要占CPU时间的,因此锁被占用的时间越短,自旋等待的效果就越好,反之,锁被占用的时间很长,那么自旋的线程只会白白消耗CPU资源。因此自旋等待的时间必须有一个限度,如果超过这个限度仍然没有获得锁,就应当按照传统方式挂起线程。自旋的次数默认是10,用户可以通过参数更改:-XX:PreBlockSpin=11。

在JDK 1.6 之后引入了自适应的自旋锁。自适应意味着自旋的时间不在固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定的。


锁清除,锁粗化

StringBuffer 中的append方法是被synchronized方法修饰的,所以存在锁。

JVM锁优化_第1张图片
锁清除:由于sb是局部变量,所以不会产生线程安全问题,锁可以被清除


JVM锁优化_第2张图片
锁粗化:sb由于多次append,所以锁可以扩展到第一个append()操作到最后一个append()操作,只需要加一次锁




JVM锁优化_第3张图片
Mark Word

轻量级锁

轻量级是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

加锁:

1) 在代码进入同步块的时候,如果此对象没有被锁定(锁标志位为“01”状态,是否为偏向锁为“0”),JVM首先在当前线程的栈帧建立一个名为锁记录的(Lock Record)的空间,用于存储对象当前的Mark Word(官方称之为 Displaced Mark Word)。

JVM锁优化_第4张图片

2 )  JVM使用CAS操作尝试将对象的Mark Word 更新为指向Lock Record 的指针。

如果这个操作成功,那么这个线程就拥有了该对象的锁,并且将对象的Mark Word 的锁标志位转变为"00",即表示该对象处于轻量级锁状态。


JVM锁优化_第5张图片

如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

解锁:

1)通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。

2)如果替换成功,整个同步过程就完成了。

3)如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。


偏向锁

偏向锁的“偏”,就是偏心的“偏”,它的意思就是这个锁会偏向于第一个获取它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程永远不需要再进行同步。

引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换Thread ID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为01)或者轻量级锁(标志位为00)的状态,后续的同步操作就如轻量级锁那样执行。转换图:

JVM锁优化_第6张图片

参考:

http://blog.csdn.net/truong/article/details/74942155

http://blog.csdn.net/truong/article/details/74941345

http://blog.csdn.net/hsuxu/article/details/9472389

http://blog.csdn.net/hsuxu/article/details/9472381

你可能感兴趣的:(JVM锁优化)