`
frank-liu
  • 浏览: 1661458 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java thread中对异常的处理策略

阅读更多

前言

    想讨论这个话题有一段时间了。记得几年前的时候去面试,有人就问过我一个类似的问题。就是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接口的对象实例来处理。这些细节的东西如果没有碰到过确实很难回答。

参考材料

Java 7 Concurrency Cookbook

http://stackoverflow.com/questions/1369204/how-to-throw-a-checked-exception-from-a-java-thread/1369828#1369828

http://stackoverflow.com/questions/6546193/how-to-catch-an-exception-from-a-thread

分享到:
评论

相关推荐

    Java基础知识点总结.docx

    无论是工作学习,不断的总结是必不可少...Java两种线程类:Thread和Runnable 315 Java锁小结 321 java.util.concurrent.locks包下常用的类 326 NIO(New IO) 327 volatile详解 337 Java 8新特性 347 Java 性能优化 362

    java 面试题 总结

    异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的...

    基于JAVA的搜索引擎 lucene-2.2.0

    Exception in thread "main" java.io.FileNotFoundException: no segments* file found in org.apache.lucene.store.FSDirectory@E:\index: files: at org.apache.lucene.index.SegmentInfos$FindSegmentsFile.run...

    Java面试宝典2020修订版V1.0.1.doc

    41、Java的异常处理机制是什么? 23 42、一个静态方法,里面可不可以用this和super关键字 24 三、JavaScript/JQuery/Ajax部分 24 1、用js和jQuery怎么进行表单验证 24 3、列举javaScript的3种主要数据类型,2种复合...

    Java并发编程(学习笔记).xmind

    框架通过在框架线程中调用应用程序代码将并发性引入应用程序,因此对线程安全的需求在整个应用程序中都需要考虑 基础知识 线程安全性 定义 当多个线程访问某个类时,这个类始终能表现出正确的行为,...

    Java程序设计习题(含答案解析).doc

    ( F ) 37、一个异常处理中 finally语句块只能有一个或者可以没有。(F ) 38、语句import java.applet.Applet;最后的Applet是代表类(class)。( T ) 39、类体中private修饰的变量在本类中能访问,类生成的对象也能访问...

    超级有影响力霸气的Java面试题大全文档

     异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获...

    Java开发技术大全 电子版

    6.2Java的异常处理机制221 6.3异常类的层次结构222 6.3.1运行时异常222 6.3.2检查型异常223 6.3.3自定义异常224 6.4捕获和处理异常224 6.5抛出异常228 6.6try-catch-finally语句的嵌套231 6.7用户自定义异常...

    java线程池概念.txt

    只有当阻塞队列满了,如果还有任务添加到线程池的话,会尝试new 一个Thread的进行救急处理,立马执行对应的runnable任务;如果继续添加任务到线程池,且线程池中的线程数已经达到了maximumPoolSize,那么线程就会就...

    Java™ Servlet 规范.

    2.2.1 关于 Single Thread Model..............................................................................................................16 2.3 Servlet 生命周期 .......................................

    java核心知识点整理.pdf

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    bascomtask:轻量级并行Java任务

    Java中有许多方法可以并行化较大粒度的工作单元,这些方法通常涉及与服务或数据存储之类的外部源进行联系。 还涉及复杂性,这些复杂性会Swift使此类代码难以管理和更改。 面向对象的机制可以大大简化此过程:每个...

    第7章-JUC多线程v1.1.pdf

    4.如果线程池满了, 按照拒绝策略对任务进行处理 JDK中提供了一个线程池工厂: Executors ,很多工厂方法, 可以创建多种线程池 1.单一线程池 ExecutorService newSingleThreadExecutor = Executors....

    JAVA核心知识点整理(有效)

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    Spring-Reference_zh_CN(Spring中文参考手册)

    在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select标签 13.9.8...

    Spring中文帮助文档

    在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. checkboxes标签 13.9.6. radiobutton标签 13.9.7. radiobuttons...

    Android C++高级编程:使用NDK_Onur Cinar, 于红PDF电子书下载 带书签目录 完整版

    4.8 从原生代码中调用Java 108 4.8.1 异步通信 108 4.8.2 启用Directors 109 4.8.3 启用RTTI 109 4.8.4 重写回调方法 109 4.8.5 更新HelloJni Activity 110 4.9 小结 110 第5章 日志、调试及故障处理 111 ...

    Spring 2.0 开发参考手册

    在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select...

    Spring API

    在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. checkboxes标签 13.9.6. radiobutton标签 13.9.7. radiobuttons...

    spring chm文档

    在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select...

Global site tag (gtag.js) - Google Analytics