ReentrantLock的实现是基于其内部类 FairSync(公平锁)和 NonFairSync(非公平锁)实现的。
ReentrantLock公平锁与非公平锁的实现原理区别就是抽象方法 tryAcquire
的实现不同。
锁的公平性和非公平性
在并发环境中,多个线程需要对同一资源进行访问,同一时刻只能有一个线程能够获取到锁并进行资源访问,那么剩下的这些线程怎么办呢?
1、公平锁FairSync:
公平锁:多个线程按照申请锁的顺序来获取锁,采用先来后到,先来先服务的原则。
公平锁的优点是等待锁的线程不会饿死,(效率高)。
缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
2、非公平锁:
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。(如插队现象)
非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。
缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。(效率低)
源码分析:
ReentrantLock
我们可以查看它的源码,无参构造函数默认使用的是非公平锁。也可以指定构造函数的boolean类型来得到公平锁或者非公平锁。
例如我们创建了一个 ReetrantLock,并给构造函数传了一个 true,我们可以查看 ReetrantLock 的构造函数。
如果是 true 的话,那么就会创建一个 ReentrantLock 的公平锁,然后并创建一个 FairSync ,FairSync 其实是一个 Sync 的内部类,它的主要作用是同步对象以获取公平锁。
1 | static final class FairSync extends Sync { |
Sync 是 ReentrantLock 中的内部类,Sync 继承 AbstractQueuedSynchronizer 类,AbstractQueuedSynchronizer 就是我们常说的 AQS ,它是 JUC(java.util.concurrent) 中最重要的一个类,通过它来实现独占锁和共享锁。
1 | abstract static class Sync extends AbstractQueuedSynchronizer {...} |
此处有两个值需要关心:
1 | //持有该锁的当前线程 |
对比公平锁与非公平锁源码:
我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:**hasQueuedPredecessors()**。
公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:
1 | if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { |
而 hasQueuedPredecessors() 是 AQS 中的方法,它主要是用于检查是否有等待队列的。。
ReentrantLock 的公平锁和非公平锁都委托了 AbstractQueuedSynchronizer#acquire 去请求获取。
- tryAcquire 是一个抽象方法,是公平与非公平的实现原理所在。
- addWaiter 是将当前线程结点加入等待队列之中。公平锁在锁释放后会严格按照等到队列去取后续值,而非公平锁在对于新晋线程有很大优势。
- acquireQueued 在多次循环中尝试获取到锁或者将当前线程阻塞。
- selfInterrupt 如果线程在阻塞期间发生了中断,调用 Thread.currentThread().interrupt() 中断当前线程。
- ReentrantLock 对线程的阻塞是基于 LockSupport.park(this); (AbstractQueuedSynchronizer#parkAndCheckInterrupt,先决条件是当前节点有限次尝试获取锁失败。)
ReentrantLock锁的释放
ReentrantLock锁的释放是逐级释放的,也就是说在可重入性场景中,必须要等到场景内所有的加锁的方法都释放了锁, 当前线程持有的锁才会被释放。
释放的方式很简单, state字段减一即可:
1 | protected final boolean tryRelease(int releases) { |