StampedLock
1. StampedLock
- ReentrantReadWriteLock 的性能已经很好了但是他底层还是需要进行一系列的cas操作去加锁;
- StampedLock如果是读锁上锁是没有这种cas操作的,性能比ReentrantReadWriteLock 更好
- StampedLock控制锁有三种模式(写,悲观读,乐观读)
- 乐观读锁,即读获取锁的时候,是不加锁的,直接返回一个值;然后执行业务代码后验证这个值是否有被人修改(写操作加锁)
- 如果没有被人修改则返回业务代码的处理结果;如果被人修改了则需要升级为悲观读锁,重新执行业务代码,变为读写锁模式
注意:
这种模式必须在tryOptimisticRead和validate之间做业务处理,否则可能会导致数据不一致;因为如果在validate后有大量业务代码,这时数据可能被写锁修改,且读操作也没有任何保护措施。
- 基本语法
//获取戳(不存在锁)
long stamp = lock.tryOptimisticRead();
//执行业务代码
//验证戳
if(lock.validate(stamp)){
//返回 业务代码结果
}
//如果没有返回则表示被人修改了 需要升级成为readLock
lock.readLock();
2. 创建一个数据容器DataContainer
package org.example.StampedLock;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
/**
* 一个数据容器
* * 不支持重入 * 不支持条件
*/
@Slf4j
public class DataContainer {
int i;
//记录被修改后的戳
long stampw = 0;
public void setI(int i) {
this.i = i;
}
private final StampedLock lock = new StampedLock();
@SneakyThrows
public int read() {
//尝试一次乐观读
long stamp = lock.tryOptimisticRead();
log.info("乐观读拿到的戳{}", stamp);
//1s后验戳
TimeUnit.SECONDS.sleep(1);
/**
*此处写业务代码,业务处理后,验证
*/
//睡眠1s后,写锁来了,一定验证失败
if (!lock.validate(stamp)) {
log.info("验戳失败,被写线程改变了,stampw:{}", stampw);
//锁升级
try {
stamp = lock.readLock();
log.info("升级完成,加锁成功,stamp: {}, i:{}", stamp, i);
TimeUnit.SECONDS.sleep(1);
/**
*此处写业务代码
*/
log.info("升级读锁完毕,stamp:{}, i:{}", stamp, i);
return i;
} finally {
log.info("升级锁解锁,stamp:{}", stamp);
lock.unlockRead(stamp);
}
}
log.info("验戳完毕,stamp:{},i:{}", stamp, i);
return i;
}
@SneakyThrows
public void write(int i) {
//cas 加锁
stampw = lock.writeLock();
log.info("写锁加锁成功,stampw:{}", stampw);
try {
this.i = i;
TimeUnit.SECONDS.sleep(5);
} finally {
log.info("写锁解锁,stampw:{},i:{}", stampw, i);
lock.unlockWrite(stampw);
}
}
}
3. 创建测试类StampedLockTest
package org.example.StampedLock;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class StampedLockTest {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
dataContainer.setI(1);
//读
new Thread(() -> {
int i = dataContainer.read();
log.info("i:" + i);
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
//写
new Thread(() -> {
dataContainer.write(9);
}, "t2").start();
}
}
4. 查看打印
- 乐观读先拿到锁,睡眠1s,写锁加锁成功,stamp改变,乐观读验证失败,等待写锁释放后,乐观读升级到悲观读,stamp又改变一次,执行业务代码,释放悲观读。
5. 能否替代ReentrantReadWriteLock ?
- 否
1、不支持重入
2、不支持条件队列