コードのブロックが許容範囲を超えて実行された後、強制的にJavaに例外をスローさせることはできますか?
はい。ただし、通常、コードのランダムな行で別のスレッドに強制的に割り込みをかけることは非常に悪い考えです。これは、プロセスをシャットダウンする場合にのみ行います。
できることは、一定時間後にタスクにThread.interrupt()
を使用することです。ただし、コードがこれをチェックしない限り機能しません。 ExecutorServiceはFuture.cancel(true)
でこれを簡単にすることができます
コードがそれ自体の時間を計り、必要なときに停止するのははるかに優れています。
これが私が知っている最も簡単な方法です:
final Runnable stuffToDo = new Thread() {
@Override
public void run() {
/* Do stuff here. */
}
};
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future future = executor.submit(stuffToDo);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
future.get(5, TimeUnit.MINUTES);
}
catch (InterruptedException ie) {
/* Handle the interruption. Or ignore it. */
}
catch (ExecutionException ee) {
/* Handle the error. Or ignore it. */
}
catch (TimeoutException te) {
/* Handle the timeout. Or ignore it. */
}
if (!executor.isTerminated())
executor.shutdownNow(); // If you want to stop the code that hasn't finished.
または、TimeLimitedCodeBlockクラスを作成してこの機能をラップし、次のように必要な場所で使用できます。
new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
// Do stuff here.
}}.run();
私は他の回答のいくつかを単一のユーティリティメソッドにコンパイルしました。
public class TimeLimitedCodeBlock {
public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
runWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<T> future = executor.submit(callable);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
return future.get(timeout, timeUnit);
}
catch (TimeoutException e) {
//remove this if you do not want to cancel the job in progress
//or set the argument to 'false' if you do not want to interrupt the thread
future.cancel(true);
throw e;
}
catch (ExecutionException e) {
//unwrap the root cause
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
このユーティリティメソッドを使用したサンプルコード:
public static void main(String[] args) throws Exception {
final long startTime = System.currentTimeMillis();
log(startTime, "calling runWithTimeout!");
try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
@Override
public void run() {
try {
log(startTime, "starting sleep!");
Thread.sleep(10000);
log(startTime, "woke up!");
}
catch (InterruptedException e) {
log(startTime, "was interrupted!");
}
}
}, 5, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
log(startTime, "got timeout!");
}
log(startTime, "end of main method!");
}
private static void log(long startTime, String msg) {
long elapsedSeconds = (System.currentTimeMillis() - startTime);
System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
}
私のマシンでサンプルコードを実行した結果:
0ms [ main] calling runWithTimeout!
13ms [ pool-1-thread-1] starting sleep!
5015ms [ main] got timeout!
5016ms [ main] end of main method!
5015ms [ pool-1-thread-1] was interrupted!
計測したいテストコードの場合、time
属性を使用できます。
@Test(timeout = 1000)
public void shouldTakeASecondOrLess()
{
}
本番コードの場合、単純なメカニズムはありません。また、使用するソリューションは、コードをタイミングに合わせて変更できるかどうかによって異なります。
時間指定されているコードを変更できる場合、単純なアプローチは、時間設定されたコードに開始時刻を記憶させ、これに対して現在時刻を定期的に記憶させることです。例えば。
long startTime = System.currentTimeMillis();
// .. do stuff ..
long elapsed = System.currentTimeMillis()-startTime;
if (elapsed>timeout)
throw new RuntimeException("tiomeout");
コード自体がタイムアウトをチェックできない場合、別のスレッドでコードを実行し、完了またはタイムアウトを待つことができます。
Callable<ResultType> run = new Callable<ResultType>()
{
@Override
public ResultType call() throws Exception
{
// your code to be timed
}
};
RunnableFuture future = new FutureTask(run);
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(future);
ResultType result = null;
try
{
result = future.get(1, TimeUnit.SECONDS); // wait 1 second
}
catch (TimeoutException ex)
{
// timed out. Try to stop the code if possible.
future.cancel(true);
}
service.shutdown();
}
編集:ピーター・ローリーは完全に正しいです:スレッドを中断するほど簡単ではなく(私の元の提案)、Executors&Callablesは非常に便利です...
スレッドに割り込むのではなく、タイムアウトに達するとCallableに変数を設定できます。呼び出し可能オブジェクトは、タスク実行の適切な時点でこの変数をチェックして、いつ停止するかを知る必要があります。
呼び出し可能オブジェクトはFutureを返します。これにより、Futureの結果を「取得」しようとするときのタイムアウトを指定できます。このようなもの:
try {
future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
myCallable.setStopMeAtAppropriatePlace(true);
}
Future.get、Executors、およびCallableを参照してください...
https://docs.Oracle.com/javase/8/docs/api/Java/util/concurrent/Callable.html
2つのオプションを提案できます。
メソッド内で、外部イベントを待機せずにループしていると想定して、ローカルフィールドを追加し、ループのたびに時間をテストします。
void method() {
long endTimeMillis = System.currentTimeMillis() + 10000;
while (true) {
// method logic
if (System.currentTimeMillis() > endTimeMillis) {
// do some clean-up
return;
}
}
}
スレッドでメソッドを実行し、呼び出し元を10秒にカウントします。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
method();
}
});
thread.start();
long endTimeMillis = System.currentTimeMillis() + 10000;
while (thread.isAlive()) {
if (System.currentTimeMillis() > endTimeMillis) {
// set an error flag
break;
}
try {
Thread.sleep(500);
}
catch (InterruptedException t) {}
}
このアプローチの欠点は、method()が値を直接返すことができず、インスタンスフィールドを更新してその値を返す必要があることです。
フレームワークやAPIを使用せずに、非常にシンプルなソリューションを作成しました。これは、よりエレガントで理解しやすいように見えます。クラスはTimeoutBlockと呼ばれます。
public class TimeoutBlock {
private final long timeoutMilliSeconds;
private long timeoutInteval=100;
public TimeoutBlock(long timeoutMilliSeconds){
this.timeoutMilliSeconds=timeoutMilliSeconds;
}
public void addBlock(Runnable runnable) throws Throwable{
long collectIntervals=0;
Thread timeoutWorker=new Thread(runnable);
timeoutWorker.start();
do{
if(collectIntervals>=this.timeoutMilliSeconds){
timeoutWorker.stop();
throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
}
collectIntervals+=timeoutInteval;
Thread.sleep(timeoutInteval);
}while(timeoutWorker.isAlive());
System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
}
/**
* @return the timeoutInteval
*/
public long getTimeoutInteval() {
return timeoutInteval;
}
/**
* @param timeoutInteval the timeoutInteval to set
*/
public void setTimeoutInteval(long timeoutInteval) {
this.timeoutInteval = timeoutInteval;
}
}
例:
try {
TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
Runnable block=new Runnable() {
@Override
public void run() {
//TO DO write block of code to execute
}
};
timeoutBlock.addBlock(block);// execute the runnable block
} catch (Throwable e) {
//catch the exception here . Which is block didn't execute within the time limit
}
FTPアカウントに接続しなければならなかったとき、これは非常に役に立ちました。次に、ダウンロードしてアップロードします。 FTP接続がハングするか、完全に切断される場合があります。これにより、システム全体がダウンしました。そして、私はそれを検出し、それが起こらないようにする方法が必要でした。だから私はこれを作成して使用しました。かなりうまくいきます。
タスクを新しいスレッドに入れてタイマーをメインスレッドに入れる代わりに、タイマーを新しいスレッドに入れてタスクをメインスレッドに入れます。
public static class TimeOut implements Runnable{
public void run() {
Thread.sleep(10000);
if(taskComplete ==false) {
System.out.println("Timed Out");
return;
}
else {
return;
}
}
}
public static boolean taskComplete = false;
public static void main(String[] args) {
TimeOut timeOut = new TimeOut();
Thread timeOutThread = new Thread(timeOut);
timeOutThread.start();
//task starts here
//task completed
taskComplete =true;
while(true) {//do all other stuff }
}
それを行うにはハックな方法があります。
作業が完了したかどうかを示すブールフィールドを設定します。次に、コードブロックの前に、タイムアウト後にコードを実行するタイマーを設定します。タイマーは、コードブロックの実行が完了したかどうかを確認し、完了していない場合は例外をスローします。それ以外の場合は何もしません。
もちろん、コードブロックの最後でフィールドをtrueに設定して、作業が完了したことを示す必要があります。
特定のタイムアウト時間内にメッセージをSQSにプッシュすることがタスクの場合、同様の問題に直面しました。別のスレッドを介して実行し、タイムアウトを指定して将来のオブジェクトを待機するという単純なロジックを使用しました。これにより、タイムアウトの場合にTIMEOUT例外が発生します。
final Future<ISendMessageResult> future =
timeoutHelperThreadPool.getExecutor().submit(() -> {
return getQueueStore().sendMessage(request).get();
});
try {
sendMessageResult = future.get(200, TimeUnit.MILLISECONDS);
logger.info("SQS_Push_SUCCESSFUL");
return true;
} catch (final TimeoutException e) {
logger.error("SQS_Push_TIMEOUT_EXCEPTION");
}
ただし、別のスレッドで実行されているコードを停止できない場合があり、その場合は真のネガが発生します。
たとえば、私の場合、リクエストがSQSに到達し、メッセージがプッシュされている間に、コードロジックが指定されたタイムアウトになりました。現在、実際には私のメッセージはキューにプッシュされましたが、メインスレッドはTIMEOUT例外のために失敗したと想定していました。これは、解決するのではなく回避できるタイプの問題です。私の場合のように、ほとんどすべての場合に十分なタイムアウトを提供することで回避しました。
中断するコードがアプリケーション内にあり、API呼び出しのようなものではない場合は、単に使用できます
future.cancel(true)
ただし、Java docsは、実行がブロックされることを保証することを示していることを忘れないでください。
"このタスクの実行をキャンセルしようとします。タスクが既に完了している場合、すでにキャンセルされている場合、または他の理由でキャンセルできなかった場合、この試行は失敗します。 、cancelが呼び出されたときにこのタスクが開始されていない場合、このタスクは実行されません。タスクが既に開始されている場合、mayInterruptIfRunningパラメーターは、タスクを停止しようとしてこのタスクを実行するスレッドを中断するかどうかを決定します。」
CompletableFutureの方法が必要な場合は、次のようなメソッドを使用できます。
public MyResponseObject retrieveDataFromEndpoint() {
CompletableFuture<MyResponseObject> endpointCall
= CompletableFuture.supplyAsync(() ->
yourRestService.callEnpoint(withArg1, withArg2));
try {
return endpointCall.get(10, TimeUnit.MINUTES);
} catch (TimeoutException
| InterruptedException
| ExecutionException e) {
throw new RuntimeException("Unable to fetch data", e);
}
}
スプリングを使用している場合は、例外がスローされた場合にメソッドを3回再試行するように、メソッドに@Retryable
注釈を付けることができます。