原則Javaのスレッドプールのスレッドプールやプレゼンテーションのいくつかのタイプ

どのような状況下で、スレッドプールを使用しますか?
1.単一タスク処理時間が比較的短い
多数のタスクが処理すべき2

スレッドプールを使用する利点:
1.創造と破壊スレッドのに費やした時間に、システムリソースのコスト削減
スレッドプールが使用されていない2、それはシステムメモリにその結果多数のスレッドを作成するためにシステムを引き起こす可能性と「過切り替え」消費します。

スレッドプールは動作します:
なぜ、スレッドプールを使用できますか?
このようないくつかのリモートソースからの短い多数のタスクを処理するための多くのサーバーアプリケーションのようなWebサーバ、データベースサーバ、ファイルサーバやメールサーバなど。特定の方法を要求すると、サーバに到着、このアプローチは、または場合によってポーリングによってJMSキューデータベースでネットワーク・プロトコル(例えばHTTP、FTP、またはPOP)を介してもよいです。かかわらず、要求が到着するかの、サーバ・アプリケーションは、多くの場合、次のとおりです。シングルタスク処理時間が非常に短く、要求の数は膨大です。
サーバーアプリケーションを構築するための単純なモデルでは次のようになります。要求は、新しいスレッドを作成するために到着し、その後、新しいスレッドで要求を処理する時はいつでも。実際には、プロトタイピングのためにこの方法ではうまく動作しますが、この方法で実行するサーバーアプリケーションを展開しようとした場合、この方法のように深刻な欠点は、非常に明白です。スレッドを作成し、破壊に費やさ各リクエストサーバのための新しいスレッドを作成し、各リクエストのオーバーヘッドのために優れた新しいスレッドを作成する:各要求(スレッドごとのリクエスト)未満の一つのアプローチであるスレッドに対応しますより多くの実際のユーザー要求に費やす処理時間とリソースをより消費するシステムリソースと時間。
スレッドを作成し、破壊のオーバーヘッドに加えて、スレッドアクティビティは、システムリソースを消費します。JVMにあまりにも多くのスレッドを作成することにより、メモリの過剰消費にシステムを引き起こし、メモリが不足したりする「過剰切り替えます。」資源の不足を防ぐために、アプリケーションサーバは、任意の瞬間のプロセスを要求の数を制限するためにいくつかの方法が必要です。
スレッドプールのスレッドは、ライフサイクルコストとリソースの不足のためのソリューションを提供します。複数のタスクがスレッドを再利用を通じて、スレッド作成オーバーヘッドは、タスクの数に償却されます。利点は、要求が到着したときに、スレッドがすでに存在しているので、うっかりスレッドの作成による遅延をなくす、ということです。このように、あなたはすぐにサービスを要求することができ、アプリケーションの応答性にします。また、適切にプール内のスレッドの数を調整することにより、それは要求の数が一定の閾値を超えた場合、それはリソースの不足を防止することができる治療を取得するスレッドまで、他の新たに到着した要求の待機に必須です。
スレッドプールの代替
ファーユニークなマルチスレッドを使用して、アプリケーション・サーバー内のスレッドプールから。前述したように、時々 、それぞれの新しいタスクのための新しいスレッドを作成することは非常に賢明です。タスクがあまりにも頻繁にタスクの平均処理時間が短すぎる時に作成されている場合しかし、それはパフォーマンスの問題が発生しますタスクごとに新しいスレッドを作成します。
他の一般的なスレッドモデルは、タスクの特定のタイプのバックグラウンド・スレッドとタスクキューが割り当てられます。AWTと、このモデルの使用にスイング、このモデルではGUIイベントスレッドがある、ユーザーインターフェースの変更につながるすべての作業は、スレッドで実行されます。しかし、一つだけAWTスレッドがあるので、そうAWTでタスクを実行することが望ましいされていない、完了するまでにかなりの時間がかかる場合がありますスレッド。そのため、Swingアプリケーションは、多くの場合、実行時間の長い時間のための追加のワーカースレッド、同じUIに関連するタスクが必要です。
各タスクは、バックグラウンドスレッドに対応し、シングルスレッド方式(シングルバックグラウンドスレッド)いくつかのケースでは作業の方法が非常に望ましいです。各タスクは、スレッドのアプローチは非常にうまく機能した場合、長期実行中のタスクのごく少量。そして限り、スケジューリング予測可能性は非常に重要ではないとして、このような低優先度のバックグラウンドタスクとして非常によく、バックグラウンドスレッドの仕事上の単一の方法は、ケースです。しかし、ほとんどのサーバーアプリケーションは、短期的なタスクまたはサブタスクを大量に処理するためのもので、低コストで効果的にこれらのタスクの一部としてだけでなく、資源管理措置やタイミングの予測を処理できるようにする仕組みを持っていることが望ましいことが多いです。スレッドプールはこれらの利点を提供します。
作業キュー
スレッドプールの実用化に、用語「スレッド・プール」、やや誤解を招く、スレッドプール「明白な」実装は常に私たちは、ほとんどの場合、必要な結果を生成しませんので。用語「スレッドプール」は、第1のJavaプラットフォームに登場し、それは、より少ない製品オブジェクト指向方法であってもよいです。しかし、この用語は、まだ広くを継続するために使用されます。
我々は簡単に利用できるスレッドを待っているクライアントクラスは、スレッドが実行するタスクに渡されたタスクが完了すると、スレッドプールに戻さスレッドプールのクラス、が、このメソッドを実装することができますが、いくつかの可能性がありますがマイナスの影響。たとえば、プール内の空で、何が起こりますか?プールう呼び出し元のタスクを通過するスレッドプールを見つけようとすると、呼び出し側は、スレッドの利用可能なプールを待つ、スレッドがそれをブロックする、空です。私たちは、多くの場合、ブロックされて提出されたスレッドを防ぐために、バックグラウンドスレッドを使用したい理由の一つ。完全に、発信者をブロックされたスレッドプールを達成するための「明白な」例のように、あなたは問題を解決しようとする私たちに終止符を打つことができます。
我々は通常待つ()と待機は、新しい作品が到着したスレッドに通知するために()に通知使用する作業キューを組み合わせたワーカースレッドの同じ固定セットをしたいです。作業キューは、典型的には、いくつかの関連するモニターオブジェクトとリンクリストとして実装されています。リスト1は、作業キューの単純な組み合わせの一例を示しています。スレッドAPIは、Runnableインタフェースの使用に特別な要件を課すが、このモデルのRunnableオブジェクトキューを使用すると、スケジューラの作業キューと公共契約ではありませんが。
スレッドプールのキューでの作業リスト1.
輸入java.util.LinkedList;

パブリッククラスワークキュー

{

private final int nThreads;
private final PoolWorker[] threads;
private final LinkedList queue;

public WorkQueue(int nThreads) {
    this.nThreads = nThreads;
    queue = new LinkedList();
    threads = new PoolWorker[nThreads];
    for (int i = 0; i < nThreads; i++) {
        threads[i] = new PoolWorker();
        threads[i].start();
    }
}

public void execute(Runnable r) {
    synchronized (queue) {
        queue.addLast(r);
        queue.notify();
    }
}

private class PoolWorker extends Thread {
    public void run() {
        Runnable r;
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        queue.wait();
                    } catch (InterruptedException ignored) {
                    }
                }
                r = (Runnable) queue.removeFirst();
            }

            // If we don't catch RuntimeException,

            // the pool could leak threads

            try {
                r.run();
            } catch (RuntimeException e) {
                // You might want to log something here
            }
        }
    }
}

}

あなたはリスト1は、通知を使用して達成されたことに気づいたかもしれません()ではなくてnotifyAllよりも()。ほとんどの専門家はのnotifyAllを(使用をお勧めします)の代わりに、通知()、および正当な理由:特定の条件下でのみ使用とらえどころのない通知()リスクを、使用して、この方法が適切です。一方、適切に使用される場合、通知())(のnotifyAllより望ましい性能特徴を有する;特に、通知()はるかに少ないコンテキストスイッチを引き起こし、それは、サーバアプリケーションが重要です。
リスト1に、(通知安全な使用を満たすために作業キューの一例である)必要があります。そのため、ご注意ください、それ以外の場合には()あなたのプログラムでそれを使用し続けますが、Notifyを使用してください。
スレッドプールを使用することのリスク
スレッドプールは、マルチスレッド・アプリケーションを構築するための強力なメカニズムですが、が、それを使用して、リスクがないわけではありません。、そのようなプールに関連するデッドロックのようなスレッドプールに特異的な他のリスク、少数のに対して脆弱では、そのような同期エラーとデッドロックのような脆弱な他の任意のマルチスレッドアプリケーションに対して脆弱スレッドプールを構築し、すべてのアプリケーションのリスクと同時不十分なリソースとスレッドリーク。
デッドロック
任意のマルチスレッドアプリケーションは、デッドロックの危険性を持っています。私たちが言うとき、プロセスまたはスレッドのセットのそれぞれはセットのみで、他のプロセスが発生する可能性がイベントを待っていると、そのプロセスやスレッドのデッドロックのこのセット。デッドロックの最も単純な場合は、次のとおりスレッドがオブジェクトのX、Y及びオブジェクトのロックを待って上に排他的ロックを保持し、スレッドBは、オブジェクトYの排他ロックを保持しているが、オブジェクトのXロックを待っています。ロック待機を破るためにいくつかの方法がある場合を除き(Javaのロックは、このメソッドをサポートしていない)、それ以外の場合はデッドロックスレッドは永遠に待機します。
任意のマルチスレッドプログラムは、デッドロックの危険性を持っていますが、別のデッドロックを導入したスレッドプールは、その場合には、すべてのプールのスレッドが別のタスクキューにすでに結果の実装を待ってブロックされているかもしれないがどのスレッドを実行することはできませんので、タスクが、タスクが空きました。スレッドプールは、シミュレーションは、多くのインタラクティブなオブジェクトを必要とする実装するために使用されている場合、オブジェクトは、タスク実行キューとして続くクエリを送信するためにお互いにシミュレートすることができ、クエリオブジェクトとの同期は、応答を待って、これは起こります。
リソースが不足し
他の代替スケジューリングメカニズム(私たちの一部は、すでに議論されている)に比べて、彼らは通常、非常によく行われています。一つの利点は、スレッドプールがあることです。しかし、これは適切にスレッドプールのサイズを調整する場合にのみです。スレッドには、メモリおよびその他のシステムリソースを含め、多くのリソースを消費します。必要なThreadオブジェクトのメモリに加えて、各スレッドは、2つの可能な実行コールスタックの多くを必要とします。また、JVMは、各Javaスレッドのネイティブスレッドを作成することができます、これらのネイティブスレッドは、追加のシステムリソースを消費します。最後に、オーバーヘッドをスケジュールする、スレッドの切り替えがすることは非常に小さいですが、多くのスレッドがある場合は、コンテキストスイッチは、真剣に、プログラムのパフォーマンスに影響を与える可能性があります。
スレッドプールが大きすぎる場合、真剣にスレッドシステムのパフォーマンスによって消費されるそれらのリソースに影響を与える可能性があります。プールのスレッドがいくつかのリソースを消費しており、これらのリソースをより効果的に他のタスクに使用される可能性があるため、スレッド間の切り替えは、時間の無駄、とあなたが実際にリソース不足につながる可能性が必要以上超えたスレッドの使用となります。外の使用独自のスレッドのリソースに加えて、仕事はときにそのようなJDBC接続、ソケットやファイルなどのサービス要求を、追加のリソースを必要とするかもしれません。これらはまた、障害、例えば、割り当てることができないJDBC接続を引き起こす可能性があまりにも多くの同時要求があり、また、限られた資源です。
同時エラー
スレッドプールや他のキューイングメカニズムが使用待ち()と通知()メソッドに依存し、これらの方法では使用することは困難です。エンコードが正しくない場合は、通知は、スレッドが処理する作業キューにもかかわらず、アイドル状態のままで、その結果、失われることがあります。これらのメソッドを使用する場合は、注意が必要。でも、専門家は彼らにミスをすることができます。そして、既存の最良の利用は、すでに独自のプールのutil.concurrentパッケージが議論記述することなく、次の例のために、その仕事を達成するために知っています。
スレッドの漏れが
タスクを実行するスレッドを削除するときに、スレッドプールのスレッドリークの様々な種類の深刻なリスクであり、タスクが完了した後、スレッドはプールに戻っていないとき、これはプールから発生します。状況は、タスクがのRuntimeExceptionまたはErrorをスローした場合、スレッドリークが発生発生します。プールはそれらをキャッチしていない場合、スレッドが終了しますと、スレッドプールのサイズは、恒久的に1によって削減されます。回数はこれが十分に発生した場合、タスクを処理するために使用可能なスレッドが存在しないため、最終的には、スレッドプールは空であり、システムが停止します。
一部のタスクは、これらのタスクを停止し、スレッドにつながる、ユーザーからいくつかのリソースまたは入力のために永遠に待つことができ、これらのリソースが利用可能になる保証することはできません、ユーザーはまた、帰宅している可能性があり、かつそのタスクが永久に停止されます漏れ同じ問題。スレッドは永続的なタスクで消費されている場合は、それが実際にプールから削除されました。このようなタスクのために、彼らはどちらかだけで、自分のスレッドに与えられるか、単にそれらを限られた時間待機させてください。
要求の過負荷が
ちょうどサーバーへの要求に圧倒され、これが可能です。この場合、我々は、キュー内で実行されるのを待っていることは、あまりにも多くのシステムリソースを消費し、資源の不足を引き起こす可能性があるため、各着信要求が私たちの仕事のキュー、タスクにキューイングされているしたくない場合があります。この場合、自分自身に依存して何をすべきかを決定する。いくつかのケースでは、あなたは、単にプロトコルが後で要求を再試行より高いレベルに頼って、要求を破棄することができます、また、サーバが一時的にビジー状態であることを示す応答を使用することができます要求を拒否します。
スレッドプールの有効利用のためのガイドライン
限り、あなたはいくつかの簡単なルールに従うよう、スレッド・プールには、サーバーアプリケーションを構築するために非常に効果的な方法することができます:
そのタスクの同期の結果、他のタスクを待つ列に並ぶないでください。デッドロックの一種で、すべてのスレッドが順番に結果を待っているタスクをキューに入れられたタスクの一部によって占有されている、上記のように、これは、デッドロックのそのフォームにつながるかもしれないが、彼らはすべてのため、これらのタスクを実行することはできませんスレッドは忙しいです。
時間は、スレッドの組み合わせを使用して、非常に長い操作であってもよい場合には注意します。このようなプログラムは、このようなリソースのI / Oの完了を待たなければならない場合には、最大待機時間を指定してください、その後、タスクが無効であるか、後で実行するために再キューイングされています。スレッドが最終的にいくつかの進歩を行いますミッションが正常に完了し、にリリースされることがあります。これは、ことを確認するために行います。
タスクを理解します。効果的にスレッドプールのサイズを調整するには、あなたは理解する必要がキューに入れられたタスクと彼らがやっているです。彼らは、CPUバウンド(CPUバウンド)んですか?彼らは、I / O制限されている(I / Oバウンド)のですか?あなたの答えはどのように調整するアプリケーションに影響します。あなたは非常に異なる特性を有する異なるジョブ・クラスを持っている場合は、それに応じて各プールを調整することができますので、意味をなさない可能性があるさまざまなタスククラスの複数の作業キューを設定しました。
プールのサイズを変更する
===
スレッドが少なすぎるか、あまりにも多くのスレッド:基本的に2種類のエラーを回避しているスレッドプールのサイズを変更します。幸いなことに、ほとんどのアプリケーションのために、あまりにも、あまりにも少しかなり広い間のマージン。
ことを思い出し:アプリケーション・スレッドで、このようなI / Oを待っているような操作に遅いものの、2つの主な利点を有しているが、処理を継続すると、複数のプロセッサを利用することができる可能にしました。、計算機プロセッサ限定的な追加のスレッドに近いNを有するN個のアプリケーションを実行しているスレッドの数が全体の処理能力を向上させることができる、スレッドの数がNを超えた場合に追加のスレッドが動作しないときに添加しました。それは追加のコンテキスト切り替えのオーバーヘッドをもたらすことになるので、実際には、あまりにも多くのスレッドでも、パフォーマンスを低下させる可能性があります。
ベストスレッドプールのサイズは、自然とタスクの作業キューで利用可能なプロセッサの数に依存します。場合スレッドだけキューにN個のプロセッサを有するシステムにおいて、前記タスクの全ての計算された性質、スレッドプールは、一般的にNまたはN +最大CPU使用率を有する場合。
これらのタスクのために、(例えば、ソケットミッションHTTPリクエストからの読み込み)を完了するためにI / Oのためではないすべてのスレッドが取り組んできたのでサイズは、利用可能なプールを超えるために必要なプロセッサの数を待つ必要があるかもしれません。プロファイリングを使用することで、要求(WT)およびサービス時間(ST)の間の典型的な待機時間の割合を推定することができます。我々はこの比のWT / STを呼び出す場合、いくつかのN *(1 + WT / STに対する必要N個のプロセッサを有するシステムのための ) プロセッサを維持するスレッドが十分に利用されています。
プロセッサの使用率は、プロセス内のスレッドプールのサイズを調整するための唯一の考慮事項ではありません。スレッドプールの数の増加に伴い、あなたは、スケジューラは、メモリの条件を制限するために使用することができます実行、またはなどのソケット、開いているファイルハンドルやデータベース接続など、他のシステムリソースを、制限されることがあり
あなた自身のプールを記述することなく、
ダグ・リーは、このようなコレクションなど、キューやハッシュテーブルへの同時アクセスの下で非常によく実行すると、セマフォ、ミューテックスを含んutil.concurrent並行処理ユーティリティの優れたオープンソースのライブラリを書きましたいくつかの作業キューの実装。パッケージには、広く使われている、正しい作業キューベースのスレッドプールを達成するために、効果的なPooledExecutorクラスです。あなたがあなた自身のスレッドプール、このエラーが発生しやすいの書き込みしようとしない、あなたは逆util.concurrentにいくつかのユーティリティを使用して検討することができます。リンクや詳細については、参考文献を参照してください。
util.concurrentライブラリはまた、JSR 166にインスピレーションを与え、JSR 166は、Java Community Processの(Java Community Process(JCP)の)であるワーキンググループ、彼らは同時Javaクラスライブラリを開発しようとしているグループのjava.util.concurrentパッケージに含まユーティリティ、このパッケージは、Java開発キット1.5リリースのために使用すべきです。
スレッドプールは、サーバアプリケーションを整理する際に有用なツールです。これは、概念としては非常にシンプルですが、実装とプールの使用ではなく、)(そのようないくつかのデッドロックなどの問題、資源の不足に注意を払うと、()を待って通知する複雑さを必要としています。あなたのアプリケーションは、スレッドプールを必要としていることが判明した場合、最初から記述することなく、たとえばPooledExecutorため、執行クラスutil.concurrentのいずれかを使用することを検討してください。あなたは短命の仕事を処理するために、独自のスレッドを作成したい場合、あなたは間違いなく代わりにスレッドプールを使用することを検討すべきです。
JDKは一般的なカテゴリを導入したスレッドプールが付属しています:
1、newFixedThreadPoolは、労働者の指定された数は、スレッドプールをスレッド作成します。スレッドプールのスレッド労働者の数は、最初のタスクの最大数はキューにプールに提出される予定に達した場合はいつでも、ワーカースレッドを作成するためのタスクを提出します。
パッケージテスト。

輸入java.util.concurrent.ExecutorService。
輸入java.util.concurrent.Executors。

パブリッククラスThreadPoolExecutorTest {
パブリック静的無効メイン(文字列[] args){
ExecutorServiceのfixedThreadPool = Executors.newFixedThreadPool(3)。
以下のために(INT i = 0; iは<10; I ++){
最終int型のインデックス= I。
fixedThreadPool.execute(新しいRunnableを(){
ます。public void実行(){
しようと{
System.out.printlnは(インデックス);
のThread.sleep(2000);
}キャッチ(InterruptedExceptionある電子){
e.printStackTrace();
}
}
}) ;
}
}
}

2、newCachedThreadPoolは、キャッシュされたスレッドプールを作成します。スレッドプールのこのタイプにより特徴付けられる:
1)ワーカースレッドの数は事実上無制限の(実際には、限界があり、数はInterger MAX_VALUEである)、これは、スレッドプールのスレッドを追加する柔軟であることができる作成します...
2)ワーカースレッドが指定した時間アイドル状態になっている場合、あるスレッドプールにタスクを送信する時間がない場合(デフォルトは1分です)、その後、ワーカースレッドは自動的に終了します。あなたが新しいタスクを提出した場合に終了すると、しかし、スレッドプールは、ワーカースレッドを再作成します。
パッケージテスト。

輸入java.util.concurrent.ExecutorService。
輸入java.util.concurrent.Executors。

パブリッククラスThreadPoolExecutorTest {
パブリック静的無効メイン(文字列[] args){
ExecutorServiceのcachedThreadPool = Executors.newCachedThreadPool()。
以下のために(INT i = 0; iは<10; I ++){
最終int型のインデックス= I。
{試す
のThread.sleep(インデックス* 1000);
}キャッチ(InterruptedExceptionある電子){
e.printStackTrace();
}
cachedThreadPool.execute(新しいRunnableを(){
公共ボイドラン(){
System.out.printlnは(インデックス);
}
})。
}
}
}

3、newSingleThreadExecutorシングルスレッド執行を作成し、それは(私はこれがその特徴だと思います)このスレッド異常終了場合は、注文の履行を確保するために、それを置き換えるために別のがあるでしょう、唯一のタスクを実行するために独自のワーカースレッドを作成すること、です。単一のワーカースレッドの最大の特徴は、シーケンス内のさまざまなタスクを実行することが保証され、かつ任意の時間に1つのスレッドがアクティブであるよりも、これ以上はありません。
パッケージテスト。

輸入java.util.concurrent.ExecutorService。
輸入java.util.concurrent.Executors。

パブリッククラスThreadPoolExecutorTest {
パブリック静的無効メイン(文字列[] args){
ExecutorServiceのsingleThreadExecutor = Executors.newSingleThreadExecutor()。
以下のために(INT i = 0; iは<10; I ++){
最終int型のインデックス= I。
singleThreadExecutor.execute(新しいRunnableを(){
ます。public void実行(){
しようと{
System.out.printlnは(インデックス);
のThread.sleep(2000);
}キャッチ(InterruptedExceptionある電子){
e.printStackTrace();
}
}
}) ;
}
}
}

4、newScheduleThreadPoolは、固定サイズのスレッドプールを作成するだけでなく、タイマーに類似タスク実行のタイミングや周期性をサポートします。(このスレッドプールは一時的に完全に原則として徹底的に理解されていない)
テストのためのパッケージ変更を。

輸入java.util.concurrent.Executors。
輸入java.util.concurrent.ScheduledExecutorService。
輸入java.util.concurrent.TimeUnit。

パブリッククラスThreadPoolExecutorTest {
パブリック静的無効メイン(文字列[] args){
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5)。
scheduledThreadPool.schedule(新しいRunnableを(){
公共ボイドラン(){
System.out.printlnは( "3秒の遅延");
}
}、3、TimeUnit.SECONDS)。
}
}

ディレイ3秒を実施表しています。
定期的に次のサンプルコードを実行する:
パッケージのテスト;

輸入java.util.concurrent.Executors。
輸入java.util.concurrent.ScheduledExecutorService。
輸入java.util.concurrent.TimeUnit。

パブリッククラスThreadPoolExecutorTest {
パブリック静的無効メイン(文字列[] args){
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5)。
scheduledThreadPool.scheduleAtFixedRate(新しいRunnableを(){
公共ボイドラン(){
System.out.printlnは( "1秒遅延、および3秒ごとにexcute");
}
}、1、3、TimeUnit.SECONDS)。
}
}

遅延実行は、3秒ごとに1秒を表します。
要約:
.FixedThreadPoolプログラム効率及び糸消費オーバーヘッドを作成する際に利点を節約を改善するスレッドプールを有し、典型的な、優れたスレッドプールです。スレッドプールは、実行可能なタスクでない場合しかし、スレッドプールのアイドルは、つまり、それは労働者を解放するだけでなく、いくつかのシステムリソースを占有されることはありません。
II。スレッドプールのアイドルは、つまり、スレッドプールは、実行可能なタスクがないときにCachedThreadPool機能がある場合は、ワーカースレッド、占有リソースを解放するためにワーカースレッドを解放します。しかし、しかし、新しいタスクの出現が、また新しいワーカーが、また、特定のオーバーヘッドを作成します。CachedThreadPoolを使用する場合にも、我々はダウンし、同時に大きな原因システムを実行するため、多数のスレッドに、それ以外の場合は、制御タスクの数に注意を払う必要があります。

ます。https://www.jianshu.com/p/8a1357d538ceで再現

おすすめ

転載: blog.csdn.net/weixin_34309543/article/details/91205997