用锁最佳实践
1、永远只在更新对象的成员变量时加锁
2、永远只在访问可变的成员变量时加锁
3、永远不在调用其他对象的方法时加锁
1.写锁
2.悲观读锁
3.乐观读
相对于ReadWriteLock 有啥区别
ReadWriteLock两种模式是:读锁, 写锁。而stampedLock 支持三种,分别为 写锁,乐观读锁,乐观读。写锁和悲观读锁都是互斥的,不同的是, stampedLock 写锁和悲观读锁加锁成功之后,都会返回一个stamp ,然后解锁的时候,需要传入stamp。
示例代码
final StampedLock sl = new StampedLock(); // 获取/释放悲观读锁示意代码 long stamp = sl.readLock(); try { //省略业务相关代码 } finally { sl.unlockRead(stamp); } // 获取/释放写锁示意代码 long stamp = sl.writeLock(); try { //省略业务相关代码 } finally { sl.unlockWrite(stamp); }
StampedLock 之所以能比ReadWriteLock的性能好,关键是 stampedLock 支持乐观读的方式,ReadWriteLock支持多个线程同时读,但是多个线程读的时候,所有写的操作会被阻塞;而StampedLock提供的乐观读,是应许一个线程获取写锁的,就是说不是所有的写锁操作都是被阻塞。注意这里,我们用的是“乐观读”这个词,而不是“乐观读锁”,是要提醒你,乐观读这个操作是无锁的,所以相比较 ReadWriteLock 的读锁,乐观读的性能更好一些。
class Point{ private int x ,y; final StampedLock sl = new StampedLock(); // 计算到原点的距离 int distanceFromOrigin(){ // 乐观读 long stamp = sl.tryOptimisticRead(); // 这里无锁的 // 读入局部变量 // 读的过程数据可能被修改 int curX = x, curY = y; // 判断执行读操作期间 // 是否存在写操作,如果存在 // 则sl.validate 返回false if(!s1.validate(stamp)){ // 升级为悲观读锁 stamp = sl.readLock(); try{ curX = x; curY = y; }finally{ // 释放悲观读锁 sl.unLockRead(stamp); } } return Math.sqrt( curX * curY + curX + curY; ) } }
StampedLock 使用注意事项
1、对于读多写少的场景StampedLock的性能很好,简单的应用场景基本上可以替代ReadWriteLock,但是StampedLock的功能仅仅是ReadWriteLock的子集,在使用的时候,还是有几个地方需要注意一下
2、StampedLock 命名上并没有增加Reentrant, 想必你已经知道,StampedLock 应该是不可重入的。事实上,的确是这样的,StampedLock 不支持重入。这个是在使用中必须要特别注意的。另外,StampedLock 悲观读锁、写锁都不支持条件变量,这个也需要你注意。
使用注意
那就是:如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。
final StampedLock = new StampedLock(); Thread T1 = new Thread(() -> { // 获取写锁 lock.writeLock(); // 永远阻塞在此处,不释放写锁 LockSupport.park(); }); T1.start(); // 保证T1获取写锁 Thread.sleep(100); Thread T2 = new Thread(() -> { // 阻塞在悲观读锁 lock.readLock(); }); T2.start(); // 保证T2阻塞读锁 Thread.sleep(100); // 中断线程T2 // 会导致线程T2所在CPU 飙升、 T2.interrupt(); T2.join();
所以,使用 StampedLock 一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。这个规则一定要记清楚。
StampedLock 读模板:
final StampedLock sl = new StampedLock(); // 乐观锁 long stamp = sl.tryOptimisticRead(); // 读入方法局部变量 // ... // 校检stamp if(!sl.validate(stamp)){ // 升级为悲观读锁 stamp = sl.readLock(); try{ // 读入方法局部变量 ..... }finally{ // 释放悲观读锁 sl.unLockRead(stamp); } } // 使用方法局部变量执行业务操作
StampedLock 写模板
long stamp = sl.writeLock(); try{ // 写共享变量 ..... }finally{ sl.unlockWrite(stamp); }