Recently in a production environment abnormal extreme thread pool of swallowing problems, studied the principle behind, found that is static initialization exception thrown ExceptionInInitializerError. This scene is rare at ordinary times, and it has been recorded here.

Problem description

After refining the core idea of swallowing exception code, an example looks like this:

@Slf4j
public class ExceptionSingleton {

    private ExceptionSingleton(a) {
        // A runtime exception is thrown during singleton instantiation
        throw new RuntimeException("ExceptionSingleton constructor exception.");
    }

    private static class SingletonHolder {
        // lazy singleton
        private volatile static ExceptionSingleton INSTANCE = new ExceptionSingleton();
    }

    public static ExceptionSingleton getInstance(a) {
        return SingletonHolder.INSTANCE;
    }

    static class Processor implements Runnable {

        @Override
        public void run(a) {
            try {
                ExceptionSingleton.getInstance();
            } catch (Exception e) {
                // An attempt was made to catch a RuntimeException thrown during the singleton construction, but it did not work
                log.error("can not catch this exception here", e); }}}public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new Processor(););

        Thread.currentThread().join();
    }
}
Copy the code

The idea behind this code is to catch runtimeExceptions thrown by singleton instantiations in the thread pool thread and print a log. No log is printed on the actual execution result, and the Processor submitted to the thread pool appears to have disappeared into thin air.

To explore the reasons behind this, let’s explore a few points:

ExceptionInInitializerError

  • Comments can be seen from the type, ExceptionInInitializerError initialization or static block in a static class variable will be thrown:

    /**
    * Signals that an unexpected exception has occurred in a static initializer.
    * An <code>ExceptionInInitializerError</code> is thrown to indicate that an
    * exception occurred during evaluation of a static initializer or the
    * initializer for a static variable.
    *
    * <p>As of release 1.4, this exception has been retrofitted to conform to
    * the general purpose exception-chaining mechanism.  The "saved throwable
    * object" that may be provided at construction time and accessed via
    * the {@link #getException()} method is now known as the <i>cause</i>,
    * and may be accessed via the {@link Throwable#getCause()} method, as well
    * as the aforementioned "legacy method."
    *
    * @author  Frank Yellin
    * @sinceJDK1.1 * /
    public class ExceptionInInitializerError extends LinkageError {
        /**
        * This field holds the exception if the
        * ExceptionInInitializerError(Throwable thrown) constructor was
        * used to instantiate the object
        *
        * @serial* * /
        private Throwable exception;
        // ...
    }
    Copy the code
  • Also need to pay attention to in a static class variable or a static block to initialize all exceptions thrown in, you need to use ExceptionInInitializerError for packaging. Pay special attention to is: if it is thrown RuntimeException, JDK will automatically use ExceptionInInitializerError for packaging.

So, the first sample of singleton thrown RuntimeException, actually the JDK packing into ExceptionInInitializerError. The try catch block inside the Processor catches an Exception instead of a Throwable, and of course no Exception logs are printed. Don’t forget the relationship between Exception, Error and Throwable:

FutureTask

Although there are no capture ExceptionInInitializerError Processor, but why didn’t print related thread pool internal error log?

This requires a deep dive into the thread pool code. We see that the sample submission method used is submit:

publicFuture<? > submit(Runnable task) {if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
Copy the code

The submit method is very succinct. It starts by creating a new FutureTask using the newTaskFor method. The first entry of the FutureTask is the user-submitted business logic runnable:

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
Copy the code

Runnable is cached in the internal field callable:


public class FutureTask<V> implements RunnableFuture<V> {
    /** The underlying callable; nulled out after running */
    private Callable<V> callable;

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    // ...
}

Copy the code

The FTask is then submitted to the thread pool queue. Ftask is then fetched from the thread pool and executed:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while(task ! =null|| (task = getTask()) ! =null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted. This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && ! wt.isInterrupted()) wt.interrupt();try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    / / the task execution
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    // Call afterExecute to do some final processing (such as printing execution exceptions)afterExecute(task, thrown); }}finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally{ processWorkerExit(w, completedAbruptly); }}Copy the code

Task.run () actually executes the FutureTask#run() method:

 public void run(a) {
    if(state ! = NEW || ! UNSAFE.compareAndSwapObject(this, runnerOffset,
                                        null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if(c ! =null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                // All exceptions and errors are caught here
                result = null;
                ran = false;
                setException(ex);
            }
            if(ran) set(result); }}finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if(s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}Copy the code

Callable is user-defined business logic. If any exception or error is thrown in the Callable, it will be caught in the try-catch block and cached on the outcome field through setException(ex) :

/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final statefinishCompletion(); }}Copy the code

Therefore, exceptions thrown by tasks submitted to the thread pool via Submit are actually cached in FutureTask.

Repair methods

From the above discussion, the problem of abnormal swallowing can be solved in several ways.

Catch Throwable instead of Exception

Since a static code is ExceptionInInitializerError thrown an Exception occurs, it and the Exception is a subclass of Throwable. Therefore, we can fix the swallowing problem by capturing Throwable:

static class Processor implements Runnable {

        @Override
        public void run(a) {
            try {
                ExceptionSingleton.getInstance();
            } catch (Throwable e) {
                log.error("catch throwable here", e); }}}Copy the code

Submit tasks to the thread pool using Execute instead of Submit

Tasks submitted through the Submit method are automatically wrapped as FutureTask, resulting in exceptions being cached rather than thrown directly. Execute will not be wrapped, so execute will fix the problem:

executorService.execute(new Processor());
Copy the code

Further, notice from the runWorker method that afterExecute of ThreadPoolExecutor can also be overridden to print exceptions, such as:

class MyThreadExecutor extends ThreadPoolExecutor {
    // ...

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        // log throwable}}Copy the code

The Future of consumption

In fact, the sample of this question is not rigorous enough when using Submit. Since Summit returns an instance of a Future, those familiar with asynchronous programming should know that we should consume the Future. So you can fix it like this:

Future task = executorService.submit(new Processor());
task.get(500, TimeUnit.MILLISECONDS);
Copy the code

Call FutureTask’s GET method, which checks for an error exception inside the method and throws it out.

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ExceptionInInitializerError
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:206)
	at org.demo.singleton.ExceptionSingleton.main(ExceptionSingleton.java:50)
Caused by: java.lang.ExceptionInInitializerError
	at org.demo.singleton.ExceptionSingleton.getInstance(ExceptionSingleton.java:24)
	at org.demo.singleton.ExceptionSingleton$Processor.run(ExceptionSingleton.java:32)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException: ExceptionSingleton constructor exception.
	at org.demo.singleton.ExceptionSingleton.<init>(ExceptionSingleton.java:16)
	at org.demo.singleton.ExceptionSingleton.<init>(ExceptionSingleton.java:12)
	at org.demo.singleton.ExceptionSingleton$SingletonHolder.<clinit>(ExceptionSingleton.java:20)...7 more
Copy the code

The resources

  • When Does Java Throw the ExceptionInInitializerError? : when thrown ExceptionInInitializerError wrong good article