Web全栈~33.线程的中断

Web全栈~33.线程的中断

上一期

取消/关闭的场景

       一般线程通过start方法启动之后,就会开始执行run方法,run方法运行结束后线程退出。但是呢,又有一些场景可能会需要我们自己让它提前退出。

       很多线程的运行模式是死循环,就说上一期的生产者/消费者模式中,消费者主体就是一个死循环,它不停地从队列中接受任务,执行任务,在停止程序时,我们需要一种“优雅”的方法以关闭该线程。

       再比如说下载。当用户想要远程从服务器中下载文件的时候,很多时候都会需要一个取消下载的功能等等等...

       在Java中,停止一个线程的主要机制是中断,中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。

public boolean isInterrupted()
public void interrupt()
public static boolean interrupted()

       isInterrupted()和interrupt()是实例方法,调用它们需要通过线程对象;interrupted()是静态方法,实际会调用Thread.currentThread()操作当前线程。

       每个线程都有一个标志位,表示该线程是否被中断了。

       isInterrupted:返回对应线程的中断标志位是否为true。

       interrupted:返回当前线程的中断标志位是否为true,但它还有一个重要的副作用,就是清空中断标志位,也就是说,连续两次调用interrupted(),第一次返回的结果为true,第二次一般就是false(除非同时又发生了一次中断)。

       interrupt:表示中断对应的线程。

线程的状态

       interrupt()对线程的影响与线程的状态和在进行的IO操作有关。而IO操作的影响和具体IO以及操作系统有关。

       RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度。

       WAITING/TIMED_WAITING:线程在等待某个条件或超时。

       BLOCKED:线程在等待锁,试图进入同步块。

       NEW/TERMINATED:线程还未启动或已结束。

RUNNABLE

       如果线程在运行中,且没有执行IO操作,interrupt()只是会设置线程的中断标志位,没有任何其他作用。线程应该在运行过程中合适的位置检查中断标志位,比如,如果主体代码是一个循环,可以在循环开始处进行检查

    @Override
    public void run() {
     
        while(!Thread.currentThread().isInterrupted()){
     
            System.out.println("循环中...");
        }
        System.out.println("done");
    }

WAITING/TIMED_WAITING

       线程调用join/wait/sleep方法会进入WAITING或TIMED_WAITING状态,在这些状态时,对线程对象调用interrupt()会使得该线程抛出InterruptedException。需要注意的是,抛出异常后,中断标志位会被清空,而不是被设置。

public class Test extends Thread{
     
    @Override
    public void run() {
     
        try{
     
            Thread.sleep(100);
        }catch (InterruptedException interruptedException){
     
            System.out.println(isInterrupted());
        }
    }

    public static void main(String[] args){
     
        Thread thread = new Test();
        thread.start();
        try{
     
            Thread.sleep(100);
        }catch (InterruptedException interruptedException){
     

        }
        thread.interrupt();
    }
}

       捕获到InterruptedException,通常表示希望结束该线程

       向上传递该异常,这使得该方法也变成了一个可中断的方法,需要调用者进行处理。

       有些情况,不能向上传递异常,比如Thread的run方法,它的声明是固定的,不能抛出任何受检异常,这时,应该捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,使得其他代码有办法知道它发生了中断。

BLOCKED

       如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正“中断”。

public class Test extends Thread{
     
    private static Object lock = new Object();
    private static class A extends Thread {
     
        @Override
        public void run() {
     
            synchronized (lock) {
     
                while (!Thread.currentThread().isInterrupted()) {
     
                    
                }
            }
            System.out.println("exit");
        }
    }
    public static void test() throws InterruptedException {
     
        synchronized (lock) {
     
            A a = new A();
            a.start();
            Thread.sleep(1000);
            a.interrupt();
            a.join();
        }
    }
    public static void main(String[] args) throws InterruptedException {
     
        test();
    }
}

       test方法在持有锁lock的情况下启动线程a,而线程a也去尝试获得锁lock,所以会进入锁等待队列,随后test调用线程a的interrupt方法并调用join等待线程线程a结束,线程a会结束吗?不会,interrupt方法只会设置线程的中断标志,而并不会使它从锁等待队列中出来。

       在使用synchronized关键字获取锁的过程中不响应中断请求,这是synchronized的局限性。如果这对程序是一个问题,应该使用显式锁。第16章会介绍显式锁Lock接口,它支持以响应中断的方式获取锁。

NEW/TERMINATED

       如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。

总结

       interrupt方法不一定会真正“中断”线程,它只是一种协作机制,如果不明白线程在做什么,不应该贸然地调用线程的interrupt方法,以为这样就能取消线程。对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,外部调用者应该调用这些方法而不是直接调用interrupt。

       Java并发库的一些代码就提供了单独的取消/关闭方法,比如,Future接口提供了如下方法以取消任务,再如,ExecutorService提供了如下两个关闭方法。

boolean cancel(boolean mayInterruptIfRunning);
void shutdown();
List<Runnable> shutdownNow();

       Future和ExecutorService的API文档对这些方法都进行了详细说明

你可能感兴趣的:(web,java,多线程,并发编程)