遺言執行者
Executors は Java のユーティリティ クラスです。さまざまなタイプのスレッド プールを作成するためのファクトリ メソッドを提供します。
![][2]
上の図から、スレッド プールを作成する Executors メソッドと作成されたスレッド プールはすべて ExecutorService インターフェイスを実装していることもわかります。一般的に使用される方法は次のとおりです。
newFiexedThreadPool(int Threads)
: 固定数のスレッドでスレッド プールを作成します。
newCachedThreadPool()
: キャッシュ可能なスレッド プールを作成し、execute を呼び出すと、以前に構築されたスレッドが再利用されます (スレッドが利用可能な場合)。使用可能なスレッドがない場合は、新しいスレッドが作成され、プールに追加されます。60 秒間使用されなかったスレッドを終了し、キャッシュから削除します。
newSingleThreadExecutor()
シングルスレッドのエグゼキュータを作成します。
newScheduledThreadPool(int corePoolSize)
タイミングと定期的なタスクの実行をサポートするスレッド プールを作成します。これは、ほとんどの場合、Timer クラスの置き換えに使用できます。
このクラスは比較的強力なようで、ファクトリ モデルを使用し、比較的強力なスケーラビリティを備えています。重要なのは、次のような使いやすさです。
ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;
固定サイズのスレッドプールを作成できます。
しかし、なぜこのクラスを使用してスレッド プールを作成することは推奨されないと言えるのでしょうか?
私が述べたのは「推奨されない」ということですが、Alibaba Java Development Manual にも明記されており、Executor を使用してスレッド プールを作成することは「許可されない」という言葉が使われています。

執行者の何が問題なのか
Alibaba Java Development Manual には、Executor を使用してスレッド プールを作成すると OOM (OutOfMemory、メモリ オーバーフロー) が発生する可能性があると記載されていますが、その理由は説明されていません。そこで、なぜ Executor の使用が許可されないのかを見てみましょう。
エグゼキューターの使用が OOM につながる状況をシミュレートする簡単な例から始めましょう。
/**
* @author Hollis
*/
public class ExecutorsDemo {
private static ExecutorService executor = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
}
}
JVM パラメーターを指定することにより、-Xmx8m -Xms8m
上記のコードを実行すると OOM がスローされます。
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
上記のコードは、ExecutorsDemo.java
の行 16 がコード内にあることを示していますexecutor.execute(new SubThread());
。
なぜ遺言執行者に欠陥があるのか
上記の例から、作成されたスレッド プールには OOM のリスクがあることがわかりますがExecutors
、その理由は何でしょうか? それを分析するには詳細なExecutors
ソースコードが必要です。
実際、上記のエラー メッセージに手がかりがあり、OOM の本当の原因はメソッドであることが上記のコードで述べられていますLinkedBlockingQueue.offer
。
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
読者がコードを見ると、最下層が実際に次のようにLinkedBlockingQueue
実装されていることがわかります。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
Java でのキューのブロックについてある程度の知識がある読者であれば、ここでの理由を理解できるかもしれません。
Java には主にとBlockingQueue
という2 つの実装があります。ArrayBlockingQueue
LinkedBlockingQueue
ArrayBlockingQueue
これは配列で実装された制限付きブロッキング キューであり、容量を設定する必要があります。
LinkedBlockingQueue
リンク リストで実装された有界ブロッキング キューです。容量は任意に設定できます。設定しない場合は、最大長が の無制限ブロッキング キューになりますInteger.MAX_VALUE
。
ここでの問題は、** が設定されていない場合、最大長が Integer.MAX_VALUE の無制限のブロッキング キューになるということです。** つまり、LinkedBlockingQueue
容量を設定しない場合、デフォルトの容量は になりますInteger.MAX_VALUE
。
newFixedThreadPool
作成時にはLinkedBlockingQueue
容量は指定されません。現時点では、LinkedBlockingQueue
それは無制限のキューです。無制限のキューの場合、キューにタスクが継続的に追加される可能性があります。この場合、タスクが多すぎるため、メモリ オーバーフローの問題が発生する可能性があります。
上記の問題は主にnewFixedThreadPool
2 つのファクトリ メソッドに反映されています。これら 2 つのメソッドが安全であるというnewSingleThreadExecutor
意味ではありません。これら 2 つのメソッドによって作成されるスレッドの最大数は である可能性があり、非常に多くのスレッドを作成すると、必然的に OOM が発生します。newCachedThreadPool
newScheduledThreadPool
Integer.MAX_VALUE
スレッドプールを作成するための正しい姿勢
主にデフォルト実装の使用を避けるために、スレッド プールを作成するために Executor を使用しないでください。その場合は、コンストラクターを直接呼び出してThreadPoolExecutor
スレッド プールを自分で作成できます。作成時にBlockQueue
容量を指定するだけです。
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(10));
この場合、送信されたスレッドの数が現在の使用可能なスレッドの数を超えると、そのスレッドはスローされます。java.util.concurrent.RejectedExecutionException
これは、現在のスレッド プールで使用されているキューが制限されたキューであり、キューが制限されている場合、キューは新しいリクエストの処理を続行できないためです。一杯。ただし、例外 (Exception) はエラー (Error) よりも優れています。
独自の定義に加えてThreadPoolExecutor
。他の方法もあります。このとき、まず考えるべきは、Apache や guava などのオープンソース ライブラリです。
著者は、guava が提供する ThreadFactoryBuilder を使用してスレッド プールを作成することを推奨しています。
public class ExecutorsDemo {
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
pool.execute(new SubThread());
}
}
}
上記の方法でスレッドを作成すると、OOM 問題を回避できるだけでなく、スレッド名をカスタマイズできるため、エラーの原因を追跡しやすくなります。
思考的な質問として、記事の著者は次のように述べています: エラー (Error) よりも例外 (Exception) がある方が良いのに、なぜそう言えるのですか?