性能调优之锁优化
线程安全问题触发条件:
1.存在共享数据
2.多个线程同时操作共享数据
锁之syncronized
特征:可重入(获得了外面的锁,则自动获得了里面的锁),非公平(线程有可能会饿死)
原理:
1)早期:
2)堆内存中对象存储方式(锁,本质上是堆中某个对象的对象头中通过mark word标记的一种状态):
1.对象头
1)Mark Word,用来存储对象自身的运行时数据,例如对象哈希code,锁状态的标志,线程所持有的锁等
2)类型指针,虚拟机通过该指针来确定这个对象是哪一个类的实例
3)数组长度(仅当对象是数组的时候)
2.实例数据
3.对齐填充
3)锁状态,其中重量级锁就是syncronized早期锁,又叫常规锁。从上到下,性能 依次降低,开销依次递增。
偏向锁:
1.默认启动5s后开启,当只有一个线程竞争锁资源的时候,相比于关闭偏向锁的应用,性能有小幅度提升(2%),当线程竞争激烈的时候,使用偏向锁,性能反而会有所下降。
2.关闭方式,添加运行时参数:-XX:-UseBiasedLocking
3.开启并取消延迟:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
轻量级锁:即乐观锁,通过CAS操作竞争锁资源
重量级锁:即悲观锁,线程之间完全互斥。
4)锁升级过程
syncronized锁有一个升级过程:
1.检测是无锁还是偏向锁
2.如果是偏向锁,校验线程id,看是不是同一个线程在竞争锁资源,如果是,直接执行同步代码,如果不是,CAS替换线程id。
3.如果替换成功,执行同步代码,如果替换失败,说明已经有别的线程Y占用了锁,则撤销偏向锁,暂停线程Y并检查状态,如果不活动或已经退出,则释放线程Y,唤醒线程X,同时X获得锁
4.如果线程Y活动,则升级为轻量级锁,由线程Y持有
5.线程X进行CAS操作,如果成功,则获得锁并执行同步代码,如果失败,进行自旋操作 ,在限制次数内,如果成功,就获得该轻量级锁。
6.如果超过自旋次数,线程X仍未获得锁,则说明锁资源竞争相当激烈,此时锁就会升级为重量级锁,通过操作系统的互斥锁来实现。
使用场景:
优化机制:
1.逃逸分析(又叫做锁消除):分析变量能否逃出他的作用域,如果不能逃出,jvm会自动取消掉他的锁
下图中,object变量是一个局部变量,且不可能发生逃逸,所以不是共享变量,不可能发生线程安全问题,jvm会自动取消掉这段syncronized代码块。
而如果代码写成这样,则可能发生逃逸,因为object作为someMethod的返回值,且赋值给了object2,而object2又是一个成员变量,所以可能发生线程安全问题。
2.锁粗化:将多个连续的加锁解锁操作连接在一起,扩展成一个范围更大的锁。
上图代码会被优化成下面代码
逃逸分析和锁粗化默认开启,如要手动开启,添加如下运行时参数
3.锁分级:也就是上文所说的偏向锁,轻量级锁,重量级锁
总结:syncronized优化机制包括锁分级,锁消除,锁粗化。
锁之reentrantLock
核心API:
1.lock:表示获得锁
2.tryLock:表示尝试获得加锁,如果失败返回false
3.tryLock(timeout,TimeUnit):带超时的tryLock
4.lockInterruptibly:如果当前线程interrupt了就抛异常,如果没有就去获得锁
5.unlock:解锁
示例代码:
特性:
1.互斥
2.可重入
3.公平性:
1)线程会按照向lock申请锁的顺序去依次获得锁,不允许插队,牺牲了一部分性能来获得公平性,这一点是syncronized锁不具备的
2)创建锁对象的时候传入一个boolean参数,true表示创建一个公平锁,false表示创建一个非公平锁,不传则默认创建的是非公平锁
4.condition:替代传统线程通信中,object的wait(),notify()方法
核心API:
注意点:操作condition的代码,必须在lock的保护之下
原理:基于AQS和CAS
面试题:syncronized和reentrantLock的区别?
1.syncronized基于jvm实现,看不到源码,而reentrantlock基于jdk实现,方便查看源码
2.jdk1.6之前,syncronized性能比reentrantlock差很多,1.6之后,性能差不多
3.reentrantlock实现了公平性,有丰富的api,并且有condition操作,syncronized不具备
4.使用时,如果两者都可以满足需要,官方建议使用syncronized,代码更加简洁易读。当然,也可以根据自己的喜好来。
锁之ReentrantReadWriteLock(读写锁)
适用于读多写少的场景,实际上是两个锁,一个readlock,一个writelock
示例代码:
特性:
1.公平性
2.可重入
3.锁降级:一个写锁可以降级成读锁,但是一个读锁不能升级成一个写锁
面试题:reentrantlock和reentrantReadWriteLock的区别?
1.reentrantlock完全互斥
2.reentrantReadWriteLock读锁共享,写锁互斥,在读多写少的场景下,reentrantReadWriteLock的性能要比reentrantlock好很多,但是在写多场景下,优势就不明显了。
锁之StampedLock
背景:
reentrantReadWriteLock存在的问题:
1.写线程“饥饿”问题,由于一个线程尝试获取写锁的时候,不能有其他的读锁存在,所以如果读线程非常多,该线程可能久久拿不到写锁,存在饥饿问题
2.写锁可以降级成读锁,但是读锁却不能升级成写锁。
特性:
锁调优: