ReentrantLock 以及 Condition深度解析

前言

之前写过关于AQS的文章,也讲了关于 ReetrantLock 的源码分析。这里主要是想结合源码分析下Condition 的 await 和 signal ,写ReentrantLock那么还是得提下 AQS 的。

一、关于AQS 中几个重要的属性

  1. AQS 中维护了一个很重要的变量 state, 它是int型的,表示加锁的状态,初始状态值为0;
  2. 另外 AQS 还维护了一个很重要的变量 exclusiveOwnerThread,它表示的是获得锁的线程,也叫独占线程,ReentrantLock 中的可重入就是用到该属性,当 state != 0exclusiveOwnerThread 等于当前线程那么表示的是当前线程来获取锁了,是可以直接获取锁成功,即锁的可重入,这也表示 ReentrantLock 是可重入锁。
  3. AQS 中还有一个用来存储获取锁失败线程的队列,它是一个FIFO的双链表结构,以及 headtail 结点。
  4. 双链表的结点 Node 包含的主要属性如下:

NodeSHARED 和 EXCLUSIVE : 用于标识该结点是共享模式还是独占模式

int : waitStatus : 标识该结点的等待状态,有如下几种值:

CANCELLED:值为1,表示该结点被取消了,比如使用 tryLock(1000, TimeUnit.MILLISECONDS) 获取锁超时就会被取消

SIGNAL: 值为-1,表示此结点的后继结点的线程已经通过LockSupport.park()挂起了,需要前面的结点在释放锁或者取消的时候来唤醒该线程

CONDITION: 值为-2,表示该结点在condition队列中,一般是通过 condition.await() 操作,然后该结点插入 condition 的尾部

PROPAGATE: 值为-3,共享模式的头结点可能处于此状态,表示往下传播。假设有2个线程同时释放锁,通过竞争,其中一条负责唤醒后继结点,而另一条则将头结点设置为此状态,新结点唤醒后,直接根据头结点的此状态来唤醒 下下个结点

注意,当 new Node() 的时候 waitStatus 默认等于0。

二、ReetrantLock 的结构

  1. ReetrantLock 包含公平锁和非公平锁,默认的是非公平锁。
  2. ReetrantLock 包含一个抽象的内部类: Sync,Sync 继承了 AQS,如下:
 abstract static class Sync extends AbstractQueuedSynchronizer
  1. ReetrantLock 中的 公平和非公平锁分别继承自 Sync

三、ReetrantLock 的获取和释放锁的分析

这里就以默认的非公平锁的源码来分析,版本为 jdk1.8

ReentrantLock lock = new ReentrantLock()

try {
   lock.lock()
   ...
} finally {
  lock.unLock()
}

synchronized 不一样,ReentrantLock 是不会主动释放锁的,所以需要在 finally 块中主动调用 unLock 释放。

这里假设有A,B,C,D 4个线程同时调用 lock 获取锁,且每个线程获取锁之后执行的任务都非常耗时。

1. lock.lock()
  1. 会调用 ReentrantLock 的 lock 方法,如下:
    public void lock() {
        sync.lock();
    }

由于默认的是非公平锁,所以 调用的是 NonfairSync 的 lock 方法,如下:

final void lock() {
    // state 的默认值是0,所以第一个线程来获取锁的时候 cas 操作返回true
    // 线程释放锁的时候会把 state 再次设置为 0
    if (compareAndSetState(0, 1)) // 注释【1】
        // 把获取锁的线程设置为独占线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 注释【2】
 }

由于线程 A 是第一个请求锁的,所以通过 CAS 把 AQS 的 state 值设置为1,表示 线程A已经获取了锁,且设置线程A 为独占线程。

此时,假设线程B再来获取锁的时候 走到注释【1】,此时state的值为1,所以cas 操作会失败,接着走到 注释【2】acquire 是 AQS 里面的方法,如下:

   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

上面的方法包含了3个重要的步骤:

  1. tryAcquire : 再次尝试去获取锁,该方法在是在 ReentrantLock 的内部类 NonfairSync 中。
  2. addWaiter :把当前线程转化为Node结点,且插入等待队列的末尾,如果是第一个插入等待队列的结点,还需要 new 一个空的结点作为 head 结点。
  3. acquireQueued:这个方法 判断 node 结点的前驱结点是否是 head结点,如果是,那么再次尝试去获取锁,如果获取锁成功,那么设置当前的 node 为head结点;如果获取锁失败,那么会阻塞(挂起)当前线程。
tryAcquire
protected final boolean tryAcquire(int acquires) {
     return nonfairTryAcquire(acquires);
}
       final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 表示之前获取锁的线程已经释放了锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 表示之前获取锁的线程未释放锁,然后判断当前线程是否就是之前获取锁的线程
            // 如果是,那么state + 1,这里就是锁的重入了
            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;
        }

tryAcquire 获取锁成功则返回 true,否则返回 false 。下面看看addWaiter(Node.EXCLUSIVE), Node.EXCLUSIVE 前面也说过,指的是独占锁,代码如下:

    private Node addWaiter(Node mode) {
        // new Node ,Node 的thread 赋值为当前线程
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        // 第一个获取锁失败的线程走到这里时,head 和 tail 都等于null
        if (pred != null) { 
        // 不等于null,说明等待队列里面有等待的线程了,
        // 下面的操作就是把 node 插入到队列的而尾部然后返回 node 
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 走到这里,说明等待队列为空
        enq(node);
        return node;
    }

由于 线程B 是第一个未获取到锁的线程,此时 headtail 都是 null,所以走到 enq(node):

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail; //注释【1】
            if (t == null) { // 注释【2】
                if (compareAndSetHead(new Node())) //注释【3】
                    tail = head; //注释【4】
            } else {
                node.prev = t; //注释【5】
                if (compareAndSetTail(t, node)) { //注释【6】
                    t.next = node; //注释【7】
                    return t; //注释【8】
                }
            }
        }
    }

这里画图来展示吧:
注释【1】:由于此时 tail == null ,所以注释【2】成立;
注释【3】:new 一个空的结点,然后通过 cas 操作把这个空结点设置为 head,此时,head 结点的 waitStatus 值为0(默认值)
注释【4】:让 tail 指向 head,执行完 的效果如下:
在这里插入图片描述
此时 head 和 tail 都指向的是一个空结点,里面对应的线程null,且 waitStatus = 0;
for 循环会再次走到 注释【1】,t 赋值为 tail,所以注释【2】条件不成立,走到 注释【5】,即 线程B 的prev指向t也就是指向 tail,如下图:
在这里插入图片描述
接着走到注释【6】 通过cas 操作让 tail 指向 线程B对应的 node 结点。
注释【7】的作用就是 让 tnext 指针指向 结点 node,然后返回 node 结点,如下:

在这里插入图片描述
到此,addWaiter(Node mode) 分析完成了,下面就说说acquireQueued(final Node node, int arg),代码如下:

   final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); // 注释【1】
                if (p == head && tryAcquire(arg)) { // 注释【2】
                    setHead(node); // 注释【3】
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())// 注释【4】,线程在这里阻塞,到时候唤醒的时候会接着走for循环
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

注意,这里面也是一个无限循环操作,此时线程B已经在等待队列里面了:

注释【1】:此时 node 结点的前驱结点是 head ,所以执行 tryAcquire,由于线程A执行耗时的操作,暂时不会释放锁,所以 注释【2】的 tryAcquire 条件不满足。走到注释【4】。

注释【4】:包含 shouldParkAfterFailedAcquireparkAndCheckInterrupt 我们逐一分析,先看 shouldParkAfterFailedAcquire,方法如下:

   private static boolean shouldParkAft
   erFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) // 注释【1】
            /***
            * 表示已经设置为SIGNAL,那么在这个pred结点释放锁的时候
            * 能够唤醒pred的后继结点对应的线程
            */
            return true;
        if (ws > 0) {
            // 表示前驱结点被取消了,那么从pred结点开始往前,
            // 从等待队列中删除所有已经取消的结点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else { // 注释【2】
            // 通过 cas 把pred结点的waitStatus 设置为 SIGNAL
            // 只有设置为SIGNAL后,才能唤醒后续结点
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

接着来, 线程B 对应结点的 前驱结点是 head 结点,前面说过了,head 结点的 waitStatus 的值为0,所有会走到 注释【2】,设置 head 的 waitStatus = -1(SIGNAL = -1),返回 false,返回到 acquireQueued 方法中,继续执行 for 循环,由于线程A 依然没有释放锁,还是走到 shouldParkAfterFailedAcquire方法,此时 pred 的waitStatus = SIGNAL,所已返回true,接着执行 acquireQueued 中的 parkAndCheckInterrupt 方法了,如下:

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

这个方法很简单,调用LockSupport.park阻塞 线程B
以上,执行完成后,head 的 waitStatus 的值为 -1。如下图:
在这里插入图片描述

到此,线程B 的 获取锁的操作执行完毕,被阻塞了,现在轮到 线程C 来请求锁,流程是一样的,还是最终走到这里:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

由于线程A未释放锁,所以 线程C 尝试获取锁tryAcquire失败,接着走 addWaiter,此时就是将 线程C 对应的结点插入到 等待队列的尾部,插入后如下图:
在这里插入图片描述
然后 走到 acquireQueued,前面分析过过代码,这里就简单说下结果。这里会先判断 线程C结点的前驱结点是不是 head 结点,显然不是,线程C结点的前驱结点是 线程B对应的结点,所以会走到 shouldParkAfterFailedAcquire(pred, node),同样经过2次循环把 线程B对应的结点的waitStatus值设置为-1,然后阻塞线程C。如下图所示:
在这里插入图片描述
同样,线程D的操作和线程C的操作是一样的,执行完获取锁的流程后如下图:
在这里插入图片描述
从上面的分析可知,ReentrantLock 所形成的等待队列非 tail结点的 waitStatus = -1

ReentrantLocklock方法总结:

  1. 当一个线程获取锁后,会将 state 置为1,然后 设置 exclusiveOwnerThread 为当前线程
  2. 如果当前线程再去获取锁的话,会把 state 加1 ,即锁重入
  3. 其他线程再去获取锁,如果锁未被其他线程释放,那么会把此线程对应的结点插入等待队列的末尾,然后判断此线程对应结点的前驱结点是否是 head 结点,如果是 head 结点,那么会尝试获取锁,获取到锁就直接return,未获取到锁,会把前驱结点的waitStatus值设置为SIGNAL(-1),然后阻塞此线程;如果不是head结点,同样的逻辑 ,把前驱结点的waitStatus值设置为SIGNAL(-1),然后阻塞此线程,对应如下的流程图:

在这里插入图片描述

2. unlock()

unlock()的代码如下:

public void unlock() {
    sync.release(1);
}

release(int arg)调用的是 AQS 里面的逻辑,如下:

    public final boolean release(int arg) {
        // 调用 ReentrantLock 的内部列 Syn 中的 tryRelease
        if (tryRelease(arg)) {
            Node h = head;
            // 成功释放锁,waitStatus != 0表示有后继结点需要唤醒
            if (h != null && h.waitStatus != 0) // 注释【1】
                unparkSuccessor(h); // 唤醒后继结点 注释【2】
            return true;
        }
        return false;
    }

tryRelease 是 调用 ReentrantLock 的内部列 Syn 中的 方法,如下:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 判断如果执行这个方法的线程不是获取锁的线程则抛出 IllegalMonitorStateException
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // c == 0 表示释放锁成功,c 不等于0的情况比方说有2个方法f1,f2,都对应同一个ReentrantLock 锁,且f1中调用了f2
    // 那么线程1执行f1然后接着执行f2,此时就是可重入锁,导致state=2,所以释放f2方法的锁时state -1 = 1,直到释放f1方法锁时,state才等于0
    
    if (c == 0) {
        free = true;
        // 把独占线程设置为null
        setExclusiveOwnerThread(null); 
    }
    // 通过 cas 操作设置 state 的值。
    setState(c);
    return free;
}

tryRelease 的方法很简单,上面注释的很清楚;接着看 release 方法中的注释【1】,if (h != null && h.waitStatus != 0),我们还是通过之前的例子来加以说明,假设此时线程A执行完任务调用unLock来释放锁,tryRelease 执行完之后 state 值变成0,上面的B、C、D执行完的结果图再贴下:
在这里插入图片描述
可知 head != null,且 head 的 waitStatus = -1,所以 if (h != null && h.waitStatus != 0)条件成立,执行后续的唤醒操作 unparkSuccessor(h),如下:

private void unparkSuccessor(Node node) {
    // 这里的 node 指的是 head 结点,并不是待唤醒打的结点
    int ws = node.waitStatus;
    // 如果 ws < 0,那么 通过cas把ws设置为0,表示该结点的后继结点已经被我唤醒了
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); // 注释【1】
   
    Node s = node.next;
    // 因为 node 指的是 head 结点,所以要唤醒的是 head 的后继结点
    // 如果 待唤醒的结点是 null,或者 被取消了 waitStatus大于0 只有一种情况CANCELLED=1
    if (s == null || s.waitStatus > 0) { // 注释【2】
        s = null;
        // 从后向前遍历找到最靠近head且未被取消的结点,为何是从后往前呢?
        // 这种情况针对的是 Semaphore等,因为多个线程可能同时释放锁,
        // 从前往后遍历可能前面的结点对应的线程已经被唤醒了
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)// 注释【3】
        LockSupport.unpark(s.thread); // 调用 unpark 唤醒被挂起的线程
}

线程A 释放锁,走到 unparkSuccessor 的时候,由于 headwaitStatus = -1,所以通过 cas 操作 把 waitStatus 设置为0,如下图:
在这里插入图片描述
head 结点的 next 结点是线程B对应的结点,所以 if (s == null || s.waitStatus > 0)不成立,直接走到注释【3】来唤醒线程B 对应的结点 LockSupport.unpark(s.thread),前面说过,一个线程被唤醒后要执行的代码是之前被阻塞那里的for循环,我把之前的代码再复制一遍,如下:

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); // 注释【1】
                if (p == head && tryAcquire(arg)) { // 注释【2】
                    setHead(node); // 注释【3】
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())// 注释【4】,线程在这里阻塞,到时候唤醒的时候会接着走for循环
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

上面的注释【4】表示是之前线程被挂起的地方,被唤醒后,接着进行for循环,接着走到注释【1】结点B 的前驱结点 就是 head , 所以注释【2】p == head,由于线程A已经释放了锁,所以 tryAcquire(arg)也成立,tryAcquire会把state置为1且设置线程B为独占线程(exclusiveOwnerThread);

接着执行 注释【3】,setHead(node)即把结点B设置为 head 结点,如下:

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

先把 head 指向 结点B,然后把结点Bthread属性设置为null,结点Bprev指向null,如下图:
在这里插入图片描述
p.next = null 的操作之就是断开老head和新的head结点的连接执行完成后的结果如下:

在这里插入图片描述
此时线程B就可以执行它的任务啦,执行完成后就会唤醒后继的结点C

现在总结下 unLock()方法。

  1. 线程释放锁的时候,会把 state 减 1,然后判断 state 是否等于 0,等于则表示释放锁成功,且设置 exclusiveOwnerThread = null
  2. 线程被唤醒后会被设置为新的 head 结点,且会把 thread属性置为null;

以上,ReentrantLocklockunLock 都已经分析完了,下面看看相关联的 Conditionawaitsignal 的原理。

三、Condition 的 await 和 signal 原理:

1. Condition 的await 和 signal与 Object 的 wait 和 notify 的区别:
  1. Object.wait 是随机唤醒的,比如说有4个线程 A,B,C,D 都调用了 wait 方法被挂起了,当其他线程调用 notify 来唤醒被挂起的线程时,被唤醒的线程是随机的,可能是A,B,C,D中的任意一个。
    Condition.signal 唤醒的是最先调用 await 方法挂起的线程。

  2. Condition.signal 执行线程唤醒,并不是立即唤醒的,如果等待队列中有任务,会等到前面的任务都执行完才会接着执行唤醒线程的任务。

举个例子:假设有4个线程A、B、C、D,A先获取锁执行耗时操作,然后 B、C、D被插入等待队列,A释放锁后唤醒B,B执行 await 操作后释放锁且阻塞自己,C获取锁后调用 signal 唤醒线程B,可以看到 B被唤醒后并没有立即执行,而是等D线程任务执行完成之后才执行唤醒后的操作。测试代码如下:

public class TestCondition {
    public static void main(String[] args) throws InterruptedException {

        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        Thread aThread = new Thread(new MThread(lock, condition));
        aThread.setName("[A]");

        Thread waitThread = new Thread(new WaitThread(lock, condition));
        waitThread.setName("[B]");

        Thread signalThread = new Thread(new SignalThread(lock, condition));
        signalThread.setName("[C]");

        Thread dThread = new Thread(new MThread(lock, condition));
        dThread.setName("[D]");

        aThread.start();

        Thread.sleep(100);

        waitThread.start();
        signalThread.start();
        dThread.start();
    }
}

class MThread implements Runnable{
    private final ReentrantLock lock;
    private final Condition condition ;

    public MThread(ReentrantLock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+" 开始执行等待3s , time = " + System.currentTimeMillis());
            Thread.sleep(3_000);
            System.out.println(Thread.currentThread().getName()+" 执行结束, time = "+ System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

/**
 *  condition 的 await 的操作:
 *  1 .new Node(Thread.currentThread(), Node.CONDITION) 插入条件队列的尾部
 *  2. 如果等待队列中有其他线程结点,则唤醒后续的一个结点
 *  3. 阻塞当前线程
 *
 *  signal 操作:
 *  1. 把这个待唤醒的线程结点插入等待队列的尾部
 *  2. 唤醒这个结点的线程;分如下2种情况:
 *  如果等待队列里面没有没有其他的结点,那么该线程会获取锁然后执行逻辑;
 *  如果队列的前面还有其他的等待线程,那么要等等待队列前面的先执行完才轮到自己
 */
class WaitThread implements Runnable {
    private final ReentrantLock lock;
    private final Condition condition ;

    public WaitThread(ReentrantLock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+" >>>>开始执行 await,等待被唤醒 , time = " + System.currentTimeMillis());
            condition.await();
            System.out.println(Thread.currentThread().getName()+" >>>>被唤醒 执行结束, time = "+ System.currentTimeMillis());

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


class SignalThread implements Runnable {
    private final ReentrantLock lock;
    private final Condition condition ;

    public SignalThread(ReentrantLock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+" --- 开始执行 signal,去唤醒其他线程 , time = " + System.currentTimeMillis());
            condition.signal();
            System.out.println(Thread.currentThread().getName()+" --- 执行结束, time = "+ System.currentTimeMillis());
        } finally {
            lock.unlock();
        }
    }
}

执行的结果如下:
在这里插入图片描述
为什么是这样执行的呢?且看下面的分析:

1. await 的代码如下:
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 先把当前线程加入到condition队列中
    Node node = addConditionWaiter();
    // 释放当前线程持有的锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 这个方法不用管,一般返回false
    while (!isOnSyncQueue(node)) { // 注释【1】
        // 挂起当前的线程
        LockSupport.park(this);
        // 判断在挂起的时候是否被中断
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 注释【2】
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

addConditionWaiter 的代码如下:就是将当前线程包装成 Node,然后插入 con
dition 队列的尾部:

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 如果 lastWaiter 被取消了,则清除被取消的结点
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 当前线程包装成 Node ,waitStatus 设置为 CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 下面就类似于单链表的尾插法,把当前结点插入链表尾部
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    // 返回当前 node
    return node;
}

fullyRelease(node),就是释放当前线程对应的锁:

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

就看下 fullyRelease 中的 release(int arg) 方法就行了,这个方法之前分析了,这里就说下结论:

  1. 当线程B释放锁的时候,会通过 release 方法把线程C 设置为 head 结点,且唤醒线程C;
  2. 然后线程B 会执行 LockSupport.park(this); 挂起自己

下面线程C 获取锁执行 signal 唤醒线程B:

signal :
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

从上面的 first = firstWaite 可知,signal 是从第一个开始唤醒,即哪个线程先执行 await 就先唤醒哪个线程,这是前面说的和Object.wait()第一个区别。

doSignal 如下:

private void doSignal(Node first) {
    do {
        // 让 firstWaiter执向下一个结点,如果下一个结点为null,则lastWaiter赋值为null
        // 即 firstWaiter 和 lastWaiter 都等于 null,即没有其他线程在 condition队列中
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

transferForSignal 如下:

final boolean transferForSignal(Node node) {
    // 如果 线程任务被取消,即线程被用户执行中断了,如果没有被取消,那么 设置waitStatus = 0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
   
   // 把 结点插入到等待队列的尾部,注意返回的 p 是之前的尾部结点
    Node p = enq(node);
    int ws = p.waitStatus;
    // ws > 0 表示之前的尾部结点被取消了,设置 p 的waitStatus 为 SIGNAL,
    // 表示p结点后面有结点等待被唤醒,即刚插入等待队列尾部的结点 node 
    // 如果被取消,或者设置waitStatus 失败则唤醒node对应的线程,
    // 这个很容易理解吧,前面的结点取消了,那就直接轮到自己了,所以唤醒
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

这个方法注释的很清楚了,总结就是下面几点:

  1. 把 waitStatus 从CONDITION 变成0,因为要插入等待队列的尾部,而尾部的结点只是等待被唤醒,所以不需要设置生 SIGNAL或者其他。
  2. 通过enq方法把结点插入等待队列的尾部,返回的是node的前驱结点也就是老的tail结点
  3. p 表示的是老的tail结点,node 是新的tail结点,所以要把 pwaitStatus 设置为Node.SIGNAL,表示后继结点被LockSupport.park() 挂起了,需要被唤醒。如果 p 被取消了,或者设置 waitStatus 失败,则直接唤醒结点 node。 这也就是为什么说,signal 去唤醒线程,不会立即执行,而是要等到等到队列中的线程全部执行完成—因为signal 唤醒的时候把之前await 的线程插入到等待队列的尾部了。

这也解释了,为什么线程C执行signal 去唤醒线程B,会先执行线程D的任务,D的任务执行完之后才会执行线程B的任务了。

好了,本篇文章就结束了,有什么问题大家一起交流哈!

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值