キューに入れる前にThreadPoolExecutorでスレッドを最大に増やす方法は?


99

私はしばらくの間、多くの人が使用するスレッドプールThreadPoolExecutorをサポートするデフォルトの動作にイライラしてきましたExecutorService。Javadocsから引用するには:

corePoolSizeを超えてmaximumPoolSize未満のスレッドが実行されている場合、キューがいっぱいの場合にのみ、新しいスレッドが作成されます

これは、次のコードでスレッドプールを定義した場合、が無制限であるため、2番目のスレッドを開始しないことを意味しますLinkedBlockingQueue

ExecutorService threadPool =
   new ThreadPoolExecutor(1 /*core*/, 50 /*max*/, 60 /*timeout*/,
      TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(/* unlimited queue */));

制限付きキューがあり、キューがいっぱいの場合のみ、コア番号を超えるスレッドが開始されます。多くのジュニアJavaマルチスレッドプログラマーは、このの動作に気付いていませんThreadPoolExecutor

これで、これが最適ではない特定のユースケースがあります。私は自分のTPEクラスを書かずに、それを回避する方法を探しています。

私の要件は、おそらく信頼できないサードパーティにコールバックするWebサービスに対するものです。

  • コールバックをWebリクエストと同期させたくないので、スレッドプールを使用します。
  • 私は通常、1分間に数回これらを取得するのでnewFixedThreadPool(...)、ほとんどが休止状態である多数のスレッドを使用したくありません。
  • 頻繁にこのトラフィックのバーストが発生し、スレッド数をいくつかの最大値(50としましょう)にスケールアップしたいと思います。
  • すべてのコールバックを最善の方法で試行する必要があるので、50を超える追加のコールバックをキューに入れますnewCachedThreadPool()

スレッドを開始する前にThreadPoolExecutorキューを制限していっぱいする必要があるこの制限を回避するにはどうすればよいですか?タスクをキューに入れる前に、より多くのスレッドを開始するにはどうすればよいですか?

編集:

@FlavioはThreadPoolExecutor.allowCoreThreadTimeOut(true)、コアスレッドをタイムアウトさせて終了させるためにを使用することについて良い点を示しています。私はそれを考えましたが、それでもコアスレッド機能が欲しかったです。可能であれば、プール内のスレッド数がコアサイズを下回らないようにしたいと思いました。


1
あなたの例が最大10のスレッドを作成することを考えると、固定サイズのスレッドプール上で拡大/縮小するものを使用することで実際に節約はありますか?
bstempi 2013年

良い点@bstempi。その数はやや恣意的でした。質問でそれを50に増やしました。このソリューションがあるので、まだ実際に動作させたい同時スレッドの数はまだ正確にはわかりません。
灰色

1
しまった!私がここでできるなら10票、私がいるのとまったく同じ立場です。
ユージーン

回答:


50

ThreadPoolExecutor追加のスレッドが開始される前にキューが制限されていっぱいである必要がある場合に、この制限を回避するにはどうすればよいですか。

私は最終的に、によるこの制限に対するややエレガントな(多分少しハックな)解決策を見つけたと思いますThreadPoolExecutor。これは、拡張含まれLinkedBlockingQueue、それが返すようにfalseするためにqueue.offer(...)、すでにいくつかのタスクがキューイングがあるとき。現在のスレッドがキューに入れられたタスクに追いついていない場合、TPEは追加のスレッドを追加します。プールがすでに最大スレッドにある場合、RejectedExecutionHandlerが呼び出されます。その後put(...)、キューに入れるハンドラです。

offer(...)戻ることができfalseput()ブロックしないキューを作成するのは確かに奇妙なので、これはハック部分です。しかし、これはキューのTPEの使用法でうまく機能するため、これを実行しても問題はありません。

これがコードです:

// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    private static final long serialVersionUID = -6903933921423432194L;
    @Override
    public boolean offer(Runnable e) {
        // Offer it to the queue if there is 0 items already queued, else
        // return false so the TPE will add another thread. If we return false
        // and max threads have been reached then the RejectedExecutionHandler
        // will be called which will do the put into the queue.
        if (size() == 0) {
            return super.offer(e);
        } else {
            return false;
        }
    }
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
        60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            // This does the actual put into the queue. Once the max threads
            //  have been reached, the tasks will then queue up.
            executor.getQueue().put(r);
            // we do this after the put() to stop race conditions
            if (executor.isShutdown()) {
                throw new RejectedExecutionException(
                    "Task " + r + " rejected from " + e);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }
});

このメカニズムにより、タスクをキューに送信すると、次のThreadPoolExecutorことが行われます。

  1. スレッド数を最初にコアサイズ(ここでは1)まで拡大します。
  2. キューにそれを提供します。キューが空の場合、既存のスレッドによって処理されるようにキューに入れられます。
  3. キューにすでに1つ以上の要素がある場合、offer(...)はfalseを返します。
  4. falseが返された場合、最大数(ここでは50)に達するまで、プール内のスレッド数を増やします。
  5. 最大の場合、それは RejectedExecutionHandler
  6. RejectedExecutionHandlerその後、FIFO順で最初に使用可能なスレッドによって処理されるキューにタスクを置きます。

上記のコード例では、キューは制限なしですが、制限付きキューとして定義することもできます。たとえば、1000の容量を追加すると、LinkedBlockingQueue次のようになります。

  1. スレッドを最大に拡大する
  2. その後、1000タスクでいっぱいになるまで待ちます
  3. 次に、キューでスペースが利用可能になるまで、呼び出し元をブロックします。

また、で使用する必要がある場合offer(...)は 、タイムアウトとしての代わりRejectedExecutionHandleroffer(E, long, TimeUnit)メソッドを使用できますLong.MAX_VALUE

警告:

シャットダウンにexecutor タスクが追加されることが予想される場合は、executor-serviceがシャットダウンされたときにRejectedExecutionExceptionカスタムからスローする方が賢明なRejectedExecutionHandler場合があります。これを指摘してくれた@RaduToaderに感謝します。

編集:

この回答に対する別の微調整は、アイドルスレッドがあるかどうかをTPEに尋ね、存在する場合にのみ項目をエンキューすることです。このための真のクラスを作成し、それにourQueue.setThreadPoolExecutor(tpe);メソッドを追加する必要があります。

その場合、offer(...)メソッドは次のようになります。

  1. tpe.getPoolSize() == tpe.getMaximumPoolSize()その場合、単にを呼び出すかどうかを確認してくださいsuper.offer(...)
  2. そうでない場合はtpe.getPoolSize() > tpe.getActiveCount()super.offer(...)アイドルスレッドのように見えるので呼び出します。
  3. それ以外の場合は、false別のスレッドのforkに戻ります。

多分これ:

int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
    return super.offer(e);
} else {
    return false;
}

TPEのgetメソッドは、volatileフィールドにアクセスするか、(の場合getActiveCount())TPEをロックしてスレッドリストをウォークするため、負荷が高いことに注意してください。また、タスクが不適切にキューに入れられたり、アイドルスレッドがあったときに別のスレッドが分岐したりする可能性がある競合状態もあります。


私も同じ問題で苦労し、executeメソッドをオーバーライドしてしまいました。しかし、これは本当に良い解決策です。:)
バティ

限り、私は契約を破るという考えたくないのでQueue、これを達成するために、あなたのアイデアだけでは確かじゃない: groovy-programming.com/post/26923146865
bstempi

3
最初のいくつかのタスクがキューに入れられ、その新しいスレッドが生成された後にのみ、ここで奇妙なことはありませんか?たとえば、あなたの一つのコアスレッドが単一長時間実行タスクでビジー状態の場合、あなたが呼び出しexecute(runnable)、その後、runnable単にキューに追加されます。を呼び出すexecute(secondRunnable)と、secondRunnableがキューに追加されます。ただし、を呼び出すとexecute(thirdRunnable)thirdRunnable新しいスレッドで実行されます。runnableそしてsecondRunnable一度だけ実行しますthirdRunnable(または、元の長時間実行タスク)の完成です。
Robert Tupelo-Schneck 2013年

1
はい、ロバートは正しいです。高度にマルチスレッド化された環境では、使用できるフリースレッドがあるときにキューが大きくなることがあります。TPEを拡張する以下の解決策-はるかにうまく機能します。私はロバートの提案は上記のハックが面白いであっても、その答えとしてマークされるべきだと思う
ワナ・ノウ全て

1
「RejectedExecutionHandler」は、シャットダウン時にエグゼキュータを支援しました。現在、shutdown()は(requeのため)新しいタスクの追加を妨げないため、shutdownNow()を使用する必要があります
Radu Toader

28

コアサイズと最大サイズを同じ値に設定し、でコアスレッドをプールから削除できるようにしallowCoreThreadTimeOut(true)ます。


+1そうだと思いましたが、それでもコアスレッド機能を使いたかったのです。休止期間中にスレッドプールを0スレッドにしたくありませんでした。それを指摘するために質問を編集します。しかし、優れた点。
灰色

ありがとうございました!それはそれを行う最も簡単な方法です。
ドミトリー・オヴチンニコフ

28

この質問には他にも2つの答えがありますが、これが一番良いと思います。

これは、現在受け入れられている回答の手法に基づいています。

  1. キューのoffer()メソッドをオーバーライドして(時々)falseを返す
  2. これにより、ThreadPoolExecutorは新しいスレッドを生成するか、タスクを拒否します。
  3. 実際に拒否時にタスクをキューにRejectedExecutionHandler入れるよう設定します。

問題はいつoffer()falseを返すべきかです。キューにいくつかのタスクがある場合、現在受け入れられている回答はfalseを返しますが、コメントで指摘したように、これは望ましくない影響を引き起こします。または、常にfalseを返す場合は、キューで待機しているスレッドがある場合でも、新しいスレッドを生成し続けます。

解決策は、Java 7を使用LinkedTransferQueueしてをoffer()呼び出すことtryTransfer()です。待機中のコンシューマスレッドがある場合、タスクはそのスレッドに渡されるだけです。それ以外の場合はoffer()falseを返し、はThreadPoolExecutor新しいスレッドを生成します。

    BlockingQueue<Runnable> queue = new LinkedTransferQueue<Runnable>() {
        @Override
        public boolean offer(Runnable e) {
            return tryTransfer(e);
        }
    };
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue);
    threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                executor.getQueue().put(r);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    });

私は同意しなければなりません、これは私にとって最もきれいに見えます。ソリューションの唯一の欠点は、LinkedTransferQueueが無制限であるため、余分な作業なしに容量制限のタスクキューを取得できないことです。
Yeroc、2017年

プールが最大サイズに達すると問題が発生します。プールが最大サイズに拡大され、すべてのスレッドが現在タスクを実行しているとしましょう。runnableが送信されると、このオファーの実装はfalseを返し、ThreadPoolExecutorがaddWorkerスレッドを試行しますが、プールはすでに最大に達しているため、実行可能は単に拒否されます。あなたが書いたrejectedExceHandlerによると、それは再びキューに提供され、このモンキーダンスは最初から再び起こります。
Sudheera 2017

1
@Sudheera私はあなたが間違っていると信じています。 queue.offer()は、実際にを呼び出しているためLinkedTransferQueue.tryTransfer()、falseを返し、タスクをエンキューしません。ただし、RejectedExecutionHandler呼び出しqueue.put()は失敗せず、タスクをエンキューします。
Robert Tupelo-Schneck 2017

1
@ RobertTupelo-Schneckは非常に便利でいいです!
ユージーン

1
@ RobertTupelo-Schneck魅力のように動作します!私はなぜそのようなものがJavaの箱から出していないのか分かりません
Georgi Peev

7

注:私は今、他の回答を好み、推奨しています

これは、私にずっと簡単に感じられるバージョンです。新しいタスクが実行されるたびにcorePoolSizeを増やし(maximumPoolSizeの制限まで)、次に、corePoolSizeを減らします(ユーザーが指定した「コアプールサイズ」の制限まで)。タスクが完了しました。

別の言い方をすると、実行中またはエンキューされたタスクの数を追跡し、ユーザーが指定した「コアプールサイズ」とmaximumPoolSizeの間である限り、corePoolSizeがタスクの数と等しいことを確認します。

public class GrowBeforeQueueThreadPoolExecutor extends ThreadPoolExecutor {
    private int userSpecifiedCorePoolSize;
    private int taskCount;

    public GrowBeforeQueueThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        userSpecifiedCorePoolSize = corePoolSize;
    }

    @Override
    public void execute(Runnable runnable) {
        synchronized (this) {
            taskCount++;
            setCorePoolSizeToTaskCountWithinBounds();
        }
        super.execute(runnable);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable throwable) {
        super.afterExecute(runnable, throwable);
        synchronized (this) {
            taskCount--;
            setCorePoolSizeToTaskCountWithinBounds();
        }
    }

    private void setCorePoolSizeToTaskCountWithinBounds() {
        int threads = taskCount;
        if (threads < userSpecifiedCorePoolSize) threads = userSpecifiedCorePoolSize;
        if (threads > getMaximumPoolSize()) threads = getMaximumPoolSize();
        setCorePoolSize(threads);
    }
}

書かれたように、ユーザの変更をサポートしていません。このクラスは、建設後corePoolSizeまたはmaximumPoolSizeを指定し、直接または経由して作業キューの操作をサポートしていませんremove()purge()


synchronizedブロック以外は好きです。タスクの数を取得するためにキューを介して呼び出すことができますか。または多分AtomicInteger
灰色

避けたかったのですが、これが問題です。execute()別々のスレッドに多数の呼び出しがある場合、各スレッドは、(1)必要なスレッドの数を把握し、(2)setCorePoolSizeその数に(3)呼び出しますsuper.execute()。手順(1)と(2)が同期されていない場合、コアプールサイズを大きい数値の後に小さい数値に設定するという不幸な順序付けを防ぐ方法がわかりません。スーパークラスフィールドに直接アクセスすると、代わりに比較と設定を使用してこれを行うことができますが、同期せずにサブクラスでそれを行うための明確な方法がありません。
Robert Tupelo-Schneck 2013年

taskCountフィールドが有効である限り(つまりAtomicInteger)、その競合状態のペナルティは比較的低いと思います。2つのスレッドが互いの直後にプールサイズを再計算する場合、適切な値を取得する必要があります。2番目のスレッドがコアスレッドを縮小する場合は、キューのドロップなどが発生しているはずです。
グレー

1
悲しいことに、それよりも悪いと思います。タスク10と11がを呼び出すとしますexecute()。それぞれが呼び出しatomicTaskCount.incrementAndGet()、それぞれ10と11を取得します。ただし、同期化しないと(タスク数の取得とコアプールサイズの設定を超えて)(1)タスク11がコアプールサイズを11に設定します(2)タスク10がコアプールサイズを10に設定します(3)タスク10がを呼び出しsuper.execute()、(4)タスク11が呼び出しsuper.execute()てキューに入れられます。
Robert Tupelo-Schneck 2013年

2
私はこの解決策にいくつかの真剣なテストをしました、そしてそれは明らかに最高です。高度にマルチスレッド化された環境では、フリースレッドがある場合でも(フリースレッドのTPE.executeの性質により)エンキューされることがありますが、それはまれに発生します。発生するので、これは各マルチスレッド実行でほぼ発生します。
2014

6

そのサブクラスThreadPoolExecutorがあり、追加creationThresholdでオーバーライドされますexecute

public void execute(Runnable command) {
    super.execute(command);
    final int poolSize = getPoolSize();
    if (poolSize < getMaximumPoolSize()) {
        if (getQueue().size() > creationThreshold) {
            synchronized (this) {
                setCorePoolSize(poolSize + 1);
                setCorePoolSize(poolSize);
            }
        }
    }
}

多分それも役立つかもしれませんが、あなたのはもちろん芸術的に見えます…


面白い。これをありがとう。コアサイズが変更可能であることを実際には知りませんでした。
灰色

もう少し考えてみると、キューのサイズをチェックするという点では、このソリューションは私のソリューションよりも優れています。offer(...)メソッドをfalse条件付きでのみ返すように私の答えを微調整しました。ありがとう!
灰色

4

推奨される回答では、JDKスレッドプールに関する問題の1つだけが解決されます。

  1. JDKスレッドプールは、キューイングに偏っています。したがって、新しいスレッドを生成する代わりに、タスクをキューに入れます。キューが制限に達した場合にのみ、スレッドプールは新しいスレッドを生成します。

  2. 負荷が軽くなってもスレッドのリタイアは発生しません。たとえば、プールにヒットするジョブのバーストがあり、プールが最大になり、一度に最大2つのタスクの軽負荷が続く場合、プールはすべてのスレッドを使用して軽負荷にサービスを提供し、スレッドのリタイアを防ぎます。(2つのスレッドのみが必要になります…)

上記の動作に不満があったので、先に進み、上記の欠点を克服するためのプールを実装しました。

解決するには2)Lifoスケジューリングを使用すると、問題が解決します。このアイデアは、ACMアプリケーション2015カンファレンスでベンモーラーによって提示されました: Systems @ Facebookスケール

そこで、新しい実装が生まれました。

LifoThreadPoolExecutorSQP

これまでのところ、この実装はZELの非同期実行パフォーマンスを向上させます。

この実装はスピン対応であり、コンテキストスイッチのオーバーヘッドを削減し、特定のユースケースで優れたパフォーマンスを実現します。

それが役に立てば幸い...

PS:JDKフォーク結合プールはExecutorServiceを実装し、「通常の」スレッドプールとして機能します。実装はパフォーマンスが高く、LIFOスレッドスケジューリングを使用しますが、内部キューサイズ、リタイアタイムアウトなどを制御できません。最も重要なタスクはキャンセルすると中断されました


1
この実装に多くの外部依存関係があることは残念です。それを私にとって役に立たなくする:-/
Martin L.

1
本当に良い点です(2位)。残念ながら、実装は外部の依存関係から明確ではありませんが、必要に応じて採用できます。
Alexey Vlasov

1

注:私は今、他の回答を好み、推奨しています

キューを変更してfalseを返すという元のアイデアに従って、別の提案があります。このタスクでは、すべてのタスクをキューに入れることができますが、タスクがの後execute()にエンキューされるたびに、キューが拒否するセンチネルのノーオペレーションタスクが続くため、新しいスレッドが生成され、ノーオペレーションがすぐに実行されます。キューから何か。

ワーカースレッドはLinkedBlockingQueue新しいタスクを求めてをポーリングしている可能性があるため、使用可能なスレッドがある場合でも、タスクがエンキューされる可能性があります。使用可能なスレッドがある場合でも新しいスレッドの生成を回避するには、キューで新しいタスクを待機しているスレッドの数を追跡し、キューに待機しているスレッドよりも多くのタスクがある場合にのみ新しいスレッドを生成する必要があります。

final Runnable SENTINEL_NO_OP = new Runnable() { public void run() { } };

final AtomicInteger waitingThreads = new AtomicInteger(0);

BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    @Override
    public boolean offer(Runnable e) {
        // offer returning false will cause the executor to spawn a new thread
        if (e == SENTINEL_NO_OP) return size() <= waitingThreads.get();
        else return super.offer(e);
    }

    @Override
    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
        try {
            waitingThreads.incrementAndGet();
            return super.poll(timeout, unit);
        } finally {
            waitingThreads.decrementAndGet();
        }
    }

    @Override
    public Runnable take() throws InterruptedException {
        try {
            waitingThreads.incrementAndGet();
            return super.take();
        } finally {
            waitingThreads.decrementAndGet();
        }
    }
};

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue) {
    @Override
    public void execute(Runnable command) {
        super.execute(command);
        if (getQueue().size() > waitingThreads.get()) super.execute(SENTINEL_NO_OP);
    }
};
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        if (r == SENTINEL_NO_OP) return;
        else throw new RejectedExecutionException();            
    }
});

0

私が考えることができる最良の解決策は、拡張することです。

ThreadPoolExecutorいくつかのフックメソッドを提供しています beforeExecuteafterExecute。拡張機能では、タスクをフィードするために制限付きキューを使用し、オーバーフローを処理するために2番目の制限なしキューを使用し続けることができます。誰かがを呼び出すとsubmit、リクエストを制限付きキューに入れようとする可能性があります。例外が発生した場合は、タスクをオーバーフローキューに置くだけです。次に、afterExecuteフックを利用して、タスクの完了後にオーバーフローキューに何かがあるかどうかを確認できます。このようにして、エグゼキューターは最初にその有界キュー内のものを処理し、時間が許す限りこの無界キューから自動的にプルします。

それはあなたの解決策よりも多くの仕事のように見えますが、少なくともそれはキューに予期しない動作を与えることを含みません。スローするのがかなり遅い例外に依存するよりも、キューとスレッドのステータスをチェックするためのより良い方法があると私は想像します。


私はこの解決法が好きではありません。ThreadPoolExecutorが継承用に設計されていなかったのは確かです。
scottb 2013年

実際、JavaDocには拡張機能の例があります。彼らは、ほとんどはおそらくフックメソッドを実装するだけだと述べていますが、拡張するときに他に注意する必要があることを伝えています。
bstempi 2013年

0

注:JDK ThreadPoolExecutorの場合、有界キューがある場合、offerがfalseを返したときにのみ新しいスレッドが作成されます。少しのBackPressureを作成し、呼び出し元スレッドでrunを直接呼び出すCallerRunsPolicyを使用すると便利なものを取得できます。

プール内のスレッドの数がかもしれないが、私は、タスクがプールで作成されたスレッドから実行する必要があるとスケジューリングのためのuboundedキューを持って育てるか、縮小の間corePoolSizemaximumPoolSizeそう...

私がやってしまった完全なコピーペーストからThreadPoolExecutor変化しているためビットはメソッドを実行 残念ながら、これは拡張することによって行うことができませんでした(それはプライベートメソッドを呼び出します)。

新しいリクエストが到着し、すべてのスレッドがビジーなときにすぐに新しいスレッドを生成したくありませんでした(一般に、存続期間の短いタスクがあるためです)。私はしきい値を追加しましたが、必要に応じて自由に変更してください(おそらく、ほとんどの場合、IOはこのしきい値を削除する方が良いでしょう)

private final AtomicInteger activeWorkers = new AtomicInteger(0);
private volatile double threshold = 0.7d;

protected void beforeExecute(Thread t, Runnable r) {
    activeWorkers.incrementAndGet();
}
protected void afterExecute(Runnable r, Throwable t) {
    activeWorkers.decrementAndGet();
}
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) && this.workQueue.offer(command)) {
            int recheck = this.ctl.get();
            if (!isRunning(recheck) && this.remove(command)) {
                this.reject(command);
            } else if (workerCountOf(recheck) == 0) {
                this.addWorker((Runnable) null, false);
            }
            //>>change start
            else if (workerCountOf(recheck) < maximumPoolSize //
                && (activeWorkers.get() > workerCountOf(recheck) * threshold
                    || workQueue.size() > workerCountOf(recheck) * threshold)) {
                this.addWorker((Runnable) null, false);
            }
            //<<change end
        } else if (!this.addWorker(command, false)) {
            this.reject(command);
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.