preface

Today, my friend met a small problem. If the task submitted by the thread pool fails to catch the exception, it will be thrown to where. I did not study before, in line with the principle of seeking truth from facts, I looked at the code.

The body of the

A small problem

Consider the following code. What’s the difference? Can you guess if there will be any abnormal calls? Where, if at all? :

 ExecutorService threadPool = Executors.newFixedThreadPool(1); threadPool.submit(() -> { Object obj = null; System.out.println(obj.toString()); }); threadPool.execute(() -> { Object obj = null; System.out.println(obj.toString()); });Copy the code

The source code parsing

Let’s take a look at the code, which essentially wraps the executable we submitted to the past into a future

public Future<? > submit(Runnable task) {if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; } protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { returnnew FutureTask<T>(runnable, value); } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; } public static <T> Callable<T> Callable (Runnable task, T result) {public static <T> Callable (Runnable task, T result) {if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result); }	 static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); returnresult; }}Copy the code

It will then actually commit to the queue for the thread pool scheduler to handle:

/** * The code is very clean, a typical producer/consumer model, * not to get into the details here, but if the submission to the workQueue succeeds, who is the consumer? Public void execute(Runnable) public void execute(Runnable) public void execute(Runnablecommand) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command.true)) 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); }Copy the code

So let’s look at the thread pool core flow:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{ /** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); }}final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; The try {//getTask() method tries to grab data from the queuewhile(task ! = null || (task = getTask()) ! = null) { w.lock();if((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && ! wt.isInterrupted()) wt.interrupt(); Try {// Overwrite this method with burying points like beforeExecute(wt, task); Throwable thrown = null; Task.run (); 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); }}Copy the code

Method of submission

So we can call the Run method directly, look at the method of Submit, we know that the final pass is a FutureTask, that is, the Run method here will be called, let’s look at the implementation:

	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); } if (ran) set(result); } } finally { //省略 }  protected void setException(Throwable t) { if(UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; PutOrderedInt (this, stateOffset, EXCEPTIONAL); // Unsafe. putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); }}Copy the code

So if we call GET(), we’ll GET it. For example, we can override the After Execute method to GET the actual exception:

protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if(t == null && r instanceof Future<? >) {try {//get will check the status of the task first, and wrap 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 handler. Printstacktrace (); }}Copy the code

Way of execution

So what’s different about Exeture? In this case, the Runnable will be passed directly, so it will throw directly:

 try { 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); }Copy the code

Where does this exception go? Let’s see how the JVM handles it:

if(! destroy_vm || JDK_Version::is_jdk12x_version()) { // JSR-166: change call from from ThreadGroup.uncaughtException to // java.lang.Thread.dispatchUncaughtExceptionif(uncaught_exception.not_null()) {Handle group(this, javA_lang_threadGroup (threadObj())); { KlassHandle recvrKlass(THREAD, threadObj->klass()); CallInfo callinfo; KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass()); /* This is similar to a method table, which actually calls Thread# dispatchUncaughtException method template (dispatchUncaughtException_name, "dispatchUncaughtException") */ LinkResolver::resolve_virtual_call(callinfo, threadObj, recvrKlass, thread_klass, vmSymbols::dispatchUncaughtException_name(), vmSymbols::throwable_void_signature(), KlassHandle(), false, false, THREAD); CLEAR_PENDING_EXCEPTION; methodHandle method = callinfo.selected_method(); if (method.not_null()) { JavaValue result(T_VOID); JavaCalls::call_virtual(&result, threadObj, thread_klass, vmSymbols::dispatchUncaughtException_name(), vmSymbols::throwable_void_signature(), uncaught_exception, THREAD); } else { KlassHandle thread_group(THREAD, SystemDictionary::ThreadGroup_klass()); JavaValue result(T_VOID); JavaCalls::call_virtual(&result, group, thread_group, vmSymbols::uncaughtException_name(), vmSymbols::thread_throwable_void_signature(), threadObj, // Arg 1 uncaught_exception, // Arg 2 THREAD); } if (HAS_PENDING_EXCEPTION) { ResourceMark rm(this); jio_fprintf(defaultStream::error_stream(), "\nException: %s thrown from the UncaughtExceptionHandler" " in thread \"%s\"\n", pending_exception()->klass()->external_name(), get_thread_name()); CLEAR_PENDING_EXCEPTION; }}}Copy the code

As you can see, the thread #DispatchUncoghtException method is finally called:

Private void dispatchUncaughtException (Throwable e) {/ / the default invokes the ThreadGroup implementation getUncaughtExceptionHandler().uncaughtException(this, e); }Copy the code

 public void uncaughtException(Thread t, Throwable e) { if(parent ! = null) { parent.uncaughtException(t, e); }else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if(ueh ! = null) { ueh.uncaughtException(t, e); }else if(! (e instanceof ThreadDeath)) {// You can see that system.err is called in system.err."Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); }}}Copy the code

If the environment is Tomcat, you will get catalina.out:

conclusion

For thread pools, including thread exception handling, the following are recommended:

1 TRY/CATCH directly, that’s what people do

2 threads directly override the entire method:

 Thread t = new Thread(); t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {  public void uncaughtException(Thread t, Throwable e) { LOGGER.error(t + " throws exception: "+ e); }}); / / if the Thread pool mode: the ExecutorService threadPool = Executors. NewFixedThreadPool (1, r - > {Thread t = new Thread (r); t.setUncaughtExceptionHandler( (t1, e) -> LOGGER.error(t1 +" throws exception: " + e)); return t; });Copy the code

Protected void afterExecute(Runnable r, Throwable t) {

Welcome to share the discussion with us. Will bring you one or two knowledge points every day, grow together