Javaエクセキュータを探しています。たとえば、スロットル/スループット/ペーシングの制限を指定できます。たとえば、1秒間に100のタスクを処理できると言っても過言ではありません。送信され、キューに入れられて後で実行される必要がありますこれの主な目的は、外部APIまたはサーバーにアクセスするときに制限に達しないようにすることです。
Base Java(確認したので、疑問です)か、信頼できる他の場所(Apache Commonsなど)のどちらがこれを提供するのか、または自分で書く必要があるかどうか疑問に思っています。自分で書いてもかまいませんが、どこかに「標準」バージョンがある場合は、少なくとも最初にそれを確認したいと思います。
Guavasを見てください RateLimiter :
レートリミッター。概念的には、レートリミッターは構成可能なレートで許可を配布します。パーミットが利用可能になるまで、それぞれのquire()は必要に応じてブロックし、それを取得します。取得後、許可証を発行する必要はありません。レートリミッターは、物理リソースまたは論理リソースへのアクセスレートを制限するためによく使用されます。これは、レートの代わりに同時アクセス数を制限するセマフォとは対照的です(ただし、同時実行性とレートは密接に関連していることに注意してください。たとえば、リトルの法則を参照してください)。
そのスレッドセーフですが、それでも@Beta
。とにかく試してみる価値があるかもしれません。
レートリミッターに関して、Executor
への各呼び出しをラップする必要があります。よりクリーンなソリューションのために、ExecutorService
のある種のラッパーを作成できます。
Javadocから:
final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
void submitTasks(List<Runnable> tasks, Executor executor) {
for (Runnable task : tasks) {
rateLimiter.acquire(); // may wait
executor.execute(task);
}
}
Java Executorはそのような制限を提供していません。スレッドの量による制限のみです。これは、あなたが探しているものではありません。
一般に、エグゼキュータはそのようなアクションを制限するための間違った場所です。それは、スレッドが外部サーバーを呼び出そうとしているときでなければなりません。これを行うには、たとえば、スレッドが要求を送信する前にスレッドが待機する Semaphore を制限します。
呼び出しスレッド:
public void run() {
// ...
requestLimiter.acquire();
connection.send();
// ...
}
同時に(単一の)セカンダリスレッドを定期的に(60秒ごとなど)取得したリソースを解放するようにスケジュールします。
public void run() {
// ...
requestLimiter.drainPermits(); // make sure not more than max are released by draining the Semaphore empty
requestLimiter.release(MAX_NUM_REQUESTS);
// ...
}
1秒間に100個のタスクを処理できると言うだけです-より多くのタスクが送信された場合、それらはキューに入れられて後で実行される必要があります
Executors.newFixedThreadPool(int limit)
を調べる必要があります。これにより、同時に実行できるスレッドの数を制限できます。複数のスレッドを送信すると、それらはキューに入れられ、後で実行されます。
ExecutorService threadPool = Executors.newFixedThreadPool(100);
Future<?> result1 = threadPool.submit(runnable1);
Future<?> result2 = threadPool.submit(runnable2);
Futurte<SomeClass> result3 = threadPool.submit(callable1);
...
上記のスニペットは、100個以下のスレッドを同時に実行できるExecutorService
を使用する方法を示しています。
更新:
コメントを検討した後、これが私が思いついたものです(ちょっと愚かな)。実行されるスレッドを手動で追跡するのはどうですか?最初にそれらをArrayList
に保存し、最後の1秒間にすでに実行されたスレッドの数に基づいてExecutor
に送信するのはどうですか。
では、200のタスクが維持されているArrayList
に送信されたとしましょう。反復して100をExecutor
に追加できます。 2番目のパスでは、the Executor
などで完了したスレッドの数に基づいて、さらにスレッドを追加できます
シナリオによっては、以前の応答の1つで提案されているように、ThreadPoolExecutorの基本的な機能でうまくいく場合があります。
ただし、スレッドプールが複数のクライアントで共有されていて、スロットルしたい場合は、それぞれの使用を制限し、1つのクライアントがすべてのスレッドを使用しないようにすると、BoundedExecutorが機能します。
詳細については、次の例をご覧ください。
個人的には、このシナリオは非常に興味深いものでした。私の場合は、スロットルする興味深いフェーズは、古典的なプロデューサー/コンシューマーコンカレント理論のように、消費側のフェーズであることを強調したかったのです。これは、以前に提案された回答の一部とは逆です。つまり、送信スレッドをブロックするのではなく、レート(タスク/秒)ポリシーに基づいて消費スレッドをブロックします。したがって、キューに準備ができているタスクがある場合でも、スレッドの実行/消費は、スロットルポリシーを満たすための待機をブロックする可能性があります。
そうは言っても、Executors.newScheduledThreadPool(int corePoolSize)が良い候補だと思います。この方法では、executorの前に単純なキューが必要で(単純なLinkedBlockingQueueが適しています)、定期的なタスクをスケジュールして、キューから実際のタスクを選択します(ScheduledExecutorService.scheduleAtFixedRate)。したがって、これは簡単な解決策ではありませんが、前述のようにコンシューマーを抑制しようとする場合は、十分に機能するはずです。
Runnable内で制限できます。
public static Runnable throttle (Runnable realRunner, long delay) {
Runnable throttleRunner = new Runnable() {
// whether is waiting to run
private boolean _isWaiting = false;
// target time to run realRunner
private long _timeToRun;
// specified delay time to wait
private long _delay = delay;
// Runnable that has the real task to run
private Runnable _realRunner = realRunner;
@Override
public void run() {
// current time
long now;
synchronized (this) {
// another thread is waiting, skip
if (_isWaiting) return;
now = System.currentTimeMillis();
// update time to run
// do not update it each time since
// you do not want to postpone it unlimited
_timeToRun = now+_delay;
// set waiting status
_isWaiting = true;
}
try {
Thread.sleep(_timeToRun-now);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// clear waiting status before run
_isWaiting = false;
// do the real task
_realRunner.run();
}
}};
return throttleRunner;
}