前言
- 之前看过一个文章,里面有一个面试题,是关于多线程的问题。
- 问题描述的是:如果一个线程死了,会发生什么?
- 个人在没有看文章后续讲解时,当时心里产生了几个答案。可惜最后发现没有回答到点上,所以打算记录一下 -_-
分析过程
- 首先定义一个ThreadPoolExecutor线程池。再往里面添加两个线程。
private final static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
public static void main(String[] args) {
test();
}
private static void test() {
EXECUTOR.execute(() -> doIt("execute"));
EXECUTOR.submit(() -> doIt("submit"));
}
private static void doIt(String name) {
System.out.println(name + " 执行了");
throw new RuntimeException(name + " 异常了");
}
- 执行结果是
execute 执行了
submit 执行了
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: execute 异常了
at com.web.test.test20210226.Test1.doIt(Test1.java:26)
at com.web.test.test20210226.Test1.lambda$test$0(Test1.java:20)
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)
- 这里我们得出的结论:
- 这里往线程池添加了两个线程,抛异常后线程没有互相影响。
- 线程池的execute()方法会直接抛出异常信息,而这里的submit()方法没有抛出异常信息。
- 但是submit()方法后面一般都会跟着future.get()方法来获取线程执行结果,现在线程抛异常了,那么得到的执行结果会是什么呢?
private final static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
public static void main(String[] args) {
test();
}
private static void test() {
Future<?> future = EXECUTOR.submit(() -> doIt("submit"));
try {
future.get();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void doIt(String name) {
System.out.println(name + " 执行了");
throw new RuntimeException(name + " 异常了");
}
- 执行结果:
submit 执行了
java.util.concurrent.ExecutionException: java.lang.RuntimeException: submit 异常了
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.web.test.test20210226.Test1.test(Test1.java:23)
at com.web.test.test20210226.Test1.main(Test1.java:17)
Caused by: java.lang.RuntimeException: submit 异常了
at com.web.test.test20210226.Test1.doIt(Test1.java:31)
at com.web.test.test20210226.Test1.lambda$test$0(Test1.java:21)
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)
- 这里在调用future.get()方法后,也抛出异常信息。所以可以得出结论:
- 线程池执行submit()方法没有直接抛出异常信息,而如果调用future.get()方法才会抛出异常信息。
- 根据打印的异常信息,里面都打印了 at.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)。点进去看一下ThreadPoolExecutor源码。
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 {
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 {
// 线程池中worker的回收机制实现。
processWorkerExit(w, completedAbruptly);
}
}
- 原理可以看到在task.run()方法异常后,在catch语句里直接把抛出异常信息。
- 然后看processWorkerExit(w, completedAbruptly)方法。这个方式是当线程异常中断后,对worker的回收机制实现。
/**
* Performs cleanup and bookkeeping for a dying worker. Called
* only from worker threads. Unless completedAbruptly is set,
* assumes that workerCount has already been adjusted to account
* for exit. This method removes thread from worker set, and
* possibly terminates the pool or replaces the worker if either
* it exited due to user task exception or if fewer than
* corePoolSize workers are running or queue is non-empty but
* there are no workers.
*
* @param w the worker
* @param completedAbruptly if the worker died due to user exception
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 这里移除掉之前的异常中断的线程
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 重新添加一个线程到队列里去
addWorker(null, false);
}
}
- 所以这里得出结论:当线程抛异常后,线程不会回收到线程池而是直接移除线程,并且创建一个新的线程放入线程池。
- 在来看看submit(Runnable task)方法执行源码。传入的参数会被封装成一个FutureTask对象,并且也是执行的execute()方法。
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
-----------------------------
/**
* Returns a {@code RunnableFuture} for the given runnable and default
* value.
*
* @param runnable the runnable task being wrapped
* @param value the default value for the returned future
* @param <T> the type of the given value
* @return a {@code RunnableFuture} which, when run, will run the
* underlying runnable and which, as a {@code Future}, will yield
* the given value as its result and provide for cancellation of
* the underlying task
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
- 看看FutureTask对象执行的run()方法源码
/**
* Causes this future to report an {@link ExecutionException}
* with the given throwable as its cause, unless this future has
* already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon failure of the computation.
*
* @param t the cause of failure
*/
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// t 是run()方法中保存的异常信息
outcome = t;
// 为变量state赋值为EXCEPTIONAL(3)
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
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;
// 这里是重点,保存异常信息并修改变量state值
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);
}
}
----------------------------------------
/**
* The run state of this task, initially NEW. The run state
* transitions to a terminal state only in methods set,
* setException, and cancel. During completion, state may take on
* transient values of COMPLETING (while outcome is being set) or
* INTERRUPTING (only while interrupting the runner to satisfy a
* cancel(true)). Transitions from these intermediate to final
* states use cheaper ordered/lazy writes because values are unique
* and cannot be further modified.
*
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
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; // 被中断
- 在catch语句里,没有直接抛出异常信息,而是调用了setException(ex)方法保存异常信息。并且把变量state赋值为EXCEPTIONAL
- 在看一下fufuretask.get()方法源码执行流程。
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
// 这里当变量state的状态大于1,则直接返回state。所以这里返回setException()方法里修改的值3
s = awaitDone(false, 0L);
return report(s);
}
-------------------------
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
// 当变量state的值为3时,抛出异常
throw new ExecutionException((Throwable)x);
}
- 在report()方法里直接抛出ExecutionException和之前保存的异常信息。
最后
- 得出的结论是:
- 如果是用execute方法执行线程,那么会直接抛异常:如果是用submit方法提交线程,不会直接打印异常,而是需要调用future.get()方法才会打印异常。
- 当线程抛异常后不会影响其他线程。
- 当线程抛异常后,线程不会回收到线程池而是直接移除线程,并且创建一个新的线程放入线程池中。
- 虚心学习,共同进步 -_-
- 参考文章链接