【Java】之 线程中断

一个在行为良好的软件与勉强运行的软件之间的最主要区别就是:
行为良好的软件能很完善地处理失败、关闭和取消


一、简介


Java没有提供任何机制来安全地终结线程。

Thread.stop** 和suspend`等方法提供了终结,但由于存在一些严重的缺陷,因此应该避免使用

中断Interruption:这是一中协作机制,能够使一个线程终止另一个线程的当前工作。


(1)问题


1. 为什么需要线程中断?

用于处理失败、关闭和取消等过程。

  1. Java中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占方式来停止任务。

即,采用协作式的机制(interruption),使请求取消的任务和代码都遵循一种协商好的协议。
通过推迟中断请求的处理,开发人员能指定更灵活的中断策略,从而使应用程序在响应性和健壮性之间实现合理的平衡。


2. 什么是线程中断?

中断:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。

线程在调用Object classwait() join() sleep()方法时被阻塞,那么interrupt会生效,该线程的中断状态将被清除,抛出InterruptedException异常

如果目标线程是被 I/O 或者 NIO中的Channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。

如果以上条件都不满足,则会设置此线程的中断状态

每个线程都有一个boolean类型的中断状态。
当中断线程时,这个线程的中断状态将被设置为 true。


3. 有什么中断策略?

当发现中断请求时,应该做哪些工作(如果需要的话),哪些工作单元对于中断来说是原子操作,以及以多快的速度来响应中断。

最合理的中断策略:某种形式的线程级(Thread-Level)取消操作或服务级(Service-Level)

取消操作:尽快退出,在必要时进行清理,通知某个所有者该线程已退出。

区分 任务线程

  • 任务:不会在其自己的拥有的线程中执行,而是在某个服务(例如线程池)拥有的线程中执行。

对于非线程所有者的代码来说(例如,对于线程池而言,任何在线程池实现以外的代码),应该小心地保存中断状态,这样拥有线程的代码才能对中断作出响应,即使 “非所有者” 代码也可以作出响应。

比如:
当你为一户人家打扫房屋时,即使主人不在,也不应该把在这段时间内收到的邮件扔掉,而应该把邮件收起来,等主人回来以后再交给他们处理


4. 如何响应中断?

当调用可中断的阻塞函数时,例如Thread.sleepBlockingQueue.put

有两种实用策略可用于处理 InterruptedException

  1. 传递异常(可能在执行某个特定于任务的清除操作之后),从而使你的方法也可以成为可中断的阻塞方法。

  2. 恢复中断状态,从而使调用栈中的上层代码能够对其进行处理。

try {
    Thread.sleep(10000);
    
} catch (InterruptedException e) {

    // 再次调用
    Thread.interrupt();
}


二、取消与中断


通常,中断是实现取消的最合理方式。

(1)取消 Thread.stop

public class InterruptedThread {

    static class MyThread extends Thread {

        private int i = 0;
        private int j = 0;

        public void run() {

            synchronized (this) {

                ++i;
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ++j;
            }
        }

        public void print() {

            System.out.println("i = " + i + "  j = " + j);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        MyThread myThread = new MyThread();

        myThread.start();

        Thread.sleep(1000);

        myThread.stop(); // 不建议这样做

        while (myThread.isAlive()) {

        }

        myThread.print();
    }
}

结果如下:
i = 1 j = 0

说明:当调用Thread.stop时候,线程就直接退出了,没有执行 ++j


(2)中断 Thread.interrupte

public class InterruptedThread {

    static class MyThread extends Thread {

        private int i = 0;
        private int j = 0;

        public void run() {

            synchronized (this) {

                ++i;
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ++j;
            }
        }

        public void print() {

            System.out.println("i = " + i + "  j = " + j);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        MyThread myThread = new MyThread();

        myThread.start();

        Thread.sleep(1000);

        myThread.interrupt();

        while (myThread.isAlive()) {

        }

        myThread.print();
    }
}

结果:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.donaldy.thread.interrupt.InterruptedThread$MyThread.run(InterruptedThread.java:16)
i = 1  j = 1


三、中断几种方式


(1)取消标志 volatile

private volatile boolean cancelled;

while(!cancelled) {
    // do something
}

(2)Thread.interrupt()

Thread.interrupt();

(3)使用 Future 来取消

Future<?> task = taskExec.submit(r);

task.cancel(true);


四、阻塞队列处理


背景:
生产者线程生成素数,并将它们放入一个阻塞队列。

如果生产者的速度超过了消费者的处理速度,队列将被填满,put方法也会阻塞。

这时候:
当生产者在put方法中阻塞时,如果消费者希望取消生产者任务,那么将发生什么情况?


(1)使用标识状态 volatile

它可以调用 cancel 方法来设置 cancelled标识,
但此时生产者却永远不能检查这个标志,
因为它无法从阻塞的 put方法中恢复过来(因为队列一直是满的,没有唤醒它)

这就导致结果:
任务可能永远不会检查取消标志,因此永远不会结束。

public class BrokenPrimeProducer extends Thread {

    private final BlockingQueue<BigInteger> queue;

    private volatile boolean canclled = false;

    BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!canclled) {
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException consumed) {}
    }

    public void cancel() {
        this.canclled = true;
    }
}

(2)使用中断

每次迭代循环中,有两个位置可以检测出中断:

  1. 在阻塞的 put方法调用中
    queue.put()
  2. 循环开始处查询中断状态
    (!Thread.currentThread().isInterrupted())
public class PrimeProducer extends Thread {

    private final BlockingQueue<BigInteger> queue;

    PrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted()) {
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException consumed) {
            // 允许线程退出
        }
    }
}
发布了404 篇原创文章 · 获赞 270 · 访问量 42万+

猜你喜欢

转载自blog.csdn.net/fanfan4569/article/details/101992757