JavaのツールクラスExecutorの詳細説明

遺言執行者

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 つの実装がありますArrayBlockingQueueLinkedBlockingQueue

ArrayBlockingQueueこれは配列で実装された制限付きブロッキング キューであり、容量を設定する必要があります。

LinkedBlockingQueueリンク リストで実装された有界ブロッキング キューです。容量は任意に設定できます。設定しない場合は、最大長が の無制限ブロッキング キューになりますInteger.MAX_VALUE

ここでの問題は、** が設定されていない場合、最大長が Integer.MAX_VALUE の無制限のブロッキング キューになるということです。** つまり、LinkedBlockingQueue容量を設定しない場合、デフォルトの容量は になりますInteger.MAX_VALUE

newFixedThreadPool作成時にはLinkedBlockingQueue容量は指定されません。現時点では、LinkedBlockingQueueそれは無制限のキューです。無制限のキューの場合、キューにタスクが継続的に追加される可能性があります。この場合、タスクが多すぎるため、メモリ オーバーフローの問題が発生する可能性があります。

上記の問題は主にnewFixedThreadPool2 つのファクトリ メソッドに反映されています。これら 2 つのメソッドが安全であるというnewSingleThreadExecutor意味ではありません。これら 2 つのメソッドによって作成されるスレッドの最大数は である可能性があり、非常に多くのスレッドを作成すると、必然的に OOM が発生します。newCachedThreadPoolnewScheduledThreadPoolInteger.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) がある方が良いのに、なぜそう言えるのですか?

おすすめ

転載: blog.csdn.net/zy_dreamer/article/details/132350963