StampedLock的理解和使用
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
StampedLock的理解和使⽤StampedLock介绍
StampedLock是为了优化可重⼊读写锁性能的⼀个锁实现⼯具,jdk8开始引⼊
相⽐于普通的ReentranReadWriteLock主要多了⼀种乐观读的功能
在API上增加了stamp的⼊参和返回值
不⽀持重⼊
StampedLock如何使⽤和使⽤价值
我看了上⾯的介绍仍然对StampedLock⼀头雾⽔,下⾯我们来揭开StampedLock神秘的⾯纱
1、对于悲观读和悲观写的⽅法与ReentranReadWriteLock读写锁效果⼀样下⾯是StampedLock的悲观读、写锁的实现
static ExecutorService service = Executors.newFixedThreadPool(10);
static StampedLock lock = new StampedLock();
static long milli = 5000;
static int count = 0;
private static long writeLock() {
long stamp = lock.writeLock(); //获取排他写锁
count+=1;
lock.unlockWrite(stamp); //释放写锁
System.out.println("数据写⼊完成");
return System.currentTimeMillis();
}
private static void readLock() {//普通的读锁
service.submit(() -> {
int currentCount = 0;
long stamp = lock.readLock(); //获取排他读锁
try {
currentCount = count; //获取变量值
try {
LISECONDS.sleep(milli);//模拟读取需要花费20秒
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlockRead(stamp); //释放读锁
}
System.out.println("readLock==" + currentCount); //显⽰最新的变量值
});
try {
LISECONDS.sleep(500);//要等⼀等读锁先获得
} catch (InterruptedException e) {
e.printStackTrace();
}
}
测试⼀下效果:
public static void main(String[] args) {
long l1 = System.currentTimeMillis();
readLock();
long l2 = writeLock();
System.out.println(l2-l1);
}
输出结果:
数据写⼊完成
5064
readLock==0
写被读锁阻塞了5秒
2、对于乐观读(如果没有进⼊写模式)可以减少⼀次读锁的性能消耗,并且不会阻塞写⼊的操作(乐观读遇到写后转化为悲观,相当于滞后⼀步)
我们添加了⼀个乐观读的⽅法,这个⽅法仍然模拟读取延迟5秒,乐观读实现需要参考下⾯的编程模式(获取乐观锁、校验是否进⼊写模式)
private static void optimisticRead() {
service.submit(() -> {
long stamp = lock.tryOptimisticRead(); //尝试获取乐观读锁
int currentCount = count; //获取变量值
try {
LISECONDS.sleep(milli);//模拟读取需要花费20秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!lock.validate(stamp)) { //判断count是否进⼊写模式
stamp = lock.readLock(); //已经进⼊写模式,没办法只能⽼⽼实实的获取读锁
try {
currentCount = count; //成功获取到读锁,并重新获取最新的变量值
} finally {
lock.unlockRead(stamp); //释放读锁
}
}
//⾛到这⾥,说明count还没有被写,那么可以不⽤加读锁,减少了读锁的开销
System.out.println("optimisticRead==" + currentCount); //显⽰最新的变量值
});
try {
LISECONDS.sleep(500);//要等⼀等读锁先获得
} catch (InterruptedException e) {
e.printStackTrace();
}
}
测试⼀下效果:
public static void main(String[] args) {
long l1 = System.currentTimeMillis();
optimisticRead();
long l2 = writeLock();
System.out.println(l2-l1);
}
输出结果:
数据写⼊完成
553
(中间等了⼤概5秒)
optimisticRead==1
写没有被被读锁阻塞,乐观读最终⾛了悲观读读取了最新值
证明乐观读锁⽐悲观读锁性能更好
public class StampedLockXingnengTest {
static ExecutorService service = Executors.newCachedThreadPool();
static StampedLock lock = new StampedLock();
static int count = 0;
public static void main(String[] args) throws InterruptedException {
long begin = System.currentTimeMillis();
List<Callable<Object>> list = new ArrayList<>();
for(int i = 0;i < 10000;i++){
list.add(() -> {
readLock();
//optimisticRead();
return null;
});
}
service.invokeAll(list);
long end = System.currentTimeMillis();
System.out.println(end-begin);
//乐观95 91 102 98 92
//悲观265 253 293 265 287
}
private static void readLock() {//普通的读锁
int currentCount = 0;
long stamp = lock.readLock(); //获取排他读锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlockRead(stamp); //释放读锁
}
}
private static void optimisticRead() {
long stamp = lock.tryOptimisticRead(); //尝试获取乐观读锁
int currentCount = count; //获取变量值
if (!lock.validate(stamp)) { //判断count是否进⼊写模式
stamp = lock.readLock(); //已经进⼊写模式,没办法只能⽼⽼实实的获取读锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlockRead(stamp); //释放读锁
}
}
}
}
针对以上代码我分别对乐观读锁和悲观读锁测试了5次,每次10000并发并且每次回sleep10ms,测试结果如下
乐观(ms)95 91 102 98 92
悲观(ms)265 253 293 265 287
乐观读锁整体性能⼤概是悲观读锁3倍
总结
可以看到相⽐直接⽤悲观读锁,乐观读锁可以:
1、进⼊悲观读锁前先看下有没有进⼊写模式(说⽩了就是有没有已经获取了悲观写锁)
2、如果其他线程已经获取了悲观写锁,那么就只能⽼⽼实实的获取悲观读锁(这种情况相当于退化成了读写锁)
3、如果其他线程没有获取悲观写锁,那么就不⽤获取悲观读锁了,减少了⼀次获取悲观读锁的消耗和避免了因为读锁导致写锁阻塞的问题,直接返回读的数据即可(必须再tryOptimisticRead和validate之间获取好数据,否则数据可能会不⼀致了,试想如果过了validate再获取数据,这时数据可能被修改并且读操作也没有任何保护措施)。