JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

标签: java 多线程 uncaughtexceptionhandler | 发表时间:2015-12-28 19:29 | 作者:u013256816
出处:http://blog.csdn.net

当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境中,线程抛出的异常是不能用try....catch捕获的,这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。

首先来看一个示例:

package com.exception;

public class NoCaughtThread
{
	public static void main(String[] args)
	{
		try
		{
			Thread thread = new Thread(new Task());
			thread.start();
		}
		catch (Exception e)
		{
			System.out.println("==Exception: "+e.getMessage());
		}
	}
}

class Task implements Runnable
{
	@Override
	public void run()
	{
		System.out.println(3/2);
		System.out.println(3/0);
		System.out.println(3/1);
	}
}
运行结果:

1
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at com.exception.Task.run(NoCaughtThread.java:25)
	at java.lang.Thread.run(Unknown Source)
可以看到在多线程中通过try....catch试图捕获线程的异常是不可取的。

Thread的run方法是不抛出任何检查型异常的,但是它自身却可能因为一个异常而被中止,导致这个线程的终结。
首先介绍一下如何在线程池内部构建一个工作者线程,如果任务抛出了一个未检查异常,那么它将使线程终结,但会首先通知框架该现场已经终结。然后框架可能会用新的线程来代替这个工作线程,也可能不会,因为线程池正在关闭,或者当前已有足够多的线程能满足需要。当编写一个向线程池提交任务的工作者类线程类时,或者调用不可信的外部代码时(例如动态加载的插件),使用这些方法中的某一种可以避免某个编写得糟糕的任务或插件不会影响调用它的整个线程。

package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class InitiativeCaught
{
	public void threadDeal(Runnable r, Throwable t)
	{
		System.out.println("==Exception: "+t.getMessage());
	}
	
	class InitialtiveThread implements Runnable
	{
		@Override
		public void run()
		{
			Throwable thrown = null;
			try
			{
				System.out.println(3/2);
				System.out.println(3/0);
				System.out.println(3/1);
			}
			catch(Throwable e)
			{
				thrown =e;
			}
			finally{
				threadDeal(this,thrown);
			}
		}
	}
	
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(new InitiativeCaught().new InitialtiveThread());
		exec.shutdown();
	}

}
运行结果:

1
==Exception: / by zero
上面介绍了一种主动方法来解决未检测异常。在Thread ApI中同样提供了UncaughtExceptionHandle,它能检测出某个由于未捕获的异常而终结的情况。这两种方法是互补的,通过将二者结合在一起,就能有效地防止线程泄露问题。

如下:

package com.exception;

import java.lang.Thread.UncaughtExceptionHandler;

public class WitchCaughtThread
{
	public static void main(String args[])
	{
		Thread thread = new Thread(new Task());
		thread.setUncaughtExceptionHandler(new ExceptionHandler());
		thread.start();
	}
}

class ExceptionHandler implements UncaughtExceptionHandler
{
	@Override
	public void uncaughtException(Thread t, Throwable e)
	{
		System.out.println("==Exception: "+e.getMessage());
	}
}
运行结果:

1
==Exception: / by zero
同样可以为所有的Thread设置一个默认的UncaughtExceptionHandler,通过调用Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法,这是Thread的一个static方法。

如下:

package com.exception;

import java.lang.Thread.UncaughtExceptionHandler;

public class WitchCaughtThread
{
	public static void main(String args[])
	{
		Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
		Thread thread = new Thread(new Task());
		thread.start();
	}
}

class ExceptionHandler implements UncaughtExceptionHandler
{
	@Override
	public void uncaughtException(Thread t, Throwable e)
	{
		System.out.println("==Exception: "+e.getMessage());
	}
}

运行结果:

1
==Exception: / by zero

如果采用线程池通过execute的方法去捕获异常,先看下面的例子:

public class ExecuteCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		Thread thread = new Thread(new Task());
		thread.setUncaughtExceptionHandler(new ExceptionHandler());
		exec.execute(thread);
		exec.shutdown();
	}
}
ExceptionHandler可参考上面的例子,运行结果:

1
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.exception.Task.run(NoCaughtThread.java:25)
	at java.lang.Thread.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
可以看到并未捕获到异常。

这时需要将异常的捕获封装到Runnable或者Callable中,如下所示:

package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecuteCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(new ThreadPoolTask());
		exec.shutdown();
	}
}

class ThreadPoolTask implements Runnable
{
	@Override
	public void run()
	{
		Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());
		System.out.println(3/2);
		System.out.println(3/0);
		System.out.println(3/1);
	}
}

运行结果:

1
==Exception: / by zero
只有通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler,而通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。
下面两个例子:

package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SubmitCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.submit(new Task());
		exec.shutdown();
	}
}
package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SubmitCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.submit(new ThreadPoolTask());
		exec.shutdown();
	}
}
运行结果都是:

1
这样可以证实我的观点。接下来通过这个例子可以看到捕获的异常:
package com.exception;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SubmitCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		Future<?> future = exec.submit(new Task());
		exec.shutdown();
		try
		{
			future.get();
		}
		catch (InterruptedException | ExecutionException e)
		{
			System.out.println("==Exception: "+e.getMessage());
		}
	}
}
运行结果:

1
==Exception: java.lang.ArithmeticException: / by zero
希望我整理的这些能够给各位有所帮助。

作者:u013256816 发表于2015/12/28 11:29:44 原文链接
阅读:137 评论:0 查看评论

相关 [java 多线程 uncaughtexceptionhandler] 推荐:

JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

- - CSDN博客综合推荐文章
当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境中,线程抛出的异常是不能用try....catch捕获的,这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等. 可以看到在多线程中通过try....catch试图捕获线程的异常是不可取的.

Java Thread多线程

- - CSDN博客推荐文章
Java Thread多线程. Java 多线程例子1 小例子. super("zhuyong");//设置线程的名字,默认为“TestThread”. Java 多线程例子2 前台线程(用户线程) 后台线程(守护线程 ). 1,setDaemon(true)后就是后台线程(守护线程 ),反之就是前台线程(用户线程).

Java多线程之synchronized

- - CSDN博客推荐文章
这里通过三个测试类阐述了synchronized应用的不同场景. 首先是最基本的synchronized Method的使用.  * @see 概述:Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor) .  * @see 说明:当synchronized关键字修饰一个方法时,则该方法为同步方法 .

java多线程总结

- - Java - 编程语言 - ITeye博客
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 对于直接继承Thread的类来说,代码大致框架是:. class 类名 extends Thread{. * @author Rollen-Holt 继承Thread类,直接调用run方法.             System.out.println(name + "运行     " + i);.

Java多线程学习

- - CSDN博客编程语言推荐文章
  线程是一种轻量级的进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单. 即多个线程可以同时执行,就像有多条流水线一样,可以同时进行工作,是并发执行的.   程序是由进程组成的,进程是由线程组成的.

Java多线程(二)同步

- - CSDN博客编程语言推荐文章
如果你的java基础较弱,或者不大了解java多线程请先看这篇文章 java多线程(一)线程定义、状态和属性. 同步一直是java多线程的难点,在我们做android开发时也很少应用,但这并不是我们不熟悉同步的理由. 希望这篇文章能使更多的人能够了解并且应用java的同步. 在多线程的应用中,两个或者两个以上的线程需要共享对同一个数据的存取.

[Java] Java 多线程案例分析

- - V2EX
现要从 hbase中导出 2016 年整年的,大约 10w只股票行情数据,数据总量约 100t. 汇总到 hdfs中供需求方使用. 已知数据量规模大概是 100t,那么单台机器处理肯定不是不行的,先不说大多数磁盘都没这么大,即便磁盘有这么大,单台机器处理对于内存和 cpu 要求也很高,所以我们将问题一般化,使用数量有限的低配机器.

Java 多线程内存模型

- - ITeye博客
Java 多线程内存模型.       Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果. 在此之前,主流程序怨言(如C/C++等)直接使用物理硬件(或者说操作系统的内存模型),因此,会由于不同的平台上内存模型差异,导致程序在一套平台上并发完成正常,而在另一套平台上并发访问却经常出错,因此经常需要针对不同的平台来编写程序.

Java多线程之wait()和notify()

- - CSDN博客推荐文章
直接看测试代码吧,细节之处,详见注释.  * Java多线程之wait()和notify()的妙用 .  * @see 问题:同时启动两个线程和同时启动四个线程,控制台打印结果是不同的 .  * @see      同时启动两个线程时,控制台会很规律的输出1010101010101010 .  * @see      同时启动四个线程时,控制台起初会规律的输出10101010,一旦某一刻输出一个负数,那么后面的输出就会"一错再错" .

java多线程设计wait/notify机制

- - CSDN博客推荐文章
  当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait() , 放弃对象锁..   之后在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:.   # 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内.