简介
StampedLock
是jdk1.8
中新增的一个类,是一种根据时间戳实现的锁机制,主要作用是为了提升读操作的性能。基本原理是加读锁、写锁都会生成一个时间戳,而在读取数据之前可以通过验证时间戳的方式来判断中途是否加了写锁,如果没有其他线程加写锁,那就可以直接读取数据而不用读锁了,这就是乐观读。
使用
下面通过一个读读操作和读写操作分别演示下StampedLock
的使用步骤及效果
@Slf4j
class StampedLockData {
private final StampedLock lock = new StampedLock();
private int data;
public StampedLockData(int data) {
this.data = data;
}
// 读操作
public int read() {
long stamp = lock.tryOptimisticRead();
log.info("乐观读 {}", stamp);
sleep(1000);
if (lock.validate(stamp)) {
log.info("验证时间戳成功 {}", stamp);
return data;
}
log.info("升级成读锁 {}", stamp);
try {
stamp = lock.readLock();
return data;
} finally {
log.info("释放读锁 {}", stamp);
lock.unlock(stamp);
}
}
// 写操作
public void write(int newData) {
long stamp = lock.writeLock();
log.info("加写锁 {}", stamp);
try {
sleep(2000);
this.data = newData;
} finally {
log.info("释放写锁 {}", stamp);
lock.unlock(stamp);
}
}
// 睡眠
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
上面定义了一个StampedLockData
类用来对其中的data
属性进行共享操作,StampedLock
类型的属性用来加读写锁。
read
方法用来读取data
的值,开始先尝试乐观读生成一个时间戳stamp
,如果stamp
验证成功了就直接返回data
,如果失败了就升级为加读锁再读取data
返回。
write
方法是对data
进行写操作,首先加写锁writeLock
,然后再修改data
的值。
sleep
方法进行睡眠,主要是为了模拟出在乐观读的过程中如果有其他线程加了写锁的效果。
读读操作
public static void main(String[] args) {
StampedLockData stampedLockData = new StampedLockData(0);
new Thread(() -> {
stampedLockData.read();
}, "t1").start();
new Thread(() -> {
stampedLockData.read();
}, "t2").start();
}
两个线程同时执行
read
方法时,都是执行的乐观读并没有升级成读锁,而且时间戳也没有发生改变,验证了读读并发。
读写操作
public static void main(String[] args) {
StampedLockData stampedLockData = new StampedLockData(0);
new Thread(() -> {
stampedLockData.read();
}, "t1").start();
new Thread(() -> {
stampedLockData.write(10);
}, "t2").start();
}
两个线程分别进行读、写操作,可以看到
t1
在乐观读之后t2
加了写锁,此时时间戳变成了384,所以时间戳验证失败了,t1
就升级成了读锁,并且在t2
释放写锁之后t1
才获取到读锁执行任务,最后释放掉读锁,验证了读写互斥。
优缺点
StampedLock
的优点是在读操作多的情况下可以提高并发效率,缺点是不支持条件变量和重入。