1 简介
该类自JDK8加入,进一步优化了读性能,它的特点是使用读锁、写锁时都必须配合戳使用。
常见3种模式:读、写、乐观读。
- 加解读锁典型代码:
long stamp = lock.readLock();
lock.unlockRead(stamp);
- 加解写锁典型代码:
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
-
乐观读:StampedLock支持tryOptimisticRead()(乐观读),获取戳后需要做一次戳校验,如果校验通过,表示这期间没有写操作,数据安全可以使用;否则需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead(); if(!lock.validate(stamp)) { // 锁升级 }
2 简单应用
通过构建一个数据容器类,提供读取数据和写入数据的方法。代码如下:
@Slf4j(topic = "c.TestStampedLock")
public class TestStampedLock {
public static void main(String[] args) {
DataContainerStamped containerStamped = new DataContainerStamped(1);
new Thread(() -> {
containerStamped.read(1);
}, "t1").start();
new Thread(() -> {
containerStamped.write(0);
}, "t2").start();
}
}
@Slf4j(topic = "c.DataContainerStamped")
class DataContainerStamped {
private int data;
private final StampedLock lock = new StampedLock();
public DataContainerStamped(int data) {
this.data = data;
}
public int read(int readTime) {
long stamp = lock.tryOptimisticRead();
log.debug("optimistic read lock {}", stamp);
if (lock.validate(stamp)) {
log.debug("read finish {}", stamp);
return this.data;
}
log.debug("updating reading lock {}", stamp);
try {
stamp = lock.readLock();
log.debug("read lock {}", stamp);
TimeUnit.SECONDS.sleep(readTime);
log.debug("read finish {}", stamp);
return this.data;
} catch (InterruptedException e) {
e.printStackTrace();
return 0;
} finally {
log.debug("read unlock {}", stamp);
lock.unlockRead(stamp);
}
}
public void write(int newData) {
long stamp = lock.writeLock();
log.debug("write lock {}", stamp);
try {
TimeUnit.SECONDS.sleep(5);
this.data = newData;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.debug("write unlock {}", stamp);
lock.unlockWrite(stamp);
}
}
}
3 源码分析
StampedLock没有实现Lock或者ReadWriteLock接口,也没有内置AQS锁。
下面对一些属性做下简单说明:
private static final int NCPU = Runtime.getRuntime().availableProcessors();
/** Maximum number of retries before enqueuing on acquisition */
private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0;
/** Maximum number of retries before blocking at head on acquisition */
private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 0;
/** Maximum number of retries before re-blocking */
private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 0;
/** The period for yielding when waiting for overflow spinlock */
private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1
/** The number of bits to use for reader count before overflowing */
private static final int LG_READERS = 7;
// Values for lock state and stamp operations
private static final long RUNIT = 1L;
private static final long WBIT = 1L << LG_READERS;
private static final long RBITS = WBIT - 1L;
private static final long RFULL = RBITS - 1L;
private static final long ABITS = RBITS | WBIT;
private static final long SBITS = ~RBITS; // note overlap with ABITS
// Initial value for lock state; avoid failure value zero
private static final long ORIGIN = WBIT << 1;
// Values for node status; order matters
private static final int WAITING = -1;
private static final int CANCELLED = 1;
// Modes for nodes (int not boolean to allow arithmetic)
private static final int RMODE = 0;
private static final int WMODE = 1;
结点模式:
- RMODE:0,读结点
- WMODE:1,写结点
结点状态:
- 默认:0
- WAITING:-1,阻塞状态
- CANCELLED:1,取消状态
锁状态(计数):
- LG_READERS:7,低7位用于读计数
- RUNIT:1L,读计数单位
- WBIT:1L << LG_READERS,即128,写计数单位
- RBITS:WBIT - 1L,即127,读计数掩码(上限)
- RFULL:RBITS - 1,即126,该类指定的读计数的最大容量
- ABITS:RBITS | WBIT,即255
- SBITS:~RBITS,低7位为0其他位为1的long型整数。
3.1 乐观读
3.1.1 tryOptimisticRead()
乐观读源代码如下:
public long tryOptimisticRead() {
long s;
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}
public StampedLock() {
state = ORIGIN;
}
private static final long ORIGIN = WBIT << 1;
private static final long WBIT = 1L << LG_READERS;
private static final int LG_READERS = 7;
- state默认值:256(10),赋值给s
- s&WBIT 结果为0,返回s&SBITS结果是s自己即256,即乐观读初始返回戳为256
tryOptimisticRead()要与validate()配合使用,即验戳。
3.1.2 validate()
验戳源代码如下:
public boolean validate(long stamp) {
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}
- 戳stamp要与state除低7位之外全部相同,validate()才会通过,即返回true
3.2 加解写锁
3.2.1 writeLock()加写锁
独占式获取锁,必要时阻塞直到锁可用。返回一个戳,用于释放锁。源代码如下:
public long writeLock() {
long s, next; // bypass acquireWrite in fully unlocked case only
return ((((s = state) & ABITS) == 0L &&
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : acquireWrite(false, 0L));
}
执行流程如下:
- 第一步判断((s = state) & ABITS) == 0L
- 只有未加任何锁(或完全解锁)情况下,才相等
- 通过cas方式加写锁,即改变锁state(计数)加1个写单位
- cas加锁成功返回当前state
- 失败执行acquireWrite()
3.2.2 acquireWrite()
获取写锁,源代码如下:
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 (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT))
return ns;
}
else if (spins < 0)
spins = (m == WBIT && wtail == whead) ? SPINS : 0;
else if (spins > 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;
}
}
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;;) { // 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;
}
}
else if (LockSupport.nextSecondarySeed() >= 0 &&
--k <= 0)
break;
}
}
else if (h != null) { // help release stale waiters
WNode c; Thread w;
while ((c = h.cowait) != null) {
if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
(w = c.thread) != null)
U.unpark(w);
}
}
if (whead == h) {
if ((np = node.prev) != p) {
if (np != null)
(p = np).next = node; // stale
}
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 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();
U.putObject(wt, PARKBLOCKER, this);
node.thread = wt;
if (p.status < 0 && (p != h || (state & ABITS) != 0L) &&
whead == h && node.prev == p)
U.park(false, time); // emulate LockSupport.park
node.thread = null;
U.putObject(wt, PARKBLOCKER, null);
if (interruptible && Thread.interrupted())
return cancelWaiter(node, node, true);
}
}
}
}
执行流程如下:
- 有2个for循环,第一个for循环用于在添加锁阻塞队列结点之前各种操作
- 第一步先判断m锁计数如果为0 ,表示没加锁
- 执行cas加锁,返回新的锁状态
- 第二步否则判断循环计数spins < 0,spins初始值-1
- 如果此时加写锁且锁队列不为空,spins赋值为 2 6 2^6 26,否则赋值为0
- 第三步判断spins > 0
- 一旦开始执行这里,那么循环执行 2 6 2^6 26次,spins才会减到0
- 第四步上述都不满足,判断队列是否为空
- 执行队列初始化,新建结点,whead,wtail指向该结点
- 第五部上述都不满足,判断node是否为空(node结点为当前线程阻塞准备的结点)
- 新建结点,链入队列
- 第六步确保结点前驱链接正常
- 第七步确保前驱结点的后继链接正常,且break循环
- 第一步先判断m锁计数如果为0 ,表示没加锁
- 第一个循环只有2个出口,见加粗部分
- 第二个for循环,执行当前线程阻塞和阻塞前的相关操作
- 判断如果(h = whead) == p即之前链接的node结点为除哨兵结点之外的第一个结点
- 执行和第一个for循环前面几步相似的操作
- 否则判断h不为空,清理废弃(无用)的结点
- 判断whead==h即这之间没有结点被清理或者唤醒
- 判断如果node结点的前驱如果不等于p清理
- 判断如果p节点的状态==0 ,默认是0
- cas把p结点的状态设置为WAITING,-1
- 判断如果p结点的状态为CANCELLED
- 直接断开链接,等待GC
- 上述都不符合,执行阻塞操作
- 获取当前线程,把node结点的thead标志置为当前线程
- 执行UNSAFE.park操作
- 判断如果(h = whead) == p即之前链接的node结点为除哨兵结点之外的第一个结点
注意事项:
- 阻塞队列结点状态处最后加入的结点状态为0外,其他结点状态为WAITING,-1
- CANCELLED除外
- 在添加第一个阻塞结点之前会多次for循环,再次获取锁
- 能不阻塞就不阻塞
- 唤醒阻塞线程之后,从阻塞的地方继续执行
3.2.3 unlockWrite()写锁解锁
源代码如下:
public void unlockWrite(long stamp) {
WNode h;
if (state != stamp || (stamp & WBIT) == 0L)
throw new IllegalMonitorStateException();
state = (stamp += WBIT) == 0L ? ORIGIN : stamp;
if ((h = whead) != null && h.status != 0)
release(h);
}
执行流程:
- 如果戳和锁状态不相等或者写锁计数==0 抛异常
- release()唤醒结点
3.2.4 release()
源代码如下:
private void release(WNode h) {
if (h != null) {
WNode q; Thread w;
U.compareAndSwapInt(h, WSTATUS, WAITING, 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);
}
}
逻辑相对简单,如果队列不为空,唤醒阻塞结点上的线程。
3.3 加解读锁
3.3.1 readLock()加读锁
源代码如下:
public long readLock() {
long s = state, next; // bypass acquireRead on common uncontended case
return ((whead == wtail && (s & ABITS) < RFULL &&
U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
next : acquireRead(false, 0L));
}
执行流程:
-
判断阻塞队列为空,且读锁计数小于读锁计数容量,cas锁状态置为+1
-
返回此时锁状态
-
有一个判断条件失败执行acquireRead()
-
3.3.2 acquireRead()
源代码如下:
private long acquireRead(boolean interruptible, long deadline) {
WNode node = null, p;
for (int spins = -1;;) {
WNode h;
if ((h = whead) == (p = wtail)) {
for (long m, s, ns;;) {
if ((m = (s = state) & ABITS) < RFULL ?
U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) :
(m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L))
return ns;
else if (m >= WBIT) {
if (spins > 0) {
if (LockSupport.nextSecondarySeed() >= 0)
--spins;
}
else {
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);
if (U.compareAndSwapObject(this, WHEAD, null, hd))
wtail = hd;
}
else if (node == null)
node = new WNode(RMODE, p);
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;
}
}
else if (!U.compareAndSwapObject(p, WCOWAIT,
node.cowait = p.cowait, node))
node.cowait = null;
else {
for (;;) {
WNode pp, c; Thread w;
if ((h = whead) != null && (c = h.cowait) != null &&
U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
(w = c.thread) != null) // help release
U.unpark(w);
if (h == (pp = p.prev) || h == p || pp == null) {
long m, s, ns;
do {
if ((m = (s = state) & ABITS) < RFULL ?
U.compareAndSwapLong(this, STATE, s,
ns = s + RUNIT) :
(m < WBIT &&
(ns = tryIncReaderOverflow(s)) != 0L))
return ns;
} while (m < WBIT);
}
if (whead == h && p.prev == pp) {
long time;
if (pp == null || h == p || p.status > 0) {
node = null; // throw away
break;
}
if (deadline == 0L)
time = 0L;
else if ((time = deadline - System.nanoTime()) <= 0L)
return cancelWaiter(node, p, false);
Thread wt = Thread.currentThread();
U.putObject(wt, PARKBLOCKER, this);
node.thread = wt;
if ((h != pp || (state & ABITS) == WBIT) &&
whead == h && p.prev == pp)
U.park(false, time);
node.thread = null;
U.putObject(wt, PARKBLOCKER, null);
if (interruptible && Thread.interrupted())
return cancelWaiter(node, p, true);
}
}
}
}
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;;) { // spin at head
long m, s, ns;
if ((m = (s = state) & ABITS) < RFULL ?
U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) :
(m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) {
WNode c; Thread w;
whead = node;
node.prev = null;
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;
while ((c = h.cowait) != null) {
if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
(w = c.thread) != null)
U.unpark(w);
}
}
if (whead == h) {
if ((np = node.prev) != p) {
if (np != null)
(p = np).next = node; // stale
}
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;
if (deadline == 0L)
time = 0L;
else if ((time = deadline - System.nanoTime()) <= 0L)
return cancelWaiter(node, node, false);
Thread wt = Thread.currentThread();
U.putObject(wt, PARKBLOCKER, this);
node.thread = wt;
if (p.status < 0 &&
(p != h || (state & ABITS) == WBIT) &&
whead == h && node.prev == p)
U.park(false, time);
node.thread = null;
U.putObject(wt, PARKBLOCKER, null);
if (interruptible && Thread.interrupted())
return cancelWaiter(node, node, true);
}
}
}
}
执行流程如下:和acquireWrite()有很多相似之处,这里主要讲解不同
- 第一个for循环
- 第一步判断阻塞队列是否为空
- 添加第一个阻塞结点前执行多次尝试获取锁
- 第二步判断p(wtail)为空,初始化阻塞队列
- 第三步判断node(新阻塞结点)为空,初始化node
- 第四步判断如果p==h(阻塞队列为空)或者p.mode != RMODE尾结点为写结点
- cas方式把node结点置为尾结点,结束循环
- 第五步上述条件不符合,cas方式把node结点链接到尾结点的WCOWAIT
- cas方式失败,node.cowait = null;
- 上述都不符合执行阻塞操作
- 第一步判断阻塞队列是否为空
- 第二for同acquireWrite()
注:
- 读读加锁不互斥
- 如果阻塞队列要阻塞读线程尾结点为读结点,那么该结点会链接到该尾结点的WCOWAIT
- WCOWAIT为链接读结点的单链表结构,一个释放,全部都会释放。
4 与ReentrantReadWriteLock比较
-
StampedLock配合戳使用,不支持锁重入,公平与非公平,不支持条件变量;进一步优化读性能,使用乐观读。
-
ReentrantReadWriteLock底层基于AQS同步锁实现,支持锁重入,有公平锁与费公平实现。写锁支持条件变量;
5 后记
如有问题,欢迎交流讨论。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/concurrent