高性能解决线程饥饿的利器 StampedLock( 三 )


  1. 如果写锁未被占用 , 则立即尝试获取读锁 , 通过 CAS 修改状态为标志成功则直接返回 。
  2. 如果写锁被占用 , 则将当前线程包装成 WNode 读节点 , 并插入等待队列 。 如果是写线程节点则直接放入队尾 , 否则放入队尾专门存放读线程的 WNode cowait 指向的栈 。 栈结构是头插法的方式插入数据 , 最终唤醒读节点 , 从栈顶开始 。
释放锁无论是 unlockRead 释放读锁还是 unlockWrite释放写锁 , 总体流程基本都是通过 CAS 操作 , 修改 state 成功后调用 release 方法唤醒等待队列的头结点的后继节点线程 。
  1. 想将头结点等待状态设置为 0, 标识即将唤醒后继节点 。
  2. 唤醒后继节点通过 CAS 方式获取锁 , 如果是读节点则会唤醒 cowait 锁指向的栈所有读节点 。
释放读锁
unlockRead(long stamp) 如果传入的 stamp 与锁持有的 stamp 一致 , 则释放非排它锁 , 内部主要是通过自旋 + CAS 修改 state 成功 , 在修改 state 之前做了判断是否超过读线程数限制 , 若是小于限制才通过 CAS 修改 state 同步状态 , 接着调用 release 方法唤醒 whead 的后继节点 。
释放写锁
unlockWrite(long stamp) 如果传入的 stamp 与锁持有的 stamp 一致 , 则释放写锁 , whead 不为空 , 且当前节点状态 status != 0 则调用 release 方法唤醒头结点的后继节点线程 。
总结StampedLock 并不能完全代替ReentrantReadWriteLock, 在读多写少的场景下因为乐观读的模式 , 允许一个写线程获取写锁 , 解决了写线程饥饿问题 , 大大提高吞吐量 。
在使用乐观读的时候需要注意按照编程模型模板方式去编写 , 否则很容易造成死锁或者意想不到的线程安全问题 。
它不是可重入锁 , 且不支持条件变量 Conditon 。 并且线程阻塞在 readLock() 或者 writeLock() 上时 , 此时调用该阻塞线程的 interrupt() 方法 , 会导致 CPU 飙升 。 如果需要中断线程的场景 , 一定要注意调用悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly() 。
【高性能解决线程饥饿的利器 StampedLock】另外唤醒线程的规则和 AQS 类似 , 先唤醒头结点 , 不同的是 StampedLock 唤醒的节点是读节点的时候 , 会唤醒此读节点的 cowait 锁指向的栈的所有读节点 , 但是唤醒与插入的顺序相反 。