文章目录
一.简介
读写锁(ReadWriteLock)允许多个线程同时读共享变量,适用于读多写少的场景,读多写少场景中比这更快的方案就是StampedLock,比读写锁性能好。
二.锁模式
ReadWriteLock支持两种模式:一种是读锁,一种是写锁,而StampedLock支持三种模式,分别:写锁、悲观读锁和乐观读。其中,写锁、悲观读锁的语义和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支持乐观读的方式,ReadWriteLock支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;而StampedLock提供的乐观读,是允许一个线程获取写锁的,也就是锁不是所有的写操作都被阻塞。(乐观读这个操作是无锁的)
@Data
public class Point {
private double x, y;//内部定义表示坐标点
private final StampedLock s1 = new StampedLock();//定义了StampedLock锁,
public Point(double x, double y) {
this.x = x;
this.y = y;
}
void move(double deltaX, double deltaY) {
long stamp = s1.writeLock();//这里的含义和distanceFormOrigin方法中 s1.readLock()是类似的
try {
x += deltaX;
y += deltaY;
} finally {
s1.unlockWrite(stamp);//退出临界区,释放写锁
}
}
double distanceFormOrigin() throws InterruptedException {//只读方法
long stamp = s1.tryOptimisticRead(); //试图尝试一次乐观读 返回一个类似于时间戳的邮戳整数stamp 这个stamp就可以作为这一个所获取的凭证
double currentX = x, currentY = y;//读取x和y的值,这时候我们并不确定x和y是否是一致的
Thread.sleep(1000);
if (!s1.validate(stamp)) {//判断这个stamp是否在读过程发生期间被修改过,如果stamp没有被修改过,责任无这次读取时有效的,因此就可以直接return了,反之,如果stamp是不可用的,则意味着在读取的过程中,可能被其他线程改写了数据,因此,有可能出现脏读,如果如果出现这种情况,我们可以像CAS操作那样在一个死循环中一直使用乐观锁,知道成功为止
stamp = s1.readLock();//也可以升级锁的级别,这里我们升级乐观锁的级别,将乐观锁变为悲观锁, 如果当前对象正在被修改,则读锁的申请可能导致线程挂起.
try {
currentX = x;
currentY = y;
System.out.println("currentX :"+currentX);
System.out.println("currentY :"+currentY);
} finally {
s1.unlockRead(stamp);//退出临界区,释放读锁
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
public static void main(String[] args) throws InterruptedException {
Point point = new Point(10, 10);
Thread t1 = new Thread(
()->{
try {
System.out.println("测试:" + point.distanceFormOrigin());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
Thread t2 = new Thread(()->{
point.move(20,20);
});
t1.start();
t2.start();
t2.join();
}
}
结果
currentX :30.0
currentY :30.0
测试:42.42640687119285
注意事项
对于读多写少的场景 StampedLock 性能很好,简单的应用场景基本上可以替代 ReadWriteLock,但是 StampedLock 的功能仅仅是 ReadWriteLock 的子集,在使用的时候,还是有几个地方需要注意一下。
- StampedLock不支持重入
- StampedLock 的悲观读锁、写锁都不支持条件变量,也就是没Condition。
- 如果是线程使用writeLock()或者readLock()获得锁之后,线程还没执行完就被interrupt()的话,会导致CPU飙升。(如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly())。
三.总结
读模板
final StampedLock sl =
new StampedLock();
// 乐观读
long stamp =
sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验stamp
if (!sl.validate(stamp)){
// 升级为悲观读锁
stamp = sl.readLock();
try {
// 读入方法局部变量
.....
} finally {
//释放悲观读锁
sl.unlockRead(stamp);
}
}
//使用方法局部变量执行业务操作
......
写模板
long stamp = sl.writeLock();
try {
// 写共享变量
......
} finally {
sl.unlockWrite(stamp);
}
参考
《Java并发编程实战》
公众号
微信号:bigdata_limeng