可重入锁(可以对同一个锁进行重复加锁)。ReentrantLock
自 JDK 1.5 被引入,功能上与synchronized
关键字类似。ReentrantLock 内部是基于 AbstractQueuedSynchronizer(以下简称AQS
)实现的。ReentrantLock 在功能上比 synchronized 更为丰富。比如 ReentrantLock 在加锁期间,可响应中断,可设置超时等。
ReenTrantLock与synchronized对比:
特性 | synchronized | ReentrantLock | 相同 |
---|---|---|---|
可重入 | 是 | 是 | |
响应中断 | 否 | 是 | |
超时等待 | 否 | 是 | |
公平锁 | 否 | 是 | |
非公平锁 | 是 | 是 | |
是否可尝试加锁 | 否 | 是 | |
是否是Java内置特性 | 是 | 否 | |
自动获取/释放锁 | 是 | 否 | |
对异常的处理 | 自动释放锁 | 需手动释放锁 |
synchronized 使用的是对象或类进行加锁,而 ReentrantLock 内部是通过 AQS 中的同步队列进行加锁。除此之外,ReentrantLock提供了丰富的接口用于获取锁的状态。比如可以通过isLocked()
查询 ReentrantLock 对象是否处于锁定状态, 也可以通过getHoldCount()
获取 ReentrantLock 的加锁次数,也就是重入次数等。而 synchronized 仅支持通过Thread.holdsLock
查询当前线程是否持有锁。
与Synchronized的差异:
- ReentrantLock 通过方法 lock()与unlock()来进行加锁与解锁操作,与synchronized 会 被 JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出 现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操 作。
- ReentrantLock相比synchronized的优势是等待可中断(会在获取所得时候,判断是否为可中断)、公平锁、多个锁。这种情况下需要 使用ReentrantLock
ReenTrantLock的特性:
1、可重入性(通过AQS中的states属性来进行设置)
2、公平锁(线程在同步队列中通过 FIFO 的方式获取锁,每个线程最终都能获取锁)与非公平锁(线程会通过“插队”的方式去抢占锁,抢不到的则进入同步队列进行排队)
非公平锁性能高?在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程 A 持有一个锁,并且线程 B 请求这个锁。由于这个线程已经被线程 A 持有,因此 B 将被挂起。当 A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果 C 也请求这个锁,那么 C 很有可能会在 B 被完全唤醒前获得、使用以及释放这个锁。这样的情况时一种“双赢”的局面:B 获得锁的时刻并没有推迟,C 更早的获得了锁,并且吞吐量也获得了提高。另一个可能的原因。即公平锁线程切换次数要比非公平锁线程切换次数多得多,因此效率上要低一些。
ReentrantLock源码分析:
1、结构
ReenTrantLock是基于AQS来实现的,AQS很好的封装了同步队列的管理,线程的阻塞与唤醒等操作。
Syns是一个静态抽象类,继承了AQS。而公平锁(FairSync)与非公平锁(NonfairSync)则继承自Sync。ReenTrantLock的主要逻辑都是基于这几个累不累来实现的、
2、锁的获取
其加锁的步骤大致如下:
-
首先应该尝试获取锁,如果没有线程占用锁(status为0),则获取锁成功,设置独占锁为当前线程(调用setExclusiveOwnerThread(current))来将当前线程设置为独占锁),接着判断占有锁的线程是否为当前线程(如果是,则修改status,设置为重入锁),否则的话就返回false表示尝试获取锁失败。
-
接着将申请锁的线程放入到CLH对类中进行等待(先将节点初始化,再将结点加入到为节点当中{通过CAS+自选}),此时线程未获取到锁的线程入队
-
最后,将线程挂起。这个操作是让已经入队的线程尝试获取锁,若成功则获取锁;若失败则线程会被挂起(要求其前驱结点为singal状态)。直到获取到锁(通过自旋+CAS)
流程图:
1、公平锁
公平锁和非公平锁不同之处在于,公平锁在获取锁的时候,不会先去检查state状态(避免抢占),而是直接执行aqcuire(1)。
释放所:
流程大致为先尝试释放锁,若释放成功,那么查看头结点的状态是否为SIGNAL,如果是则唤醒头结点的下个节点关联的线程,如果释放失败那么返回false表示解锁失败。当前释放锁的线程若不持有锁,则抛出异常。若持有锁,计算释放后的state值是否为0,若为0表示锁已经被成功释放,并且则清空独占线程,最后更新state值,返回当前状态是否已经释放
于2020/09/29完成
参考链接: