zoukankan      html  css  js  c++  java
  • Java thread中对异常的处理策略

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

    前言

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

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

    线程里抛出异常

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

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

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

    Java代码  收藏代码
    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的示例里我们也见过。比如说下面的一部分代码:

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

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

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

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

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

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

    unchecked exception

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

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

    Java代码  收藏代码
    1. public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
    2.     checkAccess();  
    3.     uncaughtExceptionHandler = eh;  
    4. }  

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

    Java代码  收藏代码
    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接口部分:

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

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

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

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

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

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

    Java代码  收藏代码
    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. }  

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

    Java代码  收藏代码
    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接口的对象实例来处理。这些细节的东西如果没有碰到过确实很难回答。

  • 相关阅读:
    LFS Linux From Scratch 笔记2(经验非教程)BLFS
    LFS Linux From Scratch 笔记(经验非教程)
    Using VNC on a debian/Ubuntu server with a OS X Mac
    Use weechat (IRC client) on OS X. MacBook Pro
    HC
    Vim 7.4.1952 with Python/Ruby/Lua/Perl/C Syntax built for Ubuntu 16.04 x86_64
    How to make workflow chart using several tools in Linux?
    STM32 串口 重定向
    有哪些大家不说,但需要知道的社会规则?
    如何理解 UL94HB , UL94-V0 , UL94-V1 , UL94-V2
  • 原文地址:https://www.cnblogs.com/googlemeoften/p/5769216.html
Copyright © 2011-2022 走看看