スレッドプールは、あなたが考えるほど簡単ではありません(続き)

序文

いくつかの時間前に書いた「スレッドプールは、あなたが考えるほど簡単ではありません」:を含め、一緒に、誰もラインと基本的なスレッドプール

  • 基本的なスレッドプールのスケジューリング機能。
  • スレッドプールの自動拡張体積の減少。
  • キューバッファスレッド。
  • スレッドプールを閉じます。

これらの機能は、また最後の3を達成することが左features

  • スレッドの戻り値を使用して実行。
  • 行うにはどのように扱う例外?
  • すべてのタスクがどのように情報を知らせて完全に実行されますか?

これは見にこれらの三つの特徴を達成するj.u.cスレッドプールがこれらの要件を達成することですか。

前にこの記事を見て、強く、あなたは上記を参照することをお勧めします「ので、単純なスレッドプールは、あなたが考えるものではありません。」

タスク完了の通知

あなたがスレッドプールを使用する場合は、多かれ少なかれ、このような要望を持っています:

タスクの実行スレッドプールが完成し、その後、このようなタスクの数など、他のものは、その上のタスクの実装との次の波の前に完了して行うために、メインスレッドに通知されます。

前の例のコード:

彼らは、完成された後、印刷まで、スレッドプールのタスクに提出さ13の合計「ジョブが終了し、」ログ。

次のように実行結果は以下のとおりです。

タスクが完了した後に、この効果は簡単です達成するために、我々は時間にスレッドプールを初期化するためのインタフェースを実装渡すことができ、このインターフェースは、コールバックです。

public interface Notify {

    /**
     * 回调
     */
    void notifyListen() ;
}
复制代码

これらは、コンストラクタスレッドプールとインターフェイスを定義しています。

だから、キーを使用すると、この機能を実現したいときにこのインタフェースのコールバック?

単純な事実を考える:あなたは、タスク、スレッドプールはすでに我々はタスクと完了するために、プール内のスレッドの数に提出したレコードを持っている限り、行われていたと思うし、ゼロにそれらの両方を送信し、その後、これはコールバックインタフェースすることができます。

だから我々は、タスクの数を記録するために必要なスレッドプールのタスクへの書き込み時:

同時実行セーフ考慮事項については、ここでのカウンターは原子を採用しますAtomicInteger


その後、我々はインターフェース定義から完了通知をコールバックすることができ、タスクが完全に実行さ0の作業になると、タスクの後、カウンター-1で完成されています。


JDKの実装

JDKでは、このような需要ThreadPoolExecutorにも関連しているAPIが、使用方法は同じではありませんが、原則の性質が類似しています。

私たちは、使用しThreadPoolExecutor、従来のクローズドプロセスを次のとおりです。

    executorService.shutdown();
    while (!executorService.awaitTermination(100, TimeUnit.MILLISECONDS)) {
        logger.info("thread running");
    }
复制代码

スレッドの実行が完了したが、提出された後、shutdown()スレッドプール、その後、サイクル呼び出し近いawaitTermination()方法を、タスクが終了すると、それはすべてが返されますtrueので、ループを終了します。

これらの2つの方法の目的とは、原則、次のように:

  • 実装shutdown()スレッドプールは、タスクはすべて本物に近いスレッドプールを終了する前に、キュー内で待機しますと同時に、新しいタスクの受信を停止し、状態にステータスが閉じられます。
  • awaitTermination タスクが完了するまでは、スレッドプールをブロックしますか、すべてのタイムアウトが発生しました。

なぜ二人はapiそれと一緒に使用しますか?

究極の目標は、まだメインです:完了した後、何かの実行のすべてのスレッドを実行している、スレッドが実際に終了する前には、メインスレッドがする必要があるされてブロックされるで。

shutdown()そして、実行をブロックしません、すぐにそれがノンストップサイクルとフォローアップの呼び出しを必要とするすべてを返します。awaitTermination()これはスレッドAPIをブロックしますので、。

実際には、我々はソースコードがわかります表示ThreadPoolExecutor通知メカニズムを使用するためにブロッキングがまだ待っているが、使用することはあるLockSupportAPIそれ。

スレッドの戻り値を持ちます

次の戻り値を持つスレッドは、この要求も非常に一般的であり、非同期スレッドを計算した後、使用量を集計最終結果を得るために必要なデータ、例えば、いくつかの。

のは、(JDKに類似して)を使用する方法を見てみましょう:

最初のタスクが達成していないRunnableインターフェイスを、結局、彼のrun()関数は、戻り値ではありません。私たちは変化を実現するCallableインターフェースのを:

このインタフェースは、戻り値を持っています。

同様に若干の変更などのタスクを提出する際:

最初はしてタスクを実行するための機能であるexecute()ため変更submit()、彼は戻り値を返します。Futureそれは、スレッドの実行によって結果を得ることができ、。

最後に、第2のステップは、すべての結果をプリントアウトすることになります。

原則

どのような機能は、初見の下で実施され、具体的な実現の前だと思いますか?

  • 第一に限定jdkスレッドapiインタフェースまたは継承、最終的に実行される実装スレッドクラス実行するか否かを、指定のrun()機能を。
  • だから我々は、スレッドの戻り値は唯一の実装以上のものになることはできませんがしたいrun()値を返すメソッドを呼び出す関数は、戻り値は、後で使用するため、最大保存されています。

例えば、ここでは新しい組み込みCallable<T>インターフェイスを:

public interface Callable<T> {

    /**
     * 执行任务
     * @return 执行结果
     */
    T call() ;
}
复制代码

そのcall機能は、単にメソッドが値を返す言及していることがあるので、私たちべき()関数は、実行スレッドでそれを呼び出すように。

そして、あるでしょうFutureインタフェース、彼の主な役割はしているスレッドの戻り値、取得することで再将这个返回值存放起来用于后续使用、ここで言及した後に使用します

自然がその実装を持っている必要がありますインターフェイスがあるのでFutureTask実装し、Futureその後のキャプチャ戻り値のインターフェースが。

達成しながら、Runnableインタフェースは、スレッドの中に自分を置きます。

だから、そのrun()関数は戻り値だけ言及して呼び出すcall()機能。


再び組み合わせたsubmit()タスクを提出してするget()のがより理解戸口になりますソース項の戻り値を取得します。

    /**
     * 有返回值
     *
     * @param callable
     * @param <T>
     * @return
     */
    public <T> Future<T> submit(Callable<T> callable) {
        FutureTask<T> future = new FutureTask(callable);
        execute(future);
        return future;
    }
复制代码

submit()非常に単純な、私たちは、着信失うことになるCallableに改宗にオブジェクトをFutureTaskターゲットし、再度呼び出しの前にするexecute()(フォローアッププロセスのと同じ流れに共通スレッドプールのスレッド)スレッドプールに投げ込まれます。

FutureTask自体はスレッドですので、あなたが直接使用することができるexecute()機能を。


future.get()関数futureオブジェクトがあるsubmit()実際のオブジェクトが返されるFutureTaskので、私たちは、ソースコードのようにそれらを直接見。

以来get()スレッドが戻らないの前に、最終的により、ブロッキング機能でnotify.wait()実現するブロックされた状態にスレッド。

そしてそうでwait()、それは目を覚ますようになったときの条件は、スレッドの戻り値で返さなければなりませんが終了します。

図は、即ち第二の部分、スレッドが完了すると(callable.call())ウェイクアップnotifyオブジェクトを、そのようなget方法は、戻すことができます。


同じ理由で、ThreadPoolExecutorそれはそれは非常に複雑に見えますので、アカウントの詳細になりますが、これらの中核であるコードを合理化することを除いて原則は、同様です。

でも、最終用途のAPIルックスが似ています。

例外処理

つまり、例外処理:最後の一つは、それの一部がピットを踏むことは容易であるスレッドプールを使用するための初心者です。

例えば、このようなシーン:

のみ作成つのスレッドのスレッドプール内のを、スレッドがループしながら、保管されているだけで一つのこと、です。

しかし、誤って例外処理サイクル、偶然、例外が捕獲されていないがスローされます。あなたはその後、どうなると思います何ですか?

スレッドを実行し続けますか?またはスレッドプールが撤退しますか?

実際には、ありませんビューの現象によって、どちらのスレッドプールのスレッドが同時に実行し続けやめませんでした、私はここで立ち往生されていたであろう。

私たちはときにdumpスレッドスナップショットがあります:

この時点で(その前に、これは新たに作成されたスレッドがあることがわかりますスレッド名を走るスレッドプールのスレッドがありThread-0、それが今ですThread-1)。

そのスレッドの状態はWAITING、スタックを介しで立ち往生発見されたCustomThreadPool.java:272オフィス。

これは、タスクキューのために、この時間は空であるので、彼はここに戻ってアップされているだろう、キューからローカルジョブで立ち往生することです。

心配友人がデジャヴのない気持ちを持っていない前に、こちらを参照してください。

はい、私は2つ書かれていました:

スレッドプールに関連する問題の議論は非常にあるときに“激烈”、実際には、ここに究極の理由とはまったく同じです。

だから、の問題を見て、コードの簡易版です。

今のコードの簡易版が、私はこの友人が、より明確にする必要があり前に疑問があると思います。

実際には、キャプチャは、内部スレッドプールのスレッドの例外が実行されますが、それはちょうど彼らが成功したかどうかをマークするために、対処しません。

失敗した現在の例外は、スレッドのうち、回収され、いったん新しい再作成Workerスレッドから続く、タスクの実行キューを取ります

だから、最後にそれがで立ち往生される从队列中取任务場所。

実際には、ThreadPoolExecutor例外処理は、ソースコードの特定の分析は、上記の二つの記事では、あまり似ていないされて、すでに何回かは述べています。

だから我々は、タスクが良い例外処理でなければならないスレッドプールを使用する場合。

概要

私は、スレッドプールダウンこの波はその内部で使用する全体的に、のようなマルチスレッドのソリューションの多くを明らかに問題が欠けていると思います:

  • ReentrantLock リエントラントロックが書かれた同時スレッドの安全性を確保します。
  • スレッド間通信を実装するには通知メカニズムのための待機を使用して(スレッドの実行結果を、待機中のスレッドプールはなど、終了します)。

最後にも学びました:

  • 標準的なスレッドプールは、プロセスを閉じました。
  • スレッドを使用する方法の戻り値を持っています。
  • スレッドの例外トラップの重要性。

(テストコードと組み合わせて使用​​)最後に、すべてのソースコード:

github.com/crossoverJi...

親指を共有し、私にとって最大のサポートです

おすすめ

転載: juejin.im/post/5cf864996fb9a07ef3765809