ReentrantLock
不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助。
在理解 ReentrantLock
时需要具备一些基本的知识
之前有写过一篇 《深入浅出AQS源码解析》 关于AQS的文章,对AQS原理不了解的同学可以先看一下
当一个线程已经持有锁,如果该现在再次获取锁,是否可以获取成功?如果能获取成功则说明该锁是 可重入的 ,否则是 不可重入的
公平与非公平的一个很本质的区别就是,是否遵守FIFO(也就是先来后到)。当有多个线程来申请锁的时候,是否先申请的线程先获取锁,后申请的线程后获取锁?如果是的,则是 公平锁 ,否则是 非公平锁 。
更准确地说,先申请锁的线程先获得锁竞争的权利。对于公平的排他锁而言,先申请锁的线程会先获取锁,但是对于公平的共享锁而言,先申请锁的线程会先拥有获取锁竞争的权利,其他等待共享锁的线程也会被唤醒,有可能后唤醒的线程先获取锁。
ReentrantLock
的功能主要是通过3个内部类 Sync
、 FairSync
和 NonfairSync
来实现的,这3个内部类继承了 AbstractQueuedSynchronizer
,其中 FairSync
和 NonfairSync
类继承了 Sync
,接下来我们一一解读这几个内部类。
由于ReentrantLock.Sync类中的核心代码比较少,原理也比较简单,所以就直接在代码中通过详细注释的方式来解读
abstract static class Sync extends AbstractQueuedSynchronizer { /** * 定义了一个抽象方法,用来获取锁 */ abstract void lock(); /** * NonfairSync中tryAcquire和、ReentrantLock.tryLock会使用到 * 重要功能:快速尝试获取锁,如果能够获取锁返回true,否则返回false * 在尝试获取锁的过程中,不会阻塞当前线程,一般情况下是当前线程已经持有锁时 * 才有可能是可以直接获取锁,这也是可重入功能的核心实现 */ final boolean nonfairTryAcquire(int acquires) { // 获取当前线程 final Thread current = Thread.currentThread(); /** * state是AQS对外提供的一个变量,让不同的实现类可以通过这个变量 * 来控制锁被线程获取锁的次数 */ int c = getState(); // 当state为0表示该锁是没有被任何线程持有 if (c == 0) { /** * CAS操作如果成功,说明当前线程竞争到了锁资源, * 否则被其他线程竞争到了,当前线程需要进入AQS的同步队列 * 对于尝试修改state的值的线程可以同时是多个, * 他们之间没有先后顺序,这也是非公平的重要体现 */ if (compareAndSetState(0, acquires)) { /** * 当前线程已经持有锁了,设置锁的占有者 */ setExclusiveOwnerThread(current); return true; } } /** * 如果持有锁的线程是当前线程,可以继续尝试获取锁 * 这也是可重入的重要体现 */ else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; /** * state是int类型,也就是可重入次数不能低于Integer.MAX_VALUE */ if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); /** * 获取锁以后直接设置state的值 */ setState(nextc); return true; } /** * 如果一个线程既不是第一次获取锁,又不是已经获取锁, * 则该线程无法获取锁,需要进入AQS的同步队列排队 */ return false; } protected final boolean tryRelease(int releases) { /** * 计算释放releases个资源后state的值 */ int c = getState() - releases; /** * 持有锁的线程如果不是当前线程,无法释放资源 */ if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; /** * 当所有的资源全部释放掉(c=0)时,锁的持有者需要设置为null, * 让后续线程可以来竞争锁 */ if (c == 0) { free = true; setExclusiveOwnerThread(null); } /** * 修改state的状态 */ setState(c); return free; } protected final boolean isHeldExclusively() { /** * 当前线程是否持有锁 */ return getExclusiveOwnerThread() == Thread.currentThread(); }
static final class NonfairSync extends Sync { /** * 非公平锁,对外获取锁的步骤: * 首先,尝试修改state的状态(从0修改成1),如果修改成功说明当前没有任何线程持有锁 * 如果线程获取到锁,则把锁的持有线程设置为当前线程 * 如果无法获取锁,说明锁已经被线程持有,有两种情况: * 情况1:持有锁的线程是当前线程,可以走可重入的流程 * 情况2:持有锁的线程不是当前线程,需要进入AQS去排队 */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } /** * 尝试快速获取锁 */ protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
static final class FairSync extends Sync { /** * 阻塞方式获取锁 */ final void lock() { acquire(1); } /** * 尝试获取公平锁,与上面分析的nonfairTryAcquire方法很类似, * 重点描述彼此之间的区别 */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { /** * 公平锁与非公平锁很大的一个区别是: * 在尝试获取锁的时候,如果AQS的同步队列中有其他线程在等待获取锁 * 则尝试获取锁失败,需要进入AQS的同步队列排队 * hasQueuedPredecessors方法判断AQS的同步队列是否有线程在等待 */ if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
ReentrantLock
类的实现方式比较简单,主要是依靠 NonfairSync
和 FairSync
实现的功能
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; /** * 默认是非公平锁 */ public ReentrantLock() { sync = new NonfairSync(); } /** * 获取锁,获取的时候申请1个资源 */ public void lock() { sync.lock(); } /** * 可中断的方式获取锁 */ public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } /** * * 尝试获取锁,公平锁和非公平锁都是直接去尝试获取锁 * 一般在使用该方法的时候,如果尝试获取锁失败,会有后续操作, * 可能是直接调用lock以阻塞的方式来获取锁 */ public boolean tryLock() { return sync.nonfairTryAcquire(1); } /** * 带有超时时间的方式尝试获取锁 */ public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } /** * 释放锁,释放掉1个资源 */ public void unlock() { sync.release(1); } }