六、线程的异常处理
默认处理
线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),也就是说各个线程需要自己把自己的checked exception处理掉。
那么未捕获的异常哪里去了呢?
实际上一个异常被抛出后,如果没有被捕获处理,则会一直向上抛。异常一旦被Thread.run() 抛出后,就不能在程序中对异常进行捕获,最终只能由JVM捕获。
那JVM又是怎么处理线程中抛出的异常?
实际上JVM是调用Thread 的 dispatchUncaughtException方法:
/**
* 向 handler 分派未捕获的异常。 该方法仅由JVM调用。
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
/**
* Returns the handler invoked when this thread abruptly terminates
* due to an uncaught exception. If this thread has not had an
* uncaught exception handler explicitly set then this thread's
* <tt>ThreadGroup</tt> object is returned, unless this thread
* has terminated, in which case <tt>null</tt> is returned.
* @since 1.5
* @return the uncaught exception handler for this thread
*/
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
UncaughtExceptionHandler必须显示的设置,否则默认为null。若为null,则使用线程默认的handler,即该线程所属的ThreadGroup。ThreadGroup自身就是一个handler,查看ThreadGroup的源码就可以发现,ThreadGroup实现了Thread.UncaughtExceptionHandler接口,并实现了默认的处理方法。默认的未捕获异常处理器处理时,会调用 System.err 进行输出,也就是直接打印到控制台了。
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) { // 父级优先处理
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) { // 没有配置handler时的处理方式
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
自定义异常处理:
Thread提供了一个setUncaughtExceptionHandler(), 我们只需要将自定义未捕获的异常处理器作为参数传入进去就可以了。
/**
* @describe 自定义异常处理
* @author Li DongWei
*/
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread th1 = new Thread(myThread);
th1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获线程抛出的异常!");
}
});
th1.start();
}
}
class MyThread implements Runnable{
private int i = 10;
@Override
public void run() {
//发生异常
int i = 1 / 0;
}
}
控制台输出

七、死锁的解决方案
什么是死锁
两个或多个线程互相持有对方需要的锁而导致这些线程全部处于永久阻塞状态。如:线程A持有对象1的锁,等待对象2的锁;线程B持有对象2的锁,等待对象1的锁。
发生死锁的四个必要条件
互斥:对于访问某些公共资源的线程需实现线程同步,即不能同时访问。
不剥夺:未使用完不可强行剥夺
请求和保持:进程至少持有一个资源同时要请求新的资源。
环路等待:存在一个线程-资源环形链。在进程集合{p0,p1...pn}中,p0正在等待p1占用的资源,p1等待p2占用的资源...pn等待p0占用的资源。
我们强调所有四个条件必须同时成立才会出现死锁。环路等待条件意味着请求和保持条件,这样四个条件并不完全独立。
解决死锁的三种方案:
1.资源排序
2.加锁时限
如果一个线程没有在指定的时间期限内获取到锁,则结束当前线程并释放掉已获得的锁。终止线程的方法:stop()会释放掉锁但易导致数据不一致。suspend()终止线程但不会释放掉锁。
3.死锁检测