ReentrantLock可重入、可打断、Condition原理剖析

ReentrantLock可重入、可打断、Condition原理剖析

本文紧接上文的AQS源码,如果对于ReentrantLock没有基础可以先阅读我的上一篇文章学习ReentrantLock的源码

ReentrantLock锁重入原理

重入加锁其实就是将AQS的state进行加一操作

然后释放锁资源将AQS的state进行减一操作

当state为0时才会彻底的释放锁资源

ReentrantLock可打断原理

在ReentrantLock中可打断就是在等待锁的过程中可以被interrupt打断(需要调用lockInterruptibly),lock方法设置了打断标记,但是只有在线程获得锁的时候才能知道自己有没有在阻塞的过程中有没有被打断。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted; // 返回打断标记
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())// 我们这边会检查打断,如果打断的话返Thread.interrupted()
                interrupted = true;  // 这里将打断标记置为true后,继续进入循环。直到获得到锁返回标记
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

首先我们需要调用加锁的lockInterruptibly()方法

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

可打断主要原因在如下代码解释。用异常代替了返回标记,让线程可以直接再park的过程中直接结束

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();  // 再被打断的时候不会将其标记置为true,而是直接抛出一个异常,打断当前的等待。
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

ReentrantLock条件变量原理

Condition是一个接口,实际上是ReentrantLock的ConditionObject类作为其实现类。

首先我们创建一个condition就会调用其构造方法。其实就是产生一个新的conditionObject

final ConditionObject newCondition() {
    return new ConditionObject();
}

await()源码

接下来,简单剖析一个源码

  • 首先我们得知道,ConditionObject中维护着一个等待的双向链表,其实和阻塞链表是很相似的,不同在于不需要前驱进行唤醒。然后在ConditionObject中维护头和尾的引用就是firstWaiter和lastWaiter成员变量。
public final void await() throws InterruptedException {
    if (Thread.interrupted())   // 如果被打断,抛异常
        throw new InterruptedException();
    Node node = addConditionWaiter();  // 1.向AQS中添加一个等待链表的node
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { // 是否在阻塞链表中
        LockSupport.park(this); // 不是的话直接进行阻塞起来
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)// 被打断了就得把它放阻塞链表
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  // 多线程的遗留状态处理
        interruptMode = REINTERRUPT;   
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
  1. 添加等待node
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {  
        unlinkCancelledWaiters(); // 这个和阻塞队列相似不过是全部遍历,清除不在等待队列上的node
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)              // 添加到waitting尾部 
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

signal源码

signal唤醒源码,简单介绍就是直接将等待的firstWaiter指向的等待链表的第一个进行解除阻塞,然后将其放入阻塞链表中。不过多赘述。

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » ReentrantLock可重入、可打断、Condition原理剖析