AQSシリーズ
1. AQS のコア原則
2. ReentrantLock の例と原則
3. CountDownLatch / セマフォの例と使用シナリオ
4. BlockingQueue の例と使用シナリオ
記事ディレクトリ
I. 概要
日常の開発では、データの処理やデータのプッシュが完了するときに、「あるプログラムが少し遅い」と感じ、複数のスレッドで実行させても、処理ロジックが異なる場合があります。スレーブ テーブルを更新する前にマスター テーブルを更新するか、複数のタスク間に依存関係がある場合は、最初にタスク 1 を実行し、次にタスク 2 を実行します。この場合は、BlockingQueue を使用できます。
BlockingQueue は、同時プロデューサーとコンシューマー間のデータ転送を解決するために juc パッケージによって提供される最も便利なクラスの 1 つです。常に 1 つのスレッドだけが take/pull または put 操作を実行し、BlockingQueue はタイムアウト時に null を返すメカニズムを提供します。マルチスレッドでデータ転送操作を実行できるため、効率が向上します。
2. 基本原則
BlockingQueue はブロッキング キューであり、次の 2 種類に分類されます。
- 無制限のキュー: ほぼ無限に増加する可能性があります。
- 境界付きキュー: 最大容量を定義します。
Queue は実際にはデータを格納するためのコレクションです。List と同様に、大量のデータをキューに入れることができます。Queue には次のような特徴があります。
- 通常、データまたはリンク リストの形式で実装されます。
- 一般的なキューは FIFO (先入れ先出し) 機能であり、JVM メモリのスタックと同様に、両端キュー (Deque) 優先キューもあります。
- キューには、エンキューとデキューという 2 つの主な操作があります。
日常業務における一般的なキューは次のとおりです。
- ArrayBlockingQueue: 配列によってサポートされる境界付きキュー。
- LinkedBlockingQueue: リンク リスト ノードによってサポートされる、制限されたキューまたは解決不可能なキュー。
- PriorityBlockingQueue: 優先度ヒープによってサポートされる無制限の優先度キュー。
- DelayQueue: 優先度ヒープに基づく時間ベースのスケジューリング キュー。
3. 例
3.1 配列ブロックキュー
これは非常に単純な例です。書くとさらに複雑ですが、面倒に見えます。まず、キュー idQueue をインスタンス化してキューをブロックし、順序よく ID を保存します。最初のスレッドは無限ループでキューから値を取得します。 、そして 2 番目の各スレッドは値をキューに入れるために 5 回だけループします。コードは次のとおりです。
public class ArrayBlockingQueueTest {
public static BlockingQueue<Integer> idQueue = new ArrayBlockingQueue(10);
public static void main(String[] args) {
//这里先有一个线程从队列中获取值
new Thread(() -> {
for(;;){
try {
Integer value = idQueue.take();
System.out.println("从队列中获取到值:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//这里有一个线程每隔 1 秒往队列中放
new Thread(() -> {
for(int i=0; i < 5; i++){
try {
//往队列中插入
idQueue.put(i);
System.out.println("往队列中放入值:" + i);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
実行結果を見てみましょう。
生产者往队列中放入值:0
消费者从队列中获取到值:0
生产者往队列中放入值:1
消费者从队列中获取到值:1
消费者从队列中获取到值:2
生产者往队列中放入值:2
生产者往队列中放入值:3
消费者从队列中获取到值:3
生产者往队列中放入值:4
消费者从队列中获取到值:4
(阻塞中......)
結果からわかるように、プロデューサがブロッキング キューに値を入れると、コンシューマ スレッドはすぐにその値を消費します。プロデューサが 5 回ループした後、値をキューに入れなくなり、コンシューマ スレッドはこの場所にブロックされて待機します。これは put() メソッドと take() メソッドです。
メソッドを要約すると次のようになります。
要素の追加
方法 | 説明する |
---|---|
オファー() | 要素が正常に挿入された場合は true を返し、それ以外の場合は false を返します。 |
Offer(E e、長いタイムアウト、TimeUnit 単位) | キューに要素を挿入しようとします。キューがいっぱいの場合は待ちます。待機時間が指定された時間を超えると、割り込み例外がスローされます。 |
追加() | 要素が挿入されている場合は true を返し、挿入されていない場合は IllegalStateException をスローします。実際、add() メソッドの最下層は offer() メソッドを呼び出します。 |
置く() | 要素をキューに挿入します。キューがいっぱいの場合は、キューに挿入操作を実行する余地ができるまでブロックします。 |
要素を取り出す
方法 | 説明する |
---|---|
取った() | キューの先頭要素を取得して削除します。キューが空の場合はブロックして要素が追加されるのを待ちます。 |
ポーリング() | キューの先頭に戻ってキューを削除するか、キューが空の場合は null を返します。 |
ポーリング(長いタイムアウト、TimeUnit単位) | キューの先頭にある要素を取得して削除します。キューが空の場合は、要素が挿入されるのを待ち、タイムアウトを待ってから null を返します。 |
3.2 優先ブロッキングキュー
次の例では、5 つの乱数がランダムに生成されてキューに配置され、これらの数値が取得されます。
まず、Comparable インターフェイスを実装する新しいクラスを作成し、並べ替え機能を持たせるために CompareTo メソッドをオーバーライドする必要があります。
public class PriorityBlockingQueueTest {
public static void main(String[] args) {
BlockingQueue<PriorityItem> queue = new PriorityBlockingQueue();
Random random = new Random();
System.out.println("先随机生成5个数放入队列!");
for (int i = 0; i < 5; i++) {
int i1 = random.nextInt(10);
queue.add(new PriorityItem(i1));
System.out.println("Producer : " + i1);
}
System.out.println("从队列中获取值!");
for(;;){
try {
System.out.println("Consumer : " + queue.take().getIndex());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class PriorityItem implements Comparable {
private int index;
public PriorityItem(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
@Override
public int compareTo(Object o) {
PriorityItem item = (PriorityItem) o;
if(this.getIndex() > item.getIndex()){
return 1;
}else if(this.getIndex() < item.getIndex()){
return -1;
}
return 0;
}
}
}
結果:
先随机生成5个数放入队列!
Producer : 1
Producer : 0
Producer : 6
Producer : 0
Producer : 6
从队列中获取值!
Consumer : 0
Consumer : 0
Consumer : 1
Consumer : 6
Consumer : 6
(阻塞中.....)
印刷結果からわかるように、キューに入れられた値が順番に印刷されます。
この優先キューについては ChatGPT で説明しました。ChatGPT の説明は次のとおりです。これは完璧です。
PriorityBlockingQueue は優先キューです。プロデューサー スレッドで数値をランダムに生成し、すぐに優先キューに入れると、コンシューマー スレッドでこれらの数値を取得するときに、小さい値から大きい値の順に並べられることはありません。これは、プライオリティ キューでは、take() メソッドまたは poll() メソッドが呼び出されたときにのみ要素が並べ替えられるためです。
PriorityBlockingQueue この優先キューは、要素がキュー内で常に順序どおりに配置されるようにする、順序付けされたコレクションに似ています。プロデューサが要素をキューに入れ続け、コンシューマが要素をキューから取り出し続ける場合、コンシューマがプロデューサよりも遅く、要素がキュー内に留まらない限り、キュー内の要素の順序は変わりません。一時的にキューに保管され、消費者が取り出すのを待ちます。
したがって、PriorityBlockingQueue は、プロデューサーとコンシューマーの生産速度と消費速度が異なる状況により適しています。この場合、プロデューサがコンシューマよりも速い場合、キュー内の要素は一時的に保存され、コンシューマが要素を取得するのを待ちます。コンシューマがプロデューサよりも速い場合、コンシューマはプロデューサが新しい要素を挿入するのを待ちます。 。
つまり、PriorityBlockingQueue は、マルチスレッド環境で要素を順番に保持する必要があるシナリオに適していますが、プロデューサーとコンシューマーの速度が同じ場合、その並べ替え機能は機能しません。
3.4 遅延キュー
次の例では、最初に新しい DelayItem クラスを作成して Delayed インターフェイスを実装し、getDelay() メソッドと CompareTo() メソッドをオーバーライドします。getDealy() は有効期限を制御し、compareTo() メソッドは要素の比較に使用されます。前述したように、DelayQueue は優先順位付けされた時間ベースのスケジューリング キューです。
public class DelayQueueTest {
public static void main(String[] args) {
BlockingQueue<DelayItem> delayQueue = new DelayQueue<>();
delayQueue.add(new DelayItem(5, 1000));
delayQueue.add(new DelayItem(6, 2000));
delayQueue.add(new DelayItem(3, 3000));
delayQueue.add(new DelayItem(2, 4000));
delayQueue.add(new DelayItem(7, 5000));
delayQueue.add(new DelayItem(8, 3000));
delayQueue.add(new DelayItem(10, 4000));
System.out.println("poll():" + delayQueue.poll());
System.out.println("peek():" + delayQueue.peek().getValue());
try {
for(;;){
System.out.println("take():" + delayQueue.take().getValue());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class DelayItem implements Delayed{
private long expireTime;
private int value;
public DelayItem(int value, long expireTime) {
this.value = value;
this.expireTime = System.currentTimeMillis() + expireTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
DelayItem delayItem = (DelayItem) o;
if(delayItem.getValue() % 2 == 0){
return 1;
}else if(delayItem.getValue() < 0){
return -1;
}
return 0;
}
public int getValue() {
return value;
}
}
}
結果:
poll():null
peek():5
take():5
take():6
take():2
take():10
take():8
take():3
take():7
(阻塞中.....)
実行結果から、キュー内の要素の有効期限が切れていないため、poll() メソッドが null を取得したことがわかります。他のメソッドは次のとおりです。
方法 | 説明する |
---|---|
ポーリング() | 要素を直ちに返し、要素を削除します。期限切れの要素がない場合は null を返します。 |
ピーク() | キューの先頭要素をすぐに取得します。期限切れの要素がない場合は要素を削除しません。 |
取った() | ブロックします。要素の有効期限が切れるまで待ってから要素を取得します。有効期限が切れていない場合はブロックを続けます。 |