等待另外的线程以及捕获异常的方式

280 阅读4分钟

回顾上文:我们知道

  1. 我们可以设置线程为后台线程,通过setDaemon()这个方法,后台线程有一个特征,就是程序结束时会直接杀死所有的后台线程,而程序中如果含义非后台线程,程序就无法关闭。
  2. 单独设置线程的属性会造成大量重复的代码,可以使用线程工厂去做一些线程通用的配置事情。一个类通过实现ThreadFactory接口就表示该类是一个线程工厂类,通过newThread返回线程。

本文内容

  1. 通过继承Thread类,实现线程与任务的相结合。
  2. 学习使用线程对象的join()的用法。
  3. 打断一个线程,用interrupt去设置一个标志,并非直接关闭线程。
  4. 如何捕获在线程中抛出的异常。

继承Thread类

这种并不是定义一个任务,而是将线程和任务做结合,适合一些单一线程工作的场景。一个类直接继承Thread类,重写run()方法即可。

public class Demo extends Thread{
    @Override
    public void run() {
        System.out.println("你好");
    }

    public Demo(){
        start();
    }
}

该构造方法中直接调用了start()方法,表示new Demo()之后即会开启线程执行run()方法。

线程中加入线程

这种加入线程比较特殊,它可以使执行的方式变成顺序执行。比如A线程中调用了B线程的join方法,则A线程会等待B线程执行完毕后,再继续执行。

想使用该join()方法,要理解一点,该方法是线程对象调用的,所以我们需要在该线程中传入其他线程的对象。

我们看一个示例,在Main函数控制的线程中,将Demo线程加入(join)进来,那么主线程的 System.out.println("nihao");就需要等待Demo执行完毕之后再执行,即使Demo中调用了sleep,主线程依然要等待。

public static void main(String[] args) {
    try {
        new Demo(new Thrad1()).join();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("nihao");
}

Interrupt的一些理解

join方法可以让一个线程等待另外一个线程,如果想打断这个join呢,或者换句话说,如何让一个线程停止呢。

历史

suspend()stop()方法作为原先的方法,已经在文档中被标注为过时suspend容易导致死锁;stop不安全。所以知道存在即可。

interrupt

作为一种新的结束线程(Thread)的解决方案,但是注意,thread对象调用interrupt方法,并不会将该线程结束掉,就像这样子。

// main
Thread thread = new Thread(new Runnable..);
thread.start();

thread.interrupt();

main函数中调用threadstart方法正常开启的一个线程,会去执行runnable中的run方法。接着我们再去执行thread.interrupt();会发现thread线程并没有被打断,会依然正常将run方法执行完毕。

原因:

interrupt并非直接将线程杀死(结束),而是给这个线程赋予一个中断状态。所以我们可以通过在线程中通过isInterrupted查看当前线程是否被终断,如果中断则退出业务逻辑。

捕获线程的异常

在线程任务执行run时,如果用throw抛出的异常,你无法在run方法外捕获这种异常,它只会输出在控制台中。

这个Thread1中有个除0的错误,用throw抛出了。

static class Thread1 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println(1/0);
        }catch (Exception e){
            throw e;
        }
    }
}

主线程通过start执行该线程,用try...catch尝试捕获异常

Thread1 thread1 = new Thread1();
try {
    thread1.start();
}catch (Exception e){
    System.out.println("捕获到异常" + e);
}

执行后发现并未捕获到异常。

Thread.UncaughtExceptionHandler

该接口是专门用来捕获线程中抛出异常的,使用起来很简单:

  1. 实现该接口
  2. 重写uncaughtException方法

示例:

public class UncaughtExceptionHandleUtils implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("线程: " + t + " 异常原因: " + e);
    }
}

配置该异常处理类

我们已经定义好异常捕获器了,现在需要将它配置在相应的线程上。归根到底还是在线程对象上设置该异常捕获器,有三种设置方式:

  1. 线程本身设置
  2. 使用线程工厂设置
  3. 默认捕获器

线程本身设置

很简单,该线程对象调用setUncaughtExceptionHandler即可,将异常捕获器作为参数传入即可。

Thread1 thread1 = new Thread1();
thread1.setUncaughtExceptionHandler(new UncaughtExceptionHandleUtils());
thread1.start();

使用线程工厂

道理和上个方法一样,只是线程工厂可以重复使用,做统一的操作,如果你想创建的每个线程都有这个异常捕获器。线程工厂实现ThreadFactory接口即可。

默认捕获器 下例,在main函数中通过Thread.setDefaultUncaughtExceptionHandler设置了默认的异常捕获器。

public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandleUtils());
        Thread1 thread1 = new Thread1();
        thread1.start();
}

默认捕获器作为异常捕获的最后一步,如果没有指定uncaughtException,就会用DefaultUncaughtExceptionHandler