第二部分:并发工具类18->StampedLock:比读写锁更快的锁
1.StampedLock
读多写少的场景,StampedLock性能比读写锁更好
2.StampedLock 支持的三种锁模式
读写锁,2种模式,读锁,写锁
stampedLock是3种模式,写锁,悲观锁,乐观锁
写锁,悲观锁的语意和ReadWriteLock的写锁,读锁,语意类似,允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的
不同之处StampedLock的写锁和悲观读锁加锁成功后都会返回一个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);
}
性能好的的关键是stampedLock支持乐观读方式,ReadWriteLock支持多个线程同时读
多个线程同时读的时候,所有的写操作会被阻塞;
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);
}
}
乐观读期间,存在写操作,会把乐观读升级为悲观读锁
3.理解乐观读
数据库的乐观锁场景
version字段,每次更新product_doc表,都将version字段加1,展示的时候,version字段和其他业务字段一起返回给生产订单ui
select id,... ,version
from product_doc
where id=777
用户保存的时候,利用sql 更新订单,假设该生产订单version=9
update product_doc
set version=version+1,...
where id=777 and version=9
如果执行成功,并返回修改条数为1,那么说明从生产订单ui到执行操作,保存数据期间,没有其他人修改过这条数据,因为如果其他人修改过这条数据,那么版本一定大于9
4.数据库的乐观锁
查询的时候把version字段查出来,更新的时候利用version字段做验证。
这个version字段类似于stampedLock中的stamp
5.stampedLock使用注意事项
stampedLock没有添加Reentrant前缀,不可重入的锁
stampedLock的悲观读锁,写锁不支持条件变量
坑:如果线程阻塞在stampedLock的readLock或者writeLock时,调用interrupt方法时,会导致cpu飙升.使用stampedLock的时候一定不要使用interrupt
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();
6.StampedLock的官方实例
final StampedLock sl =
new StampedLock();
// 乐观读
long stamp =
sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验stamp
if (!sl.validate(stamp)){
// 升级为悲观读锁
stamp = sl.readLock();
try {
// 读入方法局部变量
.....
} finally {
//释放悲观读锁
sl.unlockRead(stamp);
}
}
//使用方法局部变量执行业务操作
......