[Javaコンカレントツールクラスコラボレーション] CountDownLatchおよびCyclicBarrier


上記の記事では、Java SDKで提供される相互に排他的なツールクラスを主に紹介します。マネージド同期、ロック、ReadWriteLock、StampedLock、LockSupport。以下では、Java SDKでスレッドコラボレーションを提供するツールクラスを紹介するためにいくつかの記事に分け、本日は、CountDownLatchとCyclicBarrierの2つのツールクラスについて説明します。

以下はシーンを紹介し、全文はこのシーンの周りのこれら2つのツールクラスを紹介します

事業紹介

ここに画像の説明を挿入

1.スレッド最適化ビジネスの創出

while(存在未对账订单){
  // 查询未对账订单
  Thread T1 = new Thread(()->{	pos = getPOrders();	});
  T1.start();
  // 查询派送单
  Thread T2 = new Thread(()->{ dos = getDOrders();	});
  T2.start();
  // 等待T1、T2结束
  T1.join();
  T2.join();
  // 执行对账操作
  diff = check(pos, dos);
  // 差异写入差异库
  save(diff);
} 

メインのメインスレッドは、スレッドT1とT2のjoinメソッドを呼び出し、スレッドが終了して終了するまで待機してから、調整とライブラリへの書き込み操作を実行します。

2. Javaスレッドとオペレーティングシステムスレッドの関係:

オペレーティングシステムによって実装されたスレッドは非常に安定しているため、Javaスレッドスケジューリングはオペレーティングシステムに渡され、スレッドの作成、破棄、スケジューリング、およびメンテナンスはすべてオペレーティングシステム(正確にはカーネル)によって実装されます。スレッドを使用するだけでよく、独自のスレッドスケジューリングアルゴリズムやCPUリソースのスレッドプリエンプションを設計する必要はありません。

3.ビジネスを達成するためのスレッドプール

上記では、スレッドの作成と破棄は重いことを知っているため、開発ではスレッドプールを使用してスレッドを再利用し、スレッドの作成と破棄を回避することがよくあります。
以下では、上記のビジネスを実現するためにスレッドプールを使用します。

// 创建2个线程的线程池
Executor executor = Executors.newFixedThreadPool(2);
while(存在未对账订单){
  // 查询未对账订单
  executor.execute(()-> { pos = getPOrders(); });
  // 查询派送单
  executor.execute(()-> {	dos = getDOrders();});
  
  /* ??如何实现等待??*/
  
  // 执行对账操作
  diff = check(pos, dos);
  // 差异写入差异库
  save(diff);
}   

スレッドプール内のスレッドが終了しないため、スレッド結合メソッドが無効です。どうすればよいですか?Java SDKツールキットのCountDownLatchを使用します。

4 CountDownLatch

4.1 CountDownLatchの該当するシナリオ

該当するシナリオ:CountDownLatchは主に、1つのスレッドが複数のスレッドを待機するシナリオを解決するために使用されます。

4.2 CountDownLatchを使用してスレッド待機を実現する

// 创建2个线程的线程池
Executor executor = Executors.newFixedThreadPool(2);
while(存在未对账订单){
  // 计数器初始化为2
  CountDownLatch latch = new CountDownLatch(2);
  // 查询未对账订单
  executor.execute(()-> { 	pos = getPOrders();		latch.countDown();		});
  // 查询派送单
  executor.execute(()-> {	dos = getDOrders();		latch.countDown();		});
 
  latch.await(); // 等待两个查询操作结束,实现对计数器=0的等待。
  diff = check(pos, dos); // 执行对账操作
  save(diff); // 差异写入差异库
}

最初に、CountDownLatchを作成しました。カウンターの初期値は2です。getPOrdersメソッドが実行されると、latch.countDown()、counter-1が呼び出されます。カウンター= 0の場合、ウェイクアップし、latch.await()を実行してダウンを継続します。

5.パフォーマンスをさらに最適化する

上記では、2つのスレッドを開いて、クエリの未調整の注文とクエリの配信注文を実行し、クエリが終了するまで待ってから、調整操作を実行します。

実際、まだ最適化のポイントがあります。つまり、調整操作を実行するときに、調整されていない注文と配信注文を同時にクエリできます。
ここに画像の説明を挿入
上記の画像は、私たちの意図を表すことができます。スレッドT1とスレッドT2の両方が1つのデータを生成すると(2つは互いに待機する必要があります)、引き続きダウンし(注文のクエリと注文の送信)、スレッド3の実行によって調整が実行されます。 。

実際、それは生産者-消費者モデルであり、スレッドT1とT2はどちらもデータを生成してキューに入れ、コンシューマT3がそれを消費します。

実装には2つの困難があります。

  1. スレッドT1とT2は、どちらも次のラウンドのクエリを実行するためのデータを生成します。
  2. 一方、T3に通知する必要
    があります。CountDownLatchは= 0の後でカウンターをリセットしないため、以下はCyclicBarrierを使用してこの操作を実現します。

6.サイクリックバリア

6.1 CyclicBarrierの適用可能なシナリオ

該当するシナリオ:CyclicBarrierは、お互いを待っているスレッドのグループです。

さらに、CountDownLatchのカウンターはリサイクルできません。つまり、カウンターが0になり、スレッドがawait()を呼び出すと、スレッドは直接渡されます。ただし、CyclicBarrierのカウンターはリサイクルでき、counter = 0の場合は、コールバック関数が呼び出されてカウンター値がリセットされます。

6.2 CyclicBarrierを使用してスレッドの同期(コラボレーション)を実現する

スレッド同期:スレッド間の連携を制御します。以下はCyclicBarrierを使用して上記の最適化を実現します。

// 订单队列
Vector<P> pos;
// 派送单队列
Vector<D> dos;
// 执行回调的线程池 
Executor executor = Executors.newFixedThreadPool(1);//注意这里线程池中只有一个线程
final CyclicBarrier barrier =  new CyclicBarrier(2, ()->{
	executor.execute(()->check());	}); //注意这里使用的是线程池的方式。 
void check(){
  P p = pos.remove(0);
  D d = dos.remove(0);
  // 执行对账操作
  diff = check(p, d);
  // 差异写入差异库
  save(diff);
}
  
void checkAll(){
  // 循环查询订单库
  Thread T1 = new Thread(()->{
    while(存在未对账订单){  
      pos.add(getPOrders());  // 查询订单库
      barrier.await(); // 等待
    }
  });
  T1.start();  
  // 循环查询运单库
  Thread T2 = new Thread(()->{
    while(存在未对账订单){
           dos.add(getDOrders()); // 查询运单库
      barrier.await(); // 等待
    }
  });
  T2.start();
}
  1. CyclicBarrierを作成します。カウンターの初期値は2で、= 0のときにコールバック関数のcheck()メソッドを実行します。
  2. checkall()メソッドが実行されると、2つのスレッドが開始されて注文と配達注文をそれぞれ照会し、照会は終了し、カウンターは-1になります。
  3. カウンター= 0の場合、スレッドプール内のスレッドを使用してcheck()メソッドが実行され、同時にカウンターがリセットされて、クエリオーダーと配信オーダーの次のラウンドが開始されます。

3つの問題:

  • CyclicBarrierのコールバック関数はどのスレッドで実行されますか?
    CyclicBarrierのコールバック関数は、ラウンドで最終的にバリア。待機()を実行するスレッドで実行され、チェック()メソッドが同期的に実行されます。チェック()メソッドが呼び出された後、次のラウンドを開始できます。したがって、check()が非同期実行のために別のスレッドを開始する場合、それはパフォーマンスの最適化に役割を果たしません。
  • コールバック関数で直接呼び出すのではなく、なぜスレッドプールを使用するのですか?
    私はあなたが最後の質問を通してほとんど理解したと信じています、右は非同期です。
  • シングルスレッドスレッドプールを使用する理由
    スレッドの数は1に固定されています。これにより、マルチスレッドの同時実行によるデータの不整合が防止されます。これは、注文と配信の注文が2つのキューであり、1つのスレッドが2つのキューに移動してメッセージをフェッチする場合にのみ、メッセージの不一致が発生しないためです。(実際、各ラウンドの2つのデータを1つのデータにカプセル化してキューに入れることができます。マルチスレッドフェッチではこの問題は発生しません。)

参照:Geek Time
More:Deng Xin

元の記事を34件公開 Likes0 Visits 1089

おすすめ

転載: blog.csdn.net/qq_42634696/article/details/105135835