前言
什么是父子线程
我们首先看下 Java Thread 中的初始化过程。
以下方法来自于
Thread (ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals)
在这个方法中,可以看到在创建新线程的时候,继承了一些当前线程的参数作为初始化值。比如我们熟知的 inheritableThreadLocals
。
所以说,对Java中的线程,父线程的概念,只是一种逻辑称呼,创建线程的当前线程就是新线程的父线程,新线程的一些资源来自于这个父线程。
父线程的准确称呼应该被叫做当前线程的创建线程。
当听到父线程的说法时,应该立即联想到的是创建线程,创建新线程时一些资源的供给者。
一个线程与被他创建出来的线程,除了在创建的时候(init)会有一定的依赖交互之外,对JVM来说,他们并没有什么特别的依赖联系,两个独立的线程。
正文
Case 1
Thread.start 方式使用线程
可以看到,在上面的例子中,主线程并没有 catch 到子线程的异常。
case 2
ThreadPool.execute
使用 ThreadPool.execute () 方法,也没办法在父线程中捕获异常进行处理。
ThreadPool.submit
可以看到,使用 future.get 可以在主线程中 catch 到子线程的异常进行处理。
源码分析:
futureTask.get -> futureTask.report 根据 futureTask的状态返回结果.
几种状态如下:
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
futureTask 实现了 RunnableFuture ,在任务执行的时候执行 run 方法;在 set 中修改 futuretask 的状态。
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, 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 {
// 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);
}
}
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}
处理方案
当然,我们可以在子线程中处理异常,这是没问题的。
也可以通过向 Thread 对象传递异常处理器,可以实现在发生异常时候,按照对应的逻辑处理异常。
即,实现 Thread.UncaughtExceptionHandler 这个方法来进行处理。
Thread.UncaughtExceptionHandler
public class Driver {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(1);
Thread.setDefaultUncaughtExceptionHandler(new ChildExceptionHandler());
try {
threadPool.execute(() -> {
Car.run();
});
}catch (RuntimeException e) {
System.out.println("main thread catch sub-thread-1 exception");
}
}
static class Car{
static void run(){
throw new RuntimeException("no oil ");
}
}
static class ChildExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(String.format("thread [%s] happen exception [%s]", t, e.getMessage()));
}
}
}
ThreadPool.execute 的处理
通过阅读 ThreadPool.execute 的源码(本文就不详细介绍了),我们可以看到最后是通过 Woker 线程执行的 task 任务,runWorker 源码如下:
可以看到有 afterExecute 方法可以在执行后进行调用,或者发生异常的时候进行调用。我们可以实现这个方法来进行处理。
说明:上述代码 基于JDK15.
总结
- 介绍了父子线程关系。
- 三种不同的 case 。
- 处理方案,可以通过 Thread.UncaughtExceptionHandler
- 实际开发中使用线程池,可以通过 future.get 处理异常(推荐)
几个问题
- linux 线程模型与Java线程模型。
- Thread 与 ThreaLocal 的关系,及 ThreadLocal 为什么会内存泄漏。