读多写少的场景下,竟然还有比读写锁更牛X的锁?
1)上一篇文章我们聊了读写锁,他的适用场景是读多写少的场景下,那有没有其它性能比读写锁还要牛逼的锁呢?
-
StampedLock ,java1.8诞生的。
2)StampedLock比读写锁牛在什么地方?
-
读写锁分为两种:读锁和写锁
-
StampedLock有三种模式:写锁和悲观读锁,这两个对应我们的读写锁的写锁和读锁,功能是一样的。但是他的杀手锏是乐观读,注意是乐观读,不是乐观读锁。
-
乐观读是一种操作,不涉及到锁。当多个线程在读的时候,允许一个线程获取读锁,这个就是StampedLock与读写锁不同的地方。因为不涉及到锁,所以为了保障并发安全,会有一个stamp来作为安全的标志。类似于我们数据库乐观锁的version。
3)写锁和悲观读锁与我们读写锁的细致区别是什么?
-
他两加锁的时候会返回一个stamp,然后要解锁的话,需要带着这个stamp来。
final StampedLock sl =
new StampedLock();
// 获取/释放悲观读锁示意代码
long stamp = sl.readLock();
try {
//省略业务相关代码
} finally {
sl.unlockRead(stamp);
}
// 获取/释放写锁示意代码
long stamp = sl.writeLock();
try {
//省略业务相关代码
} finally {
sl.unlockWrite(stamp);
}
4)乐观读是怎样使用的?
-
tryOptimisticRead()就是乐观读,因为是无锁的,所所以共享变量 x 和 y 读入方法局部变量时,x 和 y 有可能被其他线程修改了。因此最后读完之后,还需要再次验证一下是否存在写操作,这个验证操作是通过调用 validate(stamp) 来实现的。
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);
}
}
-
上面的代码中,如果乐观读的时候,存在写操作,那么就把它升级为悲观读锁。这样就避免了乐观读一直循环浪费大量的cpu,使用的时候尽量这样去做。
5)StampedLock有哪些注意事项?
-
千万不要在线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时调用该阻塞线程的interrupt()方法,会导致运行这个线程的cpu挂掉的。如果实在要用中断方法,那就用带interrupt的悲观读锁 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飙升
T2.interrupt();
T2.join(); -
StampedLock的功能是不如读写锁的那么多的
-
StampedLock是不支持嵌套使用的,也就是可重入锁。
6)以后有用到StampedLock的需求的时候,使用的模板应该是怎样的?
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);
}
posted on 2022-03-29 20:55 Love&Share 阅读(169) 评论(0) 编辑 收藏 举报