前言
想讨论这个话题有一段时间了。记得几年前的时候去面试,有人就问过我一个类似的问题。就是java thread中对于异常的处理情况。由于java thread本身牵涉到并发、锁等相关的问题已经够复杂了。再加上异常处理这些东西,使得它更加特殊。 概括起来,不外乎是三个主要的问题。1. 在java启动的线程里可以抛出异常吗? 2. 在启动的线程里可以捕捉异常吗? 3. 如果可以捕捉异常,对于checked exception和unchecked exception,他们分别有什么的处理方式呢?
现在, 我们就一个个的来讨论。
线程里抛出异常
我们可以尝试一下在线程里抛异常。按照我们的理解,假定我们要在某个方法里抛异常,需要在该定义的方法头也加上声明。那么一个最简单的方式可能如下:
public class Task implements Runnable { @Override public void run() throws Exception { int number0 = Integer.parseInt("1"); throw new Exception("Just for test"); } }
可是,如果我们去编译上面这段代码,会发现根本就编译不过去的。系统报的错误是:
Task.java:3: error: run() in Task cannot implement run() in Runnable public void run() throws Exception { ^ overridden method does not throw Exception 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的示例里我们也见过。比如说下面的一部分代码:
import java.util.Date; import java.util.concurrent.TimeUnit; public class FileLock implements Runnable { @Override public void run() { for(int i = 0; i < 10; i++) { System.out.printf("%s\n", new Date()); try { TimeUnit.SECONDS.sleep(1); } catch(InterruptedException e) { System.out.printf("The FileClock has been interrupted"); } } } }
我们定义了一个线程执行代码,并且在这里因为调用TimeUnit.SECONDS.sleep()方法而需要捕捉异常。因为这个方法本身就会抛出InterruptedException,我们必须要用try/catch块来处理。
我们启动该线程并和它交互的代码如下:
import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { // Creates a FileClock runnable object and a Thread // to run it FileClock clock=new FileClock(); Thread thread=new Thread(clock); // Starts the Thread thread.start(); try { // Waits five seconds TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }; // Interrupts the Thread thread.interrupt(); } }
这部分的代码是启动FileLock线程并尝试去中断它。我们可以发现在运行的时候FileLock里面执行的代码能够正常的处理异常。
因此,在thread里面,如果要处理checked exception,简单的一个try/catch块就可以了。
unchecked exception
对于这种unchecked exception,相对来说就会不一样一点。实际上,在Thread的定义里有一个实例方法:setUncaughtExceptionHandler(UncaughtExceptionHandler). 这个方法可以用来处理一些unchecked exception。那么,这种情况的场景是如何的呢?
setUncaughtExceptionHandler()方法相当于一个事件注册的入口。在jdk里面,该方法的定义如下:
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { checkAccess(); uncaughtExceptionHandler = eh; }
而UncaughtExceptionHandler则是一个接口,它的声明如下:
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); }
在异常发生的时候,我们传入的UncaughtExceptionHandler参数的uncaughtException方法会被调用。
综合前面的讨论,我们这边要实现handle unchecked exception的方法的具体步骤可以总结如下:
1. 定义一个类实现UncaughtExceptionHandler接口。在实现的方法里包含对异常处理的逻辑和步骤。
2. 定义线程执行结构和逻辑。这一步和普通线程定义一样。
3. 在创建和执行改子线程的方法里在thread.start()语句前增加一个thread.setUncaughtExceptionHandler语句来实现处理逻辑的注册。
下面,我们就按照这里定义的步骤来实现一个示例:
首先是实现UncaughtExceptionHandler接口部分:
import java.lang.Thread.UncaughtExceptionHandler; public class ExceptionHandler implements UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.printf("An exception has been captured\n"); System.out.printf("Thread: %s\n", t.getId()); System.out.printf("Exception: %s: %s\n", e.getClass().getName(), e.getMessage()); System.out.printf("Stack Trace: \n"); e.printStackTrace(System.out); System.out.printf("Thread status: %s\n", t.getState()); } }
这里我们添加的异常处理逻辑很简单,只是把线程的信息和异常信息都打印出来。
然后,我们定义线程的内容,这里,我们故意让该线程产生一个unchecked exception:
public class Task implements Runnable { @Override public void run() { int number0 = Integer.parseInt("TTT"); } }
从这代码里我们可以看到,Integer.parseInt()里面的参数是错误的,肯定会抛出一个异常来。
现在,我们再把创建线程和注册处理逻辑的部分补上来:
public class Main { public static void main(String[] args) { Task task = new Task(); Thread thread = new Thread(task); thread.setUncaughtExceptionHandler(new ExceptionHandler()); thread.start(); } }
现在我们去执行整个程序,会发现有如下的结果:
An exception has been captured Thread: 8 Exception: java.lang.NumberFormatException: For input string: "TTT" Stack Trace: java.lang.NumberFormatException: For input string: "TTT" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:492) at java.lang.Integer.parseInt(Integer.java:527) at Task.run(Task.java:5) at java.lang.Thread.run(Thread.java:722) Thread status: RUNNABLE
这部分的输出正好就是我们前面实现UncaughtExceptionHandler接口的定义。
因此,对于unchecked exception,我们也可以采用类似事件注册的机制做一定程度的处理。
总结
Java thread里面关于异常的部分比较奇特。你不能直接在一个线程里去抛出异常。一般在线程里碰到checked exception,推荐的做法是采用try/catch块来处理。而对于unchecked exception,比较合理的方式是注册一个实现UncaughtExceptionHandler接口的对象实例来处理。这些细节的东西如果没有碰到过确实很难回答。
参考材料
http://stackoverflow.com/questions/6546193/how-to-catch-an-exception-from-a-thread
相关推荐
Java多线程异常处理是Java编程中不可或缺的一部分,特别是在并发编程场景下,正确处理异常能够保证程序的稳定性和健壮性。本实验报告主要涵盖了Java异常处理机制、多线程概念与实现,以及多线程同步问题。 首先,...
线程池(Thread Pool)是Java中管理线程的一种高效策略,由`java.util.concurrent`包中的`ExecutorService`和`ThreadPoolExecutor`类提供支持。线程池可以减少频繁创建和销毁线程的开销,提高响应速度,同时提供了一...
在Java编程中,`InterruptedException`是一个特殊的异常类型,主要用于处理线程中断的情况。当你在代码中调用诸如`Thread.sleep()`, `Thread.join()`, 或 `Object.wait()`等阻塞方法时,如果线程被中断,这些方法会...
在Java中,线程可以通过两种方式创建:继承Thread类或者实现Runnable接口。继承Thread类时,你需要重写run()方法,并直接创建Thread对象来启动线程。而实现Runnable接口则更灵活,因为Java不支持多重继承,所以这种...
综上所述,"Java Thread Programming (Sams)"涵盖了Java线程编程的各个方面,包括基础概念、创建与管理、同步机制、异常处理、线程池和高级特性。通过阅读和实践其中的代码,开发者能深入理解Java线程编程的精髓,...
总的来说,这个资源包为学习和深入理解Java线程提供了全面的材料,从基础概念到高级并发策略,涵盖了一个Java开发人员在实际工作中可能遇到的所有线程相关问题。无论是初学者还是有经验的开发者,都可以从中受益,...
### 2024年Java面试题:Java异常方面的面试题 #### 核心知识点解析 **一、Java异常架构** ...通过以上内容的深入理解,你可以更好地准备Java面试中关于异常处理的部分,并在实际开发工作中有效地运用这些知识。
Java异常处理是Java编程中必不可少的一部分,它用于处理程序运行时可能出现的错误和异常情况。异常是程序执行过程中遇到的不正常情况,比如除以零、文件未找到、网络连接失败等。Java通过异常处理机制来确保程序在...
Java异常处理是编程过程中的重要组成部分,它帮助开发者在程序执行期间识别并处理错误和意外情况。Java异常是程序运行时出现的不正常状态,它们通常由Java虚拟机(JVM)或者Java类库在遇到特定问题时抛出。异常分为...
【Java线程详解】 在Java编程中,线程(Thread)是...同时,理解JVM对线程的调度以及线程安全问题的处理也是提升Java并发编程能力的关键。在实际开发中,应结合具体需求灵活运用各种线程控制手段,以实现最佳性能。
- **异常分类**:区分可预见的和不可预见的异常,对不同类型的异常采取不同的处理策略。 - **避免过度使用全局异常**:全局异常处理应该作为最后的防线,而不是替代局部的异常处理。局部处理能更精确地捕获和修复...
在Java编程中,线程是并发执行任务的基本单元,有效地利用线程可以提高程序的执行效率。然而,不正确的线程使用可能导致各种问题,如数据不一致、死锁等。以下是一些Java线程编程中需要注意的关键点: 1. **同步...
以下将详细介绍Android异常处理机制及其优化策略。 首先,Android系统在遇到未被捕获的异常时,会触发系统默认的错误报告,弹出“应用无响应”(ANR)对话框,这不仅对用户不友好,也无助于开发者定位问题。为了...
以下是对如何进行Java故障排查,特别是利用Thread Dump进行问题定位的详细说明: 1. **获取Thread Dump** - 使用JDK自带的`jstack`工具:通过命令行执行`jstack <pid>`,其中`pid`是Java进程的ID,可以得到应用...
- **异步任务处理**:介绍如何在Swing应用程序中处理耗时的任务而不阻塞UI。 5. **第10章:线程组**(Thread Groups) - **线程组结构**:讲解线程组的概念及其实现机制。 - **线程管理**:讨论如何使用线程组来...
在Java编程中,异常处理是一种重要的机制,用于处理程序执行过程中可能出现的错误或异常情况。Java中的异常分为两大类:**受检异常**(Checked Exceptions)和**非受检异常**(Unchecked Exceptions)。受检异常是在...
1. **try-catch-finally**:这是Java中最基本的异常处理方式,通过try块包围可能抛出异常的代码,catch块捕获异常并进行处理,finally块确保无论是否发生异常都会执行的代码。 2. **全局异常捕获**:在应用程序的主...
在Android开发中,确保应用程序的稳定性和用户体验是至关重要的。当程序出现未预期的异常时,如果不进行妥善...在实际项目中,开发者应根据需求选择合适的处理策略,并确保在处理异常时不会影响到其他功能的正常运行。
理解并妥善处理`NullPointerException`是Java编程的基本功,良好的编程习惯和对null的理解能帮助我们编写更健壮的代码,避免这种常见的运行时错误。在实际开发中,不断实践和学习,才能更好地应对各种异常情况。