線程池中的一個線程異常了會被怎么處理?
拋異常出來并打印在控制臺上(只對了一半脖含,根據提交方式的不同(execute和 submit))
其他線程任務不受影響
異常線程會被回收
下面進行驗證:
1.拋異常出來并打印在控制臺上?
熟悉Executors線程池(本文線程池都是指Executors)都知道 有兩種提交線程的方式execute和submit方式浦妄,下面將以這兩種提交方式來驗證蔼夜。
public class ExecutorsTest {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5
, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(4), SmallTool.getCustomThreadFactory()
, (r, executor) -> SmallTool.printTimeAndThread(" 正在放棄任務:" + r + " , 所屬executor : " + executor));
pool.execute(() -> sayHi("execute"));
Thread.sleep(1000);
pool.submit(() -> sayHi("submit"));
}
public static void sayHi(String name) {
String printStr = "thread-name:" + Thread.currentThread().getName() + ",執(zhí)行方式:" + name;
System.out.println(printStr);
throw new RuntimeException(printStr + " error!!!");
}
}
結果:
thread-name:pool-llc-thread-1,執(zhí)行方式:execute
Exception in thread "pool-llc-thread-1" java.lang.RuntimeException: thread-name:pool-llc-thread-1,執(zhí)行方式:execute error!!!
at com.orion.base.ExecutorsTest.sayHi(ExecutorsTest.java:23)
at com.orion.base.ExecutorsTest.lambda$main$1(ExecutorsTest.java:15)
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)
thread-name:pool-llc-thread-3,執(zhí)行方式:submit
從運行結果可見:
execute執(zhí)行方式拋出異常顯示在控制臺了谓厘。
submit執(zhí)行方式并沒有顯示询一。
眾所周知submit底層其實也是調用的execute,因此它也有異常只是處理方法不一樣均唉,它們的區(qū)別是:
1是晨、execute沒有返回值√蚣可以執(zhí)行任務罩缴,但無法判斷任務是否成功完成〔惴觯——實現Runnable接口
2箫章、submit返回一個future【祷幔可以用這個future來判斷任務是否成功完成檬寂。——實現Callable接口
submit的話戳表,我們可以在其返回的future中拿到結果桶至。稍微修改下代碼拿诸,將其異常打印出來即可。
public class ExecutorsTest {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5
, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(4), SmallTool.getCustomThreadFactory()
, (r, executor) -> SmallTool.printTimeAndThread(" 正在放棄任務:" + r + " , 所屬executor : " + executor));
pool.execute(() -> sayHi("execute"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Future future = pool.submit(() -> sayHi("submit"));
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
System.out.println("execution Exception : " + e.getMessage());
}
}
public static void sayHi(String name) {
String printStr = "thread-name:" + Thread.currentThread().getName() + ",執(zhí)行方式:" + name;
System.out.println(printStr);
throw new RuntimeException(printStr + " error!!!");
}
}
運行結果:
thread-name:pool-llc-thread-1,執(zhí)行方式:execute
Exception in thread "pool-llc-thread-1" java.lang.RuntimeException: thread-name:pool-llc-thread-1,執(zhí)行方式:execute error!!!
at com.orion.base.ExecutorsTest.sayHi(ExecutorsTest.java:33)
at com.orion.base.ExecutorsTest.lambda$main$1(ExecutorsTest.java:13)
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)
thread-name:pool-llc-thread-3,執(zhí)行方式:submit
execution Exception : java.lang.RuntimeException: thread-name:pool-llc-thread-3,執(zhí)行方式:submit error!!!
造成這種區(qū)別塞茅,只能去查看源碼到底是怎么回事亩码。
execute
根據代碼直接點進來,找到在java.util.concurrent.ThreadPoolExecutor#runWorker中拋出了運行異常:
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 {
processWorkerExit(w, completedAbruptly);
}
}
可以見到野瘦,它拋出了異常描沟,最終還是會去到java.lang.ThreadGroup#uncaughtException進行了異常處理:
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)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
因為沒有自定義UncaughtExceptionHandler ,所以使用默認的鞭光,
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
可見上面打印的方式和這里是一致的吏廉。
異常處理有多種方式,詳情可見Java線程池「異常處理」正確姿勢:有病就得治惰许。
submit
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
可見submit底層也是調用execute席覆,但是它會先包裝成一個futureTask,它有自己的run方法
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 {
// 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);
}
}
可以看到汹买,catch那里有個setException佩伤,進去看看,debug一下:
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
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);
}
get()方法可見會將outcome的包裝成一個ExecutionException再扔出來,就驗證來上面打印的是execution Exception晦毙。
2. 其他線程任務不受影響生巡?
public class ExecutorsTest {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1
, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(16), SmallTool.getCustomThreadFactory());
for (int i = 0; i < 10; i++) {
String s = (i == 3) ? "executeException" : "execute";
pool.execute(() -> sayHi(s));
}
}
public static void sayHi(String name) {
if ("executeException".equals(name)) {
throw new RuntimeException(Thread.currentThread().getName() + " -- execute exception");
} else {
System.out.println(Thread.currentThread().getName() + " -- execute normal");
}
}
}
運行結果:
pool-llc-thread-1 -- execute normal
pool-llc-thread-1 -- execute normal
pool-llc-thread-1 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
Exception in thread "pool-llc-thread-1" java.lang.RuntimeException: pool-llc-thread-1 -- execute exception
at com.orion.base.ExecutorsTest.sayHi(ExecutorsTest.java:23)
at com.orion.base.ExecutorsTest.lambda$main$0(ExecutorsTest.java:17)
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)
由上可見,當有一個線程發(fā)生異常時见妒,其他線程是不受影響的孤荣。
異常線程會被回收?
但是線程標號已經超過maxPoolSize须揣,默認threadFactory的標號中使用atomicInteger來遞增的盐股。為什么會出現該情況,其實總歸還是在 ThreadPoolExecutor#runWorker()方法中的processWorkerExit(w, completedAbruptly) 中耻卡。
查看代碼:
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);
}
}
可見它是先從workers中刪除掉疯汁,再addWorker,addWorker就會創(chuàng)建線程劲赠,線程標號就會增加涛目。