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?