Java并发编程--锁原理之StampedLock锁
文章目录
- JDK 8 中新增的StampedLock锁的原理
- (1). 概述
- (2). 写锁writeLock
- (3). 悲观读readLock
- (4). 乐观读OptimisticRead()
- (5). 锁转换
- (6). 使用乐观读锁
JDK 8 中新增的StampedLock锁的原理
(1). 概述
StampedLock锁是并发包中JDK 8版本新增的一个锁,提供了三种模式的读写控制.当调用try系列方法尝试获取锁时,会返回一个long型的变量stamp(戳记).这个戳记代表了锁的状态,可以理解为乐观锁中的版本.释放锁的转换锁时都需要提供这个标记来判断权限.
特点:
- 所有获取锁的方法都会返回一个stamp戳记,0为失败,其他为获取成功
- 所有释放锁的方法都需要一个stamp戳记,必须和之前得到锁时的stamp一致
- 不可重入
有三种访问模式
- 乐观读
- 悲观读
- 写锁
- 写锁可以降级为读锁
都不支持条件队列
/* 等待链表队列,每一个WNode标识一个等待线程 /
static final class WNode {volatile WNode prev;
volatile WNode next;
volatile WNode cowait; // 读模式使用该节点形成栈
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; }
}
/** 锁队列状态,当处于写模式时第8位为1,读模式时前7为为1-126(读锁是共享锁,附加的readerOverflow用于当读者超过126时) */
private transient volatile long state;
/** 将state超过 RFULL=126的值放到readerOverflow字段中 */
private transient int readerOverflow;
(2). 写锁writeLock
独占锁,不可重入.请求该锁成功后会返回一个stamp戳记变量来表示该锁的版本.
writeLock():当完全没有加锁时,绕过acquireWrite,否则调用acquireWrite入队列获取锁资源,
- acquireWrite():入队自旋,并放到队列尾部,如果队列中只剩下一个结点,则在队头进一步自旋,最后会进入阻塞
unlockWrite():如果锁的状态与stamp相同,调用release()释放锁
- release():唤醒传入节点的后继节点
(3). 悲观读readLock
共享锁,不可重入
readLock():(队列为空&&没有写锁同时读锁数小于126&&CAS修改状态成功)则状态加1并返回,否则调用acquireRead()自旋获取读锁
- acquireRead():首先是入队自旋,如果队尾不是读模式则放到队列尾部,如果是读模式,则放到队尾的cowait中。如果队列中只剩下一个结点,则在队头进一步自旋.如果最终依然失败,则Unsafe().park()挂起当前线程。
- unlockRead():如果state匹配stamp,判断当前的共享次数,修改state或者readerOverflow
(4). 乐观读OptimisticRead()
在操作数据前并没有通过 CAS 设置锁的状态,仅仅是通过位运算测试
如果当前没有线程持有写锁,则简单的返回一个非 0 的 stamp 版本信息,获取该 stamp 后在具体操作数据前还需要调用 validate 验证下该 stamp 是否已经不可用,也就是看当调用 tryOptimisticRead 返回 stamp 后,到当前时间是否有其它线程持有了写锁,如果是那么 validate 会返回 0,否则就可以使用该 stamp 版本的锁对数据进行操作。
由于 tryOptimisticRead 并没有使用 CAS 设置锁状态,所以不需要显示的释放该锁。该锁的一个特点是适用于读多写少的场景,因为获取读锁只是使用位操作进行检验,不涉及 CAS 操作,所以效率会高很多,但是同时由于没有使用真正的锁,在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候可能其它写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性还是得到保障的。
/**
* 获取乐观读锁,返回stamp
*/
public long tryOptimisticRead() {
long s; //有写锁返回0,否则返回256
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}
/**
* 验证从调用tryOptimisticRead开始到现在这段时间内有无写锁占用过锁资源,有写锁获得过锁资源则返回false. stamp为0返回false.
* @return 从返回stamp开始,没有写锁获得过锁资源返回true,否则返回false
*/
public boolean validate(long stamp) {
//强制读取操作和验证操作在一些情况下的内存排序问题
U.loadFence();
//当持有写锁后再释放写锁,该校验也不成立,返回false
return (stamp & SBITS) == (state & SBITS);
}
(5). 锁转换
/**
* state匹配stamp时, 执行下列操作之一.
* 1、stamp 已经持有写锁,直接返回.
* 2、读模式,但是没有更多的读取者,并返回一个写锁stamp.
* 3、有一个乐观读锁,只在即时可用的前提下返回一个写锁stamp
* 4、其他情况都返回0
*/
public long tryConvertToWriteLock(long stamp) {
long a = stamp & ABITS, m, s, next;
//state匹配stamp
while (((s = state) & SBITS) == (stamp & SBITS)) {
//没有锁
if ((m = s & ABITS) == 0L) {
if (a != 0L)
break;
//CAS修改状态为持有写锁,并返回
if (U.compareAndSwapLong(this, STATE, s, next = s + WBIT))
return next;
}
//持有写锁
else if (m == WBIT) {
if (a != m)
//其他线程持有写锁
break;
//当前线程已经持有写锁
return stamp;
}
//有一个读锁
else if (m == RUNIT && a != 0L) {
//释放读锁,并尝试持有写锁
if (U.compareAndSwapLong(this, STATE, s, next = s - RUNIT + WBIT))
return next;
}else
break;
}
return 0L;
}
/**
* state匹配stamp时, 执行下列操作之一.
1、stamp 表示持有写锁,释放写锁,并持有读锁
2 stamp 表示持有读锁 ,返回该读锁
3 有一个乐观读锁,只在即时可用的前提下返回一个读锁stamp
4、其他情况都返回0,表示失败
*
*/
public long tryConvertToReadLock(long stamp) {
long a = stamp & ABITS, m, s, next; WNode h;
//state匹配stamp
while (((s = state) & SBITS) == (stamp & SBITS)) {
//没有锁
if ((m = s & ABITS) == 0L) {
if (a != 0L)
break;
else if (m < RFULL) {
if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT))
return next;
}
else if ((next = tryIncReaderOverflow(s)) != 0L)
return next;
}
//写锁
else if (m == WBIT) {
//非当前线程持有写锁
if (a != m)
break;
//释放写锁持有读锁
state = next = s + (WBIT + RUNIT);
if ((h = whead) != null && h.status != 0)
release(h);
return next;
}
//持有读锁
else if (a != 0L && a < WBIT)
return stamp;
else
break;
}
return 0L;
}
(6). 使用乐观读锁
使用乐观读要保证以下顺序:
// 获取版本信息(乐观锁)
long stamp = lock.tryOptimisticRead();
// 复制变量到本地堆栈
copyVaraibale2ThreadMemory();
// 校验,如果校验失败,说明此处乐观锁使用失败,申请悲观读锁
if(!lock.validate(stamp)){
// 申请悲观读锁
long stamp = lock.readLock();
try{
// 复制变量到本地堆栈
copyVaraibale2ThreadMemory();
}finally{
// 使用完之后释放锁
lock.unlock();
}
}
最后用一幅图加深理解
还没有评论,来说两句吧...