Exception handling mechanism in Java thread pool

Keywords: Java Lambda IE

Preface

When using the thread pool, I was always confused about the handling of exceptions. Sometimes, there is an exception output, sometimes not. I made a summary today.

practice

 ExecutorService exec1 = Executors.newFixedThreadPool(1);

        exec1.submit(()->{     
            Object obj = null;
            System.out.println(obj.toString());
        });
        
        //The above method cannot output an exception. The exception has been swallowed
        
        exec1.submit(()->{
            try {
                Object obj = null;
                System.out.println(obj.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        // Add try catch to output exception
        java.lang.NullPointerException
    	at com.alvinlkk.threadpool.ExecutorTest.lambda$main$1(ExecutorTest.java:24)
    	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:1142)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    	at java.lang.Thread.run(Thread.java:745)
        

        exec1.execute(()->{       
            Object obj = null;
            System.out.println(obj.toString());
        });
        
        //Can output exception
        Exception in thread "pool-1-thread-1" java.lang.NullPointerException
        at com.alvinlkk.threadpool.ExecutorTest.lambda$main$1(ExecutorTest.java:23)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
        
        
        ThreadPoolExecutor executor3 = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                if (t == null && r instanceof Future<?>) {
                    try {
                        //get first checks the status of the task, and then wraps the above exception as ExecutionException
                        Object result = ((Future<?>) r).get();
                    } catch (CancellationException ce) {
                        t = ce;
                    } catch (ExecutionException ee) {
                        t = ee.getCause();
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt(); // ignore/reset
                    }
                }
                if (t != null){
                    //exception handling
                    t.printStackTrace();
                }
            }
        };
        executor3.execute(()->{
            Object obj = null;
            System.out.println(obj.toString());
        });
        //Can output exception
        Exception in thread "pool-2-thread-1" java.lang.NullPointerException
    	at com.alvinlkk.threadpool.ExecutorTest.lambda$main$3(ExecutorTest.java:73)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    	at java.lang.Thread.run(Thread.java:745)
        

Summary

  1. The thread pool uses the execute() method to output exceptions, but uses the submit() method, without any other processing, the exceptions will be swallowed. What's the difference between using execute and submit in the specific thread pool? Let's talk about it. If you can use execute, don't use submit.
  2. Try to catch exceptions directly. I think it's more convenient and commonly used.
  3. When creating a thread pool, override the protected void afterExecute(Runnable r, Throwable t) {} method.
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                if (t == null && r instanceof Future<?>) {
                    try {
                        //get first checks the status of the task, and then wraps the above exception as ExecutionException
                        Object result = ((Future<?>) r).get();
                    } catch (CancellationException ce) {
                        t = ce;
                    } catch (ExecutionException ee) {
                        t = ee.getCause();
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt(); // ignore/reset
                    }
                }
                if (t != null){
                    //exception handling
                    t.printStackTrace();
                }
            }

principle

Why are submit exceptions swallowed and execute not?

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        //The incoming task will be encapsulated as futureTask, and then execute will also be called
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
  /**
    * The code is still fresh, a typical producer / consumer model,
    * The details will not be tangled here for the moment, so if the submission to workQueue is successful, who is the consumer?
    * Obviously, you can study the same details in this new worker by yourself. Here we will find out
    * The core is the inner class of Worker
    */
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))    //The task will be added to the work in the execute method
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
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 {
                        //Call the run method of the task
                        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 {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

The run method invoked in submit is different from execute, submit is run of FutureTask class, and execute is run method of runnable.

public void run() {
        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) {
                    result = null;
                    ran = false;
                    setException(ex);   //An exception is handled instead of being thrown
                }
                if (ran)
                    set(result);
            }
        } finally {
        }
    }

Reference resources

https://segmentfault.com/a/1190000010777336#articleHeader6

Posted by carlheaton on Sun, 05 Jan 2020 16:45:00 -0800