この記事では主に Java スレッド プールの使用法と原理に関する関連情報を詳しく紹介しますので、興味のある方は参考にしてください
スレッド プールとは何ですか?
Java を使用すると新しいスレッドを簡単に作成できますが、オペレーティング システムがスレッドを作成するにはコストもかかります。したがって、スレッドの再利用に基づいて、スレッド プールを使用してタスクを実行した後、一定期間存続するスレッド プールを使用します (ユーザーはスレッドの生存時間を設定できます)。アイドル スレッド (これについては後で説明します))、新しいタスクが来ると、アイドル スレッドが直接再利用されるため、スレッドの作成と破棄のロスがなくなります。もちろん、アイドル状態のスレッドもリソースの無駄になります (すべてのアイドル状態のスレッドには生存時間の制限があります) が、頻繁にスレッドを作成して破棄するよりははるかに優れています。
以下は私のテストコードです
/* * @TODO 线程池测试 */ @Test public void threadPool(){ /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完 * 调用CountDownLatch#coutDown()方法(其实就是自减1) * 当所有的线程都执行完其值就为0 */ CountDownLatch count = new CountDownLatch(50000); long start = System.currentTimeMillis(); Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程 for(int i=0;i<50000;i++){ pool.execute(new Runnable() { @Override public void run() { System.out.println("hello"); count.countDown(); } }); } while(count.getCount()!=0){//堵塞等待5w个线程运行完毕 } long end = System.currentTimeMillis(); System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms"); } /** *@TODO 手动创建线程测试 */ @Test public void thread(){ CountDownLatch count = new CountDownLatch(50000); long start = System.currentTimeMillis(); for(int i=0;i<50000;i++){ Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); count.countDown(); } }); thread.start(); } while(count.getCount()!=0){//堵塞等待5w个线程运行完毕 } long end = System.currentTimeMillis(); System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms"); }
スレッドプール内の5wスレッドで実行するには約400ミリ秒かかり、スレッドプールを使用せずに実行するには約4350ミリ秒かかります。効率は明らかです(読者はそれをテストできます)それ自体は同じですが、コンピューターの構成が異なるため、出てくるデータは異なりますが、スレッドを作成するよりもスレッドプールを使用した方が確実に高速です)。
Java はスレッド プールをどのように使用しますか?
スレッド プールは上記のテスト コードで使用されており、以下で正式に導入されます。
すべての Java スレッド プールの最上位は Executor インターフェイスであり、Executor インターフェイスは 1 つだけあり、すべてのタスクを実行するために使用されます。Java は、Executor から継承してメソッドを拡張する ExecutorService インターフェイスも提供します。抽象クラス AbstractExecutorService は ExecutorService を実装し、最後に ThreadPoolExecutor は上記の抽象クラスから継承します。私たちがよく使用する Java スレッド プールは、作成されたこのクラスのインスタンスです。
上記で使用した Executor は、さまざまなビジネスのスレッド プール パラメーターをカプセル化し、新しい操作を実行する構文糖です。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
上記はExecutors.newFixedThreadPool(10)のソースコードです。
次のポイントはここです。ThreadPoolExecutor 構築メソッドの各パラメーターの意味について説明します。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
上記の構築方法が最も包括的です。
以下では、ソースコードに基づいて、より説得力のあるいくつかのパラメーターの意味を説明します。
以下は ThreadPoolExecutor#execute メソッドです。これは、上記のインターフェイスによって呼び出される実行の実際の実行者です。
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
ctl は、アトミック ステートメントの CAS 操作を提供するクラスである AtomicInteger インスタンスで、スレッド プールで現在実行されているスレッドの数に -2^29 を加えたものを記録するために使用され、workCountOf メソッドはその絶対値を取得します。 (ソース コードで実装方法を確認できます)。この値が corePoolSize より小さい場合、addWorker メソッドが呼び出されます (新しい Worker を作成するために使用されます。Worker は Thread を作成します。スレッド作成プロセス中、addWorkd は corePoolSize または maxnumPoolSize の値の比較に従います (true が渡された場合は corePoolSize に基づいて比較され、false は maxnumPoolSize に基づいて比較されます)。値がその値以上である場合、作成は失敗します)。現在実行中のスレッドの数が corePoolSize 未満の場合、スレッド プールが作成され、正常に作成されることがわかります (
Running 状態のスレッド プールについては簡単に説明するだけです)。
実行中のスレッドの数が corePoolSize 以上の場合、isRunning が SHUTDOWN と比較される (その値 = 0) という 2 番目の if を入力します。前述したように、c は現在実行中のスレッドの数に -2 を加えたものに等しいです。 ^29 の場合、現在実行中のスレッド データが 2^29 に達すると、その値 = 0 になり、isRunning が false を返し、else で addWorkd を実行すると false が返されるため (addWorkd もそれをチェックします)、これはスレッド プールが最大 2^29 のスレッドの同時実行をサポートします (これで十分です)。
workQueue.offer(command) は、実行可能ファイルを待機キューに追加するためのもので、待機キューに参加した後、runWorker メソッドはキューからタスクの実行を取得します。現在のキューが境界付きキュー (ArrayBlockingQueue) を使用している場合、キューがいっぱいの場合、オファーは false を返し、スレッドの場合は false が渡されます。ここで実行中 この数値が maxnumPoolSize 以上の場合、このスレッド タスクはスレッド プールによって拒否され、reject(command) が実行されます。拒否メソッドは、ThreadPoolExecutor 構築メソッドの RejectedExecutionHandler (拒否戦略) を使用します。後で詳しく説明します。
上記の説明をソース コードと組み合わせると、ThreadPoolExecutor のパラメーターについての次の説明が理解しやすくなります。
スレッドプールでのスレッドの作成と拒否の戦略
corePoolSize、maxnumPoolSize、およびBlockingQueueは一緒に議論する必要があります
スレッド プールで実行されているスレッドが corePoolSize より小さい場合、新しいスレッド タスクは常に実行用の新しいスレッドを作成します。corePoolSize より大きい場合、BlockingQueue を渡すと、タスクは待機キューに追加されます。 in は無制限のキュー (LinkedBlockingQueue) です。これは、「無限に多くの」タスクを格納できるキューです。これは、maxnumPoolSize とは関係がありません。スレッド プール内の値は corePoolSize ですが、制限付きキュー (ArrayBlockingQueue、SynchronousQueue) を渡す場合、キューがいっぱいでスレッド数が maxmunPoolSize 未満の場合、スレッド数が maxnumPoolSize を超えるまで新しいスレッドが作成されます。スレッドの数が maxnumPoolSize より大きい場合、タスクへの参加はスレッド プールによって拒否されます。
RejectedExecutionHandler の拒否戦略は 4 つの AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy を実装します。ユーザーはこのインターフェイスを実装して独自の拒否戦略を実装することもできます。最初の拒否戦略は例外を直接スローするもので、2 番目の拒否戦略は新しいものです。タスクは直接実行され、3 番目のタスクはキュー内の最も古いタスクをキャンセルし、4 番目のタスクは現在のタスクをキャンセルします。
以上がJava におけるスレッド プールの使用法と原理の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。