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 { TimeUnit.MILLISECONDS.sleep(milli);//模拟读取需要花费20秒 } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlockRead(stamp); //释放读锁 } System.out.println("readLock==" + currentCount); //显示最新的变量值 }); try { TimeUnit.MILLISECONDS.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); }
因为我对悲观读操作进行了5秒的数据读取延迟,所以写操作要等5秒后读锁释放才能写入数据
输出结果:
数据写入完成的时间比获取读锁晚5043ms
读到的数据仍然是写入前的0
2、对于乐观读(如果没有进入写模式)可以减少一次读锁的性能消耗,并且不会阻塞写入的操作
我们添加了一个乐观读的方法,这个方法仍然模拟读取延迟5秒,乐观读实现需要参考下面的编程模式(获取乐观锁、校验是否进入写模式)
private static void optimisticRead() { service.submit(() -> { long stamp = lock.tryOptimisticRead(); //尝试获取乐观读锁 int currentCount = count; //获取变量值 if (!lock.validate(stamp)) { //判断count是否进入写模式 stamp = lock.readLock(); //已经进入写模式,没办法只能老老实实的获取读锁 try { currentCount = count; //成功获取到读锁,并重新获取最新的变量值 } finally { lock.unlockRead(stamp); //释放读锁 } } try { TimeUnit.MILLISECONDS.sleep(milli);//模拟读取需要花费20秒 } catch (InterruptedException e) { e.printStackTrace(); } //走到这里,说明count还没有被写,那么可以不用加读锁,减少了读锁的开销 System.out.println("optimisticRead==" + currentCount); //显示最新的变量值 }); try { TimeUnit.MILLISECONDS.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); }
直接看一下输出结果:
数据写入完成的时间比获取读锁晚543ms(说明乐观读并没有阻塞写操作)
5秒后读到的数据仍然是写入前的0
总结
可以看到相比直接用悲观读锁,乐观读锁可以:
1、进入悲观读锁前先看下有没有进入写模式(说白了就是有没有已经获取了悲观写锁)
2、如果其他线程已经获取了悲观写锁,那么就只能老老实实的获取悲观读锁(这种情况相当于退化成了读写锁)
3、如果其他线程没有获取悲观写锁,那么就不用获取悲观读锁了,减少了一次获取悲观读锁的消耗和避免了因为读锁导致写锁阻塞的问题,直接返回读的数据即可(必须再tryOptimisticRead和validate之间获取好数据,否则数据可能会不一致了,试想如果过了validate再获取数据,这时数据可能被修改并且读操作也没有任何保护措施)