当前线程内的异常

结论:在当前线程通过try catch可以捕获当前线程抛出的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package pers.fulsun;

import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

public class ExceptionInCurThread {
public static void main(String[] args) {
try {
throw new RuntimeException("在主线程抛出异常,在主线程捕获");
} catch (Exception e) {
log.info("捕获到异常" + e);
e.printStackTrace();
}
}
}

其他线程抛出的异常

无效:使用try-catch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

public class ExceptionInChildThread implements Runnable {

@Override
public void run() {
throw new RuntimeException("子线程发生了异常...");
}

/**
* 模拟子线程发生异常
*
* @throws InterruptedException
*/
private static void exceptionThread() throws InterruptedException {
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}

public static void main(String[] args) throws InterruptedException {
try {
exceptionThread();
} catch (Exception e) {
//无法捕获发生在其他线程中的异常
System.out.println("捕获到了异常?" + e.getMessage());
e.printStackTrace();
}
}
}

实际运行结果:

  • 没有被try catch捕获。
  • 后续的线程没有因为第一个线程发生异常而跳过。
  • 无法在一个线程中通过try catch捕获另外一个线程的异常

线程内部捕获

缺点:每个线程都需要编写重复的try catch 代码

1
2
3
4
5
6
7
8
9
@Override
public void run() {
try {
//do something else...
throw new RuntimeException("子线程发生了异常...");
} catch (Exception e) {
System.out.println("在线程内部捕获异常"+e);
}
}

线程异常处理器

当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器

源码说明

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}

自定义线程异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 自定义线程未捕获异常处理器
*/
public class CustomThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomThreadUncaughtExceptionHandler.class);

@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("捕获到线程发生的异常,线程信息:[{}]", JSON.toJSONString(t), e);
}
}

全局的异常处理器

给所有线程设置统一的异常处理器: 通过调用Thread的静态方法setDefaultUncaughtExceptionHandler(),设置Thread的静态属性defaultUncaughtExceptionHandler.为我们自定义的异常处理器。

1
Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Slf4j
public class ExceptionInChildThread implements Runnable {

@Override
public void run() {
throw new RuntimeException("子线程发生了异常...");
}

public static void main(String[] args) throws InterruptedException {
//设置全局的线程异常处理器
Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
exceptionThread();
}


/**
* 模拟子线程发生异常
*
* @throws InterruptedException
*/
private static void exceptionThread() throws InterruptedException {
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}

}

特定的异常处理器

给每个线程设置特定的异常处理器: 在Thread类中,还有一个实例属性private volatile UncaughtExceptionHandler uncaughtExceptionHandler;。通过给这个属性赋值,可以实现为每个线程对象设置不同的异常处理器。

测试使用: 结果: 成功捕获线程1的异常信息

1
2
3
4
5
6
7
8
9
private static void exceptionThread() throws InterruptedException {
Thread thread1 = new Thread(new ExceptionInChildThread());
//为指定线程设置特定的异常处理器
thread1.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
thread1.start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}

线程组

给线程组设置异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
ThreadGroup threadGroup = new ThreadGroup("只知道抛出异常的线程组...") {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("线程组内捕获到线程[{},{}]异常", t.getId(), t.getName(), e);
}
};
ExceptionInThreadGroup exceptionInThreadGroup = new ExceptionInThreadGroup();
new Thread(threadGroup, exceptionInThreadGroup, "线程1").start();

//优先获取绑定在thread对象上的异常处理器
Thread thread = new Thread(threadGroup, exceptionInThreadGroup, "线程2");
thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
thread.start();

线程池异常处理器

因为线程池也是通过new Thread()的方式创建的线程,所以思想与上面两种方法一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CatchThreadPoolException {
public static void main(String[] args) {
ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(5, r -> {
Thread t = new Thread(r);
//设置线程异常处理器
t.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
return t;
});

threadPoolExecutor.execute(() -> {
throw new RuntimeException("execute()发生异常");
}
);

threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
throw new RuntimeException("submit.run()发生异常");
}
});
// 关闭线程池
threadPoolExecutor.shutdown();
}
}
  • 只捕获到了通过execute()提交的任务的异常, 没有捕获到通过submit()提交的任务的异常

  • 注意:execute()submit()方式对 异常处理的不同。

捕获submit任务异常

可以通过Future对象的get()方法来获取任务的执行结果,并在调用get()方法时捕获异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) {
ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(5, r -> {
Thread t = new Thread(r);
//设置线程异常处理器
t.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
return t;
});

Future<?> future = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
throw new RuntimeException("submit.run()发生异常");
}
});
try {
future.get();
} catch (Exception e) {
log.info("future.get()发生异常[{}]",e);
} finally {
threadPoolExecutor.shutdown();
}
// 关闭线程池
threadPoolExecutor.shutdown();
}

通过submit()方法的源码可以发现,submit()是将runnable()封装成了RunnableFuture<Void>,并最终调用execute(ftask);执行。可以使用ThreadPoolExecutorafterExecute()方法捕获异常:

可以重写afterExecute()方法来捕获任务执行过程中的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Slf4j
public class CustomThreadPool extends ThreadPoolExecutor {
public CustomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingDeque<>(1024), r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
return t;
});
}

/**
* 捕获{@code FutureTask<?>}抛出的异常
*
* @param r
* @param t
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 执行任务后的逻辑
if (r instanceof FutureTask<?>) {
try {
//get()的时候会将异常内的异常抛出
((FutureTask<?>) r).get();
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
log.error("捕获到线程的异常返回值", e);
}
}
//Throwable t永远为null,拿不到异常信息
//log.error("afterExecute中捕获到异常,", t);
}
}

匿名子类的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
4,
1L,
TimeUnit.MINUTES,
new LinkedBlockingDeque<>(1024),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设置线程异常处理器
thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
return thread;
}
}
) {
/**
* 捕获{@code FutureTask<?>}抛出的异常
*
* @param r
* @param t
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (r instanceof FutureTask<?>) {
try {
//get()的时候会将异常内的异常抛出
((FutureTask<?>) r).get();
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
log.error("捕获到线程的异常返回值", e);
}
}
//Throwable t永远为null,拿不到异常信息
//log.error("afterExecute中捕获到异常,", t);
}
};

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CatchThreadPoolException {
public static void main(String[] args) {

ExecutorService executor = new CustomThreadPool(5, 10, 1, TimeUnit.MINUTES);

executor.submit(() -> {
// 任务执行的逻辑代码...
throw new RuntimeException("Task exception");
});

executor.shutdown();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2023-06-14 09:57:44 525 Thread-0 ERROR CustomThreadPool (CustomThreadPool.java:37) - 捕获到线程的异常返回值
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Task exception
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at pers.fulsun.CustomThreadPool.afterExecute(CustomThreadPool.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.RuntimeException: Task exception
at pers.fulsun.CatchThreadPoolException.lambda$main$0(CatchThreadPoolException.java:15)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
... 2 more