前言
java提供了一套完善的异常处理机制,异常的基础本文不展开,本文重点讲述线程的异常处理。
正文
本文将线程的实现分为2种,一种是实现了Runnable接口的无返回值的线程,无法在主线程感知到子线程的异常,没有被捕获的异常只会输出到控制台,那么未被捕获的异常如果没有进行处理或者日志记录,就会造成异常信息丢失;另一种是实现了Callable接口的有返回值的线程,子线程的异常信息会通过FutureTask返回值传递给主线程。最后再说一下线程池的异常处理方式。
一、线程的异常处理方法
1、主线程无法捕获子线程的异常
示例代码:
public class RunNableTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
try {
new Thread(new RunTask1()).start();
} catch (Exception e) {
System.out.println("catch excetion in main thread");
}
}
}
class RunTask1 implements Runnable {
@Override
public void run() {
int a = 1 / 0;
System.out.println("threadpool test:" + Thread.currentThread().getName());
}
}
输出:
main
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at org.jimmy.runnable.RunTask1.run(RunNableTest.java:18)
at java.lang.Thread.run(Thread.java:748)
上述代码示例中,主线程无法捕获到子线程抛出的异常,异常在子线程的Thread.run()方法中抛出后,JVM会调用dispatchUncaughtException方法最终输出到控制台。
2、Runnable子线程自行处理异常
示例代码:
public class RunNableTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
new Thread(new RunTask1()).start();
}
}
class RunTask1 implements Runnable {
@Override
public void run() {
try {
int a = 1 / 0;
} catch (Exception e) {
System.out.println("catch exception in child thread");
}
System.out.println("threadpool test:" + Thread.currentThread().getName());
}
}
输出:
main
catch exception in child thread
threadpool test:Thread-0
上述示例代码展示了子线程内的异常处理方式,这是推荐的线程内异常处理方法。
3、主线程捕获子线程返回的FutureTask抛出的异常
示例代码:
public class RunNableTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
FutureTask<String> futureTask = new FutureTask<>(new RunTask2());
new Thread(futureTask).start();
try {
String s = futureTask.get();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class RunTask2 implements Callable<String> {
@Override
public String call() throws Exception {
int a = 1 / 0;
return "hello";
}
}
输出:
main
catch exception in main thread
主线程成功捕获了Callable子线程抛出的异常。
二、线程池的异常处理方式
首先需要说明一下:线程池提交任务的方式有2种:一种是execute()方法提交Runnable无返回值的任务,另一种是submit()方法提交Callable有返回值的任务。而使用submit()方法提交任务是有坑的,如果提交之后没有使用get方法获取返回值,异常信息就丢了。
1、使用execute方法提交任务不能捕获异常
示例代码:
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 2L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(6));
System.out.println(Thread.currentThread().getName());
for (int i = 0; i < 2; i++) {
threadPoolExecutor.execute(new RunTask());
}
}
}
class RunTask implements Runnable {
@Override
public void run() {
int a = 1 / 0;
System.out.println("threadpool test:" + Thread.currentThread().getName());
}
}
输出:
main
Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at org.jimmy.threadPool.RunTask.run(ThreadPoolTest.java:24)
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)
java.lang.ArithmeticException: / by zero
at org.jimmy.threadPool.RunTask.run(ThreadPoolTest.java:24)
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)
跟上面讲的一样,Runnable接口任务无法在主线程中捕获,异常会被JVM打印到控制台,需要在子线程中捕获异常。
2、使用submit方法提交Callable任务,并获取返回值
示例代码:
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 2L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(6));
System.out.println(Thread.currentThread().getName());
List<Future<String>> futureList = new ArrayList<>();
for (int i = 0; i < 2; i++) {
Future<String> future = threadPoolExecutor.submit(new RunTask2());
futureList.add(future);
}
for (Future<String> future : futureList) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
System.out.println("catch exception in main thread");
}
}
}
}
class RunTask2 implements Callable<String> {
@Override
public String call() throws Exception {
int a = 1 / 0;
return "hello";
}
}
输出:
main
catch exception in main thread
catch exception in main thread
上述代码可以看到,主线程在使用get方法获取返回值时,也捕获到了线程池中抛出的异常。
我们再来看下只submit提交任务,不get返回值的情况。
3、只submit提交任务,不get返回值
实例代码,注释掉一部分代码:
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 2L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(6));
System.out.println(Thread.currentThread().getName());
List<Future<String>> futureList = new ArrayList<>();
for (int i = 0; i < 2; i++) {
Future<String> future = threadPoolExecutor.submit(new RunTask2());
futureList.add(future);
}
}
}
class RunTask2 implements Callable<String> {
@Override
public String call() throws Exception {
int a = 1 / 0;
return "hello";
}
}
输出:
main
程序只打印了主线程信息,线程池中的子线程信息全都被隐藏了,生产环境就会有大问题。
总结
真正实践中一般使用线程池来处理多线程问题。如果无需返回值,则使用execute()方法提交任务,并在子线程中处理异常;如果需要返回值,则使用submit()方法提交任务,然后要么在子线程中处理异常,要么通过future.get()方法把异常抛给主线程。一定不要既不在子线程处理异常,又不通过future.get()方法把异常抛给主线程。
|