Java 线程的终止-interrupt

Java线程的终止——interrupt

取消/关闭的场景

我们知道,通过线程的start方法启动一个线程后,线程开始执行run方法,run方法运行结束后线程退出,那为什么还需要结束一个线程呢?有多种情况,比如说: 
很多线程的运行模式是死循环,比如在生产者/消费者模式中,消费者主体就是一个死循环,它不停的从队列中接受任务,执行任务,在停止程序时,我们需要一种”优雅”的方法以关闭该线程。 
在一些图形用户界面程序中,线程是用户启动的,完成一些任务,比如从远程服务器上下载一个文件,在下载过程中,用户可能会希望取消该任务。 
在一些场景中,比如从第三方服务器查询一个结果,我们希望在限定的时间内得到结果,如果得不到,我们会希望取消该任务。 
有时,我们会启动多个线程做同一件事,比如类似抢火车票,我们可能会让多个好友帮忙从多个渠道买火车票,只要有一个渠道买到了,我们会通知取消其他渠道。

取消/关闭的机制 
Java的Thread类定义了如下方法: 
public final void stop()

这个方法看上去就可以停止线程,但这个方法被标记为了过时,简单的说,我们不应该使用它,可以忽略它。

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

Thread类定义了如下关于中断的方法: 
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操作有关,我们先主要考虑线程的状态: 
RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度 
WAITING/TIMED_WAITING:线程在等待某个条件或超时 
BLOCKED:线程在等待锁,试图进入同步块 
NEW/TERMINATED:线程还未启动或已结束

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

public class InterruptRunnableDemo extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            // ... 单次循环代码
        }
        System.out.println("done ");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new InterruptRunnableDemo();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

WAITING/TIMED_WAITING 
线程执行如下方法会进入WAITING状态: 
public final void join() throws InterruptedException 
public final void wait() throws InterruptedException

执行如下方法会进入TIMED_WAITING状态: 
public final native void wait(long timeout) throws InterruptedException; 
public static native void sleep(long millis) throws InterruptedException; 
public final synchronized void join(long millis) throws InterruptedException

在这些状态时,对线程对象调用interrupt()会使得该线程抛出InterruptedException,需要注意的是,抛出异常后,中断标志位会被清空,而不是被设置。比如说,执行如下代码:

Thread t = new Thread (){
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println(isInterrupted());
        }
    }
};
t.start();
try {
    Thread.sleep(100);
} catch (InterruptedException e) {
}
t.interrupt();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

程序的输出为false。

InterruptedException是一个受检异常,线程必须进行处理。我们在异常处理中介绍过,处理异常的基本思路是,如果你知道怎么处理,就进行处理,如果不知道,就应该向上传递,通常情况下,你不应该做的是,捕获异常然后忽略。

捕获到InterruptedException,通常表示希望结束该线程,线程大概有两种处理方式: 
向上传递该异常,这使得该方法也变成了一个可中断的方法,需要调用者进行处理。 
有些情况,不能向上传递异常,比如Thread的run方法,它的声明是固定的,不能抛出任何受检异常,这时,应该捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,使得其他代码有办法知道它发生了中断。

第一种方式的示例代码如下:

public void interruptibleMethod() throws InterruptedException{
    // ... 包含wait, join 或 sleep 方法
    Thread.sleep(1000);
}
  • 1
  • 2
  • 3
  • 4

第二种方式的示例代码如下:

public class InterruptWaitingDemo extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 模拟任务代码
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // ... 清理操作
                // 重设中断标志位
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(isInterrupted());
    }

    public static void main(String[] args) {
        InterruptWaitingDemo thread = new InterruptWaitingDemo();
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        thread.interrupt();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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

public class InterruptSynchronizedDemo {
    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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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

我们稍微修改下代码,去掉test方法中的最后一行a.join,即变为:

public static void test() throws InterruptedException {
    synchronized (lock) {
        A a = new A();
        a.start();
        Thread.sleep(1000);

        a.interrupt();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这时,程序就会退出。为什么呢?因为主线程不再等待线程a结束,释放锁lock后,线程a会获得锁,然后检测到发生了中断,所以会退出。

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

NEW/TERMINATE 
如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。比如说,以下代码的输出都是false。

public class InterruptNotAliveDemo {
    private static class A extends Thread {
        @Override
        public void run() {
        }
    }

    public static void test() throws InterruptedException {
        A a = new A();
        a.interrupt();
        System.out.println(a.isInterrupted());

        a.start();
        Thread.sleep(100);
        a.interrupt();
        System.out.println(a.isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        test();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

IO操作 
如果线程在等待IO操作,尤其是网络IO,则会有一些特殊的处理,我们没有介绍过网络,这里只是简单介绍下。 
如果IO通道是可中断的,即实现了InterruptibleChannel接口,则线程的中断标志位会被设置,同时,线程会收到异常ClosedByInterruptException。 
如果线程阻塞于Selector调用,则线程的中断标志位会被设置,同时,阻塞的调用会立即返回。

我们重点介绍另一种情况,InputStream的read调用,该操作是不可中断的,如果流中没有数据,read会阻塞 (但线程状态依然是RUNNABLE),且不响应interrupt(),与synchronized类似,调用interrupt()只会设置线程的中断标志,而不会真正”中断”它,我们看段代码。

public class InterruptReadDemo {
    private static class A extends Thread {
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                try {
                    System.out.println(System.in.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("exit");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        A t = new A();
        t.start();
        Thread.sleep(100);

        t.interrupt();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

线程t启动后调用System.in.read()从标准输入读入一个字符,不要输入任何字符,我们会看到,调用interrupt()不会中断read(),线程会一直运行。

不过,有一个办法可以中断read()调用,那就是调用流的close方法,我们将代码改为:

public class InterruptReadDemo {
    private static class A extends Thread {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println(System.in.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("exit");
        }

        public void cancel() {
            try {
                System.in.close();
            } catch (IOException e) {
            }
            interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        A t = new A();
        t.start();
        Thread.sleep(100);

        t.cancel();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

我们给线程定义了一个cancel方法,在该方法中,调用了流的close方法,同时调用了interrupt方法,这次,程序会输出:

-1
exit
  • 1
  • 2

也就是说,调用close方法后,read方法会返回,返回值为-1,表示流结束。

如何正确地取消/关闭线程

有三种方法可以使终止线程。

1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 

2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。 

3.  使用interrupt方法中断线程。

以上,我们可以看出,interrupt方法不一定会真正”中断”线程,它只是一种协作机制,如果不明白线程在做什么,不应该贸然的调用线程的interrupt方法,以为这样就能取消线程。

对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,类似于InterruptReadDemo中演示的cancel方法,外部调用者应该调用这些方法而不是直接调用interrupt。

Java并发库的一些代码就提供了单独的取消/关闭方法,比如说,Future接口提供了如下方法以取消任务: 
boolean cancel(boolean mayInterruptIfRunning);

再比如,ExecutorService提供了如下两个关闭方法: 
void shutdown(); 
List shutdownNow();

Future和ExecutorService的API文档对这些方法都进行了详细说明,这是我们应该学习的方式。关于这两个接口,我们后续章节介绍。

小结 
本节主要介绍了在Java中如何取消/关闭线程,主要依赖的技术是中断,但它是一种协作机制,不会强迫终止线程,我们介绍了线程在不同状态和IO操作时对中断的反应,作为线程的实现者,应该提供明确的取消/关闭方法,并用文档描述清楚其行为,作为线程的调用者,应该使用其取消/关闭方法,而不是贸然调用interrupt

原文地址:https://www.cnblogs.com/yixianyixian/p/8401633.html

时间: 2024-08-03 02:01:32

Java 线程的终止-interrupt的相关文章

java 线程的终止与线程中断

关于线程终止: 1.一般来讲线程在执行完毕后就会进入死亡状态,那该线程自然就终止了. 2.一些服务端的程序,可能在业务上需要,常驻系统.它本身是一个无穷的循环,用于提供服务.那对于这种线程我们该如何结束它呢. 一.线程的终止 在Thread类中JDK给我们提供了一个终止线程的方法stop(); 该方法一经调用就会立即终止该线程,并立即释放对象锁.如果当一个线程执行一半业务而调用了该方法,可能就会产生数据不一致问题. 数据一致性:同一时间点,你在节点A中获取到key1的值与在节点B中获取到key1

并发基础(九) java线程的终止与中断

1.简单了解一下:为何不赞成使用 Thread.stop.Thread.suspend 和 Thread.resume? ??suspend .resume.stop方法分别完成了线程的暂停.恢复.终止的工作.不建议使用原因:是因为这三个方法带来的副作用,如suspend( )方法,调用后,线程就会一直占用资源睡眠,直到调用resume( )恢复后,才可以运行.这样很容易引发死锁.同样,stop( )方法在终结一个线程时不会保证线程的资源正常释放,因此会导致程序可能工作在不确定的状态下. ??线

停止Java线程,小心interrupt()方法

程序是很简易的.然而,在编程人员面前,多线程呈现出了一组新的难题,如果没有被恰当的解决,将导致意外的行为以及细微的.难以发现的错误. 在本篇文章中,我们针对这些难题之一:如何中断一个正在运行的线程. 背景     中断(Interrupt)一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作.线程是死亡.还是等待新的任务或是继续运行至下一步,就取决于这个程序.虽然初次看来它可能显得简单,但是,你必须进行一些预警以实现期望的结果.你最好还是牢记以下的几点告诫. 首先,忘掉

java线程中的interrupt,isInterrupt,interrupted方法

在java的线程Thread类中有三个方法,比较容易混淆,在这里解释一下 (1)interrupt:置线程的中断状态 (2)isInterrupt:线程是否中断 (3)interrupted:返回线程的上次的中断状态,并清除中断状态 举个例子: [java] view plaincopy 用法: class MyThread extends Thread { ...... ...... public void run() { try { while(!Thread.currentThread()

Java线程基础知识(状态、共享与协作)

1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源 线程:CPU调度的最小单位,必须依赖进程而存在. 澄清并行和并发 并行:同一时刻,可以同时处理事情的能力 并发:与单位时间相关,在单位时间内可以处理事情的能力 高并发编程的意义.好处和注意事项 好处:充分利用cpu的资源.加快用户响应的时间,程序模

java线程中断

java线程中断[interrupt()函数] http://vikings825.iteye.com/blog/964644 停止Java线程,小心interrupt()方法  这篇写的是怎么  正确的 停止 一个线程 http://blog.csdn.net/wxwzy738/article/details/8516253 聊聊并发——生产者消费者模式 http://www.infoq.com/cn/articles/producers-and-consumers-mode/

用interrupt()中断Java线程

最近在学习Java线程相关的东西,和大家分享一下,有错误之处欢迎大家指正. 假如我们有一个任务如下,交给一个Java线程来执行,如何才能保证调用interrupt()来中断它呢? Java代码   class ATask implements Runnable{ private double d = 0.0; public void run() { //死循环执行打印"I am running!" 和做消耗时间的浮点计算 while (true) { System.out.printl

如何终止java线程

终止线程的三种方法 有三种方法可以使终止线程. 1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止. 2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend.resume一样,也可能发生不可预料的结果). 3.  使用interrupt方法中断线程. 1. 使用退出标志终止线程 当run方法执行完后,线程就会退出.但有时run方法是永远不会结束的.如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务.在这种情况下,一般是将这

Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理

相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理 以下是本文的目录大纲: 一.shutdown()  --  温柔的终止线程池 interruptIdleWorkers()  --  中断空闲worker tryTerminate()  --  尝试终止线程池 二.shutdown