最も一般的に使用されたCountDownLatchは、CyclicBarrierをはるかあなたが知っていますか?(Javaのエンジニアきっと)

たCountDownLatch、CyclicBarrierを、エンジニアはJavaのスキルになると言うことができる、非常に一般的な並行処理ツールです。だけでなく、多くの場合、プロジェクトの戦闘ではなく、圧力測定手順の作成に関与し、デモをマルチスレッド化することはとてもその使用法と実装の原則を習得することが必要である、また不可欠です。

オブセッション、そこにエコーしなければなりません!
周りの親指とガールフレンドを見つけます-

マルチスレッドたCountDownLatchの完了を待ちます

たCountDownLatchは、仕上げに他のスレッドを待っている1つまたは複数のスレッドを可能にします関節実行の実装後に相互に動作するために待機中のスレッドの組を可能なツールを使用することにより、そのたCountDownLatch。下に示した具体的な手順、矢印はタスクを示すタスクが開始するまで、すべての3つのタスクがバリア、バリア待ち時間に到達したとき、長方形は、フェンスを表します。

ステータスコードは0が続くなるまでスレッドの待機方法がブロックされている呼び出し;たCountDownLatchは、各時間カウントダウン呼状態の値は-1になり、int型のステータスコードを維持します。

仕事への複数のスレッドときは、他のスレッドは、その後、前にダウンメインスレッドの実行後に終了するのを待つ必要があるかもしれません。まず、我々は考えるかもしれないスレッドの参加方法の用途(実行の方法を参加スレッド優先呼スレッドが終了した後に、他のスレッドが実行されます)、明らかにこれを行うことができます。

Thread.join()メソッドの実装を使用して

public class RunningRaceTest {
    public static void main(String[] args) throws InterruptedException {
        Thread runner1 = new Thread(new Runner(), "1号");
        Thread runner2 = new Thread(new Runner(), "2号");
        Thread runner3 = new Thread(new Runner(), "3号");
        Thread runner4 = new Thread(new Runner(), "4号");
        Thread runner5 = new Thread(new Runner(), "5号");
        runner1.start();
        runner2.start();
        runner3.start();
        runner4.start();
        runner5.start();

        runner1.join();
        runner2.join();
        runner3.join();
        runner4.join();
        runner5.join();

        // 裁判等待5名选手准备完毕
        System.out.println("裁判:比赛开始~~");
    }
}

class Runner implements Runnable {
    @Override
    public void run() {
        try {
            int sleepMills = ThreadLocalRandom.current().nextInt(1000);
            Thread.sleep(sleepMills);
            System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Thread.join()この要求を実現することができますが、問題があり、参加したスレッドの呼び出しが生きていたならば、現在のスレッドが待っている必要がありますこれは明らかに柔軟十分ではありませんし、現在のスレッドと他の可能な例の死亡が発生する可能性があります。

より柔軟たCountDownLatch

JDK1.5と契約した後、同時たCountDownLatchツールを提供し、また機能に参加実現することが、より強力なことができます。

// 参赛选手线程
class Runner implements Runnable {
    private CountDownLatch countdownLatch;
    
    public Runner(CountDownLatch countdownLatch) {
        this.countdownLatch = countdownLatch;
    }

    @Override
    public void run() {
        try {
            int sleepMills = ThreadLocalRandom.current().nextInt(1000);
            Thread.sleep(sleepMills);
            System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 准备完毕,举手示意
            countdownLatch.countDown();
        }
    }
}

public class RunningRaceTest {
    public static void main(String[] args) throws InterruptedException {
        // 使用线程池的正确姿势
        int size = 5;
        AtomicInteger counter = new AtomicInteger();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(size, size, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), (r) -> new Thread(r, counter.addAndGet(1) + " 号 "), new ThreadPoolExecutor.AbortPolicy());
        
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < size; i++) {
            threadPoolExecutor.submit(new Runner(countDownLatch));
        }

        // 裁判等待5名选手准备完毕
        countDownLatch.await(); // 为了避免死等,也可以添加超时时间
        System.out.println("裁判:比赛开始~~");

        threadPoolExecutor.shutdownNow();
    }
}

出力:

5 号  选手已就位, 准备共用时: 20ms
4 号  选手已就位, 准备共用时: 156ms
1 号  选手已就位, 准备共用时: 288ms
2 号  选手已就位, 准备共用时: 519ms
3 号  选手已就位, 准备共用时: 945ms
比赛开始~~

同期バリアCyclicBarrierを

CyclicBarrierをされたCountDownLatchはことを除いて、同じ機能を実現することができますCyclicBarrierをリサイクルすることができながら、文の後に一度だけ使用されたCountDownLatchワンタイムオブジェクト

ビューの文字通りの意味の観点から、CyclicBarrierをスレッドすべてのグループがバリアに到達したとき、障壁を除去、またはだけバリアでブロックされ、バリアループを表します。

public class RunningRace {
    public static void main(String[] args) {
        // 使用线程池的正确姿势
        int size = 5;
        AtomicInteger counter = new AtomicInteger();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(size, size, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), (r) -> new Thread(r, counter.addAndGet(1) + " 号 "), new ThreadPoolExecutor.AbortPolicy());

        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("裁判:比赛开始~~"));
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.submit(new Runner(cyclicBarrier));
        }
    }
}

class Runner implements Runnable {
    private CyclicBarrier cyclicBarrier;

    public Runner(CyclicBarrier countdownLatch) {
        this.cyclicBarrier = countdownLatch;
    }

    @Override
    public void run() {
        try {
            int sleepMills = ThreadLocalRandom.current().nextInt(1000);
            Thread.sleep(sleepMills);
            System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms" + cyclicBarrier.getNumberWaiting());
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

CyclicBarrierをリサイクルすることができますので、コンストラクタのCyclicBarrierをはRunnableを引数を渡すことができますので、それは各ラウンドが終了した直後のRunnableタスクを実行します

たCountDownLatch設計と実装

ベースCountDownLath AQSは、2つの方法はコアのawait()と状態値コンストラクタを通過カウントダウン()は、ステータスコードになるまでスレッドがブロックされたときのawait()メソッドを呼び出し、すなわち、存在する、シンプルなフレームワークであります修飾は、状態値が1だけデクリメントされるカウントダウン()を呼び出すたびに0を返します。

メソッドを待って、それは状態0への道として実行し続けた場合、現在のスレッドがキューの同期に追加されるかどうかを選択し、waitメソッドを実行した後、状態を同期取得しようとします、詳細は2つの記事AQSの著者で見つけることができます。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果状态码不为0,尝试获取同步状态,如果失败则被加入到同步队列中
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
// 当状态码为0时返回1,否择返回-1,这个方法中参数没有任何用处
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

カウントダウン方法:カウントダウン各実行方法、値は1マイナスステータスコードであろう。

public void countDown() {
    sync.releaseShared(1);
}

CyclicBarrierをの設計と実装

たCountDownLatchは同じCyclicBarrierをアイデアを実現するだけでなく、AQSベースのフレームワークを達成するために。CyclicBarrierをことを除いて内部状態値を保持し同期更新をステータス値を達成するために、ReentrantLockのAQSのロックベースの実装によって、及び完全遮断スレッドキューに同期状態条件のコアコンセプト以外にAQS添加。

parties: 和CountdownLatch中的状态值一样,用来记录每次要相互等待的线程数量,只有parties个线程同时到达屏障时,才会唤醒阻塞的线程。

count临时计数器: 由于CyclicBarrier是可以循环使用的,count可以理解为是一个临时变量,每一轮执行完毕或者被打断都会重置count为parties值。

Generation内部类: 只有一个属性 broken表示当前这一轮执行是否被中断,如果被中断后其他线程再执行await方法会抛出异常(目的是停止本轮线程未执行线程的继续执行)。

await方法: 当执行await方法时,会同步得对内部的count执行--count操作, 如果count = 0,则执行barrierCommand任务(通过构造方法传来的Runnable参数)。

reset方法:中断本轮执行,重置count值,唤醒等待的线程然后开始下一轮,此时本轮正在执行的线程调用await方法会抛出异常。

// await方法实际执行的代码
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
TimeoutException {
    final ReentrantLock lock = this.lock;
    // 加锁,保证并发操作的一致性
    lock.lock();
    try {
        // 如果当前这一轮操作被中断,抛出中断异常(该异常只是起警示作用,没有任何其他信息)
        final Generation g = generation;
        if (g.broken)
            throw new BrokenBarrierException();
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        // 本轮执行的计数器 数值-1
        int index = --count;
        if (index == 0) {  // 计数器值=1, 本轮线程全部到达屏障,执行barrierCommand任务
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                nextGeneration();// 唤醒所有等待在条件队列上的任务
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 如果状态不等于0,循环等待直到计数器值为0,本轮执行被打破,线程被中断,或者等待超时
        for (;;) {
            try {
                if (!timed)
                    // 状态码不为0,将当前线程加入到条件队列中,进入阻塞状态
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();// 唤醒所有条件队列中的线程,重置count的值
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

重置栅栏的状态

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}
/**
 * Sets current barrier generation as broken and wakes up everyone.
 * Called only while holding lock.
 */
private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

当一轮执行完毕之后,既count=0后,CyclicBarrier的临时状态会重置为parties

/**
 * 进入下一轮
 * 唤醒所有等待线程,充值count
 */
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

总结

  1. CountDownLatch创建后只能使用一次,而CyclicBarrier可以循环使用,并且CyclicBarrier功能更完善。
  2. CountDownLatch内部的状态是基于AQS中的状态信息,而CyclicBarrier中的状态值是单独维护的,使用ReentrantLock加锁保证并发修改状态值的数据一致性。
  3. 它们的使用场景:允许一个或多个线程等待其他线程完成操作, 即当指定数量线程执行完某个操作再继续执行下一个操作。

おすすめ

転載: www.cnblogs.com/liqiangchn/p/12105295.html