目次
1. シングルエンドブロッキングキュー (BlockingQueue)
2. 両端ブロッキングキュー (BlockingDeque)
質問の引き出し
Consumer-Producer モデルの実装により、同期処理やロックを介した実装など、あらゆる実装が煩雑になります。これらは実装が比較的面倒ですが、このモデルを簡単に実装するために、JUC は BlockingQueue (片端ブロッキング キュー) と BlockingDeque (両端ブロッキング キュー) というブロッキング キュー インターフェイスを提供します。
1. シングルエンドブロッキングキュー (BlockingQueue)
原則: FIFO モードを使用して処理されるコレクション構造
FIFOとは何ですか?
FIFO (先入れ先出し) はデータを処理する一般的な方法であり、先入れ先出しモードとも呼ばれます。FIFO モードでは、最初にキューに入ったデータが最初に処理され、最後にキューに入ったデータが最後に処理されます。
FIFO モデルは、待機状況として理解できます。たとえば、スーパーマーケットのレジでは、顧客が順番にレジに並んでいます。1 人の顧客がチェックアウトして退出した後、次の顧客がチェックアウトを開始できます。FIFOモードの処理シーケンスです。
コンピューター サイエンスでは、FIFO モードはデータ バッファー、キュー、スケジューリング アルゴリズムなどのシナリオでよく使用されます。たとえば、オペレーティング システムでは、プロセス スケジューリング アルゴリズムが FIFO モードを使用して、プロセスが到着する順序に従って実行順序を決定できます。ネットワーク通信では、メッセージ キューが FIFO モードを使用して、メッセージが確実に受信されるようにすることができます。そして送信された順序で処理されます。
シングルエンド ブロッキング キュー BlockQueue の一般的なメソッド:
方法 | 説明する |
---|---|
put(item) |
指定された項目をキューに入れます。キューがいっぱいの場合は、スペースが使用可能になるまでブロックします。 |
take() |
キューから項目を取得および削除し、キューが空の場合は項目が使用可能になるまでブロックします。 |
offer(item) |
指定された項目をキューに入れようとします。キューがいっぱいの場合はすぐに false を返し、それ以外の場合は true を返します。 |
poll(timeout) |
キューから項目を取得して削除します。指定されたタイムアウト内にキューが空の場合は null を返します。 |
peek() |
キューを変更せずにキュー内の最初の項目を返します。キューが空の場合は null を返します。 |
size() |
キュー内の現在のアイテム数を返します。 |
isEmpty() |
キューが空かどうかを確認する |
isFull() |
キューがいっぱいかどうかを確認する |
clear() |
キューをクリアしてすべてのアイテムを削除します |
シングルエンド ブロッキング キュー インターフェイス BlockingQueue は、複数のサブクラスを提供します。 ArrayBlockingQueue (配列構造)、LinkedBlockingQueue (リンク リスト シングルエンド ブロッキング キュー)、PriorityBlockingQueue (優先ブロッキング キュー)、SynchronousQueue (同期キュー)
配列ブロックキュー
ケースコード:
上記のコードは次のように変更されます
package Example2129;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class javaDemo {
public static void main(String[] args){
// 创建对象和资源量
BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2);
// 创建一个包含各种美食的String数组
String[] foods = {"披萨","汉堡", "寿司", "墨西哥炸玉米卷", "牛排", "意大利面", "烤鸭", "富士山寿司", "印度咖喱", "巴西烤肉",};
int id;
// 两个厨师
for (int i=0;i<2;i++){
id = i;
new Thread(()->{
for (int j=0;j<10;j++){
try {
// 模拟做菜时间
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"已经做完菜肴"+foods[j]+"并端上座子");
queue.put(foods[j]);
}catch (Exception e){
e.printStackTrace();
}
}
},"厨师"+id).start();
}
for (int i=0;i<10;i++){
id = i;
new Thread(()->{
for (int j=0;j<2;j++){
try {
// 模拟客人吃饭的时间
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"享用完"+queue.take()+"这道菜");
}catch (Exception e){
e.printStackTrace();
}
}
},"客人"+i).start();
}
}
}
注: ブロッキング キューは、データがいっぱいになったときにスレッドが待機する問題を解決しますが、スレッドの同時実行性の問題は解決しません。
LinkedBlockingQueue
ケースコード:
package Example2130;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class javaDemo {
public static void main(String[] args) {
// 设置容量
BlockingQueue<String> queue = new LinkedBlockingQueue<>(2);
Random random = new Random();
new Thread(()->{
while (true){
try {
if (queue.size()==2){
System.out.println("队列已满");
TimeUnit.SECONDS.sleep(1);
}else {
TimeUnit.SECONDS.sleep(random.nextInt(3));
System.out.println("存入数据");
queue.put("存入数据");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
new Thread(()->{
while (true){
try {
TimeUnit.SECONDS.sleep(random.nextInt(3));
if (queue.isEmpty()){
System.out.println("队列空了啊");
TimeUnit.SECONDS.sleep(1);
}else {
System.out.println("取出数据");
queue.take();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
優先ブロッキングキュー
package Example2131;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
public class javaDemo {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
Random random = new Random();
new Thread(()->{
try {
for (int i=0;i<5;i++){
queue.put(random.nextInt(10));
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
for (int i=0;i<5;i++){
System.out.println("取出数据"+queue.take());
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
}
PriorityBlockingQueue
特徴は次のとおりです。
- 要素は優先度に従って並べ替えられます。この例では、数値が小さいほど優先順位が高くなります。
- 挿入および削除操作の時間計算量は O(logN) です。ここで、N はキュー内の要素の数です。
同期キュー
package Example2132;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class javaDemo {
public static void main(String[] args){
// 创建对象和资源量
BlockingQueue<String> queue = new SynchronousQueue<>();
// 创建一个包含各种美食的String数组
String[] foods = {"披萨","汉堡", "寿司", "墨西哥炸玉米卷", "牛排", "意大利面", "烤鸭", "富士山寿司", "印度咖喱", "巴西烤肉",};
int id;
// 两个厨师
new Thread(()->{
for (int j=0;j<10;j++){
try {
// 模拟做菜时间
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"已经做完菜肴"+foods[j]+"并端上座子");
queue.put(foods[j]);
}catch (Exception e){
e.printStackTrace();
}
}
},"厨师").start();
for (int i=0;i<10;i++){
id = i;
new Thread(()->{
try {
// 模拟客人吃饭的时间
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"享用完"+queue.take()+"这道菜");
}catch (Exception e){
e.printStackTrace();
}
},"客人"+i).start();
}
}
}
SynchronousQueue
特徴は次のとおりです。
- キューには容量がなく、各挿入操作は対応する削除操作を待つ必要があり、その逆も同様です。
- 挿入操作と削除操作はペアになっています。つまり、要素の挿入は、要素が消費されるまで待機する必要があります。
実装サブクラス間の違い:
-
ArrayBlockingQueue
(配列構造ブロッキングキュー):- 固定容量の配列に基づいて実装された有界キュー。
- オプションの戦略には、公平 (FIFO) と不公平 (デフォルト) の 2 つがあります。
- スレッドの安全性を実現するために、内部で単一のロックが使用されます。
- 要素の挿入と削除の時間計算量は O(1) です。
-
LinkedBlockingQueue
(リンク リストのシングルエンド ブロッキング キュー):- リンク リストの実装に基づいた、オプションの有界または無界のキュー。
- デフォルトは無制限ですが、最大容量を指定して制限付きキューを作成できます。
- スレッドの安全性を実現するために内部で 2 つのロックが使用されており、1 つは挿入操作用、もう 1 つは取り外し操作用です。
- 要素の挿入と削除の時間計算量は O(1) です。
-
PriorityBlockingQueue
(優先ブロッキングキュー):- ヒープ実装に基づく無制限の優先キュー。
- 要素は優先順位によって並べ替えられます。優先順位は、要素の自然な順序またはカスタム コンパレータによって決定されます。
null
要素を内部に保存することはできません。- 要素の挿入と削除の時間計算量は O(logN) です。ここで、N はキュー内の要素の数です。
-
SynchronousQueue
(同期キュー):- スレッド間で要素を直接転送するためのバッファーのないブロッキング キュー。
- 各挿入操作は、対応する削除操作を待つ必要があり、その逆も同様です。
- キュー自体は要素を格納せず、スレッド間のデータ転送にのみ使用されます。
- 挿入および削除操作は、一般に高いスケーラビリティ パフォーマンスを備えています。
2. 両端ブロッキングキュー (BlockingDeque)
BlockingDeque。FIFO および FILO 操作を実装できます。
フィロとは何ですか?
- FILO (先入れ後出し) は、後入れ先出しモードとも呼ばれるデータ処理方法です。FILO モードでは、最後に入力されたデータが最初に処理され、最初に入力されたデータが最後に処理されます。
- FILO モードは、本棚に本を置くなど、アイテムを積み重ねる状況として理解できます。新しい本を棚に置くと、既存の本の上に置かれるため、最後に置かれた本が一番上になります。本を取り出すときは、最後に一番上にある本が最初に取り出されます。これは、FILO モードの処理シーケンスと一致しています。
- コンピューター サイエンスでは、FILO モードはスタック データ構造の操作によく使用されます。スタックは、特定のデータの挿入および削除ルールを備えたデータ構造であり、最後に挿入されたデータがスタックの最上位となり、最初に挿入されたデータがスタックの最下位になります。データにアクセスしたりデータを削除したりする必要がある場合、通常は最初にスタックの最上位にあるデータを操作します。
- つまり、FILO モードは後入れ先出しモードであり、データ処理の順序を維持する方法です。スタック項目またはスタック データ構造と同様に、最後に入力されたデータが最初に処理され、最初に入力されたデータが最後に処理されます。
BlockingDeque の一般的なメソッド:
方法 | 説明する |
---|---|
addFirst(item) |
指定された項目を両端キューの先頭に追加し、キューがいっぱいの場合は例外をスローします |
addLast(item) |
指定された項目を両端キューの末尾に追加し、キューがいっぱいの場合は例外をスローします |
offerFirst(item) |
指定された項目を両端キューの先頭に追加しようとします。キューがいっぱいの場合はすぐに false を返し、それ以外の場合は true を返します。 |
offerLast(item) |
指定された項目を両端キューの末尾に追加しようとします。キューがいっぱいの場合はすぐに false を返し、それ以外の場合は true を返します。 |
putFirst(item) |
指定された項目を両端キューの先頭に配置し、キューがいっぱいの場合はスペースが使用可能になるまでブロックします。 |
putLast(item) |
指定された項目を両端キューの最後に配置し、キューがいっぱいの場合はスペースが使用可能になるまでブロックします。 |
pollFirst(timeout) |
両端キューの先頭から項目を取得して削除します。指定されたタイムアウト内にキューが空の場合は null を返します。 |
pollLast(timeout) |
両端キューの末尾から項目を取得して削除します。指定されたタイムアウト内にキューが空の場合は null を返します。 |
takeFirst() |
両端キューの先頭から項目を取得および削除し、キューが空の場合は項目が使用可能になるまでブロックします。 |
takeLast() |
両端キューの末尾から項目を取得および削除し、キューが空の場合は項目が使用可能になるまでブロックします。 |
getFirst() |
キューを変更せずに両端キューの最初の項目を返すか、キューが空の場合は例外をスローします |
getLast() |
キューを変更せずに両端キューの最後の項目を返すか、キューが空の場合は例外をスローします |
peekFirst() |
キューを変更せずに両端キューの最初の項目を返します。キューが空の場合は null を返します。 |
peekLast() |
キューを変更せずに両端キューの最後の項目を返します。キューが空の場合は null を返します。 |
size() |
両端キュー内の現在の項目数を返します。 |
isEmpty() |
両端キューが空かどうかを確認する |
clear() |
すべての項目を削除して両端キューをクリアします |
両端ブロッキング キューには、LinkedBlockingDeque というサブクラスが 1 つだけ実装されています。
ケースコード:
package Example2133;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
public class javaDemo {
public static void main(String[] args) {
BlockingDeque<Integer> deque = new LinkedBlockingDeque<>();
new Thread(()->{
for (int i=0;i<10;i++){
try {
deque.putFirst(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
new Thread(()->{
while (true){
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(deque.takeLast());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (deque.isEmpty()){
System.out.println("队列空了啦");
break;
}
}
}).start();
}
}
両端の場合、データを先頭または末尾に配置でき、先頭と末尾を取得することもできることがわかります。
3. 遅延キュー (DelayQueue)
JUCでは、BlockingQueueの実装サブクラスに属する、データ遅延を自動ポップアップするキューであるDelayQueueが提供されています。クラス オブジェクトを作成して遅延キューに挿入する場合、クラスは Delayed を継承し、compareTo() メソッドと getDelay() メソッドをオーバーライドする必要があります。
原理:
遅延時間の計算: 各要素は、現在の要素が遅延時間からどのくらい離れているかを計算するために使用される getDelay(TimeUnit Unit) メソッドを定義する Delayed インターフェイスを実装します。このメソッドは、時間単位で遅延時間を表す、long 型の時間値を返します。
キュー ストレージ: 順序付けされた優先キュー (PriorityQueue) は、要素を格納するために内部的に使用されます。要素はレイテンシに従って並べ替えられます。つまり、レイテンシが最も小さい要素がキューの先頭になります。
要素の追加: Offer(E e) メソッドを呼び出して要素をキューに追加します。要素が挿入されるとき、その位置は遅延時間に基づいて決定されます。
要素の取得: take() メソッドを呼び出して、遅延後に到着した要素をキューから削除します。キューが空の場合、スレッドはブロックされ、要素が取得できるまで待機します。
追加と削除の同期: キューの追加と削除の操作を同期して、マルチスレッド環境での安全性を確保します。
スケジュールされた削除: キュー内の要素の保存時間が遅延時間を超えると、その要素は自動的に削除されます。
一般的に使用される方法:
メソッド名 | 説明する |
---|---|
enqueue(item, delay) |
指定されたものをキューに入れitem 、delay ミリ秒後に実行します。 |
dequeue() |
最も古い遅延タスクをキューから取り出して返します。 |
getDelay(item) |
item 指定された の残りの遅延時間をミリ秒単位で返します。item 有効期限が切れている場合は負の数を返します。 |
remove(item) |
从队列中移除指定的 item 。 |
size() |
返回队列中延迟任务的数量。 |
isEmpty() |
判断队列是否为空。 |
clear() |
清空队列,移除所有的延迟任务。 |
getExpiredItems(now) |
返回所有已过期的任务,并从队列中移除它们。 |
getNextExpiringItem() |
返回下一个即将过期的任务,但不从队列中移除它。 |
案例代码:
package Example2134;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
class Student implements Delayed {
private String name;
// 设置停留时间
private long delay;
// 设置离开时间
private long expire;
Student(String name, long delay , TimeUnit unit){
this.name=name;
this.delay = TimeUnit.MILLISECONDS.convert(delay,unit);
this.expire = System.currentTimeMillis()+this.delay;
}
@Override
public String toString() {
return this.name+"同学已经到达预计停留的时间"+TimeUnit.SECONDS.convert(this.delay,TimeUnit.MILLISECONDS)+"秒,已经离开了";
}
// 延迟时间计算
@Override
public long getDelay(@NotNull TimeUnit unit) {
return unit.convert(this.expire - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
// 队列弹出计算
@Override
public int compareTo(@NotNull Delayed o) {
return (int) (this.delay-this.getDelay(TimeUnit.MILLISECONDS));
}
}
public class javaDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Student> students = new DelayQueue<Student>();
students.put(new Student("黄小龙",3,TimeUnit.SECONDS));
students.put(new Student("张三",1,TimeUnit.SECONDS));
students.put(new Student("李四",5,TimeUnit.SECONDS));
while (!students.isEmpty()){
Student stu = students.take();
System.out.println(stu);
TimeUnit.SECONDS.sleep(1);
}
}
}