转载:http://shmilyaw-hotmail-com.iteye.com/blog/1881302

前言

想讨论这个话题有一段时间了。记得几年前的时候去面试,有人就问过我一个类似的问题。就是java thread中对于异常的处理情况。由于java thread本身牵涉到并发、锁等相关的问题已经够复杂了。再加上异常处理这些东西,使得它更加特殊。 概括起来,不外乎是三个主要的问题。1. 在java启动的线程里可以抛出异常吗? 2. 在启动的线程里可以捕捉异常吗? 3. 如果可以捕捉异常,对于checked exception和unchecked exception,他们分别有什么的处理方式呢?

现在, 我们就一个个的来讨论。

线程里抛出异常

我们可以尝试一下在线程里抛异常。按照我们的理解,假定我们要在某个方法里抛异常,需要在该定义的方法头也加上声明。那么一个最简单的方式可能如下:

  1. public class Task implements Runnable {
  2. @Override
  3. public void run() throws Exception {
  4. int number0 = Integer.parseInt("1");
  5. throw new Exception("Just for test");
  6. }
  7. }

可是,如果我们去编译上面这段代码,会发现根本就编译不过去的。系统报的错误是:

  1. Task.java:3: error: run() in Task cannot implement run() in Runnable
  2. public void run() throws Exception {
  3. ^
  4. overridden method does not throw Exception
  5. 1 error

由此我们发现这种方式行不通。也就是说,在线程里直接抛异常是不行的。可是,这又会引出一个问题,如果我们在线程代码里头确实是产生了异常,那该怎么办呢?比如说,我们通过一个线程访问一些文件或者对网络进行IO操作,结果产生了异常。或者说访问某些资源的时候系统崩溃了。这样的场景是确实可能会发生的,我们就需要针对这些情况进行进一步的讨论。

异常处理的几种方式

在前面提到的几种在线程访问资源产生了异常的情况。我们可以看,比如说我们访问文件系统的时候,会抛出IOException, FileNotFoundException等异常。我们在访问的代码里实际上是需要采用两种方式来处理的。一种是在使用改资源的方法头增加throws IOException, FileNotFoundException等异常的修饰。还有一种是直接在这部分的代码块增加try/catch部分。由前面我们的讨论已经发现,在方法声明加throws Exception的方式是行不通的。那么就只有使用try/catch这么一种方式了。

另外,我们也知道,在异常的处理上,一般异常可以分为checked exception和unchecked exception。作为unchecked exception,他们通常是指一些比较严重的系统错误或者系统设计错误,比如Error, OutOfMemoryError或者系统直接就崩溃了。对于这种异常发生的时候,我们一般是无能为力也没法恢复的。那么这种情况的发生,我们会怎么来处理呢?

checked exception

在线程里面处理checked exception,按照我们以前的理解,我们是可以直接捕捉它来处理的。在一些thread的示例里我们也见过。比如说下面的一部分代码:

  1. import java.util.Date;
  2. import java.util.concurrent.TimeUnit;
  3. public class FileLock implements Runnable {
  4. @Override
  5. public void run() {
  6. for(int i = 0; i < 10; i++) {
  7. System.out.printf("%s\n", new Date());
  8. try {
  9. TimeUnit.SECONDS.sleep(1);
  10. } catch(InterruptedException e) {
  11. System.out.printf("The FileClock has been interrupted");
  12. }
  13. }
  14. }
  15. }

我们定义了一个线程执行代码,并且在这里因为调用TimeUnit.SECONDS.sleep()方法而需要捕捉异常。因为这个方法本身就会抛出InterruptedException,我们必须要用try/catch块来处理。

我们启动该线程并和它交互的代码如下:

  1. import java.util.concurrent.TimeUnit;
  2. public class Main {
  3. public static void main(String[] args) {
  4. // Creates a FileClock runnable object and a Thread
  5. // to run it
  6. FileClock clock=new FileClock();
  7. Thread thread=new Thread(clock);
  8. // Starts the Thread
  9. thread.start();
  10. try {
  11. // Waits five seconds
  12. TimeUnit.SECONDS.sleep(5);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. };
  16. // Interrupts the Thread
  17. thread.interrupt();
  18. }
  19. }

这部分的代码是启动FileLock线程并尝试去中断它。我们可以发现在运行的时候FileLock里面执行的代码能够正常的处理异常。

因此,在thread里面,如果要处理checked exception,简单的一个try/catch块就可以了。

unchecked exception

对于这种unchecked exception,相对来说就会不一样一点。实际上,在Thread的定义里有一个实例方法:setUncaughtExceptionHandler(UncaughtExceptionHandler). 这个方法可以用来处理一些unchecked exception。那么,这种情况的场景是如何的呢?

setUncaughtExceptionHandler()方法相当于一个事件注册的入口。在jdk里面,该方法的定义如下:

  1. public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
  2. checkAccess();
  3. uncaughtExceptionHandler = eh;
  4. }

而UncaughtExceptionHandler则是一个接口,它的声明如下:

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

在异常发生的时候,我们传入的UncaughtExceptionHandler参数的uncaughtException方法会被调用。

综合前面的讨论,我们这边要实现handle unchecked exception的方法的具体步骤可以总结如下:

1. 定义一个类实现UncaughtExceptionHandler接口。在实现的方法里包含对异常处理的逻辑和步骤。

2. 定义线程执行结构和逻辑。这一步和普通线程定义一样。

3. 在创建和执行改子线程的方法里在thread.start()语句前增加一个thread.setUncaughtExceptionHandler语句来实现处理逻辑的注册。

下面,我们就按照这里定义的步骤来实现一个示例:

首先是实现UncaughtExceptionHandler接口部分:

  1. import java.lang.Thread.UncaughtExceptionHandler;
  2. public class ExceptionHandler implements UncaughtExceptionHandler {
  3. public void uncaughtException(Thread t, Throwable e) {
  4. System.out.printf("An exception has been captured\n");
  5. System.out.printf("Thread: %s\n", t.getId());
  6. System.out.printf("Exception: %s: %s\n",
  7. e.getClass().getName(), e.getMessage());
  8. System.out.printf("Stack Trace: \n");
  9. e.printStackTrace(System.out);
  10. System.out.printf("Thread status: %s\n", t.getState());
  11. }
  12. }

这里我们添加的异常处理逻辑很简单,只是把线程的信息和异常信息都打印出来。

然后,我们定义线程的内容,这里,我们故意让该线程产生一个unchecked exception:

  1. public class Task implements Runnable {
  2. @Override
  3. public void run() {
  4. int number0 = Integer.parseInt("TTT");
  5. }
  6. }

从这代码里我们可以看到,Integer.parseInt()里面的参数是错误的,肯定会抛出一个异常来。

现在,我们再把创建线程和注册处理逻辑的部分补上来:

  1. public class Main {
  2. public static void main(String[] args) {
  3. Task task = new Task();
  4. Thread thread = new Thread(task);
  5. thread.setUncaughtExceptionHandler(new ExceptionHandler());
  6. thread.start();
  7. }
  8. }

现在我们去执行整个程序,会发现有如下的结果:

  1. An exception has been captured
  2. Thread: 8
  3. Exception: java.lang.NumberFormatException: For input string: "TTT"
  4. Stack Trace:
  5. java.lang.NumberFormatException: For input string: "TTT"
  6. at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  7. at java.lang.Integer.parseInt(Integer.java:492)
  8. at java.lang.Integer.parseInt(Integer.java:527)
  9. at Task.run(Task.java:5)
  10. at java.lang.Thread.run(Thread.java:722)
  11. Thread status: RUNNABLE

这部分的输出正好就是我们前面实现UncaughtExceptionHandler接口的定义。

因此,对于unchecked exception,我们也可以采用类似事件注册的机制做一定程度的处理。

总结

Java thread里面关于异常的部分比较奇特。你不能直接在一个线程里去抛出异常。一般在线程里碰到checked exception,推荐的做法是采用try/catch块来处理。而对于unchecked exception,比较合理的方式是注册一个实现UncaughtExceptionHandler接口的对象实例来处理。这些细节的东西如果没有碰到过确实很难回答。

Java thread中对异常的处理策略的更多相关文章

  1. JAVA 线程中的异常捕获

    在java多线程程序中,所有线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),也就是说各个线程需要自己把自己的checked e ...

  2. 第33节:Java面向对象中的异常

    Java中的异常和错误 Java中的异常机制,更好地提升程序的健壮性 throwable为顶级,Error和Exception Error:虚拟机错误,内存溢出,线程死锁 Exception:Runt ...

  3. Java -- Thread中start和run方法的区别

    一.认识Thread的 start() 和 run() 1.start(): 我们先来看看API中对于该方法的介绍: 使该线程开始执行:Java 虚拟机调用该线程的 run 方法. 结果是两个线程并发 ...

  4. Java Thread中,run方法和start方法的区别

     两种方法的区别: 1.start方法 用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦 ...

  5. java项目中常见的异常及处理

    Java开发中常见异常及处理方法 1.JAVA异常 异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API ...

  6. Java中测试异常的多种方式

    使用JUnit来测试Java代码中的异常有很多种方式,你知道几种? 给定这样一个class. Person.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...

  7. Java开发中常见的异常问题

    要调试程序,自然需要对程序中的常见的异常有一定的了解,因此在这里我将一些常见的Java程序中的异常列举出来给大家参考 AD: 作为一名开发者,Java程序员,很自然必须熟悉对程序的调试方法.而要调试程 ...

  8. Java Thread join() 的用法

    Java Thread中, join() 方法主要是让调用改方法的thread完成run方法里面的东西后, 在执行join()方法后面的代码.示例: class ThreadTesterA imple ...

  9. Java中的异常和处理详解

    简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?. ...

随机推荐

  1. 【转】CentOS图形界面的开启与关闭

    源自:http://blog.sina.com.cn/s/blog_4a1f76860100zpus.html 安装CentOS 5.6系统的时候我没有先装任何组件,现在用X Window,需要再安装 ...

  2. 服务器响应HTTP请求状态码(转)

    当服务器响应HTTP请求时,其状态行的信息为HTTP的版本号,状态码,及解释状态码的简单说明: 1.客户方错误: 100 客户必须继续发出请求 101 客户要求服务器根据请求转换HTTP协议版本 2. ...

  3. 繁简转换OpenCC,autogb 和 autob5,iconv,python的jianfan包

    OpenCC OpenCC 是跨平台.多语言的开放中文转换库,除了基本的简繁转换功能外,用户还可以选择对不同用词习惯和异体字的处理方式. OpenCC 还提供方便的网页转换界面. OpenOffice ...

  4. Google市场推广统计

    Google Play作为Android最大的应用市场,也存在这推广等常用的行为,那么如何统计呢,Google Analytics SDK或者其他的SDK都提供了方法,实际上是可以不需要任何sdk,完 ...

  5. 多个viewpager可能产生的问题

    由于Fragment的方便性,现在很多人开始大量使用Fragment. 今天使用时遇到各问题,记录下来并分享下. 使用Fragment都会用FragmentActivity ,特别是在用到ViewPa ...

  6. Ubuntu_16.04 配置 Apache Rwrite URL 重写

    Ubuntu Apache配置Rwrite URL重写 0. apache目录

  7. 在C语言中基本数据类型所占的字节数

    基本数据类型所占的字节数其实跟C语言本身没有太大的关系,它取决于编译器的位数,下面这张表说明了不同编译器下基本数据类型的长度: 32位编译器中各基本类型所占字节数: 注:对于32位的编译器,指针变量的 ...

  8. HTTP协议(超文本传输协议)

    一.HTTP的简介: 超文本传输协议. 它是基于TCP连接的(默认端口号是80).所以在传输数据前客户端需向服务器发送连接请求.当服务器同意连接请求,建立连接后才可以发送数据报文. 二.HTTP的报文 ...

  9. JBoss 系列六十九:CDI 基本概念

    概述 如果说EJB,JPA是之前JEE(JEE5及JEE5之前)中里程碑式的规范,那么在JEE6,JEE7中CDI可以与之媲美,CDI(Contexts and Dependency Injectio ...

  10. git切换远程

    已经开发一段时日,公司突然提出要换git仓库 查看目前所有的分支 $git branch -va 添加新的远程仓库 $ git remot add [name] [url] 查看下目前配置 $ git ...