JUC之StampedLock读写锁增强辅助类
一、简介
StampedLock类,在JDK1.8时引入,是对读写锁ReentrantReadWriteLock的增强,该类提供了一些功能,优化了读锁,写锁的访问,同时是读写锁之间可以互相转换,更细粒度控制并发。
首先明确下,该类的设计初衷是作为一个内部工具类,用于辅助开发其他线程安全组件,用得好,该类可以提升系统性能,用不好,容易产生死锁和其他莫名的问题
1.1 StampedLock的引入
有ReentrantReadWriteLock,还要引入StampedLock?
前面介绍的ReadWriteLock
可以解决多线程同时读,但只有一个线程能写的问题。
如果我们深入分析ReadWriteLock
,会发现它有个潜在的问题:
如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。
要进一步提升并发执行效率,Java 8引入了新的读写锁:StampedLock
。
StampedLock
和ReadWriteLock
相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。
乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。
反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。
显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
ReentrantReadWriteLock使得多个读线程同时持有读锁(只要写锁未被占用),而写锁是独占的。
但是,读写锁如果使用不当,很容易产生“饥饿”问题:比如在读线程非常多,写线程很少的情况下,很容易导致写线程“饥饿”,虽然使用“公平”策略可以一定程度上缓解这个问题,但是“公平”策略是以牺牲系统吞吐量为代价的。
1.2 StampedLock的特点
StampedLock的主要特点概括一下,有以下几点:
1. 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为0表示获取失败,其余都表示成功;
2. 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
3. StampLock是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
4. StampLock有三种访问模式:
①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
③Optimistic reading(乐观读模式):这是一种优化的读模式。5. StampedLock支持读锁和写锁的相互转换
我们知道RRW中,当线程获取到写锁后,可以降级为读锁,但是读锁是不能直接升级为写锁的。
StampedLock提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。6. 无论写锁还是读锁,都不支持Condition等待
我们知道,在ReentrantReadWriteLock中,当读锁被使用时,如果有线程尝试获取写锁,该写线程会阻塞。
但是,在Optimistic reading(乐观读)中,即使读线程获取到了读锁,写线程尝试获取写锁也不会阻塞,这相当于对读模式的优化,但是可能会导致数据不一致的问题。所以,当使用Optimistic reading获取到读锁时,必须对获取结果进行校验。
二、StampLock源码分析
属性
//处理器数量
private static final int NCPU = Runtime.getRuntime().availableProcessors();
//在当前节点加入队列前,如果队列头尾节点相等,即属性whead和wtail相等,先让其自旋一定的大小,自旋的值
private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0;
//在阻塞当前线程前,如果队列头尾节点相等,即属性whead和wtail相等,先让其自旋一定的大小,自旋的值
private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 0;
//HEAD_SPINS的最大值
private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 0;
//读锁大小溢出时,超过126,线程自增的随机数&上OVERFLOW_YIELD_RATE时会yeild,下面具体实现中会解释道
private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1
//读锁最大的bit位
private static final int LG_READERS = 7;
//获取悲观读成功时,state增加的值
private static final long RUNIT = 1L;
//写锁获取成功,state增加的值,写锁标志位(10000000)128
private static final long WBIT = 1L << LG_READERS;
//在获取当前读锁的个数,判断当前stampedLock是属于读锁的状态,127
private static final long RBITS = WBIT - 1L;
//最大的读锁大小,126
private static final long RFULL = RBITS - 1L;
//包含读锁标志位和写锁标志位和起来,在获取锁和释放锁中使用,比如用于判断state是否处于读锁还是写锁,还是无锁状态
private static final long ABITS = RBITS | WBIT;
//用于在乐观锁和释放锁使用
private static final long SBITS = ~RBITS; // note overlap with ABITS
//StampedLock初始化,state的初始值100000000,256
private static final long ORIGIN = WBIT << 1;
//如果当前线程被中断,获取读写锁时,返回的值
private static final long INTERRUPTED = 1L;
//节点的等待状态
private static final int WAITING = -1;
//节点的取消状态
private static final int CANCELLED = 1;
//节点属于读模式
private static final int RMODE = 0;
//节点属于写模式
private static final int WMODE = 1;
//stampedLock队列中的头节点
private transient volatile WNode whead;
//stampedLock队列中的尾节点
private transient volatile WNode wtail;
//读锁的视图,不可重入,并且不支持condition
transient ReadLockView readLockView;
//写锁的视图,不可重入并且不支持condition
transient WriteLockView writeLockView;
//读写锁的视图
transient ReadWriteLockView readWriteLockView;
//stampedLock的状态,用于判断当前stampedLock是属于读锁还是写锁还是乐观锁
private transient volatile long state;
//读锁溢出时,记录额外的读锁大小private transient int readerOverflow;
//UnSafe的使用,如果不清楚的话,可以看我分享的美团的UnSafe介绍
private static final sun.misc.Unsafe U;
//属性state的偏移量,定义成静态的原因是所有的实例的内存布局是一样的,偏移量大小都是相等的
private static final long STATE;
//属性whead的偏移量
private static final long WHEAD;
//属性wtail的偏移量
private static final long WTAIL;
//内部类WNode的next属性偏移量
private static final long WNEXT;
//内部类WNode的status的属性偏移量
private static final long WSTATUS;
//内部类WNode的cowait属性偏移量,读模式的节点队列
private static final long WCOWAIT;
//Thread类中的parkBlocker属性偏移量,记录当前线程被那个对象阻塞起来
private static final long PARKBLOCKER;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = StampedLock.class;
Class<?> wk = WNode.class;
STATE = U.objectFieldOffset(k.getDeclaredField("state"));
WHEAD = U.objectFieldOffset(k.getDeclaredField("whead"));
WTAIL = U.objectFieldOffset(k.getDeclaredField("wtail"));
WSTATUS = U.objectFieldOffset(wk.getDeclaredField("status"));
WNEXT = U.objectFieldOffset(wk.getDeclaredField("next"));
WCOWAIT = U.objectFieldOffset(wk.getDeclaredField("cowait"));
Class<?> tk = Thread.class;
PARKBLOCKER = U.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
} catch (Exception e) { throw new Error(e); }}
构造函数
//state被初始化为256,写锁标志位128,读锁位
public StampedLock() { state = ORIGIN;}
内部类
//等待节点类 static final class WNode { //前驱节点 volatile WNode prev; //当前节点的下一个节点 volatile WNode next; //等待的读模式节点 volatile WNode cowait; // list of linked readers //节点对应的线程 volatile Thread thread; // non-null while possibly parked //节点的状态 volatile int status; // 0, WAITING, or CANCELLED //当前节点是处于读锁模式还是写锁模式 final int mode; // RMODE or WMODE //构造函数,传入读写锁模式和前驱节点 WNode(int m, WNode p) { mode = m; prev = p; } } //读锁视图 final class ReadLockView implements Lock { public void lock() { //调用StampedLock的readLock方法,下面会介绍 readLock(); } public void lockInterruptibly() throws InterruptedException { //调用StampedLock的readLockInterruptibly方法,下面会介绍 readLockInterruptibly(); } public boolean tryLock() { //调用StampedLock的tryReadLock方法,下面会介绍 return tryReadLock() != 0L; } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { //调用StampedLock的tryReadLock(time, unit)方法 return tryReadLock(time, unit) != 0L; } public void unlock() { //调用StampedLock的unstampedUnlockRead方法 unstampedUnlockRead(); } //不支持Condition,和ReadWriteLock的区别 public Condition newCondition() { throw new UnsupportedOperationException(); } } //写锁视图 final class WriteLockView implements Lock { public void lock() { //调用StampedLock的writeLock方法,下面会详细介绍 writeLock(); } public void lockInterruptibly() throws InterruptedException { //调用StampedLock的writeLockInterruptibly方法,下面会详细介绍 writeLockInterruptibly(); } public boolean tryLock() { //调用StampedLock的tryWriteLock方法,下面会详细介绍 return tryWriteLock() != 0L; } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { //调用StampedLock的tryWriteLock(time, unit)方法,下面会详细介绍 return tryWriteLock(time, unit) != 0L; } public void unlock() { //调用StampedLock的unstampedUnlockWrite方法 unstampedUnlockWrite(); } //不支持Condition public Condition newCondition() { throw new UnsupportedOperationException(); } } //读写锁视图 final class ReadWriteLockView implements ReadWriteLock { //获取读锁视图,如果读锁视图未初始化,初始化读锁视图 public Lock readLock() { return asReadLock();//return ((v = readLockView) != null ? v : (readLockView = new ReadLockView())); } //获取写锁视图,如果写锁视图未初始化,初始化写锁视图 public Lock writeLock() { return asWriteLock(); //((v = writeLockView) != null ? v : (writeLockView = new WriteLockView())); } }
写锁的获取、释放
1. 写锁的获取
//获取排他锁,如果不能马上获取到,必要的时候会将其阻塞,writeLock方法不支持中断操作 public long writeLock() { long s, next; // bypass acquireWrite in fully unlocked case only //如果当前StampedLock的状态state为初始状态即256(100000000),&上255等于0,表明当前属于无锁状态,写锁可以获取成功,由于是对共享变量的操作,使用cas,进行变量的更新。否则&255不等于0表明当前处于有锁状态,调用acquireWrite方法,下面会介绍 return ((((s = state) & ABITS) == 0L && U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ? next : acquireWrite(false, 0L)); } //非阻塞的获取写锁,如果获取写锁失败返回stamp为0,如果当前处于无锁状态并且cas更新StampedLock的state属性成功,返回s+WBIT的stamp public long tryWriteLock() { long s, next; return ((((s = state) & ABITS) == 0L && U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ? next : 0L); } //超时的获取写锁,并且支持中断操作 public long tryWriteLock(long time, TimeUnit unit) throws InterruptedException { //将其时间转成纳秒 long nanos = unit.toNanos(time); if (!Thread.interrupted()) { long next, deadline; //先调用上面介绍的非阻塞tryWriteLock获取写锁,如果能加锁成功直接返回 if ((next = tryWriteLock()) != 0L) return next; //如果时间小于等于0直接返回,加写锁失败 if (nanos <= 0L) return 0L; //System.nanoTime()可能返回负,如果和传入的时间相加等于0,deadline等于1 if ((deadline = System.nanoTime() + nanos) == 0L) deadline = 1L; //调用下面介绍的acquireWrite方法,如果超时,返回的结果不是中断的值INTERRUPTED,加锁成功,返回对应的Stamp值(state+WBIT) if ((next = acquireWrite(true, deadline)) != INTERRUPTED) return next; } //否则抛出中断异常 throw new InterruptedException(); } //中断的获取写锁,获取不到写锁抛出中断异常 public long writeLockInterruptibly() throws InterruptedException { long next; //当前线程没有被中断,并且调用acquireWrite方法不是返回INTERRUPTED中断标志位,否则抛出中断异常,如果返回的标志位是0,也表示获取写锁失败 if (!Thread.interrupted() && (next = acquireWrite(true, 0L)) != INTERRUPTED) return next; //抛出中断异常 throw new InterruptedException(); } private long acquireWrite(boolean interruptible, long deadline) { WNode node = null, p; //将其当前节点,作为队列的尾节点,如果当前队列头结点和尾节点相等,并且StampedLock的状态属性state为WBIT,先自旋一段时间,如果自旋还是没有获取写锁成功,再将其作为队列的尾节点加入 for (int spins = -1;;) { // spin while enqueuing long m, s, ns; //如果当前state等于256,属于无锁状态,直接加写锁,如果加锁成功直接返回 if ((m = (s = state) & ABITS) == 0L) { if (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT)) return ns; } //如果spins小于0,并且当前的StampedLock属于写锁状态,以及头尾节点相等,spins赋值SPINS,让其当前线程自旋一段时间获取写锁 else if (spins < 0) spins = (m == WBIT && wtail == whead) ? SPINS : 0; else if (spins > 0) { // LockSupport.nextSecondarySeed() >= 0永真 if (LockSupport.nextSecondarySeed() >= 0) --spins; } //如果当前队列为空队列,即尾节点为空,初始化队列 else if ((p = wtail) == null) { // initialize queue //构造写模式的头节点 WNode hd = new WNode(WMODE, null); //如果当前头结点设置成功,将其尾节点设置为头结点 if (U.compareAndSwapObject(this, WHEAD, null, hd)) wtail = hd; } //如果当前节点为空,初始化当前节点,前驱节点为上一次的尾节点 else if (node == null) node = new WNode(WMODE, p); //如果尾节点已经改变,重新设置当前节点的前驱节点 else if (node.prev != p) node.prev = p; //将其当前节点设置为尾节点,如果写锁获取成功,直接退出 else if (U.compareAndSwapObject(this, WTAIL, p, node)) { p.next = node; break; } } //阻塞当前线程,再阻塞当前线程之前,如果头节点和尾节点相等,让其自旋一段时间获取写锁。如果头结点不为空,释放头节点的cowait队列 for (int spins = -1;;) { WNode h, np, pp; int ps; //头尾节点相等 if ((h = whead) == p) { //设置初始自旋的值 if (spins < 0) spins = HEAD_SPINS; //如果spins还是小于MAX_HEAD_SPINS,将其扩大2倍 else if (spins < MAX_HEAD_SPINS) spins <<= 1; //自旋获取写锁 for (int k = spins;;) { // spin at head long s, ns; //如果写锁设置成功,将其当前节点的前驱节点设置为空,并且将其节点设置为头节点 if (((s = state) & ABITS) == 0L) { if (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT)) { whead = node; node.prev = null; return ns; } } //LockSupport.nextSecondarySeed() >= 0永真,k做自减操作 else if (LockSupport.nextSecondarySeed() >= 0 && --k <= 0) break; } } //如果头结点不为空 else if (h != null) { WNode c; Thread w; //如果头结点的cowait队列(RMODE的节点)不为空,唤醒cowait队列 while ((c = h.cowait) != null) { //cowait节点和对应的节点都不为空唤醒其线程,循环的唤醒cowait节点队列中Thread不为空的线程 if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) && (w = c.thread) != null) //UnSafe的使用不了解的,可以看我的分享 U.unpark(w); } } //如果头结点不变 if (whead == h) { //如果当前节点的前驱节点和尾节点不一致,将p设置为当前节点的前驱节点,可以使节点往头节点移动,如果当前节点的前驱节点已经是头结点 if ((np = node.prev) != p) { if (np != null) //将其p设置为当前节点的前驱节点 (p = np).next = node; } //如果当前节点的前驱节点状态为0,将其前驱节点设置为等待状态 else if ((ps = p.status) == 0) U.compareAndSwapInt(p, WSTATUS, 0, WAITING); //如果当前节点的前驱节点状态为取消 else if (ps == CANCELLED) { //重新设置当前节点的前驱节点 if ((pp = p.prev) != null) { node.prev = pp; pp.next = node; } } else { long time; //如果传入的时间为0,阻塞直到UnSafe.unpark唤醒 if (deadline == 0L) time = 0L; //如果时间已经超时,取消当前的等待节点 else if ((time = deadline - System.nanoTime()) <= 0L) //取消节点的cancelWaiter方法下面会介绍 return cancelWaiter(node, node, false); //获取当前线程 Thread wt = Thread.currentThread(); //设置线程Thread的parkblocker属性,表示当前线程被谁阻塞,用于监控线程使用 U.putObject(wt, PARKBLOCKER, this); //将其当前线程设置为当前节点 node.thread = wt; //当前节点的前驱节点为等待状态,并且队列的头节点和尾节点不相等或者StampedLock当前状态为有锁状态,队列头节点不变,当前节点的前驱节点不变,阻塞当前线程 if (p.status < 0 && (p != h || (state & ABITS) != 0L) && whead == h && node.prev == p) //模拟阻塞当前线程,只有调用UnSafe.unpark()唤醒,如果time不等于0,时间到也会自动唤醒 U.park(false, time); //当前节点的线程置为空 node.thread = null; //当前线程的监控对象也置为空 U.putObject(wt, PARKBLOCKER, null); //如果传入的参数interruptible为true,并且当前线程中断,取消当前节点 if (interruptible && Thread.interrupted()) //取消节点的cancelWaiter方法下面会介绍 return cancelWaiter(node, node, true); } } } //取消等待节点 private long cancelWaiter(WNode node, WNode group, boolean interrupted) { //node和group为同一节点,要取消的节点,都不为空时 if (node != null && group != null) { Thread w; //将其当前节点的状态设置为取消状态 node.status = CANCELLED; //如果当前要取消节点的cowait队列不为空,将其cowait队列中取消的节点去除 for (WNode p = group, q; (q = p.cowait) != null;) { if (q.status == CANCELLED) { U.compareAndSwapObject(p, WCOWAIT, q, q.cowait); p = group; } else p = q; } //group和node为同一节点 if (group == node) { //唤醒状态没有取消的cowait队列中的节点 for (WNode r = group.cowait; r != null; r = r.cowait) { if ((w = r.thread) != null) U.unpark(w); } //将其当前取消节点的前驱节点的下一个节点设置为当前取消节点的next节点 for (WNode pred = node.prev; pred != null; ) { // unsplice WNode succ, pp; //如果当前取消节点的下一个节点为空或者是取消状态,从尾节点开始,寻找有效的节点 while ((succ = node.next) == null || succ.status == CANCELLED) { WNode q = null; // find successor the slow way //从尾节点开始寻找当前取消节点的下一个节点 for (WNode t = wtail; t != null && t != node; t = t.prev) if (t.status != CANCELLED) q = t; //如果当前取消节点的next节点和从尾节点寻找的节点相等,或者将其寻找的节点q设置为下一个节点成功 if (succ == q || U.compareAndSwapObject(node, WNEXT, succ, succ = q)) { //判断当前取消节点的曾经的下一个节点为空并且当前取消节点为尾节点 if (succ == null && node == wtail) //将其尾节点设置为当前取消节点的前驱节点 U.compareAndSwapObject(this, WTAIL, node, pred); break; } } //如果当前取消节点的前驱节点的下一节点为当前取消节点 if (pred.next == node) // unsplice pred link //将其前驱节点的下一节点设置为当前取消节点的next有效节点 U.compareAndSwapObject(pred, WNEXT, node, succ); //唤醒当前取消节点的下一节点,观察其新的前驱节点 if (succ != null && (w = succ.thread) != null) { succ.thread = null; U.unpark(w); } //如果当前取消节点的前驱节点状态不是取消状态,或者其前驱节点的前驱节点为空,直接退出循环 if (pred.status != CANCELLED || (pp = pred.prev) == null) break; //重新设置当前取消节点的前驱节点 node.prev = pp; //重新设置pp的下一节点 U.compareAndSwapObject(pp, WNEXT, pred, succ); //将其前驱节点设置为pp,重新循环 pred = pp; } } } WNode h; // Possibly release first waiter //头节点不为空 while ((h = whead) != null) { long s; WNode q; // similar to release() but check eligibility //头节点的下一节点为空或者是取消状态,从尾节点开始寻找有效的节点(包括等待状态,和运行状态) if ((q = h.next) == null || q.status == CANCELLED) { for (WNode t = wtail; t != null && t != h; t = t.prev) if (t.status <= 0) q = t; } //如果头节点没有改变 if (h == whead) { //头节点的下一有效节点不为空,并且头节点的状态为0,并且当前StampedLock的不为写锁状态,并且头节点的下一节点为读模式,唤醒头结点的下一节点 if (q != null && h.status == 0 && ((s = state) & ABITS) != WBIT && // waiter is eligible (s == 0L || q.mode == RMODE)) //唤醒头结点的下一有效节点,下面会介绍release方法 release(h); break; } } //如果当前线程被中断或者传入进来的interrupted为true,直接返回中断标志位,否则返回0 return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; } } //唤醒头结点的下一有效节点 private void release(WNode h) { //头结点不为空 if (h != null) { WNode q; Thread w; //如果头结点的状态为等待状态,将其状态设置为0 U.compareAndSwapInt(h, WSTATUS, WAITING, 0); //从尾节点开始,到头节点结束,寻找状态为等待或者0的有效节点 if ((q = h.next) == null || q.status == CANCELLED) { for (WNode t = wtail; t != null && t != h; t = t.prev) if (t.status <= 0) q = t; } //如果寻找到有效节点不为空,并且其对应的线程也不为空,唤醒其线程 if (q != null && (w = q.thread) != null) U.unpark(w); } }
2. 写锁的释放
//根据传入的stamp释放写锁 public void unlockWrite(long stamp) { WNode h; //如果当前StampedLock的锁状态state和传入进来的stamp不匹配或者传入进来的不是写锁标志位,往外抛出IllegalMonitorStateException异常 if (state != stamp || (stamp & WBIT) == 0L) throw new IllegalMonitorStateException(); /** * 释放写锁为什么不直接减去stamp,再加上ORIGIN,而是(stamp += WBIT) == 0L ? ORIGIN : stamp来释放写锁,位操作表示如下: * stamp += WBIT 即0010 0000 0000 = 0001 1000 0000 + 0000 1000 0000 * 这一步操作是重点!!!写锁的释放并不是像ReentrantReadWriteLock一样+1然后-1,而是通过再次加0000 1000 0000来使高位每次都产生变化,为什么要这样做?直接减掉0000 1000 0000不就可以了吗?这就是为了后面乐观锁做铺垫,让每次写锁都留下痕迹。 * 大家知道cas ABA的问题,字母A变化为B能看到变化,如果在一段时间内从A变到B然后又变到A,在内存中自会显示A,而不能记录变化的过程。在StampedLock中就是通过每次对高位加0000 1000 0000来达到记录写锁操作的过程,可以通过下面的步骤理解: * * 第一次获取写锁: * 0001 0000 0000 + 0000 1000 0000 = 0001 1000 0000 * 第一次释放写锁: * 0001 1000 0000 + 0000 1000 0000 = 0010 0000 0000 * 第二次获取写锁: * 0010 0000 0000 + 0000 1000 0000 = 0010 1000 0000 * 第二次释放写锁: * 0010 1000 0000 + 0000 1000 0000 = 0011 0000 0000 * 第n次获取写锁: * 1110 0000 0000 + 0000 1000 0000 = 1110 1000 0000 * 第n次释放写锁: * 1110 1000 0000 + 0000 1000 0000 = 1111 0000 0000 * * 可以看到第8位在获取和释放写锁时会产生变化,也就是说第8位是用来表示写锁状态的,前7位是用来表示读锁状态的,8位之后是用来表示写锁的获取次数的。这样就有效的解决了ABA问题,留下了每次写锁的记录,也为后面乐观锁检查变化提供了基础。 */ state = (stamp += WBIT) == 0L ? ORIGIN : stamp; //头结点不为空,并且头结点的状态不为0 if ((h = whead) != null && h.status != 0) //看上面release方法的介绍 release(h); } //无需传入stamp释放写锁 public boolean tryUnlockWrite() { long s; WNode h; //如果当前StampedLock的锁状态state不是写锁状态,直接返回释放失败 if (((s = state) & WBIT) != 0L) { //看上面解释 释放写锁为什么不直接减去stamp,再加上ORIGIN state = (s += WBIT) == 0L ? ORIGIN : s; //头结点不为空,并且头结点的状态不为0 if ((h = whead) != null && h.status != 0) //看上面release方法的介绍 release(h); return true; } return false; } //读写锁都可以释放,如果锁状态匹配给定的邮票,释放锁的相应模式,StampedLock的state处于乐观读时,不能调用此方法,因为乐观读不是锁 public void unlock(long stamp) { long a = stamp & ABITS, m, s; WNode h; // while (((s = state) & SBITS) == (stamp & SBITS)) { //如果当前处于无锁状态,或者乐观读状态,直接退出,抛出异常 if ((m = s & ABITS) == 0L) break; //如果当前StampedLock的状态为写模式 else if (m == WBIT) { //传入进来的stamp不是写模式,直接退出,抛出异常 if (a != m) break; //否则的话释放写锁,看上面解释为什么不是直接减去WBIT,再加上ORIGIN state = (s += WBIT) == 0L ? ORIGIN : s; //头结点不为空,并且头结点的状态不为0 if ((h = whead) != null && h.status != 0) //看看上面release方法的介绍 release(h); return; } //如果传入进来的状态是无锁模式,或者是乐观读模式,直接退出,抛出异常 else if (a == 0L || a >= WBIT) break; //如果处于读锁模式,并且读锁没有溢出 else if (m < RFULL) { //cas操作使StampedLock的state状态减1,释放一个读锁,失败时,重新循环 if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { //如果当前读锁只有一个,并且头结点不为空,并且头结点的状态不为0 if (m == RUNIT && (h = whead) != null && h.status != 0) //看上面的release方法介绍 release(h); return; } } //tryDecReaderOverflow方法看下面介绍 else if (tryDecReaderOverflow(s) != 0L) return; } //抛出异常 throw new IllegalMonitorStateException(); } private long tryDecReaderOverflow(long s) { //如果当前StampedLock的state的读模式已满,s&ABITS为126 if ((s & ABITS) == RFULL) { //先将其state设置为127 if (U.compareAndSwapLong(this, STATE, s, s | RBITS)) { int r; long next; //如果当前readerOverflow(记录溢出的读锁个数)大于0 if ((r = readerOverflow) > 0) { //readerOverflow做减1操作 readerOverflow = r - 1; //将其next设置为原来的state next = s; } else //否则的话,将其当前的state做减1操作 next = s - RUNIT; //将其state设置为next state = next; return next; } } //如果当前线程随机数&上7要是等于0,线程让步 else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0) Thread.yield(); return 0L; }
读锁的获取和释放
1. 读锁的获取
//获取非排它性锁,读锁,如果获取不到读锁,阻塞直到可用,并且该方法不支持中断操作 public long readLock() { long s = state, next; //如果头结点和尾节点相等(因为如果StampedLock只有非排他性锁,读锁或者乐观读,队列中只存在一个相同的节点),并且目前的读锁个数小于126,然后cas进行state的加1操作,如果获取成功直接退出,否则执行acquireRead方法 return ((whead == wtail && (s & ABITS) < RFULL && U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ? next : acquireRead(false, 0L));//acquireRead方法会在下面进行介绍 } //非阻塞的获取非排他性锁,读锁,如果获取成功直接返回stamp的long值,否则返回0 public long tryReadLock() { for (;;) { long s, m, next; //如果目前StampedLock的状态为写锁状态,直接返回0,获取读锁失败 if ((m = (s = state) & ABITS) == WBIT) return 0L; //如果当前状态处于读锁状态,并且读锁没有溢出 else if (m < RFULL) { //使用cas操作使state进行加1操作,如果cas成功,直接返回next,否则重新进行循环 if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) return next; } //如果读锁溢出,调用下面介绍的tryIncReaderOverflow方法,如果操作成功,直接返回,否则重新进行循环操作 else if ((next = tryIncReaderOverflow(s)) != 0L) return next; } } //记录溢出读锁的个数 private long tryIncReaderOverflow(long s) { //如果state&上255等于126 if ((s & ABITS) == RFULL) { // 使用cas操作将其state设置为127 if (U.compareAndSwapLong(this, STATE, s, s | RBITS)) { //将其记录额外溢出的读锁个数进行加1操作 ++readerOverflow; //将其state重新置为原来的值 state = s; //返回传进来的值 return s; } } //如果当前线程的随机数增加操作&上7等于0,将其线程进行让步操作 else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0) Thread.yield(); //否则直接返回0失败 return 0L; } //超时的获取非排他性锁,读锁,并且支持中断操作 public long tryReadLock(long time, TimeUnit unit) throws InterruptedException { long s, m, next, deadline; long nanos = unit.toNanos(time); //如果当前线程没有被中断 if (!Thread.interrupted()) { //并且当前StampedLock的状态不处于写锁状态 if ((m = (s = state) & ABITS) != WBIT) { //并且读锁没有溢出,使用cas操作state加1,如果成功直接返回 if (m < RFULL) { if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) return next; } //如果读锁溢出,调用tryIncReaderOverflow方法,看上面的介绍 else if ((next = tryIncReaderOverflow(s)) != 0L) return next; } //如果超时时间小于等于0,直接返回0,获取读锁失败 if (nanos <= 0L) return 0L; //如果System.nanoTime加上nanos等于0,将其deadline时间设置为1,因为System.nanoTime可能为负数 if ((deadline = System.nanoTime() + nanos) == 0L) deadline = 1L; //如果调用acquireRead方法返回不是中断的标志位INTERRUPTED,直接返回,next不等于0获取读锁成功,否则获取读锁失败 if ((next = acquireRead(true, deadline)) != INTERRUPTED) return next; } //抛出中断异常 throw new InterruptedException(); } //获取非排它性锁,读锁,如果获取不到读锁,阻塞直到可用,并且该方法支持中断操作 public long readLockInterruptibly() throws InterruptedException { long next; //如果当前线程没有被中断,并且调用acquireRead方法没有返回被中断的标志位INTERRUPTED回来,直接退出,next值不等于0获取读锁成功 if (!Thread.interrupted() && (next = acquireRead(true, 0L)) != INTERRUPTED) return next; //抛出被中断异常 throw new InterruptedException(); } //支持中断和超时的获取读锁,这个方法主要做的事情包括,如果头结点和尾节点相等,自旋一段时间,获取读锁,否则的话,如果队列为空,构建头尾节点,如果当前队列头节点和尾节点相等或者是当前StampedLock处于写锁状态,初始化当前节点,将其设置成尾节点。如果头结点的cwait队列不为空,唤醒cwait队列的线程,将其当前节点阻塞,直到被唤醒可用 private long acquireRead(boolean interruptible, long deadline) { WNode node = null, p; //如果头结点和尾节点相等,先让其线程自旋一段时间,如果队列为空初始化队列,生成头结点和尾节点。如果自旋操作没有获取到锁,并且头结点和尾节点相等,或者当前stampedLock的状态为写锁状态,将其当前节点加入队列中,如果加入当前队列失败,或者头结点和尾节点不相等,或者当前处于读锁状态,将其加入尾节点的cwait中,如果头结点的cwait节点不为空,并且线程也不为空,唤醒其cwait队列,阻塞当前节点 for (int spins = -1;;) { WNode h; //如果头尾节点相等,先让其自旋一段时间 if ((h = whead) == (p = wtail)) { for (long m, s, ns;;) { //如果当前StampedLock的state状态为读锁状态,并且读锁没有溢出,使用cas操作state进行加1操作 if ((m = (s = state) & ABITS) < RFULL ? U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L))//否则当前处于读锁,并且读锁溢出,调用tryIncReaderOverflow方法,看上面的此方法的介绍 return ns; //如果当前状态处于写锁状态,或者大于写锁的状态 else if (m >= WBIT) { //spins大于0,让其做自减操作 if (spins > 0) { if (LockSupport.nextSecondarySeed() >= 0) --spins; } else { //如果自旋操作spins减到0 if (spins == 0) { WNode nh = whead, np = wtail; //如果头尾结点没有改变,或者新的头尾节点不相等,退出自旋 if ((nh == h && np == p) || (h = nh) != (p = np)) break; } //赋予自旋的初始值 spins = SPINS; } } } } //如果尾节点为空,初始化队列 if (p == null) { // initialize queue //构造头结点 WNode hd = new WNode(WMODE, null); //使用cas构造队列的头结点,如果成功,将其尾节点设置为头结点 if (U.compareAndSwapObject(this, WHEAD, null, hd)) wtail = hd; } //如果当前节点为空,构造当前节点 else if (node == null) node = new WNode(RMODE, p); //如果头结点和尾节点相等,或者当前StampedLock的state状态不为读锁状态 else if (h == p || p.mode != RMODE) { //如果当前节点的前驱节点不是尾节点,重新设置当前节点的前驱节点 if (node.prev != p) node.prev = p; //将其当前节点加入队列中,并且当前节点做为尾节点,如果成功,直接退出循环操作 else if (U.compareAndSwapObject(this, WTAIL, p, node)) { p.next = node; break; } } //将其当前节点加入尾节点的cowait队列中,如果失败,将其当前节点的cowait置为null else if (!U.compareAndSwapObject(p, WCOWAIT, node.cowait = p.cowait, node)) node.cowait = null; //如果当前队列不为空,当前节点不为空,并且头结点和尾节点不相等,并且当前StampedLock的状态为读锁状态,并且当前节点cas加入尾节点的cowait队列中失败 else { for (;;) { WNode pp, c; Thread w; //如果头结点的cowait队列不为空,并且其线程也不为null,将其cowait队列唤醒 if ((h = whead) != null && (c = h.cowait) != null && U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) && (w = c.thread) != null) // help release //唤醒cowait队列中的节点线程 U.unpark(w); //如果当前头结点为尾节点的前驱节点,或者头尾节点相等,或者尾节点的前驱节点为空 if (h == (pp = p.prev) || h == p || pp == null) { long m, s, ns; do { //判断当前状态是否处于读锁状态,如果是,并且读锁没有溢出,state进行cas加1操作 if ((m = (s = state) & ABITS) < RFULL ? U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L))//否则进行溢出操作,看上面tryIncReaderOverflow方法介绍 return ns; } while (m < WBIT);//当前StampedLock的state状态不是写模式,才能进行循环操作 } //如果头结点没有改变,并且尾节点的前驱节点不变 if (whead == h && p.prev == pp) { long time; //如果尾节点的前驱节点为空,或者头尾节点相等,或者尾节点的状态为取消 if (pp == null || h == p || p.status > 0) { //将其当前节点设置为空,退出循环 node = null; // throw away break; } //如果超时时间为0,会一直阻塞,直到调用UnSafe的unpark方法 if (deadline == 0L) time = 0L; //如果传入的超时时间已经过期,将当前节点取消,看上面cancelWaiter方法的介绍 else if ((time = deadline - System.nanoTime()) <= 0L) return cancelWaiter(node, p, false); //获取当前线程 Thread wt = Thread.currentThread(); //设置当前线程被谁阻塞的监控对象 U.putObject(wt, PARKBLOCKER, this); //将其当前节点的线程设置为当前线程 node.thread = wt; //如果头节点和尾节点的前驱节点不相等,或者当前StampedLock的state状态为写锁,并且头结点不变,尾节点的前驱节点不变 if ((h != pp || (state & ABITS) == WBIT) && whead == h && p.prev == pp) //调用UnSafe的park来进行阻塞当前线程 U.park(false, time); //将其当前节点的线程置为空 node.thread = null; //将其当前线程的监控对象置为空 U.putObject(wt, PARKBLOCKER, null); //如果传入进来的interruptible是要求中断的,并且当前线程被中断 if (interruptible && Thread.interrupted()) //看上面cancelWaiter方法的介绍 return cancelWaiter(node, p, true); } } } } //阻塞当前线程,再阻塞当前线程之前,如果头节点和尾节点相等,让其自旋一段时间获取写锁。如果头结点不为空,释放头节点的cowait队列 for (int spins = -1;;) { WNode h, np, pp; int ps; //如果头节点和尾节点相等 if ((h = whead) == p) { //自旋的初始值 if (spins < 0) spins = HEAD_SPINS; //如果spins小于MAX_HEAD_SPINS else if (spins < MAX_HEAD_SPINS) //将其spins进行扩大两倍 spins <<= 1; //自旋一段时间获取读锁 for (int k = spins;;) { // spin at head long m, s, ns; //如果当前状态为无锁或者读锁模式 if ((m = (s = state) & ABITS) < RFULL ? //state进行cas加1操作 U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : //如果当前state状态为读锁状态,并且读锁溢出,使用tryIncReaderOverflows方法进行溢出的读锁数累加 (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) { WNode c; Thread w; //将其节点设置为头结点 whead = node; //将其当前节点的前驱节点设置为空 node.prev = null; //如果当前节点的cowait队列不为空,循环的唤醒cowait队列中,线程不为空的线程 while ((c = node.cowait) != null) { if (U.compareAndSwapObject(node, WCOWAIT, c, c.cowait) && (w = c.thread) != null) U.unpark(w); } return ns; } //如果当前状态为写状态,采取自减操作 else if (m >= WBIT && LockSupport.nextSecondarySeed() >= 0 && --k <= 0) break; } } //如果头结点不为空 else if (h != null) { WNode c; Thread w; //头结点的cowait队列不为空,循环的唤醒的cowait队列中,线程不为空的节点的线程 while ((c = h.cowait) != null) { if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) && (w = c.thread) != null) //使用UnSafe进行唤醒 U.unpark(w); } } //如果头结点没改变 if (whead == h) { //如果当前节点的前驱节点不等于尾节点 if ((np = node.prev) != p) { //当前节点的前驱节点不为空 if (np != null) //将其p设置为当前节点的前驱节点,如果前面的节点已经被唤醒,将p设置为当前节点的前驱节点,有可能其前驱节点就是头结点,重新进行循环操作 (p = np).next = node; // stale } //如果当前节点的前驱节点的状态为0 else if ((ps = p.status) == 0) //将其当前节点的状态使用cas操作将其0替换为等待状态 U.compareAndSwapInt(p, WSTATUS, 0, WAITING); //如果当前节点的前驱节点已经取消,重新设置当前节点的前驱节点 else if (ps == CANCELLED) { if ((pp = p.prev) != null) { node.prev = pp; pp.next = node; } } else { long time; //如果超时时间为0,永久阻塞,直到调用UnSafe的unpark()方法 if (deadline == 0L) time = 0L; //如果当前时间已经过期,取消当前节点 else if ((time = deadline - System.nanoTime()) <= 0L) //cancelWaiter方法可以看上面对此方法的介绍 return cancelWaiter(node, node, false); //获取当前线程 Thread wt = Thread.currentThread(); //将其当前线程的监控对象设置为当前StampedLock,监控此线程被那个对象阻塞 U.putObject(wt, PARKBLOCKER, this); //将其当前线程设置为当前队列中的线程 node.thread = wt; //如果当前节点的前驱节点为等待状态,并且头尾节点不相等或者当前StampedLock的状态为写锁状态,并且头结点不变,当前节点的前驱节点不变 if (p.status < 0 && (p != h || (state & ABITS) == WBIT) && whead == h && node.prev == p) //调用UnSafe的park方法阻塞当前线程 U.park(false, time); //将其当前节点对应的线程置为空 node.thread = null; //将其当前线程的监控对象置为空 U.putObject(wt, PARKBLOCKER, null); //如果传入进来的参数interruptible为true,并且当前线程被中断 if (interruptible && Thread.interrupted()) //取消当前节点,cancelWaiter方法可以看上面对此方法的介绍 return cancelWaiter(node, node, true); } } } }
2. 读锁的释放
//传入stamp进行读锁的释放 public void unlockRead(long stamp) { long s, m; WNode h; for (;;) { //传进来的stamp和当前stampedLock的state状态不一致,或者当前处于乐观读、无锁状态,或者传进来的参数是乐观读、无锁的stamp,又或者当前状态为写锁状态,抛出非法的锁状态异常 if (((s = state) & SBITS) != (stamp & SBITS) || (stamp & ABITS) == 0L || (m = s & ABITS) == 0L || m == WBIT) throw new IllegalMonitorStateException(); //如果当前StampedLock的state状态为读锁状态,并且读锁没有溢出 if (m < RFULL) { //state使用cas进行减1操作 if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { //如果减一操作成功,并且当前处于无锁状态,并且头结点不为空,并且头结点的状态为非0状态 if (m == RUNIT && (h = whead) != null && h.status != 0) //可以看上面对release方法的介绍 release(h); break; } } //可以看下面对tryDecReaderOverflow方法的介绍,如果不等于0直接退出,否则重新进行循环 else if (tryDecReaderOverflow(s) != 0L) break; } } //读锁溢出进行减操作的方法 private long tryDecReaderOverflow(long s) { // assert (s & ABITS) >= RFULL; //如果当前状态s&上255等于126,当前读锁已满, if ((s & ABITS) == RFULL) { //将其当前state状态设置为127 if (U.compareAndSwapLong(this, STATE, s, s | RBITS)) { int r; long next; //如果设置成功,并且溢出的锁数目大于0,将其额外的读锁数进行减1操作 if ((r = readerOverflow) > 0) { readerOverflow = r - 1; //将其next设置为传上来的state状态 next = s; } else //如果当前溢出数为0,将其当前状态进行减1操作 next = s - RUNIT; //将其state重新设置为next state = next; return next; } } //如果当前线程的随机数&上7等于0,当前线程进行让步 else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0) //线程让步 Thread.yield(); return 0L; } //无需传入stamp进行释放读锁 public boolean tryUnlockRead() { long s, m; WNode h; //如果当前状态处于读锁状态,而不是乐观读状态,或者无锁状态,或者写锁状态 while ((m = (s = state) & ABITS) != 0L && m < WBIT) { //如果当前state处于读锁状态,并且读锁没有溢出 if (m < RFULL) { //stampedLock状态state使用cas进行减1操作,如果成功,跳出循环,直接返回 if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { //如果操作成功,并且当前状态处于无锁状态,并且头结点不为空,及头结点的状态不为0 if (m == RUNIT && (h = whead) != null && h.status != 0) //看上面对release方法的介绍 release(h); return true; } } //如果当前处于读锁模式,并且读锁溢出,使用上面介绍的tryDecReaderOverflow方法,如果返回非0,直接退出,返回成功,否则重新进行循环 else if (tryDecReaderOverflow(s) != 0L) return true; } return false; }
乐观读获取、校验
1. 乐观读的获取
public long tryOptimisticRead() { long s; //如果当前state不处于写模式返回s&SBITS,否则返回0失败 return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L; }
2. 乐观读的校验
public boolean validate(long stamp) { //在校验逻辑之前,会通过Unsafe的loadFence方法加入一个load内存屏障,目的是避免copy变量到工作内存中和StampedLock.validate中锁状态校验运算发生重排序导致锁状态校验不准确的问题,不清楚的话可以看下我分享的UnSafe使用的内存屏障介绍 U.loadFence(); //如果传入进来的stamp & SBITS和state & SBITS相等 return (stamp & SBITS) == (state & SBITS); }
锁的升级
1. 升级为写锁
//升级为写锁 public long tryConvertToWriteLock(long stamp) { long a = stamp & ABITS, m, s, next; //如果传入进来的stamp和当前StampedLock的状态相同 while (((s = state) & SBITS) == (stamp & SBITS)) { //如果当前处于无锁状态,或者乐观读状态 if ((m = s & ABITS) == 0L) { //传入进来的stamp不处于无锁或者乐观读状态,直接退出,升级失败 if (a != 0L) break; //获取state使用cas进行写锁的获取,如果获取写锁成功直接退出 if (U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) return next; } //如果当前stampedLock处于写锁状态 else if (m == WBIT) { //传入进来的stamp不处于写锁状态,直接退出 if (a != m) break; //否则直接返回当前处于写锁状态的stamp return stamp; } //如果当前只有一个读锁,当前状态state使用cas进行减1加WBIT操作,将其读锁升级为写锁状态 else if (m == RUNIT && a != 0L) { if (U.compareAndSwapLong(this, STATE, s, next = s - RUNIT + WBIT)) return next; } //否则直接退出 else break; } return 0L; }
2. 升级为读锁
public long tryConvertToReadLock(long stamp) { long a = stamp & ABITS, m, s, next; WNode h; //如果传入进来的stamp和当前StampedLock的状态相同 while (((s = state) & SBITS) == (stamp & SBITS)) { //如果当前StampedLock处于无锁状态或者乐观读状态 if ((m = s & ABITS) == 0L) { //如果传入进来的stamp不处于无锁或者乐观读状态,直接退出,升级读锁失败 if (a != 0L) break; //如果当前StampedLock处于读锁、无锁或者乐观读状态,并且读锁数没有溢出 else if (m < RFULL) { //state使用cas操作进行加1操作,如果操作成功直接退出 if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) return next; } //如果读锁溢出,调用tryIncReaderOverflow方法的溢出操作,此方法可以看上面的介绍,如果返回非0,表明升级读锁成功,直接退出 else if ((next = tryIncReaderOverflow(s)) != 0L) return next; } //如果当前StampedLock的state处于写锁状态,如果锁升级成功,直接返回,否则重新循环 else if (m == WBIT) { //传入进来的stamp不处于写锁状态 if (a != m) break; //释放写锁,上面在写锁的释放有解释,为什么不直接减去WBIT,再加上ORIGIN,释放写锁加读锁 state = next = s + (WBIT + RUNIT); //如果头结点不为空,并且头结点的状态不等于0 if ((h = whead) != null && h.status != 0) //释放头结点的下一个有效节点,在上面release方法时有介绍 release(h); return next; } //如果传进来的stamp是读锁状态,直接返回传进来的stamp else if (a != 0L && a < WBIT) return stamp; //否则直接退出 else break; } return 0L; }
3. 升级为乐观读
//升级为乐观读 public long tryConvertToOptimisticRead(long stamp) { long a = stamp & ABITS, m, s, next; WNode h; //通过Unsafe的loadFence方法加入一个load内存屏障,目的是避免copy变量到工作内存中和升级乐观读的中锁状态校验运算发生重排序导致锁状态校验不准确的问题,不清楚的话可以看下我分享的UnSafe使用的内存屏障介绍 U.loadFence(); for (;;) { //如果传入进来的stamp和当前的StampedLock的状态state不一致的话直接退出 if (((s = state) & SBITS) != (stamp & SBITS)) break; //如果当前处于无锁状态,或者乐观读状态 if ((m = s & ABITS) == 0L) { //如果传入进来的stamp不是无锁或者乐观读状态直接退出 if (a != 0L) break; return s; } //如果当前处于写锁状态 else if (m == WBIT) { //传入进来的stamp不是写锁状态,直接退出 if (a != m) break; //释放写锁,上面有解释为什么释放写锁的时候不是直接减去1再加上ORIGIN state = next = (s += WBIT) == 0L ? ORIGIN : s; //如果头结点不为空,并且头结点的状态不为0 if ((h = whead) != null && h.status != 0) //看上面release方法的介绍 release(h); return next; } //如果传入的进来stamp&上255等于0,或者大于写锁状态,直接退出 else if (a == 0L || a >= WBIT) break; //如果读锁没有溢出,StampedLock的状态state使用cas进行-1操作 else if (m < RFULL) { if (U.compareAndSwapLong(this, STATE, s, next = s - RUNIT)) { //如果状态cas操作完,变为无锁,并且头结点不为空,以及头结点的状态不为0 if (m == RUNIT && (h = whead) != null && h.status != 0) //看上面release方法的介绍 release(h); return next & SBITS; } } //如果读锁溢出,看上面对tryDecReaderOverflow方法的介绍 else if ((next = tryDecReaderOverflow(s)) != 0L) return next & SBITS; } return 0L; }
其余方法
//获取读锁的个数,传入当前StampedLock的状态state private int getReadLockCount(long s) { long readers; //如果当前的读锁有溢出 if ((readers = s & RBITS) >= RFULL) //返回的锁个数为RFULL加上溢出的锁个数 readers = RFULL + readerOverflow; //直接返回没有溢出的读锁数 return (int) readers; } //判断当前是否处于写锁状态 public boolean isWriteLocked() { //当前状态&上WBIT不为0,表明当前处于写锁状态 return (state & WBIT) != 0L; } //判断当前是否处于读锁状态 public boolean isReadLocked() { //当前状态&上127不等于0,因为WBIT为128,ORIGIN为256,表明当前处于读锁状态 return (state & RBITS) != 0L; } //获取当前读锁个数,调用上面getReadLockCount的私有方法 public int getReadLockCount() { return getReadLockCount(state); } //重写的toString方法 public String toString() { long s = state; //根据当前状态判断StampedLock为无锁、或者读锁、写锁状态 return super.toString() + ((s & ABITS) == 0L ? "[Unlocked]" : (s & WBIT) != 0L ? "[Write-locked]" : "[Read-locks:" + getReadLockCount(s) + "]"); } //返回读锁视图,返回上面介绍的内部类读锁视图 public Lock asReadLock() { ReadLockView v; //不为空直接返回,否则直接初始化 return ((v = readLockView) != null ? v : (readLockView = new ReadLockView())); } //返回写锁视图,返回上面介绍的内部类写锁视图 public Lock asWriteLock() { WriteLockView v; //不为空直接返回,否则直接初始化 return ((v = writeLockView) != null ? v : (writeLockView = new WriteLockView())); } //返回读写锁视图,返回上面介绍的内部类读写锁视图 public ReadWriteLock asReadWriteLock() { ReadWriteLockView v; //不为空直接返回,否则直接初始化 return ((v = readWriteLockView) != null ? v : (readWriteLockView = new ReadWriteLockView())); }
三、实例分析
假设现在有四个线程:ThreadA、ThreadB、ThreadC、ThreadD。操作如下:
//ThreadA调用writeLock, 获取写锁
//ThreadB调用readLock, 获取读锁
//ThreadC调用readLock, 获取读锁
//ThreadD调用writeLock, 获取写锁
//ThreadE调用readLock, 获取读锁
请转到:https://segmentfault.com/a/1190000015808032?utm_source=tag-newest 查看
四、总结
StampedLock的等待队列与RRW的CLH队列相比,有以下特点:
- 当入队一个线程时,如果队尾是读结点,不会直接链接到队尾,而是链接到该读结点的cowait链中,cowait链本质是一个栈;
- 当入队一个线程时,如果队尾是写结点,则直接链接到队尾;
- 唤醒线程的规则和AQS类似,都是首先唤醒队首结点。区别是StampedLock中,当唤醒的结点是读结点时,会唤醒该读结点的cowait链中的所有读结点(顺序和入栈顺序相反,也就是后进先出)。
另外,StampedLock使用时要特别小心,避免锁重入的操作,在使用乐观读锁时也需要遵循相应的调用模板,防止出现数据不一致的问题。
StampedLock
提供了乐观读锁,可取代ReadWriteLock
以进一步提升并发性能;
StampedLock
是不可重入锁。