【Java并发学习】之详解线程的点滴(2)
前言
在前面一个小节中,我们学习了线程的属性如,ID、名字、优先级、状态的获取以及设置(如果可以操作)以及守护线程的概念和将线程设置为守护线程的方法,接下来这个小节,我们来学习多线程中的异常处理以及停止一个线程常用方法
异常处理
异常处理是一个非常重要的内容,当系统出现可能出现某些问题的时候,我们可以借助异常机制这个强有力的工具来辅助,从而可以在可能出现问题的时候,决定是否停止运行并且给予用户合理的提示或者忽略该问题,而不是直接就停止程序的运行
由于多线程的异步性特点,在多线程中处理异常与非多线程中有一些不同。在非多线程的编码过程中,我们会根据异常出现的情况进行处理,有的是直接处理即可,而有的异常,我们则会将其抛出,交由上层进行处理。而由于线程的异步性,是无法将在子线程出现的异常传递至父线程中,换句话说,子线程中出现的异常只能在子线程中进行处理,而不能将其交给父线程进行统一处理,所以,在多线程中出现的异常,有其独特的处理方式
// 检查性异常
class ExceptionTask implements Runnable{
@Override
public void run() {
File file = new File("c:/data.txt");
try {
BufferedReader reader =
new BufferedReader(
new FileReader(file));
} catch (FileNotFoundException e) {
System.out.println("file not found ");
return;
}
System.out.println("Thread finished");
}
}
// 非检查性异常
class ExceptionTask implements Runnable{
@Override
public void run() {
// 可能会出现异常
try {
int result = Integer.parseInt("123F");
System.out.println("Result is " + result);
}catch (NumberFormatException e){
System.out.println("not a number");
}
}
}
对于非检查性异常,Java中还提供了另外一种处理方式
// 实现Thread.UncaughtExceptionHandler,定制对应的异常处理器
class ExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e instanceof NumberFormatException){
System.out.println("could not format number ");
}
}
}
public void test(){
Thread thread = new Thread(exceptionTask);
// 设置线程的异常处理器,需要注意的是,必须在线程启动之前进行设置
thread.setUncaughtExceptionHandler(new ExceptionHandler());
thread.start();
}
上面的操作方式是为某一特定的线程设置非检查性异常处理器,除此之外,还可以为所有的线程设置默认的异常处理器
// 为所有线程设置默认的异常处理器
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
这三者的顺序分别为,try-catch、某一线程的异常处理器、线程的默认异常处理器,当出现异常时,JVM会坚持按照这个顺序坚持对应的异常处理器,如果找到合适的处理器,则将对应的异常交给它
停止线程
在某些情况下,除了线程执行完毕,正常结束外,我们还可能在线程执行过程中中断/结束该线程,根据线程的状态不同,结束某个线程也有不同的方式
线程中有一个特殊的状态值,如果某个线程执行线程的中断操作,则将该值设置为true,利用这个机制,可以用于判断是否应该让线程停止
public class InterruptedTest {
public static void main(String[] args) {
Runnable interruptedTask = new InterruptedTask();
Thread thread = new Thread(interruptedTask);
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); // 中断线程
}
}
class InterruptedTask implements Runnable{
@Override
public void run() {
while (true){
System.out.println("hello world");
// 检测是否已经中断,如果中断,则退出循环
if (Thread.currentThread().isInterrupted()){
break;
}
}
System.out.println("finish");
}
}
需要注意的是,在Thread中,还提供了一个静态的interrupted()
方法,该方法同样可以用于检查线程的中断状态是否已经被设置,但是该方法会清除线程的中断状态,也就是执行该方法后,如果线程的中断状态为true,则将其设置为false
上面中断线程的方法是一种比较好的方法,但是有一个缺点,由于需要进行状态的检测,所以只有当线程处于运行状态时,该状态值的改变才能被检测到,换句话说,如果此时线程处于BLOCKED
、WAITING
时,是无法中断线程的
所以,还需要有另外一种中断线程的方式,异常,当JVM检测到对应的状态位改变,并且该线程此时处于上述状态时,则利用异常机制,将中断的信息发送给对应的线程,这也正是我们在之前使用如,wait()
、sleep()
等方法的时候,需要捕获一个名为InterruptedException
的异常了
class InterruptedTask implements Runnable{
@Override
public void run() {
synchronized (this){
try {
wait(); // 等待
// TimeUnit.SECOND.sleep(1000);
} catch (InterruptedException e) {
System.out.println("wait up notify");
}
}
System.out.println("status: " + Thread.interrupted());
System.out.println("finish");
}
}
当在进行递归操作的时候,如果想要中断线程,则需要层层操作,则采用异常机制,则可以在检测到中断信号之后,直接抛出异常,退出线程
// 通过异常的方式来结束递归
class InterruptedTask implements Runnable{
@Override
public void run() {
try {
recursive(0);
} catch (InterruptedException e) {
System.out.println("finish");
}
System.out.println("status: " + Thread.interrupted());
}
void recursive(int cnt) throws InterruptedException {
System.out.println(cnt);
if (Thread.currentThread().isInterrupted()){
throw new InterruptedException();
}
TimeUnit.MICROSECONDS.sleep(100);
recursive(cnt + 1);
}
}
需要注意的是,并非所有的阻塞都是可中断的,有一些阻塞,如Socket等的是不可中断的,这也就意味着,即使使用异常机制,也无法中断这种类型的阻塞
总结
本小节我们主要学习了多线程中的异常处理,包括检查性异常的处理以及非检查性异常的处理,然后学习了中断线程的方式,包括检测中断位,以及异常机制,其中检测中断位的操作在线程被挂起或者阻塞的时候是无法进行操作的,所以当这种情况发生时,可以采用抛异常的方式来结束运行