StampedLock:一个并发编程中非常重要的票据锁( 二 )


StampedLock实现思想StampedLock内部是基于CLH锁实现的,CLH是一种自旋锁,能够保证没有“饥饿现象”的发生,并且能够保证FIFO(先进先出)的服务顺序 。
在CLH中,锁维护一个等待线程队列,所有申请锁 , 但是没有成功的线程都会存入这个队列中 , 每一个节点代表一个线程,保存一个标记位(locked),用于判断当前线程是否已经释放锁,当locked标记位为true时 ,  表示获取到锁,当locked标记位为false时,表示成功释放了锁 。
当一个线程试图获得锁时,取得等待队列的尾部节点作为其前序节点,并使用类似如下代码判断前序节点是否已经成功释放锁:
while (pred.locked) { //省略操作}只要前序节点(pred)没有释放锁,则表示当前线程还不能继续执行 , 因此会自旋等待;反之,如果前序线程已经释放锁,则当前线程可以继续执行 。
释放锁时,也遵循这个逻辑,线程会将自身节点的locked位置标记为false,后续等待的线程就能继续执行了,也就是已经释放了锁 。
StampedLock的实现思想总体来说,还是比较简单的 , 这里就不展开讲了 。
StampedLock的注意事项在读多写少的高并发环境下,StampedLock的性能确实不错,但是它不能够完全取代ReadWriteLock 。在使用的时候,也需要特别注意以下几个方面 。
StampedLock不支持重入没错 , StampedLock是不支持重入的 , 也就是说,在使用StampedLock时,不能嵌套使用,这点在使用时要特别注意 。
StampedLock不支持条件变量第二个需要注意的是就是StampedLock不支持条件变量,无论是读锁还是写锁,都不支持条件变量 。
StampedLock使用不当会导致CPU飙升这点也是最重要的一点,在使用时需要特别注意:如果某个线程阻塞在StampedLock的readLock()或者writeLock()方法上时,此时调用阻塞线程的interrupt()方法中断线程 , 会导致CPU飙升到100% 。例如 , 下面的代码所示 。
public void testStampedLock() throws Exception{ final StampedLock lock = new StampedLock(); Thread thread01 = new Thread(()->{ // 获取写锁 lock.writeLock(); // 永远阻塞在此处 , 不释放写锁 LockSupport.park(); });thread01.start(); // 保证thread01获取写锁 Thread.sleep(100); Thread thread02 = new Thread(()-> //阻塞在悲观读锁 lock.readLock() );thread02.start(); // 保证T2阻塞在读锁 Thread.sleep(100); //中断线程thread02 //会导致线程thread02所在CPU飙升thread02.interrupt();thread02.join();}运行上面的程序,会导致thread02线程所在的CPU飙升到100% 。
这里,有很多小伙伴不太明白为啥LockSupport.park();会导致thread01会永远阻塞 。这里 , 冰河为你画了一张线程的生命周期图,如下所示 。

StampedLock:一个并发编程中非常重要的票据锁

文章插图
这下明白了吧?在线程的生命周期中,有几个重要的状态需要说明一下 。
  • NEW:初始状态 , 线程被构建 , 但是还没有调用start()方法 。
  • RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态 。
  • BLOCKED:阻塞状态 , 处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized 。
  • WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态 。
  • TIME_WAITING:超时等待状态 。可以在一定的时间自行返回 。
  • TERMINATED:终止状态,当前线程执行完毕 。
看完这个线程的生命周期图,知道为啥调用LockSupport.park();会使thread02阻塞了吧?
所以 , 在使用StampedLock时 , 一定要注意避免线程所在的CPU飙升的问题 。那如何避免呢?
那就是使用StampedLock的readLock()方法或者读锁和使用writeLock()方法获取写锁时,一定不要调用线程的中断方法来中断线程,如果不可避免的要中断线程的话 , 一定要用StampedLock的readLockInterruptibly()方法获取可中断的读锁和使用StampedLock的writeLockInterruptibly()方法获取可中断的悲观写锁 。
最后,对于StampedLock的使用,JDK官方给出的StampedLock示例本身就是一个最佳实践了,小伙伴们可以多看看JDK官方给出的StampedLock示例,多多体会下StampedLock的使用方式和背后原理与核心思想 。
点击关注 , 第一时间了解华为云新鲜技术~
【StampedLock:一个并发编程中非常重要的票据锁】

推荐阅读