1)上一篇文章我们聊了读写锁,他的适用场景是读多写少的场景下,那有没有其它性能比读写锁还要牛逼的锁呢?
StampedLock ,java1.8诞生的。
2)StampedLock比读写锁牛在什么地方?
读写锁分为两种:读锁和写锁
StampedLock有三种模式:写锁和悲观读锁,这两个对应我们的读写锁的写锁和读锁,功能是一样的。但是他的杀手锏是乐观读,注意是乐观读,不是乐观读锁。
乐观读是一种操作,不涉及到锁。当多个线程在读的时候,允许一个线程获取读锁,这个就是StampedLock与读写锁不同的地方。因为不涉及到锁,所以为了保障并发安全,会有一个stamp来作为安全的标志。类似于我们数据库乐观锁的version。
3)写锁和悲观读锁与我们读写锁的细致区别是什么?
他两加锁的时候会返回一个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); }
4)乐观读是怎样使用的?
tryOptimisticRead()就是乐观读,因为是无锁的,所所以共享变量 x 和 y 读入方法局部变量时,x 和 y 有可能被其他线程修改了。因此最后读完之后,还需要再次验证一下是否存在写操作,这个验证操作是通过调用 validate(stamp) 来实现的。
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 (!sl.validate(stamp)){ // 升级为悲观读锁 stamp = sl.readLock(); try { curX = x; curY = y; } finally { //释放悲观读锁 sl.unlockRead(stamp); } } return Math.sqrt( curX * curX + curY * curY); } }
上面的代码中,如果乐观读的时候,存在写操作,那么就把它升级为悲观读锁。这样就避免了乐观读一直循环浪费大量的cpu,使用的时候尽量这样去做。
5)StampedLock有哪些注意事项?
千万不要在线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时调用该阻塞线程的interrupt()方法,会导致运行这个线程的cpu挂掉的。如果实在要用中断方法,那就用带interrupt的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。
final StampedLock lock = 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的功能是不如读写锁的那么多的
StampedLock是不支持嵌套使用的,也就是可重入锁。
6)以后有用到StampedLock的需求的时候,使用的模板应该是怎样的?
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); }