Java 锁系列(三)——ReentrantLock源码分析

Java 锁系列(三)——ReentrantLock源码分析

一、ReentrantLock 概述

ReentrantLock 是一个可重入的独占锁,是基于 AQS(AbstractQueuedSynchronized抽象队列式同步器)实现的,它有公平锁和不公平锁两种实现方式。简单查看源码,可以发现 ReentrantLock 其实是一个代理的模式,Reentranlock 的核心是其内部继承自 AbstractQueuedSynchronized 的抽象类 Sync,将 Sync 作为锁的同步基础,其中加锁/解锁等操作其实都是由 Sync 对象完成的,也是通过实例化 FairSyncNonfairSync 分别对应公平锁和非公平锁的实现。

// 传入 boolean 参数指定是否是公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

// 代理 sync 的方式实现加锁和解锁等操作
public void lock() {
    sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
......

ReentrantLock 是一个可重入锁,通过锁状态实现,当同步状态 state 为 0 时表示未获取锁,在执行获取锁操作时 state 状态设置为 1,之后每次重入都将锁状态 +1,释放锁时锁状态 -1,当锁状态回归 0 时表示当前线程释放锁资源,下个线程继续进行获取锁和释放锁操作。

ReentrantLock 中包含一个 AQS 队列,队列中节点具有 int 类型的节点状态(waitStatus 字段),节点状态指定了线程是否挂起。

状态名状态值说明
CANCELLED1由于超时或中断,该节点被取消。CANCELLED 状态的节点状态不会再改变,节点对应的线程也不会再阻塞。
SIGNAL-1这个节点的后继节点被(或即将)阻塞,所以当前节点在释放或取消时必须取消其后继节点的阻塞。为了避免竞争,获取方法必须首先表明它们需要一个信号,然后重试原子获取,然后在失败时阻塞。
CONDITION-2此节点当前位于条件队列中。在传输之前,它不会被用作同步队列节点,此时状态将被设置为0。(此值的使用与领域的其他用途无关,但简化了机制。)
PROPAGATE-3releaseShared应该传播到其他节点。这是在doreleasshared中设置的(仅针对头部节点),以确保传播继续,即使其他操作已经介入。
0节点创建时默认的状态

二、Sync 的公平和非公平实现

**公平锁: ** 多个线程相互竞争时要排队,多个线程按照申请锁的顺序来获取锁。

**非公平锁: ** 多个线程相互竞争时,先尝试插队,插队失败再排队。

Sync 包含 FairSyncNonfairSync 两个子类,分别复写了如下两个方法:

  • abstract void lock():获取锁;

  • protected boolean tryAcquire(int arg):尝试以独占模式获取锁,该方法应该查询对象的状态是否允许以独占模式获取它,如果允许则获取它。此方法始终由执行获取锁的线程调用。 如果此方法报告失败,acquire 方法可能会将线程排队(如果它尚未排队),直到收到来自某个其他线程的释放锁信号。

2.1 FairSync 公平锁

FairSyncReentrantLock 公平锁的实现。

获取锁:

// 公平锁直接进行排队
final void lock() {
    // 进行排队获取锁
    acquire(1);
}

尝试以独占模式获取锁:

先判断当前锁的状态是否为 0 未获取锁的状态,如果是则表明当前还没有任何一个线程获取到锁,再通过 hasQueuedPredecessors() 判断是否有其他线程在排队,如果没有,则 compareAndSetState 修改锁状态为已获取,然后将当前线程设置为获取到锁的线程。

如果当前锁状态不为 0 ,则判断当前线程是否为持有锁的线程,如果是则表示当前为重入,累加锁状态的值。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 获取当前锁的同步状态
    int c = getState();
    // lock为未获取锁的状态,表示没有任一个线程获取到锁
    if (c == 0) {
        // hasQueuedPredecessors: 如果队列为空或在队列头部返回false,如果前面有排队线程则返回true
        // compareAndSetState:如果锁状态为0,直接修改锁状态获取锁,CAS原子操作,只有一个线程能够成功
        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;
}

2.2 NonfairSync 非公平锁

NonfairSyncReentrantLock 非公平锁的实现。

获取锁:

// 非公平锁先插队,插队失败再排队
final void lock() {
    // CAS 尝试修改锁状态
    if (compareAndSetState(0, 1))
        // 获取锁成功,将当前线程标记为持有锁的线程,然后直接返回
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 获取锁失败,进行排队
        acquire(1);
}

尝试以独占模式获取锁:

protected final boolean tryAcquire(int acquires) {
    // 执行不公平的 tryLock
    return nonfairTryAcquire(acquires);
}

问:以上代码中 compareAndSetState(0, acquires)setState(nextc) 区别在哪 ?

这两处代码都是修改锁状态的代码,compareAndSetState 是 CAS 模式修改,保证线程安全,setState 是直接赋值,多个线程修改可能出现部分修改被覆盖。

compareAndSetState 所处位置为未获取锁的状态,可能有多个线程已经通过 c == 0 判断,所以需要通过 CAS 模式进行修改锁状态,避免多个线程同时获取到锁。

setState 处表示已经获取到锁,并且做了判断只有持有锁的线程才可以进入,所以不存在多个线程同时进入的情况,所以不需要通过 CAS 模式进行修改,直接赋值即可。

三、lock 方法实现

通过 二、Sync 的公平和非公平实现 可以看到,lock 方法的实现聚焦于 acquire 方法的实现。

3.1 获取锁流程

获取锁的流程公平锁和非公平锁实现不同,非公平锁先执行了 compareAndSetState 尝试直接修改锁状态,修改失败了才正式进入 acquire 方法。

获取锁的关键性逻辑之一是 tryAcquire 他将判断锁是否已经被持有,持有锁的是不是当前线程,据此条件判断是否可以直接修改锁状态。如果可以直接修改锁状态,此时不通过 AQS 队列获取锁,线程在 AQS 队列中并不存在对应的 node 节点。

如果还未获取到锁,此时将进入 AQS 队列排队,addWaiter 步骤中可同时进行队列初始化,队列排队的核心逻辑在 acquireQueued 方法,在该方法中将循环判断当前线程是否时候 AQS 队列的第二个 node 线程(第一个 node 为持有锁线程),如果是的话执行 tryAcquire 进获取锁尝试,如果获取锁失败,将进入 shouldParkAfterFailedAcquire 修改前一个节点的 node 节点状态,然后再次进入循环根据前一个节点状态阻塞当前节点线程,直到被唤醒。

3.2 acquire 获取锁

以独占方式获取锁,在获取锁时并没有直接将节点加入同步队列进行排队获取锁,而是通过 tryAcquire 先尝试获取锁,获取失败才创建节点进行排队。

/**
* 以独占模式获取锁,至少调用一次 tryAcquire 并获得 ture 返回,
* 否则线程会排队,可能会反复阻塞和解除阻塞,直到调用tryAcquire成功。
* 此方法可用于实现方法Lock.lock。
*/
public final void acquire(int arg) {
    // tryAcquire:以独占方式获取锁,返回获取锁是否成功true|flase,公平锁与非公平锁实现不同
    // acquireQueued:以独占不间断模式获取已在队列中的线程,由条件等待方法以及获取使用。
    // addWaiter:为当前线程和给定模式创建和排队节点。Node.EXCLUSIVE 表示独占模式
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中断当前线程
        selfInterrupt();
}

acquire 方法中聚焦 tryAcquireaddWaiteracquireQueued 的实现, tryAcquire 用于尝试以独占方式获取锁,返回获取锁是否成功 true|flase,公平锁与非公平锁实现不同,实现逻辑已在第二章介绍,接下来分析 addWaiteracquireQueued 方法的实现。

3.3 addWaiter 创建节点

addWaiter 方法用于创建一个当前线程的 node 排队节点,并将其加入队列尾部,如果队列还未初始化将进行队列初始化。

/**
* 为当前线程和给定模式创建和排队节点
*/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // CAS 模式将当前node设置为tail最后一个节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 将节点插入队列,必要时进行队列初始化。 
    enq(node);
    return node;
}

enq 是对插入逻辑的补充,在 addWaiter 中当队列未初始化,或者 CAS 更换 tail 出现冲突导致更新失败时,则需要通过 enq 进行补充插入,必要时进行队列初始化。

/**
* 将节点插入队列,必要时进行队列初始化。 
*/
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // tail 为空,表示队列还未初始化
        if (t == null) { // Must initialize
            // CAS 初始化队列,设置 head 和 tail
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            // CAS 将当前节点设置为tail,
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

通过上面代码可以分析,addWaiter 用于创建一个当前线程的节点加入排队队列,如果队列未初始化将初始化队列,队列采用链表构成,具有 prev 前置节点引用,和 next 后置节点引用。head 为一个无意义的空节点,添加节点时将新节点放在 tail 后面,并把添加的节点做为 tail,节点的线程安全操作都通过 CAS 模式来保证。

3.4 acquireQueued 队列排队

取得当前节点的钱一个节点,如果前一个节点是 head 节点,则尝试获取锁。

如果线程执行了 parkAndCheckInterrupt 方法,线程将挂起阻塞,阻塞完成后通过 Thread.interrupted() 测试当前线程是否被中断,如果被中断将 interrupted 设置为 true,当前方法执行完成后 acquire 方法中将执行 selfInterrupt 进行中断。

/**
 * 已在队列中的线程以独占不间断模式获取锁。由条件等待方法以及获取锁方法使用。
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 取得 node 前一个节点
            final Node p = node.predecessor();
            // 判断node如果是排队队列第二个节点,执行tryAcquire获取锁,获取成功返回 true(公平锁与非公平锁实现不同)
            if (p == head && tryAcquire(arg)) {
                // 将当前节点做head头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                // 返回是否中断线程标志
                return interrupted;
            }
            // shouldParkAfterFailedAcquire:检查和更新未能获取锁的节点的状态。 如果线程应该阻塞,则返回 true。
            // parkAndCheckInterrupt:挂起线程,被唤醒后检查线程是否中断
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 被中断后记录中断状态,在获取锁成功后设置线程中断标志
                interrupted = true;
        }
    } finally {
        // 获取锁失败
        if (failed)
            // 取消获取锁尝试
            cancelAcquire(node);
    }
}

3.5 shouldParkAfterFailedAcquire 检查和更新节点状态

shouldParkAfterFailedAcquire 用于检查前一个节点的状态,主要用途有两个:

  1. 剔除被取消获取锁的节点:如果前一个节点已被取消获取锁,此时节点状态 pred.waitStatus > 0 ,将会把这些取消的节点都剔除出队列;
  2. 阻塞当前线程:方法中将上个节点状态修改为 SIGNAL 状态,再次执行时如果是 SIGNAL 状态方法将返回 true,阻塞当前线程。
/**
 * 检查并更新获取锁失败节点的状态。
 * 如果线程应该阻塞,返回 true。这是所有获取回路中的主要信号控制。要求pred == node.prev。
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 上个节点状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 如果前一个节点的waitStatus == SIGNAL,则挂起
        return true;
    if (ws > 0) {
        // 前一个节点被取消,跳过前个节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // CAS 将前个节点的状态修改为 Node.SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    // 不需要挂起
    return false;
}

3.6 cancelAcquire 取消获取锁尝试

cancelAcquire 用于取消线程获取锁,取消获取锁线程的 node 节点将设置为 CANCELLED 状态,并从 AQS 队列中删除。

/**
 * 取消正在进行的获取锁尝试
 *
 */
private void cancelAcquire(Node node) {
    // 如果节点不存在则忽略
    if (node == null)
        return;
    node.thread = null;
    Node pred = node.prev;
    // 如果取消节点的前个节点也需要取消,则 pred 选择前个节点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    // 修改节点状态
    node.waitStatus = Node.CANCELLED;
    // 将 tail 修改为当前 node 节点前面一个未被取消的节点
    if (node == tail && compareAndSetTail(node, pred)) {
        // tail 修改成功后,将 pred 节点的 next 置空
        compareAndSetNext(pred, predNext, null);
    } else {
        // 如果 tail 不是最后一个节点
        int ws;
        // 如果 pred 不是 head 节点,且节点状态为 SIGNAL ,或者可以修改为 SIGNAL,且该节点包含 thread 线程,不是空节点
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            // 如果被删除节点的 next 不为空,且不是已被取消状态,则设置为 pred 的 next
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            // 唤醒后续节点
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

问:为什么需要执行 unparkSuccessor(node) 唤醒?

因为当前线程已经取消获取锁了,node 节点已经没有作用了,但是前后两个节点还是持有着当前节点的引用,这将导致队列中出现一些无用节点,且无用的 node 无法被回收。

唤醒后续节点线程后, shouldParkAfterFailedAcquire 将被重新执行,此时后续节点将重新检查上一个节点的状态,如果发现被取消将把节点剔除出队列。

3.7 unparkSuccessor 唤醒后续节点

private void unparkSuccessor(Node node) {
    // 如果状态是负的(即,可能需要信号),则清除节点状态
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 取得下一个节点,如果下一个节点为null或者节点被取消(waitStatus > 0),则
    // 从后往前遍历,找到在等待获取锁的节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        // 唤醒线程
        LockSupport.unpark(s.thread);

问:为什么需要从后往前遍历找到等待获取锁的节点?

见 3.6 cancelAcquire 取消获取锁尝试 章节,如果 node 被取消之后,其 next 引用将被设置为其自身,所以不能通过 next 向后进行遍历,只能通过 prev 从后往前遍历,并不是为了找到队列最后一个阻塞的 node。

遍历到 t != node 止,是整个队列遍历,找到 node 之后第一个等待获取锁的 node。

四、lockInterruptibly 方法实现

以独占模式获取锁,如果收到中断信号则中止获取。

4.1 可中断模式获取锁流程

lock 获取锁方法不同的是,lockInterruptibly 没有尝试通过 compareAndSetState 直接修改锁状态,进入 acquireInterruptibly 方法后率先判断了线程是否是中断状态,如果是中断状态则抛出异常,非中断状态才进行获取锁操作。

获取锁的关键性逻辑之一是 tryAcquire 他将判断锁是否已经被持有,持有锁的是不是当前线程,据此条件判断是否可以直接修改锁状态。如果可以直接修改锁状态,此时不通过 AQS 队列获取锁,可能线程在 AQS 队列中并不存在对应的 node 节点。如果 tryAcquire 获取锁失败,则进入 doAcquireInterruptibly 方法进行创建 node 节点和队列排队获取锁操作。

问:lockInterruptibly 和 lock 有什么不同?

用途不同,lockInterruptibly 中断后将取消获取锁,lock 中断后仅保留中断记号,继续尝试获取锁。

整体执行流程上大致与 lock 相同,不同的有以下几个地方:

  1. lock 方法在非公平锁中先通过 compareAndSetState 尝试直接获取锁(即插队),而 lockInterruptibly 没有这个流程;
  2. lockInterruptibly 获取锁之前先判断线程是否中断,如果是中断状态直接抛出异常,不进行获取锁操作;
  3. lockInterruptibly 基本等价于获取锁的 acquireQueued 方法,但是 acquireInterruptibly 包含 node 节点创建和加入队列操作,接收到中断后抛出异常,而 acquireQueued 接受到中断后继续进入循环获取锁,这是中断取消的关键。

获取锁的关键性逻辑之一是 tryAcquire 他将判断锁是否已经被持有,持有锁的是不是当前线程,据此条件判断是否可以直接修改锁状态。如果可以直接修改锁状态,此时不通过 AQS 队列获取锁,可能线程在 AQS 队列中并不存在对应的 node 节点。

如果还未获取到锁,此时将进入 AQS 队列排队,addWaiter 步骤中可同时进行队列初始化,队列排队的核心逻辑在 acquireQueued 方法,在该方法中将循环判断当前线程是否时候 AQS 队列的第二个 node 线程(第一个 node 为持有锁线程),如果是的话执行 tryAcquire 进获取锁尝试,如果获取锁失败,将进入 shouldParkAfterFailedAcquire 修改前一个节点的 node 节点状态,然后再次进入循环根据前一个节点状态阻塞当前节点线程,直到被唤醒。

4.2 acquireInterruptibly 可中断模式获取锁

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    // 当前线程为中断状态
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试不经过 AQS 队列直接获取到锁
    if (!tryAcquire(arg))
        // 进行 AQS 队列排队操作
        doAcquireInterruptibly(arg);
}

4.3 doAcquireInterruptibly 可中断模式队列排队

创建 node 加入 AQS 队列,以可中断的模式获取锁,如果接收到中断信号则直接抛出 InterruptedException 异常,不再进入循环。

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            // 取得 node 前一个节点
            final Node p = node.predecessor();
            // 判断node如果是排队队列第二个节点,执行tryAcquire获取锁,获取成功返回 true(公平锁与非公平锁实现不同)
            if (p == head && tryAcquire(arg)) {
                // 将当前节点做head头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            // shouldParkAfterFailedAcquire:检查和更新未能获取锁的节点的状态。 如果线程应该阻塞,则返回 true。
             // parkAndCheckInterrupt:挂起线程,被唤醒后检查线程是否中断
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 如果中断则直接抛出异常,不进入循环
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

五、tryLock 方法实现

tryLock 包含两个方法:

  • boolean tryLock():直接尝试获取锁,非公平,不阻塞。获取成功返回 true,获取失败返回 false

  • boolean tryLock(long timeout, TimeUnit unit):给定时间内尝试获取锁,响应中断。获取成功返回 true,获取失败返回 false

5.1 不带参数的 tryLock

不带参数的 tryLock 实现比较简单,内部直接调用 nonfairTryAcquire 方法进行非公平的锁获取。在 2.2 NonfairSyn 非公平锁 的实现中, tryAcquire 方法就是通过 nonfairTryAcquire 进行非公平锁的获取。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 如果当前锁未被某个线程获取,则插队直接获取锁
        if (compareAndSetState(0, acquires)) {
            // 锁状态修改成功,修改持有锁线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 持有锁的线程为当前线程,当前为重入操作,直接增加锁状态的值
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

5.2 带时间参数的 tryLock

带时间参数的 tryLock 将时间转为纳秒级时间单位,然后传给 tryAcquireNanostryAcquireNanos 方法中将创建 node 节点加入同步队列,然后以独占定时模式获取锁,尝试获取锁失败后将阻塞指定时间的获取锁线程。当阻塞时间到之后再次尝试获取锁,阻塞时间完成或者在阻塞时接收到中断信号时停止获取锁尝试,最终方法返回值将指定是否成功获取到锁。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    // 当前线程为中断状态
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试不经过 AQS 队列直接获取到锁
    return tryAcquire(arg) ||
        // 进行 AQS 队列排队操作
        doAcquireNanos(arg, nanosTimeout);
}

5.3 doAcquireNanos 定时模式队列排队

以独占定时模式获取锁,中断时停止获取锁尝试,返回获取锁是否成功。

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 结束时间
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            // 取得 node 前一个节点
            final Node p = node.predecessor();
            // 判断node如果是排队队列第二个节点,执行tryAcquire获取锁,获取成功返回 true(公平锁与非公平锁实现不同)
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            // 已经超过定时时间
            if (nanosTimeout <= 0L)
                return false;
            // shouldParkAfterFailedAcquire:检查和更新未能获取锁的节点的状态。 如果线程应该阻塞,则返回 true。
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 当前定时时间大于 spinForTimeoutThreshold = 1000 ns
                nanosTimeout > spinForTimeoutThreshold)
                // 阻塞线程,直到到达定时时间
                LockSupport.parkNanos(this, nanosTimeout);
            // 收到中断信号,抛出异常
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            // 取消获取锁尝试
            cancelAcquire(node);
    }
}

六、unlock 方法实现

unlock 方法中直接调用的 sync.release(1) ,方法进行释放锁操作。

6.1 释放锁流程

释放锁时公平锁和非公平锁都是相同的,真正开始涉及释放锁逻辑的是 release 方法,通过 tryRelease 方法释放锁,并判断锁是否完全释放,如果锁完全释放了就执行唤醒阻塞的 node 的逻辑。

6.2 release 释放锁

head 节点是持有锁的线程对应的节点,释放锁线程的 node 节点一定是 head 节点。理解以下逻辑可以发现,锁不一定被释放,如果锁被释放将返回 true 未释放返回 false,这是因为 ReentrantLock 是一个可重入锁。

public final boolean release(int arg) {
    // 释放锁,判断锁是否被完全释放
    if (tryRelease(arg)) {
        Node h = head;
        // waitStatus = 0 表示后续没有阻塞的node
        if (h != null && h.waitStatus != 0)
            // 唤醒阻塞的node
            unparkSuccessor(h);
        return true;
    }
    return false;
}

问:head 什么时候为 null?

ReentrantLock 只要进入过队列排队 head 就肯定不会为 null

但是非公平锁获取锁时通过 compareAndSetState 直接修改锁状态获取锁(见2.2),acquire 中也通过 tryAcquire 直接获取锁,如果获取成功了,则此时并不会进入 AQS 队列,队列也没有初始化,此时 head 就为 null。

6.3 tryRelease 修改锁状态

unlock 通过修改锁 State 状态码实现释放锁,当锁状态为 0 时表示该锁已经被完全释放了。

/**
 * 尝试设置状态以反映独占模式下的发布。
 * 此方法始终由执行释放的线程调用
 *
 * @param releases 锁状态减小量
 * @return 响应状态,true表示锁被往前释放,false表示只是释放了一次重入状态,线程依旧持
 *         有锁
 * @throws IllegalMonitorStateException 如果执行该方法的线程没有持有锁将抛出异常
 * @throws UnsupportedOperationException 不支持独占模式
 */
protected final boolean tryRelease(int releases) {
    // 锁状态减小
    int c = getState() - releases;
    // 如果当前线程不是持有锁线程,则抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        // 如果锁状态为 0,表示所有重入都已经执行了释放锁方法,当前锁被释放,释放锁
        setExclusiveOwnerThread(null);
    }
    // 设置锁状态
    setState(c);
    return free;
}
---------本文结束感谢您的阅读---------

评论

 热烈欢迎各位大佬专家莅临玖涯博客指导检查!

 交换友链的朋友请前往友情链接

12 : 111
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×