一个带有邮戳的锁StampedLock(jdk1.8出现)
jdk1.8真的可以作为一个宝藏,随便一个新的特性都足以写一本书,今天分析一个在jdk1.8中,引入的一个新的带有邮戳的StampedLock。这篇文章主要从使用的角度来分析一下:
一、为什么会需要StampedLock?
任何一个新引入的知识都是为了解决以往系统中出现的问题,否则新引入的将变得毫无价值。我曾经写过一些关于ReentrantReadWriteLock, ReentrantLock 和synchronized锁的文章。如果你之前了解过这些锁或者在工作中使用过,你会发现他们都有各种各样的缺点。
比如synchronized不可中断等,ReentrantLock 未能读写分离实现,虽然ReentrantReadWriteLock能够读写分离了,但是对于其写锁想要获取的话,就必须没有任何其他读写锁存在才可以,这实现了悲观读取。而且如果读操作很多,写很少的情况下,线程有可能遭遇饥饿问题。
饥饿问题:ReentrantReadWriteLock实现了读写分离,想要获取读锁就必须确保当前没有其他任何读写锁了,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,因为当前有可能会一直存在读锁。而无法获得写锁。
这时候怎么办呢?于是在jdk1.8的时候引入了一个新的锁StampedLock。
二、简单使用
StampedLock控制锁有三种模式(写,读,乐观读)
(1)写入(Writing):writeLock是一个独占锁,也是一个悲观锁。
(2)读取(Reading):readLock这时候是一个悲观锁。
(3)乐观读取(Optimistic Reading):提供了tryOptimisticRead方法返回一个非0的stamp,只有当前同步状态没有被写模式所占有是才能获取到。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。同时使用的时候一般需要读取并存储到另外一个副本,以用做对比使用。
下面干脆使用代码来实现一下这几种锁的实现。
1、写锁的实现:悲观写
1public class StampedTest {
2 private static StampedLock lock = new StampedLock();
3 private static List<String> data = new ArrayList<String>();
4 public static void write() {
5 long stamped = -1;
6 try {
7 stamped = lock.writeLock();
8 data.add("写线程写入的数据"+stamped);
9 System.out.println("写入的数据是:" + stamped);
10 TimeUnit.SECONDS.sleep(1);
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 } finally {
14 lock.unlockWrite(stamped);
15 }
16 }
17}
悲观写的意思是,认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改。
2、读锁的实现:悲观读
1public class StampedTest {
2 private static StampedLock lock = new StampedLock();
3 private static List<String> data = new ArrayList<String>();
4 public static void read() {
5 long stamped = -1;
6 try {
7 stamped = lock.readLock();
8 for (String name : data) {
9 System.out.println("读的数据是:" + name);
10 }
11 TimeUnit.SECONDS.sleep(1);
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 } finally {
15 lock.unlockRead(stamped);
16 }
17 }
18}
3、测试
1public class StampedTest {
2 private static StampedLock lock = new StampedLock();
3 private static List<String> data = new ArrayList<String>();
4 public static void main(String[] args) {
5 ExecutorService executor = Executors.newFixedThreadPool(10);
6 Runnable readTask = () -> {
7 for (;;) {
8 read();
9 }
10 };
11 Runnable writeTask = () -> {
12 for (;;) {
13 write();
14 }
15 };
16 for(int i=0;i<9;i++) {
17 executor.submit(readTask);
18 }
19 executor.submit(writeTask);
20 }
21}
注意到,这三块都是在同一个类中,为了演示方便就这样分开描述了。在运行测试的时候我们会发现,读的线程比较多,但是写的线程比较少,因此读锁执行的概率比较大一些。整个流程是先写数据,然后再读数据,在读数据的时候不会执行写操作。我们来实现一下第三种,那就乐观读取。
4、读锁的实现:乐观读
1public class StampedTest {
2 private static StampedLock lock = new StampedLock();
3 private static List<String> data = new ArrayList<String>();
4 public static void optimisticRead() {
5 //尝试去拿一个乐观锁
6 long stamped = lock.tryOptimisticRead();
7 //如果没有线程修改,我们再去获取一个读锁
8 if(lock.validate(stamped)) {
9 try {
10 stamped = lock.readLock();
11 for (String name : data) {
12 System.out.println("读的数据是:" + name);
13 }
14 TimeUnit.SECONDS.sleep(1);
15 } catch (InterruptedException e) {
16 e.printStackTrace();
17 } finally {
18 lock.unlockRead(stamped);
19 }
20 }
21 }
22}
这时候我们定义了一个新的乐观读锁,意思是在读的时候,依然可以写入。再次运行的时候你会发现这次写操作会比之前的测试次数多了,这说明在使用乐观读的时候,也发生了写操作。
三、总结
StampedLock的调度策略对待读写操作都是公平合理的。所有try方法都是尽最大努力,调用可能会成功,也可能会失败。这个类没有直接实现Lock或者ReadWriteLock方法,源码中是把他当做一个单独的类来实现的。当然,一个StampedLock可以通过asReadLock,asWriteLock,asReadWriteLock方法来得到全部功能的子集。
更多相关文章
- mysql从入门到优化(4)视图的基本操作
- mysql从入门到优化(2)数据的增删改查操作总结
- 都想学大数据开发?年轻人耗子尾汁吧~
- 社会化海量数据采集爬虫框架搭建
- 数据结构之:二分搜索树
- 2.20 从本质上理解虚拟机快照以及操作演示
- 【荐读】基于文本数据的消费者洞察
- BigDecima类型数据的处理--Non-terminating decimal expansio
- Flyway 助力数据库脚本自动化管理攻略