4. Java JUC源码分析系列笔记-锁的优化
目录
1. JVM对锁的优化
JDK1.5之前sychronized内部锁的效率很低,1.5之后做了大量优化,提升了锁的性能。
优化措施主要有以下几个:
1.1. 锁的消除
JIT(不是javac)借助逃逸分析以及内联技术,分析某个同步代码块是否只能被一个线程访问,
是的话则不生成monitor相关的字节码指令并且把字节码内联到调用方的字节码中
1.2. 锁的粗化
JIT把相邻的几个同步块合并成一个大的同步块。
- 优点
避免了一个线程反复申请、释放同一个锁的开销 - 缺点
这个线程持有锁的时间太长,导致其他线程等待其释放锁的时间也变长。
1.3. 偏向锁
基于这样一种现象,大多数锁至多被一个线程持有,这样子底层的monitor字节码的cas操作就没有必要了。
因此,在某个锁第一次被一个线程持有的时候,会把这个线程记录为偏向线程,当这个线程再来获取的时候就不用cas操作直接给他锁。
直至有第二个线程访问这把锁
1.4. 适应性锁
线程A占用了锁X,其他线程要获取锁需要等待其释放锁,有两种办法
- 暂停线程,会导致上下文切换
- 忙等,比较耗费cpu资源
jvm会根据实际情况在这两种策略切换
2. 锁的膨胀过程分析
JVM通过以上的措施对sychronized锁进行优化后,随着多线程对锁的竞争愈加激烈,sychronized锁呈现出一种膨胀的现象,具体表现为无锁 → 偏向锁 → 轻量级锁 → 重量级锁
。
锁可以升级不能降级。
2.1. 无锁
单线程的情况下没必要加锁,那么可以消除。比如单线程使用StringBuffer
2.2. 偏向锁
多线程但是每次获取锁的都是同一个线程,那么记录一下,下次直接给他可以减少很多不必要的性能开销和上下文切换
2.3. 轻量级锁
多线程但是竞争不激烈,其他线程获取锁失败的情况下只需要稍微自旋等待一下就可以获取锁,但是自旋次数有限制,如果超过该次数,则会升级为重量级锁
2.4. 重量级锁
多线程且竞争激烈,那么升级成重量级锁阻塞
3. 应用层次优化锁的使用
由上述可以看出锁的开销主要体现在竞争锁上面,因此我们开发的时候要注意降低锁的争用程度。
3.1. 降低争用程度方法
- 减少锁被持有的时间
如减少临界区长度等 - 降低锁的申请频率
如不要频繁申请、释放锁,必要时合并成一个大块
4. 调整自旋的次数
JVM虚拟机提供了-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁的等待次数。默认自旋10次