スレッド同期ツールの原則の深さ分析されたCountDownLatch

0たCountDownLatchの役割

1つ以上のスレッドが作業の完了後(複数でもよい)他のスレッドを待ち、その後、実行を再開可能にするマルチスレッド間の同期ツールとしてたCountDownLatch。

このように:

たCountDownLatchの役割

1つのデモから言えば

私たちは、デモのソースコードを直接シミュレーションレースシーンとして見ることができるこのデモのソースコードを見て与えられている取ります。レースは確かに速い選手が遅いランナーを持って実行し、各選手は、スレッドを表します。選手の終了後に銃声が起動し始めたが、最後の選手の後に、端末に到達するために、ゲームをマーキング聞きました。全体のプロセスを以下に示します。

たCountDownLatch実行中のシミュレーション

ソースは以下の

public class Race {

    private static final int N = 4;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);  // 鸣枪开始信号
        CountDownLatch doneSignal = new CountDownLatch(N);   // 等待N个运动员都跑完后,比赛结束(结束信号)

        for (int i = 0; i < N; ++i) // N个运动员准备就绪,等待枪声
            new Thread(new Runner(startSignal, doneSignal, i)).start();

        Thread.sleep(1000); // 等待所有运动员就绪
        System.out.println("所有运动员就绪");
        startSignal.countDown();      // 鸣枪,开赛
        System.out.println("比赛进行中...");
        doneSignal.await();           // 等待N个运动员全部跑完(等待doneSignal变为0)
        System.out.println("比赛结束");
    }
}

class Runner implements Runnable {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;
    private int number;

    Runner(CountDownLatch startSignal, CountDownLatch doneSignal, int number) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
        this.number = number;
    }
    public void run() {
        try {
            // 等待枪声(等待开始信号startSignal变为0)
            System.out.println(number + "号运动员准备就绪");
            startSignal.await();
            // 赛跑
            System.out.println(number + "号运动员跑步中...");
            Thread.sleep(new Random().nextInt(10) * 1000);
            // 此运动员跑到终点
            System.out.println(number + "号运动员到达终点");
            doneSignal.countDown();
        } catch (InterruptedException ex) {} // return;
    }
}
复制代码

上記のコードを実行した後、次の出力:

0号运动员准备就绪
3号运动员准备就绪
2号运动员准备就绪
1号运动员准备就绪
所有运动员就绪
比赛进行中...
0号运动员跑步中...
1号运动员跑步中...
2号运动员跑步中...
3号运动员跑步中...
2号运动员到达终点
1号运动员到达终点
0号运动员到达终点
3号运动员到达终点
比赛结束
复制代码

さて、深いコードの詳細に、どのようにされたCountDownLatch初期化、カウントダウン方法を見て、メソッドが実装されて待っています。

クラス2たCountDownLatch図。

クラスの継承関係を調べるために図に従うことによってたCountDownLatch

図クラスたCountDownLatch

3たCountDownLatchの初期化

たCountDownLatchだけコンストラクタ:

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
复制代码

彼はReentrantLockのに似て彼の内部クラスの一つであり、同期を開始し、同期もAbstractQueuedSynchronizer(AQS)継承されました。

AQSは何ですか?別の記事の著者を参照してくださいすることができます最終的にはJavaのキュー同期(AQS)はどのように一つのことです

そして、ソースコードの同期を見て

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    // 调用AQS的setState方法,将state赋值为count的值
    Sync(int count) {
        setState(count);
    }

    // 获取AQS state的当前值
    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}
复制代码

だから、たCountDownLatch初期化は、実際には、パラメータカウント状態AQSに値を割り当てることで、それはまだ状態の同期状態によって制御されます。

図4は、実施される方法を待ちます

それでも問題を説明するために、上記の例でレース。ここでは、すべてのアスリートのための砲撃待ちのシーンのみを考えます。

レースの例を思い出して、次のように発射信号を作成します。

CountDownLatch startSignal = new CountDownLatch(1);  // 鸣枪开始信号
复制代码

そして、N個のスレッド(Nは選手を表わし)を作成し、実行を開始することができ、そのstartメソッドを(選手はショットが実行を開始するのを待っている、準備ができている)を呼び出します。

次に、)(runメソッドでstartSignal.awaitを呼び出すショットがアクションを達成するのを待つことにより、(例えばstartSignalの値として、実際には0まで減少)。

私たちは彼が待っています方法を見て。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
复制代码

AQSのacquireSharedInterruptiblyメソッドを呼び出し

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 判断线程是否已经被中断
    if (Thread.interrupted())
        throw new InterruptedException();
    // 调用CountDownLatch.Sync的tryAcquireShared方法
    // 此方法判断count的值是否==0,如果==0,返回1,否则返回-1
    // 目前我们还没有执行countDown,所以count肯定不等于0,这里肯定返回-1
    // 所以会执行到AQS的doAcquireSharedInterruptibly方法中
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
复制代码

AQS.doAcquireSharedInterruptiblyの実装は次のよう

/**
 * Acquires in shared interruptible mode.
 * @param arg the acquire argument
 */
// 此方法会在count>0时将当前线程加入到等待队列中
// 由于我们目前还没有执行countDown,所以count会保持>0
// 启动的N个线程会全部加入到队列中
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 将当前线程添加到等待队列中(SHARED模式)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        // 自旋获取同步状态
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                // 依然调用CountDownLatch.Sync的tryAcquireShared方法判断
                // 如果count降为0,退出自旋
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 将node的waitStatus设置为-1(常量SIGNAL,表示需要唤醒),并阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

次のようにすべての4つのスレッド開始後、N = 4とし、すべてのスピン待ちキューに追加されます。

CountDownLatch.awaitスピン待ち

実現の5カウントダウン方法

カウントダウン方法は、実際にはAQS値の状態-1。次いで、すべての待機中のスレッドを起動する必要があり、状態== 0、0に等しい場合、現在の値は、すべてのスレッドが上に実行されることを示すかどうかを判断します。

上記のシナリオはまだあり続け、焼成後、すべての選手が実行を開始します。

このアクションショットは、実際には、メインスレッドで実行します:

startSignal.countDown();
复制代码

これは単に、すべてのスレッドがコードの後ろに待機するために引き続き、目を覚ますだろう信号の実行を再開するためにキュー内のすべてのスレッドを送信することと同じです。

特定のカウントダウンがシェーンをしましたか?

public void countDown() {
    sync.releaseShared(1);
}
复制代码

彼は、AQSのreleaseSharedメソッドを呼び出します。

public final boolean releaseShared(int arg) {
    // 调用CountDownLatch.Sync的tryReleaseShared方法
    // 该方法尝试将count值-1,并判断-1后的count是否==0,如果==0,返回true,否则false
    // 该方法的源码已经在Sync的源码中给出,可翻阅上文查看
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
复制代码

startSignal.countDown()の後のカウント== 1 startSignalの初期値ので、カウントがゼロになります。だから、tryReleaseSharedはtrueを返します。

そして、doReleaseSharedを開始し、キューのスレッドが目を覚まします。

AQSのdoReleaseShared方法。

/**
 * Release action for shared mode -- signals successor and ensures
 * propagation. (Note: For exclusive mode, release just amounts
 * to calling unparkSuccessor of head if it needs signal.)
 */
private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // Node.SIGNAL == -1
            // 由上文可知,进入队列的线程的waitStatus都等于-1
            // 所以这里为true
            if (ws == Node.SIGNAL) {
                // 尝试将waitStatus从-1改为0,如果修改成功,就恢复这个线程的执行状态
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
复制代码

ここでは、ブロックされたスレッドと再開実行、それの回復?スピンはただそこに待っています。

上記のコードは、直接勝つために、次いで(コメント欄)説明します

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 将当前线程添加到等待队列中(SHARED模式)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        // 线程被释放后,继续下一次循环
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 获取头节点,从头结点开始释放,由于count已经降为0,所以r >= 0为true
                // 然后会将自己摘除当前队列,使下一个节点成为头节点
                // 等下一个节点也恢复过来后,同样执行上面的过程
                // 这样,队列中的所有线程就被释放了
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 将node的waitStatus设置为-1(常量SIGNAL,表示需要唤醒),并阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

概要6

本論文では、どのようにソースコードレベルたCountDownLatchの作品を詳しく説明します。たCountDownLatchもAQSが達成基づき、そう、AQSのメカニズムを理解することは、この記事を理解するために不可欠です。実際には、たCountDownLatchコアは、複数のスレッド間で状態を同期するために、AQSの状態によって制御されます。

おすすめ

転載: juejin.im/post/5d14b8cae51d4577523f23be