スレッドプールの技術的背景
オブジェクト指向のプログラミングでは、オブジェクトを作成するにはメモリリソースまたはその他のリソースが必要であるため、オブジェクトを作成および破壊することは時間がかかります。これは、Javaでさらにそうです。ここでは、仮想マシンが各オブジェクトを追跡して、オブジェクトが破壊された後に収集されるようにします。
したがって、サービスプログラムの効率を改善する1つの方法は、オブジェクト、特に非常にリソースを消費するオブジェクトの作成と破壊の作成と破壊の回数を最小限に抑えることです。既存のオブジェクトを使用してサービスを提供する方法は、解決する必要がある重要な問題です。実際、これがいくつかの「リソース」テクノロジーが生産される理由です。
たとえば、Androidで一般的に見られる多くの一般的なコンポーネントは、一般に、さまざまな画像読み込みライブラリやネットワークリクエストライブラリなど、「プール」の概念と分離できません。 AndroidのメッセージングメカニズムでMeaasgeがmeaasge.obtain()を使用している場合でも、それは使用されるMeaasgeプールのオブジェクトであるため、この概念は非常に重要です。この記事で導入されたスレッドプーリングテクノロジーは、このアイデアにも準拠しています。
スレッドプールの利点:
1.スレッドプールのスレッドを再利用して、オブジェクトの作成と破壊によって引き起こされるパフォーマンスオーバーヘッドを減らします。
2.スレッドの最大並行性数を効果的に制御し、システムリソースの利用を改善し、過度のリソース競争を回避し、閉塞を避けることができます。
3.複数のスレッドの単純な管理を実行し、スレッドの使用をシンプルで効率的にします。
スレッドプールフレームワークエグゼキューター
Javaのスレッドプールは、執行者フレームワークを通じて実装されています。執行者のフレームワークには、クラスが含まれます。執行者、執行者、エグゼクティブサービス、ThreadPoolexecutor、Callable and Future、FutureTaskの使用など。
執行者:すべてのスレッドプールインターフェイスには1つの方法しかありません。
public interface executor {void execute(runnableコマンド); }executorservice:エグゼクターの動作を追加することは、エグゼキューターの実装クラスの最も直接的なインターフェイスです。
執行者:スレッドプールを作成するための一連の工場メソッドを提供し、返されたスレッドプールはすべてExecutorServiceインターフェイスを実装します。
ThreadPoolexecutor:スレッドプールの特定の実装クラス。一般的に使用されるさまざまなスレッドプールは、このクラスに基づいて実装されています。建設方法は次のとおりです。
public StreadPoolexecutor(int CorePoolsize、int maximumpoolsize、long keepalivetime、timeUnit unit、blockingqueue <runnable> workqueue){this(corepoolsize、maximumpoolsize、keepalivetime、unit、workqueue、executors.defaulthreadfactory()、defaulthler);}CorePoolsize:スレッドプールのコアスレッドの数と、スレッドプールで実行されるスレッドの数は、CorePoolsizeを超えることはなく、デフォルトで生き残ることができます。 AlowCoreThreadTimeoutをtrueに設定し、コアスレッドの数は0であり、KeepAlivetimeはすべてのスレッドのタイムアウト時間を制御できます。
Maximumpoolsize:スレッドプールで許可されるスレッドの最大数。
KeepAlivetime:アイドルスレッドが終了するタイムアウト時間を指します。
ユニット:enumであり、keepalivetimeの単位を表します。
workqueue:タスクを保存するblockingqueue <実行可能キューを表します。
BlockingQueue:BlockingQueueは、主にjava.util.concurrentでスレッドの同期を制御するために使用されるツールです。ブロッククが空の場合、ブロッキングキューから何かを取得する操作はブロックされ、ブロッキングキューが物に入るまで目覚めません。同様に、BlockingQueueがいっぱいの場合、その中に物を保存しようとする操作はブロックされ、ブロッキングキューにスペースがあるまで操作を継続するように目覚めません。ブロッキングキューは、生産者と消費者のシナリオでよく使用されます。プロデューサーは、キューに要素を追加するスレッドであり、消費者はキューから要素を取得するスレッドです。ブロッキングキューは、生産者が要素を保存するコンテナであり、消費者はコンテナからのみ要素を取得します。特定の実装クラスには、LinkedBlockingQueue、ArrayBlockingQueuedなどが含まれます。一般的に、内部ブロッキングとウェイクアップは、ロックと状態(ディスプレイロックと条件の学習と使用)を通じて達成されます。
スレッドプールの作業プロセスは次のとおりです。
スレッドプールが最初に作成されたとき、内部のスレッドはありませんでした。タスクキューはパラメーターとして渡されます。ただし、キューにタスクがある場合でも、スレッドプールはすぐに実行されません。
execute()メソッドを呼び出してタスクを追加すると、スレッドプールは次の判断を下します。
実行中のスレッドの数がCorePoolsizeよりも少ない場合は、このタスクをすぐに実行するスレッドを作成します。
実行中のスレッドの数がCorePoolsize以上の場合は、このタスクをキューに入れます。
この時点でキューがいっぱいで、実行されているスレッドの数がMaximumumpoolsizeよりも少ない場合、タスクをすぐに実行するために非コアスレッドを作成する必要があります。
キューがいっぱいで、実行されているスレッドの数がMaximumMumpoolsize以上の場合、スレッドプールは例外拒否エクセプトをスローします。
スレッドがタスクを完了すると、キューから次のタスクを実行して実行します。
スレッドには何の関係もなく、一定の期間(KeepAlivetime)の後、スレッドプールは、現在実行されているスレッドの数がCorePoolsizeよりも大きい場合、スレッドは停止すると判断します。したがって、スレッドプールのすべてのタスクが完了した後、最終的にCorePoolsizeのサイズに縮小します。
スレッドプールの作成と使用
Generationスレッドプールでは、ツールクラスエグゼクティアの静的方法を使用します。以下は、いくつかの一般的なスレッドプールです。
singlethreadexecutor:シングルバックグラウンドスレッド(バッファキューはバウンドされていません)
public static executorservice newsinglethreadexecutor(){return new finalizabledelegatedexecutorservice(new threadpoolexecutor(1、1、0l、timeunit.milliseconds、new linkedblockingqueue <runnable>()); }単一のスレッドプールを作成します。このスレッドプールには、1つのコアスレッドのみが機能しています。これは、シリアルのすべてのタスクを実行する単一のスレッドに相当します。この一意のスレッドが例外のために終了する場合、それを置き換える新しいスレッドがあります。このスレッドプールにより、すべてのタスクの実行順序がタスク提出の順序で実行されることが保証されます。
固定ThreadPool:固定サイズのコアスレッドのスレッドプールのみ(バッファキューはバウンドされていません)。
public static executorservice newfixedthreadpool(int nthreads){
新しいthreadpoolexecutorを返します(nthreads、nthreads、
0l、timeunit.milliseconds、
new linkedblockingqueue <runnable>());
}
固定サイズのスレッドプールを作成します。タスクが送信されるたびに、スレッドがスレッドプールの最大サイズに達するまでスレッドが作成されます。スレッドプールのサイズは、最大値に達すると同じままです。実行例外のためにスレッドが終了すると、スレッドプールは新しいスレッドを追加します。
cachedthreadpool:無制限のスレッドプールは、自動スレッドリサイクルを実行できます。
public static executorservice newCachedThreadPool(){return new SthreadPoolexecutor(0、integer.max_value、60l、timeUnit.seconds、new synchronousqueue <runnable>()); }スレッドプールのサイズがタスクの処理に必要なスレッドを超えると、アイドルスレッドの一部(60秒でタスク実行なし)がリサイクルされます。タスクの数が増えると、このスレッドプールは、タスクを処理するために新しいスレッドをインテリジェントに追加できます。このスレッドプールは、スレッドプールサイズを制限しません。これは、オペレーティングシステム(またはJVM)が作成できる最大スレッドサイズに完全に依存します。 Synchronousqueueは、1のバッファを備えたブロッキングキューです。
ScheduledThreadPool:固定されたコアスレッドプール、無制限のサイズを備えたコアスレッドプール。このスレッドプールは、定期的かつ定期的にタスクを実行する必要性をサポートしています。
public static executorservice newscheduledthreadpool(int corepoolsize){return new scheduledthreadpool(corepoolsize、integer.max_value、default_keepalive_millis、milliseconds、new delayedworkqueue(); }定期的にタスクを実行するスレッドプールを作成します。アイドル状態の場合、非コアスレッドプールはdefault_keepalivemillis時間内にリサイクルされます。
スレッドプールでタスクを送信する最も一般的に使用される2つの方法があります。
実行する:
executorservice.execute(runnable runable);
提出する:
FutureTask task = executorservice.submit(runnable runnable);
FutureTask <T> task = executorservice.submit(runnable runnable、t results);
FutureTask <T> task = executorservice.submit(callable <t> callable);
同じことが送信の実装(Callable Callable)の実装にも当てはまり、同じものが送信(Runnable Runnable)に適用されます。
public <t> future <t> submit(callable <t> task){if(task == null)throw new nullpointerexception(); FutureTask <T> ftask = newtaskfor(task); execute(ftask); ftaskを返します;}送信は結果を返すタスクであり、FutureTaskオブジェクトを返すタスクであることがわかります。そのため、結果はget()メソッドを介して取得できます。提出する最後の呼び出しも実行されます(実行可能な実行可能)。送信は、呼び出し可能なオブジェクトまたはfutureTaskオブジェクトに実行可能なオブジェクトのみをカプセル化します。 FutureTaskは実行可能であるため、実行中に実行できます。呼び出し可能なオブジェクトと実行可能なものがFutureTaskオブジェクトにカプセル化される方法については、Callable、Future、FutureTaskの使用を参照してください。
スレッドプールの実装の原則
スレッドプールの使用についてのみ話す場合、このブログには大きな価値がありません。せいぜい、それは執行者関連のAPIに慣れるプロセスにすぎません。スレッドプールの実装プロセスは、同期されたキーワードを使用しませんが、揮発性、ロック、同期(ブロッキング)キュー、原子関連クラス、FutureTaskなどを使用します。後者のパフォーマンスが向上しているためです。理解プロセスは、ソースコードの同時制御のアイデアをよく学ぶことができます。
最初に言及されたスレッドプーリングの利点は、次のように要約されています。
スレッドの再利用
同時の最大数を制御します
スレッドを管理します
1。スレッドマルチプレックスプロセス
スレッドマルチプレックスの原理を理解するには、まずスレッドのライフサイクルを理解する必要があります。
スレッドのライフサイクル中に、5つの状態を通過する必要があります。
スレッドは新しいスレッドを作成します。このプロセスは、スレッド名、ID、スレッドが属するグループなど、いくつかのスレッド情報を初期化することです。 Threadのstart()を呼び出した後、Java Virtual Machineはメソッドコールスタックとプログラムカウンターを作成します。同時に、hasbeenStartをtrueに作成すると、開始メソッドを呼び出すときに例外があります。
この状態のスレッドは実行を開始しませんが、スレッドが実行できることのみを意味します。スレッドの実行を開始するときに関しては、JVMのスケジューラに依存します。スレッドがCPUを取得すると、run()メソッドが呼び出されます。スレッドのrun()メソッドを自分で呼び出さないでください。次に、run()メソッドが終了するか、他のメソッドがスレッドを停止して死んだ状態に入るまで、CPUスケジューリングに従って既製のブロックを切り替えます。
したがって、スレッドの再利用を実装する原則は、スレッドを生かし続ける(準備ができている、実行、またはブロックする)ことです。次に、ThreadPoolexecutorがスレッドの再利用を実装する方法を見てみましょう。
ThreadPoolexecutorのメインワーカークラスは、スレッドの再利用を制御します。ワーカークラスの簡素化されたコードを見てください。そうすれば、理解しやすいです。
プライベートファイナルクラスワーカーはrunnable {最終スレッドスレッド; Runnable firstTask; worker(runnable firstTask){this.firsttask = firstTask; this.thread = getThreadFactory() null; while(task!= null ||(task = gettask())!= null){task.run();}}}労働者は実行可能であり、同時にスレッドを持っています。このスレッドは、開くスレッドです。新しいワーカーオブジェクトを作成すると、新しいスレッドオブジェクトが同時に作成され、ワーカー自体がパラメーターとしてtthreadに渡されます。このようにして、スレッドのstart()メソッドが呼び出されると、run()ワーカーの方法が実際に実行されています。次に、runworker()では、しばらくループがあります。これにより、実行可能なオブジェクトがgetTask()から取得され、順番に実行されます。 getTask()はどのように実行可能なオブジェクトを取得しますか?
それでも簡素化されたコード:
private runnable getTask(){if(いくつかの特別なケース){return null; } runnable r = workqueue.take(); return r;}このWorkQueueは、ThreadPoolexecutorを初期化するときにタスクを保存するブロックキューキューです。キューは実行可能な実行可能なタスクを保存します。 BlockingQueueはブロッキングキューであるため、BlockingQueue.take()が空になり、ブロック型の新しいオブジェクトが追加されてブロックされたスレッドを起動するまで待機状態に入ります。したがって、一般に、スレッドのrun()メソッドは終了しませんが、workqueueから実行可能なタスクを実行し続け、スレッド再利用の原則を達成します。
2。同時の最大数を制御します
では、いつworkqueueに実行できるのでしょうか?労働者はいつ作成されますか? WorkerのスレッドはStart()と呼ばれるのはいつですか?新しいスレッドを開き、Worker run()メソッドを実行しますか?上記の分析では、ワーカーのrunworker()が連続的にタスクを実行していることを示しています。
実行するときに上記のタスクの一部を実行すると考えるのは簡単です(実行可能runnable)。実行中にどのように行われているか見てみましょう。
実行する:
簡素化されたコード
public void execute(runnable command){if(command == null)throw new nullpointerexception(); int c = ctl.get(); //現在のスレッド数<corepoolsizeif(workercountof(c)<corepoolsize){//新しいスレッドを直接開始します。 if(addworker(command、true))return; c = ctl.get();} //アクティブスレッドの数> = corepoolsizeの数> = corepoolsize // runStateは完全に動作していないif(c)&& workqueue.offer.offer.offer.offer.(command){int recheck = ctl.get(); if(!isrunning(recheck)&& remove(command))拒否(command); //スレッドプールで指定された戦略を使用してタスクを拒否します// 2つのケース://1。非実行状態は新しいタスクを拒否します。ワーカーを追加:
簡素化されたコード
Private Boolean AddWorker(Runnable FirstTask、Boolean Core){int wc = workercountof(c); if(wc> =(core?cortulpoolsize:maximumpoolsize)){return false;} w = new worker(firstTask); finalスレッドT = w.thread; t.start();};}コードによると、スレッドプール作業中にタスクを追加する上記の状況を見てみましょう。
*実行中のスレッドの数がCorePoolsizeよりも少ない場合、このタスクをすぐに実行するスレッドを作成します。
*実行中のスレッドの数がCorePoolsize以上の場合は、このタスクをキューに入れます。
*この時点でキューがいっぱいで、実行されているスレッドの数がMaximumumpoolsizeよりも少ない場合、タスクをすぐに実行するには非コアスレッドを作成する必要があります。
*キューがいっぱいで、実行されているスレッドの数がMaximumumpoolsize以上の場合、スレッドプールは例外拒否エクセプトをスローします。
これが、AndroidのAsynctaskが並行して実行され、タスクの最大数を超えて拒否エクセプトをスローする理由です。詳細については、asynctaskソースコード解釈の最新バージョンとasynctaskのダークサイドを参照してください
AddWorkerを介して新しいスレッドが正常に作成されている場合は、start()を開始し、このワーカーのrun()で実行された最初のタスクとしてFirstTaskを使用します。
各労働者のタスクはシリアル処理ですが、複数の労働者が作成された場合、ワークキューを共有するため、並行して処理されます。
したがって、CorePoolsizeとMaximumMumpoolsizeに従って最大並行性数は制御されます。一般的なプロセスは、下の図で表すことができます。
上記の説明と写真はよく理解できます。
あなたがAndroid開発に従事していて、ハンドラーの原則に精通しているなら、この写真は非常に馴染みがあると思うかもしれません。一部のプロセスは、ハンドラー、ルーパー、およびMeaasgeが使用するプロセスと非常によく似ています。 Handler.send(メッセージ)は実行(runnuble)に相当します。ルーパーで維持されているMeaasgeキューは、ブロッキングキューと同等です。ただし、同期によりこのキューを維持する必要があります。ループループのループ()機能は、Meaasge QueueからMeaasgeを使用し、ワーカーのRunwork()を継続的にBlockingQueueから継続的に実行できます。
3.スレッドを管理します
スレッドプールを介して、スレッドの再利用を管理し、並行性数を制御し、プロセスを破壊できます。スレッドの再利用と制御の並行性は上記で説明されており、スレッド管理プロセスが散在しているため、理解しやすいです。
ThreadPoolexecutorにはCTL AtomicInteger変数があります。この変数を通じて2つのコンテンツが保存されます。
すべてのスレッドの数。各スレッドは、29ビットのスレッドが保存され、3ビットのランステートが保存される状態にあります。ビット操作を通じて異なる値が取得されます。
プライベート最終的なAtomicInteger ctl = new AtomicInteger(ctlof(running、0)); //スレッドのプライベートstatic int runstateof(int c){return c&〜capitious;} //労働者数を取得するprivate static int workercountof(int c){return c&capitious;}スレッドはプライベート静的ブールイスランニング(int c){return c <shutdown;}を実行していますここでは、スレッドプールのシャットダウンプロセスは、主にシャットダウンとシャットダウンノー()によって分析されます。まず、スレッドプールには、タスクの追加と実行を制御する5つの状態があります。次の3つの主要なタイプが紹介されています。
実行ステータス:スレッドプールは正常に実行されており、キュー内の新しいタスクとプロセスタスクを受け入れることができます。
シャットダウンステータス:新しいタスクは受け入れられませんが、キュー内のタスクが実行されます。
停止ステータス:新しいタスクはもう受け入れられなくなり、タスクのシャットダウンはキューで処理されません。この方法は、RunStateをシャットダウンに設定し、すべてのアイドルスレッドを終了しますが、まだ機能しているスレッドは影響を受けません。そのため、キュー内のタスク担当者が実行されます。
ShutdownNowメソッドは、Runstateを停止するように設定します。シャットダウン方法の違い、この方法はすべてのスレッドを終了するため、キュー内のタスクは実行されません。
概要:ThreadPoolexecutorソースコードの分析により、スレッドプールの作成、タスクの追加、スレッドプールの実行プロセスについて一般的に理解しています。これらのプロセスに精通している場合は、スレッドプールを使いやすくなります。
それから学んだ並行性制御のいくつかとプロデューサー消費者モデルのタスク処理の使用は、将来他の関連する問題を理解または解決するのに非常に役立ちます。たとえば、Androidのハンドラーメカニズム、およびLooperのMessagerキューも、Blookqueueを使用してそれを処理しても問題ありません。これは、ソースコードを読むことの利益です。
上記は、Javaスレッドプールを整理する情報です。今後も関連情報を追加し続けます。このウェブサイトへのご支援ありがとうございます!