Reputation: 1239
I'm working on a multithreading project that has a case where a Thread
may throw an Error
(not an Exception
). Not finding any solid information about how Errors are handled in multithreading, I decided to do some tests and found that results can be inconsistent.
This is my test code, along with the commented results.
public class MultiThreadError {
public static class ErrorThrowingRunnable implements Runnable{
private final boolean throwsError;
public ErrorThrowingRunnable(boolean throwsError){
this.throwsError = throwsError;
}
@Override
public void run() {
try {
// Wait between .5 and 1.5 seconds
Thread.sleep(500 + new Random().nextInt(1000));
} catch (InterruptedException ex) {}
if(throwsError){
throw new Error(Thread.currentThread().getName());
}else{
System.out.println(Thread.currentThread().getName());
}
}
}
public static void regularThreadPool(){
// Crashes individual thread; swallows error
ExecutorService threadPool = Executors.newFixedThreadPool(5);
threadPool.submit(new ErrorThrowingRunnable(false));
threadPool.submit(new ErrorThrowingRunnable(false));
threadPool.submit(new ErrorThrowingRunnable(false));
threadPool.submit(new ErrorThrowingRunnable(false));
threadPool.submit(new ErrorThrowingRunnable(true));
threadPool.shutdown();
}
public static void onDemandThreads(){
// Crashes individual thread; displays error
new Thread(new ErrorThrowingRunnable(false)).start();
new Thread(new ErrorThrowingRunnable(false)).start();
new Thread(new ErrorThrowingRunnable(false)).start();
new Thread(new ErrorThrowingRunnable(false)).start();
new Thread(new ErrorThrowingRunnable(true)).start();
}
public static void onDemandThreadPool(){
// Same as onDemandThreads()
ExecutorService threadPool = Executors.newFixedThreadPool(5);
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(true));
threadPool.shutdown();
}
public static void tooSmallThreadPool(){
// When an error is thrown, apparently the thread that threw
// the error is not reused, reducing the pool size
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.execute(new ErrorThrowingRunnable(true));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.execute(new ErrorThrowingRunnable(false));
threadPool.shutdown();
}
}
It seems as though the outcome is supposed to be what I expected: the thread throwing the Error terminates, displaying the message. It turns out that when a Runnable
is passed to an ExecutorService
using submit(Runnable)
, it's wrapped in a RunnableFuture<Void>
that doesn't handle errors, and I can't find a way to change this behavior other than directly calling execute(Runnable)
, which for some reason doesn't exhibit the same behavior.
Is there a "best practice" for this? If I know a Thread may throw an Error, is there a way to submit
it to an ExecutorService and not swallow the error?
Upvotes: 3
Views: 997
Reputation: 2607
Yes, submit your task to ExecutorService
and check the result in returned Future
.
When used:
ExecutorService es = Executors.newFixedThreadPool(1);
Future<?> result = es.submit(new Runnable() {
@Override
public void run() {
throw new Error("sample error");
}
});
try {
result.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
your stack trace will contain:
java.util.concurrent.ExecutionException: java.lang.Error: sample error
at java.util.concurrent.FutureTask.report(Unknown Source)
at java.util.concurrent.FutureTask.get(Unknown Source)
at jjj.b.B.main(B.java:23)
Caused by: java.lang.Error: sample error
at jjj.b.B$1.call(B.java:18)
at jjj.b.B$1.call(B.java:1)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Upvotes: 5
Reputation: 366
Trying to handle an Error thrown from a thread seems to be a suspicious use case...
From Java Doc : "An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it."
The only time I needed to handle Error is when I use third parties that badly manage their exceptions, that throw Error rather than RuntimeException. In this specific cases, you need to catch the Error (or the Throwable) to avoid unexpected application crash.
Upvotes: 1
Reputation: 15714
A Future
is an object that represents the result of running your operation, whether it be ready now or at some point in the future. Generally, one will call the get
method on said future in order to get a result, particularly if you are passing a Callable
rather than a Runnable
into your ExecutorService
.
If your Runnable
/Callable
throws an exception, this will be reflected in the Future
object. You will be able to test whether the Runnable
ran successfully by calling the get
method. If it was a clean run, you will get (in this case) null
back. If an exception was thrown, then get
will throw an ExecutionException
, with that exception marked as its cause.
Think of it like a coat check at a music venue. If they lose your coat, they probably won't tell you until you come with your ticket asking for your coat. The Future
serves the purpose of your ticket here.
Upvotes: 0