锁升级过程就是锁优化
在JDK最开始的时候synchronized
属于重量级的锁
,每次加锁都是通过操作系统
来申请锁
,所以会造成synchronized
的效率比较低,尤其是随着时代的发展,多线程高并发越来越多,synchronized效率低的缺点就越来越明显,所以jdk对它进行了优化,不再是一开始就向操作系统申请锁
,分成偏向锁 - 轻量级锁 - 重量级锁三个过程
。
要讲锁升级首先回顾一个知识点,synchronized
实际上是对对象加锁
的过程(锁一段代码则需指定对象,如果锁方法,普通方法锁的是方法的对象,静态方法则是这个方法所在类的calss对象),在对象头
的mark word
中最低的三位
代表锁状态
,其中1位是偏向锁位
,两位是普通锁位
,具体如下图:
这次主要关注mark word的后三位的变化,根据变化我们可以得出实际上对象的锁状态可以分成无锁、偏向锁、轻量级锁、重量级锁4个状态
,GC过程中有对对象的锁降级
。
那么接下来就看看锁是如何升级的,首先最开始对象是无锁状态
,当一个线程准备对这个对象加锁前验证这三个字节发现了无锁状态,把对象是否偏向设置为1,锁标志位还是01,并把markword
的线程ID改为当前线程ID,此时对象处于偏向锁状态
。
一个线程继续
对该该对象加锁,发现是偏向锁状态,判断偏向锁线程是否是这个线程
,如果是则是直接进入,如果偏向线程不是当前线程,也就是存在锁竞争
,那么就撤销偏向锁
,升级为轻量级锁
。
轻量级锁实现方式是各个线程在自己的线程栈生成LockRecord ,用CAS操作将markword设置为指向自己这个线程的LockRecord的指针,设置成功者得到锁,没有成功的将继续使用CAS一直循环直到成功,所以轻量级锁也叫自旋锁。
JDK自旋有默认最大值是10次,JDK6对自旋锁进行了优化,自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的,比如当前线程在刚刚成功获取过自旋锁,那么虚拟机就会认为这次自旋也很有可能会成功,那么循环次数就可以多进行几次,这就叫自适应自旋。有了自适应自旋就不用我们设置最大循环次数,由JVM监控动态设置。
自旋锁有个缺点就是等待的线程仍然在自旋运行,如果自旋的次数太多或者自旋等待的线程太多会造成CPU消耗过大,这种情况反而不如向操作系统来申请锁,阻塞其他线程。所以在这种情况下锁会升级成重量级锁,没有获取到锁的现在直接在系统级别被挂起,直到系统释放锁唤醒这些挂起的线程,这些线程再次抢锁。
锁的升级过程画了一个简单的图便于理解以上内容,如下图:
image.png不重要的两个锁优化
还有两个不重要的锁优化还是要了解了解的。
第一个是锁消除:当一段代码中加了锁,但是通过JVM分析他是线程安全的,那么JVM会把锁去掉。比如方法中一段代码有加锁,但是经过分析不会出现线程安全的问题,那么JVM就会把锁给消除。
第二个是锁粗化:当JVM检测到一段连续的多次操作都在对同一个对象多次加锁,那么JVM可能会优化成对这整段加一个锁,没有把加锁的操作分的那么细,所以叫锁粗化。
具体代码案例如下图:
image.png总结
锁升级主要分为偏向锁 - 轻量级锁 - 重量级锁三层。
偏向锁
、轻量级锁
是在Java内部的优化,属于所谓的用户态
,而重量级锁
则是向操作系统
申请,属于内核态
。
在锁竞争不激烈的时候由jvm自己解决肯定性能是最好的,但是jvm通过自旋方式
解决会消耗CPU性能,所以在锁竞争激烈
的情况下重量级锁性能更好
。
锁升级是机制层面的优化
,而锁消除和锁粗化则是jvm对代码层面的优化
。
网友评论