JDK1.8之前, 锁已经那么多了, 为什么还要有StampedLock?
一般应用, 都是读多写少, ReentrantReadWriteLock 因读写互斥, 故读时阻塞写, 因而性能上上不去。 可能会使线程饥饿
官方代码例子
public class StampedLockDemo {
// 成员变量
private double x, y;
// 锁实例
private final StampedLock sl = new StampedLock();
// 排它锁-写锁(writeLock)
void move(double deltaX, double deltaY) {
long stamp = ();
try {
x += deltaX;
y += deltaY;
} finally {
(stamp);
}
}
// 乐观读锁
double distanceFromOrigin() {
// 尝试获取乐观读锁(1)
long stamp = ();
// 将全部变量拷贝到方法体栈内(2)
double currentX = x, currentY = y;
// 检查在(1)获取到读锁票据后,锁有没被其他写线程排它性抢占(3)
if (!(stamp)) {
// 如果被抢占则获取一个共享读锁(悲观获取)(4)
stamp = ();
try {
// 将全部变量拷贝到方法体栈内(5)
currentX = x;
currentY = y;
} finally {
// 释放共享读锁(6)
(stamp);
}
}
// 返回计算结果(7)
return (currentX * currentX + currentY * currentY);
}
// 使用悲观锁获取读锁,并尝试转换为写锁
void moveIfAtOrigin(double newX, double newY) {
// 这里可以使用乐观读锁替换(1)
long stamp = ();
try {
// 如果当前点在原点则移动(2)
while (x == 0.0 && y == 0.0) {
// 尝试将获取的读锁升级为写锁(3)
long ws = (stamp);
// 升级成功,则更新票据,并设置坐标值,然后退出循环(4)
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
// 读锁升级写锁失败则释放读锁,显示获取独占写锁,然后循环重试(5)
(stamp);
stamp = ();
}
}
} finally {
// 释放锁(6)
(stamp);
}
}
}
StampedLock的特点
所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为0表示获取失败,其余都表示成功;
所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
StampedLock是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
支持锁升级跟锁降级
可以乐观读也可以悲观读
使用有限次自旋,增加锁获得的几率,避免上下文切换带来的开销
乐观读不阻塞写操作,悲观读,阻塞写得操作
StampedLock的优点
相比于ReentrantReadWriteLock,吞吐量大幅提升
StampedLock的缺点
api相对复杂,容易用错
内部实现相比于ReentrantReadWriteLock复杂得多
StampedLock的原理
每次获取锁的时候,都会返回一个邮戳(stamp),相当于mysql里的version字段
释放锁的时候,再根据之前的获得的邮戳,去进行锁释放
使用stampedLock注意点
如果使用乐观读,一定要判断返回的邮戳是否是一开始获得到的,如果不是,要去获取悲观读锁,再次去读取