CyclicBarrierをする危険性を見つけるために高度な--JUCマルチスレッド並列プログラミングのソース

個人ブログのナビゲーションページ(クリック、右側にリンク個人のブログを開くために):ダニエルは、テクノロジ・スタックにあなたを取ります 

1、学習の開始点

Baiduの翻訳は、おそらく意味します:

スレッドの一般的なバリアポイント到着のための各待ちのセットを可能にする同期補助。CyclicBarrierを便利スレッドパーティプログラムは、固定サイズが含まれ、これらのスレッドパーティーが、時にはお互いのを待たなければなりません。それは、スレッドが解放され待機した後に再使用することができますので、この障壁は、巡回バリアと呼ばれています。

CyclicBarrierをが関与処方箋が到着する最後のスレッドの後で、オプションのRunnableコマンドをサポートしていますが、任意のスレッドをリリースする前に、一度、各ポイントバリアを実行します。この障壁は、継続する前にすべての参加者に共有動作状態を更新するのに役立ちます。

ダイナミックなプレゼンテーション:

上記の分析では、我々は、完成し  たCountDownLatchソースコード、それは次のように理解することができるダウンカウンタに基づいているの共有AQSモードの使用、及びCyclicBarrierをがたCountDownLatchと比較し、それに類似している、はるかに簡単であるカウンタを追加、ソースコードの使用  ReentrantLockのとの条件を組み合わせ使用します。

2、ケースプレゼンテーションCyclicBarrierを 

//加法计数器
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐5名队员,开始游戏
         */
        // 开始战斗的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5,()->{
            System.out.println("欢迎来到王者荣耀,敌军还有五秒到达战场!全军出击!");
        });
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"第"+temp+"个进入游戏!");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3、コンストラクタを開始

//构造器1
/** 创建一个新的CyclicBarrier,它将在给定数量的参与方(线程)等待时触发,并在触发屏障时执行给定的屏障操作,由最后一个进入屏障的线程执行 */   
public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

//构造器2
/** 创建一个新的CyclicBarrier,当给定数量的参与方(线程)在等待它时,它将跳闸,并且在屏障跳闸时不执行预定义的操作 */
public CyclicBarrier(int parties) {
        this(parties, null);
    }

指定できるコアコンストラクタ、などコンストラクタで1  パーティー ゲームの評議会(ブロックするスレッド数)の参加者数と  barrierActionの 実行されるゲーム終了時にタスク協議会を。

図3に示すように、メンバ変数を開始

   /** 同步操作锁 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 线程拦截器 Condition维护了一个阻塞队列*/
    private final Condition trip = lock.newCondition();
    /** 每次拦截的线程数 */
    private final int parties;
    /* 换代前执行的任务 */
    private final Runnable barrierCommand;
    /** 表示栅栏的当前代 类似代表本局游戏*/
    private Generation generation = new Generation();
    /** 计数器 */
    private int count;
    /** 静态内部类Generation  */
    private static class Generation {
        boolean broken = false;
    }

図3に示すように、コアの方法で始まり

3.1、ソースコード解析法[のawait]

これらの2つの方法の以下の解析、それぞれ[ スケジュールされていない待機 ]と[ タイミング待ち ]!

//非定时等待
public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
//定时等待
public int await(long timeout, TimeUnit unit)throws InterruptedException,
              BrokenBarrierException,
              TimeoutException {
       return dowait(true, unit.toNanos(timeout));
   }

あなたは、異なるパラメータを使用して、[dowait]方法最後の二つの方法がなくなっている、見ることができます。ここでは、ことをやっていたものを見るために最後に、このアプローチに焦点を当てています。

//核心等待方法
 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;
            //计数器的值减为0,则需要唤醒所有线程并转换到下一代
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    //唤醒所有线程前先执行指定的任务
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //唤醒所有线程并转换到下一代
                    nextGeneration();
                    return 0;
                } finally {
                    //确保在任务未成功执行时能将所有线程唤醒
                    if (!ranAction)
                        breakBarrier();
                }
            }
            //如果计数器不为0 则执行此循环
            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    //根据传入的参数来觉得是定时等待还是非定时等待
                    if (!timed)
                        //如果没有时间限制,则直接等待,直到被唤醒
                        trip.await();
                    else if (nanos > 0L)
                        //如果有时间限制,则等待指定时间
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    //若当前线程在等待期间被中断则打翻栅栏唤醒其它线程
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // 若在捕获中断异常前已经完成在栅栏上的等待,则直接调用中断操作
                        Thread.currentThread().interrupt();
                    }
                }
                //如果线程因为打翻栅栏操作而被唤醒则抛出异常
                if (g.broken)
                    throw new BrokenBarrierException();
                //如果线程因为换代操作而被唤醒则返回计数器的值
                if (g != generation)
                    return index;
                //如果线程因为时间到了而被唤醒则打翻栅栏并抛出异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();//最终解锁
        }
    }

二段階における分析は、まず、ダウンカウンタの値がゼロであり、カウンタは、まず、第1の場合、0ではありません。

第二の場合は、カウンタは、次いで、(;;)のためのスピンを入力し、0ではありません。

現在のスレッドをブロックする方法を、同時アクセスをマルチスレッド?

私たちは、ソースコードを見て、ここでは時間制限なしで見ると、[だtrip.await ]方法:

全体のプロセスを待ちます。

1は、現在のスレッド条件ロックキューに追加されます。具体的には、入ってくるFIFOキューが条件であるキューを、待っている主要なAQSを区別するために

2、ロックを解除。ここでは、[fullyRelease]は、ロックを解除しますそうでない場合は、[acquireQueued(ノード、savedState)]デッドロックが発生し、別のスレッドのロックを取得することはできません見ることができます。

覚醒CACELLEDまで又は外又はように懸濁3、スピン(ながら)。

4、ロック[acquireQueued]メソッドを取得し、FIFOキューの状況、自分自身をロックする表面不要になった(私はすでにロックされている)から自分自身を解放

3.2、条件キューおよび待機キューサプリメントAQS

条件は2つの別々のキュー、[のawait]は、現在のスレッドがロックリソースのロックを保持し、新しいノードを作成するに基づいてリリースされているキュー、キューを待ってAQSは、現在のスレッドをブロックし、キューの条件条件尾に追加されます。[信号]は再びロックを獲得するために待機することができAQS条件ノードのキューのテールに現在のヘッドノードの移動、です。以下の図は、違いを示しています。

Condition.await実行ノード1() - ノードが待ち行列状態に追加される(3)> - >(2)とロック解除ノード待ち行列は、AQSから除去される - >(1)は、ヘッドを移動します - >(4)ノードがlastWrite更新されます

ノード2を行う信号()操作 - firstWriteシフト後>(1) - >(2)4条件キューを除去するノード - >(3)4にキュー待ちAQSに追加されたノード - >(4)更新AQS待ち行列テール

3.3概要:

A、コンディションデータ構造:

私たちは、それが条件のawaitことができる知っている()必要に応じて、あなたが一緒に多くの場所これらの条件にFIFO構造が必要になりますし、(通常はすべての)一つ以上を覚まします。だから、内部条件は、FIFOキューが必要です。
ノードfirstWaiterプライベート過渡;
プライベートlastWaiterノード過渡;

上記で説明した2つのノードをFIFOキューです。我々は以前に合わせノード(ノード)のデータ構造を挙げます。私たちは、Node.nextWaiterが便利になります発見しました!nextWaiterはFIFOキューを形成するために一緒にリンクされCondition.awaitのシリーズです。

第二に、スレッドとブロック解除

ブロッキング:ノードが待ちキューをAQSされていない場合は、リソースの後のawait()メソッドを、スレッドのロックを解除は、現在のスレッドをブロックすることは、スピン試すのを待って、待ちキューかのロックを取得し
た状態からの信号()、ノード:リリースキューへのAQSキュー、ロックを取得する通常の手順に進みます。

3.4、[signalAll] signalAllソースコード解析

[ SignalAll】この方法は、ブロックキューの状態ですべてのスレッドを覚まします

private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();//唤醒Condition中等待的线程
    }

public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
     }
/** 这个方法相当于把Condition队列中的所有Node全部取出插入到等待队列中去 */
private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
       }
/** 将节点从条件队列传输到同步队列AQS的等待队列中 */
final boolean transferForSignal(Node node) {
        //核心添加节点到AQS队列方法
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
/** 使用CAS+自旋方式插入节点到等待队列,如果队列为空,则初始化队列 */
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }

3.5、ソースコード解析方法[リセット]

最後に、私たちはフェンスをリセットする方法を見て:

バリアは初期状態にリセットされます。いずれかの当事者が現在壁に待機している場合、彼らがかかりますBrokenBarrierExceptionのリターンを。注割り込みをリセットすると複雑になることがあり、他の理由で発生すること;スレッドがそうでない場合は再同期、およびリセットを実行するための方法を選択する必要があります。最高のは、将来の使用のための新たな障壁を作成することです

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

テストコードをリセットします。

まず、手段待機中のすべてのスレッド(5つの待機中のスレッドは)[のawait]で[メソッドスローされます、目を覚ますだろうことを、フェンスを壊しBrokenBarrierException例外復帰を]。次に、新しい世代を開いて、カウントがリセットされ、世代、すべての戻り0に相当します。

4差たCountDownLatchとCyclicBarrierを

同じポイント:

1、特定の条件に達する前に待機するスレッドのセットを達成することができます

すべてのブロックされたスレッドがウェイクアップされたときにカウンタが継続的に0に減少したときに内部カウンタを持って2、!

違い:

1、CyclicBarrierをカウンタは、それ自身によって制御され、そしてたCountDownLatchカウンタを使用して制御されます

2は、CyclicBarrierをスレッド呼び出されたのawait 1によってその妨害するだけでなく、カウンターを置く、とされたCountDownLatchスレッドではないだけに、単純にカウンタの値を減少させることなく自分自身を遮断するのawaitメソッドを呼び出します。

3また、たCountDownLatchつのみ傍受、傍受及びCyclicBarrierをサイクルを達成することができます。一般CyclicBarrierをたCountDownLatch関数ではなく、その逆も、達成することができます。

5.まとめ:

あなたは最終的に] [dowait方法は、ReentrantLockの、-1の各圏のカウンタのカウント値をロックするために使用-1カウンタ値が0であるとき、それは最初に、指定されたタスクを実行します実装[cyclicBarrier.await]メソッドを呼び出すと、通話の条件[trip.signalAll()]すべてのスレッドを覚ますと、次の世代へ

-1スピン、条件[()]メソッドののawait実行を入力するとき、現在のカウンタ値が0でない場合、状態キュー状態を待つ現在のスレッドを追加し、実行を呼び出す[] [fullyReleaseカウント値tryRelease] - 1、その後、カウント値が0であるかどうかを判断、0意志が指定されたタスクを実行し、ないし、上の場合は、すべてのスレッドを覚ますと、次の世代に、その後、AQSは、キューを待っているかどうかを決定[trip.signalAll()]の条件を呼び出しますAQS待ちキューに現在のスレッドを駐車、または条件がsignalAllになるまで、キュー内のスピンロックの取得を待機キューに起こされるのを待ってAQS

推奨読書:

付属のJava / C / C ++ /機械学習/アルゴリズムとデータ構造/フロントエンド/アンドロイド/パイソン/プログラマ読み/シングル書籍図書Daquanは:

(乾燥した個人ブログでそこ開くには、右クリックしてください):技術的なドライ開花を
===== >> ①[Javaのダニエルは、高度なへの道であなたを取る] << ====
===== >> ②[+ ACMアルゴリズムデータ構造ダニエルは、高度なへの道であなたを取る] << ===
===== >> ③[データベースダニエルは高度への道であなたを取る] << == ===
===== >> ④[ダニエルWebフロントエンドの高度への道であなたを取るために] << ====
===== >> ⑤[機械学習のPythonとダニエルあなたにエントリを取ります高度なロード] << ====
===== >> ⑥[建築家ダニエルは高度への道であなたを取る] << =====
===== >> ⑦[C ++ダニエルは、道路上をお連れに進ん] << ====
===== >> ⑧[ダニエルは高度への道であなたを取るのiOS] << ====
=====> > ⑨[ウェブセキュリティダニエルは、高度なへの道であなたを取る] ===== <<
===== >> ⑩[Linuxオペレーティングシステムを、ダニエルは高度への道であなたを取る] = << ====

何の未収果物はありません、あなたの若い友人は、友人がテクニックを学びたい願って、道路の方法ですべての障害を克服することは、技術に結びつける本を理解して、コードをノック、原理を理解し、実践を行くことになります決定しましたそれはあなたの将来、あなたの夢を生活、あなたの仕事をもたらすでしょう。

 

公開された141元の記事 ウォン称賛17 ビュー8160

おすすめ

転載: blog.csdn.net/JKX_geek/article/details/102925109