Java并发18-StampedLock
StampedLock模式
读多写少场景下,StampedLock
的性能比ReadWriteLock
性能更好。
```StampedLock```支持写锁,悲观读锁和乐观读。
`StampedLock` 的写锁和悲观读锁加锁成功后,会返回一个 `stamp`;解锁的时候,需要传入这个 `stamp`。
```java
final StampedLock sl = new StampedLock();
// 获取/释放悲观读锁示意代码
long stamp = sl.readLock();
try {
//省略业务相关代码
} finally {
sl.unlockRead(stamp);
}
// 获取/释放写锁示意代码
long stamp = sl.writeLock();
try {
//省略业务相关代码
} finally {
sl.unlockWrite(stamp);
}
StampedLock
性能好的原因是因为它支持乐观读,乐观读即无锁。
官方示例展示
class Point {
private int x, y;
final StampedLock sl = new StampedLock();
//计算到原点的距离
int distanceFromOrigin() {
// 乐观读
long stamp = sl.tryOptimisticRead();
// 读入局部变量,
// 读的过程数据可能被修改
int curX = x, curY = y;
//判断执行读操作期间,
//是否存在写操作,如果存在,
//则sl.validate返回false
if (!sl.validate(stamp)){
// 升级为悲观读锁
stamp = sl.readLock();
try {
curX = x;
curY = y;
} finally {
//释放悲观读锁
sl.unlockRead(stamp);
}
}
return Math.sqrt(curX * curX + curY * curY);
}
}
使用注意事项
StampedLock
的功能仅仅是ReadWriteLock
的子集,StampedLock
不支持重入。
StampedLock
的悲观读锁、写锁都不支持条件变量。
使用StampedLock
一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()
和写锁 writeLockInterruptibly()
。
final StampedLock lock = new StampedLock();
Thread T1 = new Thread(()->{
// 获取写锁
lock.writeLock();
// 永远阻塞在此处,不释放写锁
LockSupport.park();
});
T1.start();
// 保证T1获取写锁
Thread.sleep(100);
Thread T2 = new Thread(()->
//阻塞在悲观读锁
lock.readLock()
);
T2.start();
// 保证T2阻塞在读锁
Thread.sleep(100);
//中断线程T2
//会导致线程T2所在CPU飙升
//内部实现里while循环里面对中断的处理有点问题
T2.interrupt();
T2.join();
使用模板
StampedLock
读模板:
final StampedLock sl = new StampedLock();
// 乐观读
long stamp = sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验stamp
if (!sl.validate(stamp)){
// 升级为悲观读锁
stamp = sl.readLock();
try {
// 读入方法局部变量
.....
} finally {
//释放悲观读锁
sl.unlockRead(stamp);
}
}
//使用方法局部变量执行业务操作
......
StampedLock
写模板:
long stamp = sl.writeLock();
try {
// 写共享变量
......
} finally {
sl.unlockWrite(stamp);
}
思考
找BUG
StampedLock
支持锁的降级(tryConvertToReadLock()
)和升级(tryConvertToWriteLock()
)
private double x, y;
final StampedLock sl = new StampedLock();
// 存在问题的方法
void moveIfAtOrigin(double newX, double newY){
long stamp = sl.readLock();
try {
while(x == 0.0 && y == 0.0){
//tryConvertToWriteLock(stamp)这个方法内部会释放悲观读锁stamp
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
x = newX;
y = newY;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
在锁升级成功的时候,最后没有释放最新的写锁,可以在if
块的break
上加个stamp=ws
进行释放。
别人的总结
StampedLock
读模板,先通过乐观读或者悲观读锁获取变量,然后利用这些变量处理业务逻辑,会不会存在线程安全的情况? 比如,读出来的变量没问题,但是进行业务逻辑处理的时候,这时读出的变量有可能发生变化了(比如被写锁改写了)?所以当使用乐观读锁时,是不是等业务都处理完了(比如先利用变量把距离计算完),再判断变量是否被改写,如果没改写,直接return;如果已经改写,则使用悲观读锁做同样的事情。不过如果业务比较耗时,可能持有悲观锁的时间会比较长?——Presley
两种场景,如果处理业务需要保持互斥,那么就用互斥锁,如果不需要保持互斥才可以用读写锁。
一般来讲缓存是不需要保持互斥性的,能接受瞬间的不一致。