01.はじめに
スレッドプールを使用する場合、デッドロックの問題は無視されますが、コードが「6」で記述されている限り、それは不可能です。
スレッドプールの不適切な使用から引き出された記事コードといくつかの哲学は行き詰まります
02、デッドロックとは
デッドロックとは、リソースの競合や、実行中の2つ以上のプロセス間の通信によって引き起こされるブロッキング現象を指します。外力がないと、プロセスを進めることができません。
このとき、システムがデッドロック状態にある、またはシステムにデッドロックがあると言われ、常に互いに待機しているこれらのプロセスをデッドロックプロセスと呼びます。
デッドロック定義ソース:デッドロック-バイドゥ百科事典
簡単に言うと、リソースを奪い合うスレッドのグループが互いに待機し、「永続的な」ブロッキングを引き起こします。
デモを通して簡単に理解しましょう
public class DeadlockTest {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
Object o2 = new Object();
new Thread(() -> {
synchronized (o1) {
try {
System.out.println(String.format(" >>> %s 获取 o1 锁 ", Thread.currentThread().getName()));
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println(String.format(" >>> %s 获取 o2 锁 ", Thread.currentThread().getName()));
}
}
}, "线程一").start();
Thread.sleep(10);
new Thread(() -> {
synchronized (o2) {
try {
System.out.println(String.format(" >>> %s 获取 o2 锁 ", Thread.currentThread().getName()));
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println(String.format(" >>> %s 获取 o1 锁 ", Thread.currentThread().getName()));
}
}
}, "线程二").start();
}
/**
* >>> 线程一 获取 o1 锁
* >>> 线程二 获取 o2 锁
*/
}
プログラムでは、スレッド1は最初にo1オブジェクトロックを取得し、次に50ミリ秒間スリープしてから、ウェイクアップしてo2オブジェクトロックを取得します。
スレッド2は最初にo2オブジェクトロックを取得し、次に50ミリ秒間スリープし、ウェイクアップ後にo1オブジェクトロックを取得します
このように、デッドロックは非常に微妙であり、デッドロックの詳細についてはここでは説明しません。
03、スレッドプールデッドロックの条件
コードを使用して、スレッドプールのデッドロックの原因と結果をシミュレートします。興味のある友人は、コードをプルしてローカルで実行できます。
このコードで表されるシナリオは次のとおりです。
1. JDKスレッドプールを作成し、スレッドを追加して3秒後にスレッドプール情報を出力します
2.ループ内でinnerFutureAndOutFutureメソッドを呼び出します
3. innerFutureAndOutFutureの実行プロセスは、実行する10個のCallableを送信することです。
4.ただし、Callableタスクは、Callable送信にさらに依存しているため、実行結果を取得する必要があります。
理想的には、whileループは実行を継続し、ループをインクリメントします
コンピューターでコードを監視するか、エディターに貼り付けて実行することをお勧めします
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class HungryDeadLockTest {
private static ThreadPoolExecutor executor;
public static void main(String[] args) throws InterruptedException, ExecutionException {
TimeUnit unit = TimeUnit.HOURS;
BlockingQueue workQueue = new LinkedBlockingQueue();
executor = new ThreadPoolExecutor(5, 5, 1000, unit, workQueue);
new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executor);
}).start();
int loop = 0;
while (true) {
System.out.println("loop start. loop = " + (loop));
innerFutureAndOutFuture();
System.out.println("loop end. loop = " + (loop++));
Thread.sleep(10);
}
}
public static void innerFutureAndOutFuture() throws ExecutionException, InterruptedException {
Callable<String> innerCallable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(100);
return "inner callable";
}
};
Callable<String> outerCallable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(10);
Future<String> innerFuture = executor.submit(innerCallable);
String innerResult = innerFuture.get();
Thread.sleep(10);
return "outer callable. inner result = " + innerResult;
}
};
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
System.out.println("submit : " + i);
Future<String> outerFuture = executor.submit(outerCallable);
futures.add(outerFuture);
}
for (int i = 0; i < 10; i++) {
String outerResult = futures.get(i).get();
System.out.println(outerResult + ":" + i);
}
}
}
上記のコードを見て、タスクを送信した後、最初にスレッドプールの状態を整理しましょう
まず、スレッドプールの3つのコアパラメータについて見ていきましょう。
- コアスレッド:5
- スレッドの最大数:5
- ブロッキングキュー:無制限のキュー
1. forループで実行するouterCallableを追加し、10個のタスクを追加します
このとき、コアスレッド数の5つのタスクがいっぱいになり、残りの5つのタスクが無制限のキューに追加されます。
2. innerCallableタスクはouterCallableで作成され、同じスレッドプールに送信されます
innerCallableはouterCallableのcallメソッドで実行されるため、スレッドプールで10個のタスクを実行するのと同じです。
3.outerCallableタスクが実行される前に10ミリ秒間スリープするため、すべてのouterCallablesが確実に実行されます。
まず、プログラムの結果を見てみましょう。
/**
* loop start. loop = 0
* submit : 0
* submit : 1
* submit : 2
* submit : 3
* submit : 4
* submit : 5
* submit : 6
* submit : 7
* submit : 8
* submit : 9
* java.util.concurrent.ThreadPoolExecutor@5b6dc686[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
*/
運用結果と運用フローチャートを組み合わせて、分析して質問してみましょう。
1. OuterCallableとinnerCallableはそれぞれ10回送信されます。これは、キューに入れられたタスクに10個のタスクしかない理由です。
考えてみてください。コアスレッドは5つです。コードによると、20のタスクが送信されます。理論的には、ブロッキングキューに15のタスクがあるはずですか。
forループでouterCallable送信を10回実行し、outerCallableは内部で10ミリ秒のスリープを実行しました
このとき、スレッドプールの状態は次のとおりです。5つのコアスレッドが実行され、5つのブロックされたキュータスクが実行され、5つのコアスレッドによって実行されているouterCallableが10ミリ秒のスリープからウェイクアップします。
このとき、innerCallableは、5つのコアスレッドを占めるouterCallableを介して送信されます。
コアスレッドが5つしかないため、キュー10のタスクをブロックすると、outerCallableが5つ、innerCallableが5つになります。
2.スレッドプールがデッドロックするのはなぜですか?
上記の説明を通して、答えが出てきました
5つのコアスレッドによって実行されているouterCallableは、ブロッキングキュー内のinnerCallableが結果を返すのを待機しています
デッドロック生成の標準に達し、リソースは相互に実行されるのを待っています
04、スレッドプールのデッドロックを防ぐ方法
コアポイントは次のとおりです。プールで実行される別のタスクをスレッドプールで待機しないでください
- スレッドプールの同期メソッドで非同期タスクを実行しないでください
- 非同期メソッドで非同期メソッドをブロックして待機しないでください
もちろん、上記のコードのデッドロックの問題は、スレッドプール内のスレッドの数を増やすことによっても解決できます。
コアスレッドの数を40に、スレッドの最大数を50に変更して、それでもデッドロックできるかどうかを確認できます。
この種の解決策は解決できますが、持っていない方が良いです
スレッドプールのパラメータ設定は合理化する必要があるため、特定の種類の問題を解決するために設定することはできません
05.記事の最後の要約
スレッドプールでデッドロックが発生する可能性を説明する小さな例を次に示します。
上記の説明により、プロジェクトでスレッドプールのデッドロックが発生する可能性を回避できることを願っています。