theme: jzman
「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
前面两篇文章全面分析了
synchronized
和volatile
,synchronized
通过隐式地获取锁和释放锁满足了对共享资源访问的原子性,可见性,顺序性。而volatile
借助CPU的MESI协议,并通过读写都操作主内存方式满足了对共享资源访问的可见性,有序性。有这两个关键字,是能满足绝大部分并发场景,但是不是它们两个就够了?当然不行,从这篇文章开始,我将一步一步的走进传说中JUC
,那就首先来了解一下JUC
下核心的接口Lock
吧。
一、Lock出世
对于volatile
,是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性,使用场景十分有限。
对于synchronized
,比volatile
更重一点,但提供更多的功能,不过也有缺陷。
- 一个线程获取了对应的锁,除非线程任务执行完成和异常中断,其它阻塞的线程会一直无限期地等待下去。
- 读写场景,对于锁,读写也是互斥的。
- 无法感知线程是否获取到锁。
- 没有超时释放锁的机制,也没有手动释放锁的机制。
针对上面提出的问题,大名鼎鼎的Doug Lea
开发了并发场景下使用组件,即并发包java.util.concurrent
,其中包含了线程池、阻塞队列、计时器、同步器、并发集合,当然也包括核心组件Lock
。
Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements.
Java源码对Lock
的介绍第一句如上所示,很清晰地表达了Lock
比synchronized
提供了更加广泛的锁操作,也就是说更加强大了。确实,从Java5以后,Lock
的出现解决了synchronized
的一些短板,使用起来更加灵活。
二、Lock简述
Lock
是一个接口,定义了释放锁和获取锁等抽象方法。如下图所示。
定义成接口意味着这个是一套标准的规范,基于Lock
实现很多类型的锁,如下图所示。
2.1 ReentrantLock
利用AQS
实现的一种可重入锁,属于互斥锁。可以从UML类图上可以清晰的看到,它是唯一一个实现了Lock接口的类。所谓重入,线程获取锁以后,再次获取锁不需要阻塞,而是直接在锁计数器上+1。它支持锁中断,具体实现有包括公平锁和非公平锁两种。
2.2 ReentrantReadWriteLock
可重入的读写锁,可以从UML类图上可以清晰的看到,它实现了
ReadWriteLock接口,这个接口提供了两个抽象方法,一个是ReadLock,另一个是WriteLock,如下图所示,两个方法的返回都是Lock接口。
读写锁对加锁场景业务的细分,适合的是读多写少的场景,具有以下原则:
- 读读不互斥
- 读写互斥
- 写写互斥
凡是涉及到并发下的写都会存在互斥。
2.3 StampLock
这个锁很特殊,是JDK8引入的新的锁机制。是对读写锁的改进。ReentrantReadWriteLock
在读写场景下互斥的,如果读线程过多,可能会使写线程长期获取不到锁,处于饥饿的状态。StampLock
的实现是基于类似于CAS操作的死循环反复尝试的策略,具体的实现是CLH
锁,它是一种自旋锁,可保证没有饥饿发生,并且可以保证FIFO的服务顺序。
哥佬倌,莫慌到走!觉好留个赞,探讨上评论。欢迎关注面试专栏面时莫慌 | Java并发编程,面试加薪不用愁。也欢迎关注我,一定做一个长更的好男人。