写锁相关API
写锁的获取
从大致流程来看,加写锁和加读锁很像,只是操作的状态位不同。但细节有很大不同,我觉得导致这些不同的主要原因是写锁是互斥的。
public long writeLock() { long next; return ((next = tryWriteLock()) != 0L) ? next : acquireWrite(false, 0L); } public long tryWriteLock() { long s; return (((s = state) & ABITS) == 0L) ? tryWriteLock(s) : 0L; } private long tryWriteLock(long s) { // assert (s & ABITS) == 0L; long next; if (casState(s, next = s | WBIT)) { VarHandle.storeStoreFence(); return next; } return 0L; }
注意上面用到了VarHandle.storeStoreFence(),这个是storestore内存屏障,其作用是避免这个屏障前后的store指令发生指令重排序,并保证屏障前的数据写入要在片中后的数据写入前全部刷到主存里,这里可以保证state的新值对其他cpu立即可见。
当然直接加锁失败时它也需要尝试去排队,和读锁的acquire相比,因为没有cowait的事了,所以代码短了好多。
private long acquireWrite(boolean interruptible, long deadline) { WNode node = null, p; for (int spins = -1;;) { // spin while enqueuing long m, s, ns; if ((m = (s = state) & ABITS) == 0L) { if ((ns = tryWriteLock(s)) != 0L) return ns; } else if (spins < 0) spins = (m == WBIT && wtail == whead) ? SPINS : 0; else if (spins > 0) { --spins; Thread.onSpinWait(); } else if ((p = wtail) == null) { // initialize queue WNode hd = new WNode(WMODE, null); if (WHEAD.weakCompareAndSet(this, null, hd)) wtail = hd; } else if (node == null) node = new WNode(WMODE, p); else if (node.prev != p) node.prev = p; else if (WTAIL.weakCompareAndSet(this, p, node)) { p.next = node; break; } } boolean wasInterrupted = false; for (int spins = -1;;) { WNode h, np, pp; int ps; if ((h = whead) == p) { if (spins < 0) spins = HEAD_SPINS; else if (spins < MAX_HEAD_SPINS) spins <<= 1; for (int k = spins; k > 0; --k) { // spin at head long s, ns; if (((s = state) & ABITS) == 0L) { if ((ns = tryWriteLock(s)) != 0L) { whead = node; node.prev = null; if (wasInterrupted) Thread.currentThread().interrupt(); return ns; } } else Thread.onSpinWait(); } } else if (h != null) { // help release stale waiters WNode c; Thread w; while ((c = h.cowait) != null) { if (WCOWAIT.weakCompareAndSet(h, c, c.cowait) && (w = c.thread) != null) LockSupport.unpark(w); } } if (whead == h) { if ((np = node.prev) != p) { if (np != null) (p = np).next = node; // stale } else if ((ps = p.status) == 0) WSTATUS.compareAndSet(p, 0, WAITING); else if (ps == CANCELLED) { if ((pp = p.prev) != null) { node.prev = pp; pp.next = node; } } else { long time; // 0 argument to park means no timeout if (deadline == 0L) time = 0L; else if ((time = deadline - System.nanoTime()) <= 0L) return cancelWaiter(node, node, false); Thread wt = Thread.currentThread(); node.thread = wt; if (p.status < 0 && (p != h || (state & ABITS) != 0L) && whead == h && node.prev == p) { if (time == 0L) LockSupport.park(this); else LockSupport.parkNanos(this, time); } node.thread = null; if (Thread.interrupted()) { if (interruptible) return cancelWaiter(node, node, true); wasInterrupted = true; } } } } }
写锁的释放
public void unlockWrite(long stamp) { if (state != stamp || (stamp & WBIT) == 0L) //写锁合法性校验 throw new IllegalMonitorStateException(); unlockWriteInternal(stamp); } private long unlockWriteInternal(long s) { long next; WNode h; STATE.setVolatile(this, next = unlockWriteState(s)); if ((h = whead) != null && h.status != 0) release(h); return next; } private long unlockWriteInternal(long s) { long next; WNode h; STATE.setVolatile(this, next = unlockWriteState(s)); //释放写锁状态 if ((h = whead) != null && h.status != 0) release(h); return next; } private static long unlockWriteState(long s) { //这里会更新stamp位,表示已经有过写入状态了,调用这个方法会导致乐观锁失效。 return ((s += WBIT) == 0L) ? ORIGIN : s; }
其他API
至此我们已经了解到了StampedLock的核心API,除上面所述的内容外,它也通过了读写锁、乐观锁三者之间的相互转换。
读锁转写锁
public long tryConvertToWriteLock(long stamp) { long a = stamp & ABITS, m, s, next; while (((s = state) & SBITS) == (stamp & SBITS)) { if ((m = s & ABITS) == 0L) { if (a != 0L) break; if ((next = tryWriteLock(s)) != 0L) return next; } else if (m == WBIT) { if (a != m) break; return stamp; } else if (m == RUNIT && a != 0L) { if (casState(s, next = s - RUNIT + WBIT)) { VarHandle.storeStoreFence(); return next; } } else break; } return 0L;
读转写的主要流程其实就是校验锁状态,释放读锁,然后再去获取一次写锁。
写锁转读锁
public long tryConvertToReadLock(long stamp) { long a, s, next; WNode h; while (((s = state) & SBITS) == (stamp & SBITS)) { if ((a = stamp & ABITS) >= WBIT) { // write stamp if (s != stamp) break; STATE.setVolatile(this, next = unlockWriteState(s) + RUNIT); if ((h = whead) != null && h.status != 0) release(h); return next; } else if (a == 0L) { // optimistic read stamp if ((s & ABITS) < RFULL) { if (casState(s, next = s + RUNIT)) return next; } else if ((next = tryIncReaderOverflow(s)) != 0L) return next; } else { // already a read stamp if ((s & ABITS) == 0L) break; return stamp; } } return 0L; }
转乐观锁
public long tryConvertToOptimisticRead(long stamp) { long a, m, s, next; WNode h; VarHandle.acquireFence(); while (((s = state) & SBITS) == (stamp & SBITS)) { if ((a = stamp & ABITS) >= WBIT) { // 有写锁先释放写锁 // write stamp if (s != stamp) break; return unlockWriteInternal(s); } else if (a == 0L) // already an optimistic read stamp return stamp; else if ((m = s & ABITS) == 0L) // invalid read stamp break; else if (m < RFULL) { if (casState(s, next = s - RUNIT)) { // 有读锁也要释放读锁 if (m == RUNIT && (h = whead) != null && h.status != 0) release(h); return next & SBITS; } } else if ((next = tryDecReaderOverflow(s)) != 0L) return next & SBITS; } return 0L; }
ReadLockView和WriteLockView final class ReadLockView implements Lock { public void lock() { readLock(); } public void lockInterruptibly() throws InterruptedException { readLockInterruptibly(); } public boolean tryLock() { return tryReadLock() != 0L; } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return tryReadLock(time, unit) != 0L; } public void unlock() { unstampedUnlockRead(); } public Condition newCondition() { throw new UnsupportedOperationException(); } } final class WriteLockView implements Lock { public void lock() { writeLock(); } public void lockInterruptibly() throws InterruptedException { writeLockInterruptibly(); } public boolean tryLock() { return tryWriteLock() != 0L; } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return tryWriteLock(time, unit) != 0L; } public void unlock() { unstampedUnlockWrite(); } public Condition newCondition() { throw new UnsupportedOperationException(); } } final class ReadWriteLockView implements ReadWriteLock { public Lock readLock() { return asReadLock(); } public Lock writeLock() { return asWriteLock(); } }
StampedLock也提供了单独读锁和写锁的封装类WriteLockView和ReadLockView,它俩存在的意义就是只讲锁的部分暴露出去,防止外部接口错误加解锁,我觉得符合软件设计模式中的单一职责和接口隔离原则。
注意事项
StampedLock不是重入锁,所以不能够递归使用。
StampedLock并没有提供condition。
参考资料
stackoverflow Optimistic vs. Pessimistic locking
深入理解StampedLock及其实现原理
StampedLock源码分析
死磕 java同步系列之StampedLock源码解析