原則ThreadPoolExecutorスレッドプールで

使用

ここでは、スレッドプールを作成するための最も簡単なフォームを使用して、目的はあなたがThreadPoolExecutorを使用するために使用することを示しています

    public static void main(String[] args) {
    	// 创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 10, 200, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));

        for (int i = 0; i < 10; i++) {
        	// 使用线程池
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread() + " -> run");
                }
            };
        }
        // 销毁线程池
        threadPool.shutdown();
    }
复制代码

それは、それは全体のプロセスの作成、使用、および破壊を含む、スレッドプールが終了シンプル示すことです

概念を説明します

次のソースコード分析のために準備するために、スレッドプールに関連する概念のいくつかを説明するためにここに来てThreadPoolExecutor

パラメータ

多くのパラメータを渡す必要ThreadPoolExecutorコンストラクタは、これらのパラメータの意味も非常に重要であり、私はあなたが覚えていることを願って、コンストラクタのパラメータが順に以下の通りです:

  • corePoolSize:アクティブセットすることなく、コアスレッドが自動的に破棄されていないスレッドのコア数、コアスレッド内のスレッドの数よりも多くは、自動的に待機時間設定に応じて破棄されます
  • maximumPoolSize:スレッドの最大数のスレッドの最大数、収容可能なスレッドプール
  • keepAliveTimeが:待機時間後にアイドルスレッド(非コアスレッド)が自動的にkeepAliveTimeが破棄されます
  • 単位:keepAliveTimeが時間単位
  • ワークキュー:タスクキュー、スレッドのコア数よりもスレッドの数は、新しいタスクが最初のキューに入り、その後、クッションのために、次の裁判官をすれば
  • (オプション)threadFactory:スレッドの作成に使用するスレッドファクトリが設定され、通常はスレッド名を設定するために使用
  • ハンドラ(オプション):キューがいっぱいであるか、スレッドプールが満杯になった、クラスは4つの政策を否定提供する場合、ポリシーを否定

あまり言うことができ、それは、オブジェクトがワークキューBlockingQueueのタイプ、または一般的に使用さArrayBlockingQueue LinkedBlockingQueueあることを言及する価値があります

ThreadPoolExecutorでハンドラ次の4つのオプションのパラメータを持っているがあります:

  • AbortPolicy(デフォルト):破棄され、投げられたタスク
  • CallerRunsPolicy:のタスクを実行するためにexecuteメソッドのスレッドを呼ぶことにしましょう
  • DiscardPolicy:タスクを破棄し、nothrow
  • DiscardOldestPolicy:タスクキューを加えて、キューから最長待ちタスク(タスクキューの先頭を)落とし
スレッドプールの状態

ThreadPoolExecutorクラスは、可変CTLを有し、Iは、スレッドプールフラグコードを呼び出したい、32ビット整数、スレッドプールの高いスリーステートフラグであり、残りの29は、プール内のスレッドの現在の数を表します

この高3で、いくつかの状態識別があります。

  • -1:RUNNINGは、実行中、そのスレッドプールの作業を指示します
  • 0:SHUTDOWN、閉じた状態では、最後に準備ができて、スレッドプールワーカーは、新しいタスクを受け入れることはできませんが、タスクキューのタスクにキューイング処理できることを示しています
  • 1:STOPは、状態を停止し、それがスレッドプールは動作を停止していることを示し、新しいタスクを受け入れることができないタスクキューを処理できない、それはタスクの実行を中断します
  • 2:、状態をオフにノック、片付けは最後まで、すべてのタスクが終了しているについてのスレッドプールは、ワーカースレッドの数が0であることを示し、それは生命のスレッドプールの直ちに終了()フックメソッドの終了を実行します
  • 3:終端端状態、スレッドプールの寿命の終了を示す終了エンド()メソッドの呼び出し

これらの状態間の双方向変換も見ることができます興味を持っています:

  • shutdownメソッドを呼び出した後:> SHUTDOWN - RUNNING
  • (RUNNINGまたはSHUTDOWN) - > STOP:メソッド呼び出し後shutdownNowの
  • SHUTDOWN - >片付け:ワーカースレッドプールとキューが空である場合
  • 終了メソッドが終了した後:>片付け - STOP
実行ポリシー

ジョブ投入スレッドプールの後、行動の種類は、次の手順は非常に明確に言うことができるはず実行します。

  1. スレッドプールにジョブをサブミット
  2. プール内のワーカースレッドの数はスレッドのコア数よりも小さいかどうかを判断し、より少ない、あなたがスレッドを追加し、タスクを実行する場合
  3. 完全な場合は、キューが、満杯であるかどうか、そうでない場合はキューに追加し、ステップ5に進み、
  4. 除去が失敗した場合、呼び出しに対処するための戦略を拒否し、キューからタスクを削除します
  5. スレッドプールがいっぱいの場合、フルであるかどうかを判断、対処するための戦略を呼び出すためにそれ以外の場合は、次のステップを拒否しました
  6. タスクを実行するスレッドプールに新しいスレッドを作成します。

あなたにはない深い印象は、さえ理解していないことがあり、このセクションを読んだ後、それは私たちの次のステップは、最終的にこれらのステップからのソースコードを分析することで、達成するためにどのようにされた問題ではありません。

ソースを説明しました

実行()

フレームワークのソースコードと比較すると、JDKのソースコードは、人々に特に近い、我々はあなたがここに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);
    }
复制代码

:バック煙が再び、いくつかの重要な方法を説明するまで、ここで私はいくつかの意味がの出現を理解するためにいくつかの非常に良い方法かもしれ説明したいと思い、見るために急いではいけません

  • workerCountOf:現在のスレッド・プールの数を取得します
  • isRunning:通常動作時にスレッドプールするかどうかを決定します
  • addWorker:スレッドプールにタスクを追加

さて、これらのメソッドが提供するに従って、私たちは詳細なメモでマークされ、この方法で読むために戻ってきます。

    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);
    }
复制代码

私たちは、上記とまったく同じにまとめ

addWorker(Runable、ブール)

あなただけのプロセスを実行するために勉強したい場合は、この記事があなたのために上にあることを、そうでない場合は、今だけ本当のメインイベントでは、我々はソースコードを見て:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 获取状态码
            int rs = runStateOf(c);

			// 如果线程池不在运行状态,且以下状态不成立:
			// 		线程池已经调用shutdown()停止运行,同时要添加的任务为空但任务队列不为空
			// 就不进行任务的添加操作,直接返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                // 返回添加失败
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                // 如果core为true,就判断是否超过核心线程数,否则就判断是否超过最大线程数
                // 如果超过了限制就直接返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 通过死循环,不断尝试自增工作线程的数量
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 自增成功,重新获取一下线程标识码的值
                c = ctl.get();
                // 如果线程状态发生改变,就返回重试
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        // Worker实现了Runnable接口,是经过封装后的任务类
        Worker w = null;
        try {
        	// 将任务封装为Worker对象,
        	// 同时会调用线程工厂的newThread方法来生成一个执行该任务的线程
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                // 以下为同步方法,需要使用Lock加锁
                mainLock.lock();
                try {
                	// 获取线程池状态
                    int rs = runStateOf(ctl.get());

					// 如果运行状态正常,
					// 或虽然调用shutDown()方法停止线程池,但是待添加任务为空
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果线程已经启动,就表示不能再重新启动了,则抛出异常
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 将该线程添加到workers中
                        // workers包含所有的工作线程,必须工作在同步代码块中
                        workers.add(w);
                        int s = workers.size();
                        // largestPoolSize是整个线程池曾达到过的最大容量
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                	// 解锁操作必须写在finally语句中,因为Lock在抛出异常时不会自动解锁
                    mainLock.unlock();
                }
                // 如果添加任务成功,就启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
        	// 不管怎么样,只要启动失败,就会进行操作的回滚
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回是否启动成功,而不是是否添加任务成功
        return workerStarted;
    }
复制代码

私は、全体の方法は、4つの部分に分かれて置きます。

  1. 事前チェック:そのタスクキュースレッドプールを確保するために、正常に動作させることができ
  2. 前処理:予め同期を確実にするために、スピンロックを介してワーカースレッドの数を増加させるから
  3. タスクを追加します。実行されるタスクは、カプセル化された労働者の対象となる、タスクセットに追加
  4. タスクと異常なロールバック:操作が失敗した場合、それはロールバックされ始めるだろう前に、ワーカースレッドを開始

あなたがもし読み取り専用ではない不満を私の結論を一瞥し、ほぼ同じ読み取ることができなければならないコード内のコメントのある場所を、読んでいない場合は、私は完全なコメントを補うためにここにいるので、他の場所で別のものを見つけることをお勧めしますそれでも私たちは慎重にここにまとめ、読みだけの簡単な要約であることを願って

理解することが事前に確認してください少し難しいの一部であるコードの部分があり、これは以下の通りであります:

            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
复制代码

私が最初にすべての、最初の条件が満たされると、第2の条件が満たされない場合の2つの条件が、ダイレクトfalseに返す場所を決定するために、ここでは詳細な話だろう、それが直接の失敗です。私たちは、どのような状況の下で継続するために回しますか?この方法は、それは、つまり、次の条件に該当する場合、それはそれから判断され、直接返すために失敗することはありません条件の多くを簡単になります。

  • 通常の操作でスレッドプール
  • タスクスレッドプールが閉状態にある、空のJudaiが追加され、タスクキューが空ではない(この場合には、処理キューに入れられたタスクであろう)

概要

プロセスがコンセプトでまとめられているので、そのような終了するの全スレッドプールの原則、と私は結論部分には準備ができていなかったが、このプロセスを繰り返し上記の一部である、私はそれを注意することがポイントと言うためにここにいます:

  • 手動でスレッドファクトリを渡さない場合、スレッドプールは、単に新しいスレッド(Runnableを)がスレッドを作成するのではなく、デフォルトのスレッドファクトリを提供します
  • スレッドプールのシャットダウン()メソッドを呼び出した後、あなたはすぐに閉じないスレッドプールを覚えておく必要がありますが、その後、タスクキューを処理します
  • ...というように、後で追加する考え

おすすめ

転載: juejin.im/post/5cef621a51882520724c7c01