download:前端性能优化,掌握行业实用、专业、前沿的解决方案
StampedLock读写锁
“StampedLock简介”
StampedLock的状态由版本和模式组成。get lock方法返回一个表示和控制对锁状态的访问的戳。
StampedLock提供三种模式来控制访问锁:
写入模式
获取写锁,这是独占的。当锁处于写模式时,您无法获得读锁,并且所有乐观读验证都将失败。
writeLock():
阻塞等待锁的独占获取,并返回一个戳。如果为0,则采集失败。
TryWriteLock():`尝试获取一个写锁并返回一个时间戳。如果为0,则采集失败。
Long trywritelock
(长时间,时间单位单位):当试图获取一个独占写锁时,可以等待一个事件并返回一个stamp。如果为0,则采集失败。 long writelockinterrupt():
尝试获取一个独占的写锁,它可以被中断并返回一个时间戳。如果为0,则采集失败。
UnlockWrite(long stamp):`释放独占写锁,并传入以前获取的stamp。 tryUnlockWrite():
如果持有写锁,则该锁将在没有标记值的情况下被释放。这种方法对于出错后的恢复可能很有用。
long stamp = lock . write lock();
尝试{
....
}最后{
lock.unlockWrite(戳);
}
复制代码
读取模式
独占读锁之后的悲观之路。
readLock():
阻塞等待获取非独占读锁,并返回一个stamp。如果为0,则采集失败。
TryReadLock():`尝试获取一个读锁并返回一个时间戳。如果为0,则采集失败。
长读锁(long time,时间单位单位):当试图获取读锁时,可以等待一个事件并返回一个时间戳。如果为0,则采集失败。
Readlockinterrupt():等待获取非独占读锁的块,它可以被中断并返回一个stamp。如果为0,则采集失败。
UnlockRead(long stamp):释放非独占读锁,并传入之前获取的stamp。 tryUnlockRead():
如果持有读锁,释放持有一次,不需要stamp值。这种方法对于出错后的恢复可能很有用。
长戳= lock . read lock();
尝试{
....
}最后{
lock . unlock read(stamp);
}
复制代码
乐观阅读模式
乐观是指如果读操作多,写操作少,可以乐观地认为写和读同时发生的概率很小,可以使用完全读锁,不悲观。在读取数据后,程序可以通过写入来检查它是否被更改,然后采取后续措施(重新读取更改的信息或抛出异常)。这个小小的改进可以大大提高程序的吞吐量。
StampedLock支持tryOptimisticRead()方法。阅读后,进行盖章检查。如果检查通过,则意味着在此期间没有其他线程写入,数据可以安全使用。如果检查失败,需要重新获取读锁以确保数据一致性。
TryOptimisticRead():`返回一个可以在以后验证的戳,如果以独占方式锁定,则返回零。 boolean validate(long stamp):
如果自给定的stamp发出后锁尚未被独占获取,则返回true。
long stamp = lock . trypositicread();
//检查戳记
如果(!lock.validate(stamp)){
//锁定升级
}
复制代码
此外,StampedLock提供api来实现上述三种转换方式:
`长tryConvertToWriteLock(长戳)'
如果锁定状态与给定的标记匹配,请执行下列操作之一。如果该标记指示持有写锁,则返回该标记。或者,如果是读锁,并且写锁可用,则释放读锁并返回写戳。或者,在乐观读取的情况下,写戳只有在立即可用时才返回。在所有其他情况下,该方法返回零。
` long tryConvertToReadLock(长戳)'
如果锁定状态与给定的标记匹配,请执行下列操作之一。如果标记指示持有写锁,则释放它并获得读锁。或者,如果是读锁,则返回它。或者,在乐观读取的情况下,只有当读取标记立即可用时,才会获得读取锁并返回读取标记。在所有其他情况下,该方法返回零。
长tryConvertToOptimisticRead(长戳)
如果锁的状态与给定的标记相匹配,那么如果标记指示锁被持有,则释放锁并返回观察标记。或者,如果是乐观阅读,验证后返回。在所有其他情况下,该方法都返回0,因此它作为“tryUnlock”的一种形式可能很有用。
演示示例
用下面的例子来演示StampedLock的用法。这个例子来自jdk中的javadoc。
@Slf4j
@数据
公共类点{
私有双x,y;
private final StampedLock sl = new StampedLock();
void move(double deltaX,double deltaY)抛出中断异常{
//涉及共享资源的修改,使用写锁排他操作。
long stamp = sl . write lock();
log.info("writeLock锁成功");
thread . sleep(500);
尝试{
x+= deltaX;
y+= deltaY;
}最后{
sl.unlockWrite(盖章);
log.info("解锁写锁成功");
}
}
/**
*使用乐观读锁访问共享资源。
*注意:乐观读锁需要将一个要操作的变量复制到方法栈中,以保证数据的一致性,其他写者在操作数据时可能已经修改了数据。
*而我们操作的是方法栈中的数据,也就是快照,所以返回最多的数据不是最新的数据,但是一致性还是有保证的。
*
* @返回
*/
double distanceFromOrigin()抛出InterruptedException {
long stamp = sl . trypositicread();//使用乐观读锁
log . info(" trypositicread锁成功");
//睡一秒钟
thread . sleep(1000);
double currentX = x,currentY = y;//将共享资源复制到本地方法堆栈中。
如果(!Sl.validate(stamp)) {//如果写锁被占用,可能会导致数据不一致,所以切换到正常的读锁模式。
log.info("验证戳记错误");
stamp = sl . read lock();
log.info("readLock成功");
尝试{
currentX = x;
currentY = y;
}最后{
sl.unlockRead(盖章);
log.info("解锁读取成功");
}
}
return math . sqrt(currentX * currentX+currentY * currentY);
}
void moveIfAtOrigin(double newX,double newY) { //升级
//可以从乐观模式而不是读取模式开始
长戳= sl . read lock();
尝试{
while (x == 0.0 && y == 0.0) {
long ws = sl . tryconverttowritelock(stamp);//读锁转换为写锁
如果(ws!= 0L) {
stamp = ws
x = newX
y = newY
打破;
}否则{
sl.unlockRead(盖章);
stamp = sl . write lock();
}
}
}最后{
sl.unlock(盖章);
}
}
}
复制代码
测试案例:
@测试
public void testStamped()引发InterruptedException {
Point Point = new Point();
point . setx(1);
point . sety(2);
//线程0执行了乐观读取。
Thread thread0 =新线程(()--> {
尝试{
//乐观地阅读
point . distance fromorigin();
} catch (InterruptedException e) {
e . printstacktrace();
}
},“thread-0”);
thread 0 . start();
thread . sleep(500);
//线程1执行写锁定
Thread thread1 =新线程(()--> {
//乐观地阅读
尝试{
point.move(3,4);
} catch (InterruptedException e) {
e . printstacktrace();
}
},“线程-1”);
thread 1 . start();
thread 0 . join();
thread 1 . join();
}
复制代码
结果:
![图片](https://P3-Jue Jin . byte img . com/tos-cn-I-k 3u 1 FB pfcp/913 e 41 e 58 a 104 a 83 BDA 9 aacb 224 a 567 c 6 ~ tplv-k 3u 1 FB pfcp-zoom-in-crop-mark:4550
性能比较
因为StampedLock的乐观读取模式和高性能高吞吐量,具体性能提升多少?
下图显示,与ReadWritLock相比,在一个线程的情况下,读取速度是4倍左右,写入速度是1倍。
![图片](https://P3-Jue Jin . byte img . com/tos-cn-I-k 3u 1 FB pfcp/c 6 ebfa 5c 54537 a 07 b 02973 b 74 b 44 ~ tplv-k 3u 1 FB pfcp-zoom-in-crop-mark:4533
下图显示,当有16个线程时,读性能是几十倍,写性能接近10倍:
![图片](https://P3-Jue Jin . byte img . com/tos-cn-I-k 3u 1 FB pfcp/31c 9 f 81 e 3c 5c 401484818 c 066 df 6908 f ~ tplv-k 3u 1 FB pfcp-zoom-in-crop-mark:4550
下图显示了吞吐量的提高:
![图片](https://P3-Jue Jin . byte img . com/tos-cn-I-k 3u 1 FB pfcp/00230309061 f 49 f 8957 da 62d 44 FDC 7 c 6 ~ tplv-k 3u 1 FB pfcp-zoom-in-crop-mark:44
那么这是否意味着“戳记锁”可以在所有方向上取代“ReentrantReadWriteLock”呢?答案是否定的,“戳记锁”相对于“ReentrantReadWriteLock”有以下两个问题:
不支持条件变量“Condition”。
支持不可重入
所以最终选择StampedLock还是ReentrantReadWriteLock取决于具体的业务场景。
摘要
本文主要介绍“盖章锁”的功能和使用。从原理上来说,虽然' Stamped Lock '没有像其他锁一样定义内部类来实现AQS框架,但是' Stamped Lock '的基本实现思想是使用CLH队列来管理线程,通过同步状态值来指示锁的状态和类型。