简单理解JVM中的锁优化 偏向锁、轻量级锁、偏向锁、重量级锁

为了换取性能,JVM在内置锁上做了非常多的优化,膨胀式的锁分配策略就是其一。这里只讲这几种锁的概念,并不讲解锁的细节和详细的膨胀过程。

1.偏向锁

在某些时候,对于某个锁而言,可能并不存在多个锁来对他进行竞争。也就是说,访问该锁的始终都是同一个线程(同一个,而不是一个时间点只有一个),那么这种情况下,就完全没有必要进行复杂的获取锁的操作。
就比如说我们可能在方法中创建了一个StringBuffer类型的示例,局部对象是线程安全的,其append()方法会进行同步,但是其实这种同步是没有必要的,这种情况下,偏向锁就能对性能有很多的帮助了。
偏向锁假定将来只有第一个申请锁的线程会使用锁(不会再有任何其他线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程尝试获取锁,锁就要进行膨胀了,膨胀为轻量级锁。

2.轻量级锁

轻量级锁是对偏向锁的更进一步的优化,他可以允许多个线程来申请锁,但是这些线程申请锁的时间可能并不是在同一时间。比如说我之前一个线程获取了锁执行完操作之后将锁释放掉了,下一个线程才来申请锁。这种情况下锁的其实并不存在锁的竞争,每个线程对于锁的申请都不在一个时间点上,这种时候,轻量级锁就起作用了。
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换。
使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节利用CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(在同一时间点上有两个线程申请锁,不适合继续使用轻量级锁),接下来膨胀为重量级锁。

3.重量级锁

内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。

4.自旋锁

该锁可以看做是在获取重量级锁之前的最后一项优化,可以看出来JVM就是千方百计不想去变成重量级锁。
其思想就是,可能是上一个线程在获取了锁之后,可能很快就结束了操作,然后就释放掉了锁,可能他只执行了几千个时钟周期。这种情况下另外一个获取锁的线程完全没有必要因为获取不到锁而阻塞。因为执行的时间很短,我们可以让另外的获取锁的线程先进行自旋(执行循环语句)等待,而不是直接因为获取锁失败而进行阻塞,待拥有锁的线程释放掉锁了之后,当前自选的锁就可以获取到锁了。这样操作有什么好处?因为没有因为获取不到锁而阻塞,就减少两次系统调用(被阻塞是一次系统调用,唤醒又是一次系统调用,用户态到内核态的切换时钟周期很长),系统调用相比较于自旋更加浪费时间。自选锁在合适的场景下能显著的提高吞吐量。比如JDK1.8中的ConcurrentHashMap中就利用到了自旋的思想

发布了162 篇原创文章 · 获赞 44 · 访问量 8836

猜你喜欢

转载自blog.csdn.net/P19777/article/details/103757670