StampedLock
(1)StampedLock邮戳锁支持三种模式,分别是:写锁,读锁,乐观读。
ReentrantReadWriteLock读写锁的缺点是在读多写少的情况下,写线程会被阻塞,写线程可能会一直抢不到锁发生饥饿现象
邮戳锁可以避免这种情况,在乐观读的时候,不会对其他的写线程阻塞,其他的线程可以获取到写锁
(2)邮戳锁的乐观读可以升级为写锁,读写锁不能由读锁升级为写锁
(3)无论写锁还是读锁,都不支持Conditon等待
class Point { // 成员变量 private double x, y; // 锁实例 private final StampedLock sl = new StampedLock(); // 排它锁-写锁(writeLock) void move(double deltaX, double deltaY) { long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } // 一个只读方法 // 其中存在乐观读锁到悲观读锁的转换 double distanceFromOrigin() { // 尝试获取乐观读锁 long stamp = sl.tryOptimisticRead(); // 将全部变量拷贝到方法体栈内 double currentX = x, currentY = y; // 检查在获取到读锁stamp后,锁有没被其他写线程抢占 if (!sl.validate(stamp)) { // 如果被抢占则获取一个共享读锁(悲观获取) stamp = sl.readLock(); try { // 将全部变量拷贝到方法体栈内 currentX = x; currentY = y; } finally { // 释放共享读锁 sl.unlockRead(stamp); } } // 返回计算结果 return Math.sqrt(currentX * currentX + currentY * currentY); } // 获取读锁,并尝试转换为写锁 void moveIfAtOrigin(double newX, double newY) { long stamp = sl.tryOptimisticRead(); try { // 如果当前点在原点则移动 while (x == 0.0 && y == 0.0) { // 尝试将获取的读锁升级为写锁 long ws = sl.tryConvertToWriteLock(stamp); // 升级成功,则更新stamp,并设置坐标值,然后退出循环 if (ws != 0L) { stamp = ws; x = newX; y = newY; break; } else { // 读锁升级写锁失败则释放读锁,显示获取独占写锁,然后循环重试 sl.unlockRead(stamp); stamp = sl.writeLock(); } } } finally { sl.unlock(stamp); } } }
StampedLock的实现思想
在StampedLock中使用了CLH自旋锁,如果发生了读失败,不立刻把读线程挂起,锁当中维护了一个等待线程队列。所有申请锁但是没有成功的线程都会记录到这个队列中,每一个节点(一个节点表示一个线程)保存一个标记位(locked),用于判断当前线程是否已经释放锁。新加入的节点会加入到队列的末尾,当前等待队列尾部的节点作为其前序节点,并使用类似如下代码(一个空的死循环)判断前序节点是否已经成功的释放了锁: while(pred.locked){ }
解释:pred表示当前试图获取锁的线程的前序节点,如果前序节点没有释放锁,则当前线程就执行该空循环并不断判断前序节点的锁释放,即类似一个自旋锁的效果,避免被系统挂起。当循环一定次数后,前序节点还没有释放锁,则当前线程就被挂起而不再自旋。