介绍
StampedLock为JDK1.8引入,可以认为它是读写锁(关于读写锁的介绍请看前一篇文章)的改进。读写锁使读与读之间并发,但读与写之间是阻塞的。如果有大量读线程会导致写线程一直阻塞从而可能引起写线程的“饥饿”。StampedLock则提供了一种乐观读策略,类似无锁操作,不会阻塞写线程。
StampedLock使用示例
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
//对x,y的修改,使用写锁
void move(double deltaX, double deltaxY){
//申请获取写锁,返回一个"邮戳"
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaxY;
} finally {
//使用申请写锁获得的"邮戳"进行释放锁
sl.unlockWrite(stamp);
}
}
//只读方法,先使用乐观读,失败则转化为悲观读
double distanceFromOrigin(){
//尝试获取一个乐观读
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
//判断validate在读取过程中是否发生修改
if(!sl.validate(stamp)){
//发生了冲突(在乐观读的时候有其它线程进行了修改),则升级锁级别为悲观锁进行读
//另外一种处理方式:也可以像处理CAS操作那样在一个死循环中一直使用乐观读,直到成功为止
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
//释放悲观读锁通过stamp
sl.unlockRead(stamp);
}
}
//乐观读获取成功则进行相关业务计算
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
通过上面的例子可以看出StampedLock通过引入了乐观读来提高系统并行。
StampedLock申请获取读锁相关API
//获取读锁(必要时阻塞,不响应中断)
public long readLock()
//尝试获取读锁,不阻塞,立即返回
public long tryReadLock()
//超时限制的获取读锁,响应中断(返回0则表示锁不可用,例如规定时间内未获取到)
public long tryReadLock(long time, TimeUnit unit)
throws InterruptedException
//获取读锁,必要时阻塞,响应中断
public long readLockInterruptibly() throws InterruptedException
StampedLock内部实现思想
StampedLock内部实现基于CLH锁。CLH锁是一种自旋锁,它保证没有饥饿发生,并且可以保证FIFO的服务顺序。
CLH锁的基本思想:
CLH锁会维护一个线程等待队列,申请锁但没成功的的线程会记录在这个队列中,每个节点都会保存一个locked标记位(判断当前线程是否释放锁).过程大致如下:
1.创建一个Node,将locked设置为true(表示需要获取锁)
2.线程Node添加到队列的尾部并获取一个指向其前序节点的引用
3.线程在其前序节点locked字段字段进行自旋,直到前序节点释放锁(locked设置为false)
4.前序节点将locked设置为false释放了锁,同时也会回收它的前序节点
StampedLock基于这种思想,但是实现上更加复杂。
StampedLock内部维护的等待链表队列结构如下:
//每个WNode代表一个等待线程
/** Wait nodes */
static final class WNode {
volatile WNode prev;
volatile WNode next;
//d读节点链表
volatile WNode cowait; // list of linked readers
volatile Thread thread; // non-null while possibly parked
//有没有取消 0:WAITING 1:CANCELLED
volatile int status; // 0, WAITING, or CANCELLED
//读写模式 0:读 1:写
final int mode; // RMODE or WMODE
WNode(int m, WNode p) { mode = m; prev = p; }
}
//CLH队列头部(指向链表头部)
/** Head of CLH queue */
private transient volatile WNode whead;
//CLH队列尾部(指向链表尾部)
/** Tail (last) of CLH queue */
private transient volatile WNode wtail;
//当前锁的状态
/** Lock sequence/state */
private transient volatile long state;
上面看到有一个重要字段state,用来表示当前锁的状态。它是一个64位的long整数,倒数第8位表示写锁状态(为1表示写锁占用)。末尾7位表示当前正在读取的线程数量(如果溢出,会使用readerOverflow进行统计),前面56位表示写锁释放的次数。
state变量结构如下:
state初始化:
/** The number of bits to use for reader count before overflowing */
private static final int LG_READERS = 7;
private static final long WBIT = 1L << LG_READERS;
//初始化将第8位设置为1(...1 0000 0000), ...表示前面高55位
// Initial value for lock state; avoid failure value zero
private static final long ORIGIN = WBIT << 1;
写锁的申请和释放
/**
* Exclusively acquires the lock, blocking if necessary
* until available.
*
* @return a stamp that can be used to unlock or convert mode
*/
public long writeLock() {
long s, next; // bypass acquireWrite in fully unlocked case only
return ((((s = state) & ABITS) == 0L && //是否有读写锁占用,没有的话等于0
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
//写锁标志位设置为1成功返回next,否则通过acquireWrite进行锁的获取
next : acquireWrite(false, 0L));
}
/**
* If the lock state matches the given stamp, releases the
* exclusive lock.
*
* @param stamp a stamp returned by a write-lock operation
* @throws IllegalMonitorStateException if the stamp does
* not match the current state of this lock
*/
public void unlockWrite(long stamp) {
WNode h;
//检查申请锁的邮戳是否发生了改变
if (state != stamp || (stamp & WBIT) == 0L)
throw new IllegalMonitorStateException();
//将state写标志位设置为0并增加释放锁的次数(如果释放锁的次数高56的限制,则设置为ORIGIN)
state = (stamp += WBIT) == 0L ? ORIGIN : stamp;
//头节点非空并且status不等于0(不等于0表示未释放锁)
if ((h = whead) != null && h.status != 0)
//唤醒队列中下一个线程
release(h);
}
读锁的申请和释放
/**
* Non-exclusively acquires the lock, blocking if necessary
* until available.
*
* @return a stamp that can be used to unlock or convert mode
*/
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)) ? //state读线程数量增加
//申请成功返回next,申请失败则 调用acquireRead继续申请
next : acquireRead(false, 0L));
}
/**
* If the lock state matches the given stamp, releases the
* non-exclusive lock.
*
* @param stamp a stamp returned by a read-lock operation
* @throws IllegalMonitorStateException if the stamp does
* not match the current state of this lock
*/
public void unlockRead(long stamp) {
long s, m; WNode h;
//自旋
for (;;) {
//检查当前state和stamp的状态是否正确
if (((s = state) & SBITS) != (stamp & SBITS) ||
(stamp & ABITS) == 0L || (m = s & ABITS) == 0L || m == WBIT)
throw new IllegalMonitorStateException();
if (m < RFULL) {
// 读线程数量小于最大值则state标志的读线程数量减1
if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) {
//读锁全部释放,唤醒下个节点
if (m == RUNIT && (h = whead) != null && h.status != 0)
release(h);
break;
}
}
//读线程溢出情况处理
else if (tryDecReaderOverflow(s) != 0L)
break;
}
}
StampedLockd的readLock()的缺陷
StampedLockd的readLock()会出现疯狂占用CPU的情况,如下示例所示:
public class StampedLockCPUTest {
static Thread[] holdCpuThreads = new Thread[3];
static final StampedLock lock = new StampedLock();
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
long readLong = lock.writeLock();
//模拟一直等待不释放锁
LockSupport.parkNanos(600000000000L);
lock.unlockWrite(readLong);
}
}).start();
Thread.sleep(100);
for (int i = 0; i< 3; i++) {
holdCpuThreads[i] = new Thread(new HoldCPUReadThread());
holdCpuThreads[i].start();
}
Thread.sleep(10000);
//线程中断后,会占用CPU
for(int i = 0; i < 3; i++){
holdCpuThreads[i].interrupt();
}
}
public static class HoldCPUReadThread implements Runnable {
@Override
public void run() {
long lockr = lock.readLock();
System.out.println(Thread.currentThread().getName() + "获得读锁");
lock.unlockRead(lockr);
}
}
}
上面示例可以看出,三个读锁最终会被挂起,挂起线程使用Unsafe.park()函数,在park()函数遇到中断的时候,不会抛出异常,而是直接返回。则程序会继续自旋,当又到park()函数的时候,由于中断标识依然存在,则park()函数又直接返回,程序又继续自旋...。这样自旋就陷入了死循环(达不到退出条件),就会出现疯狂占用CPU的情况.
对于这个情况,需要对中断进行处理,例如退出,抛出异常等。但在jdk8中并未处理,需要特别注意下.
Java高并发程序设计(第2版)