多线程(3) 多线程之线程的停止和中断

一。使用interrupt通知而不是强制

线程停止的场景:

一般我们都会让线程运行到结束,但有时比如用户取消了操作,服务需要被快速关闭,服务超时或者出错等。就需要停止线程。

协作机制:

线程的启动很容易但是停止很困难,因为JAVA并没有提供任何机制来安全的终止线程。但它提供了中断(interrupt),一种协作机制,由一个线程来终止另一个线程的当前工作。

thread.interrupt() : 设置中断标志为设为True,仅此而已。程序可以自己选择是否判断中断标志并做处理。

这种协作的方法是很必要的,我们很少希望线程能够立即停止,因为立即停止会使共享的数据结构处于不一致的状态。相反协作的方式:他们会首先清除当前的执行操作,然后再停止。

因为任务本身比发出取消的代码更清楚如何执行清除工作。

良好的系统和勉强运行的系统差别:就是在如何完善处理失败,取消,关闭的场景.

  • 1.1 普通中断,run方法中没有sleep,wait时停止线程
public class RightWayStopThreadWithoutSleep implements Runnable {

    @Override
    public void run() {
        int num = 0;
        // 判断是否中断
        while(!Thread.currentThread().isInterrupted() && num < Integer.MAX_VALUE/2) {
            if(num%10000 == 0) {
                System.out.println(num);
            }
            num ++;
        }
        System.out.println("打印结束");
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        Thread.sleep(500);
     // 中断子线程 thread.interrupt(); } }
  • 1.2  阻塞状态即sleep时被中断

sleep方法会判断中断标志,抛出异常,同时清楚中断标志

public class RightWayStopThreadWithSleep {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () ->{
            int num =0;
            while(num<300 && !Thread.currentThread().isInterrupted()) {
                if(num%100 == 0) {
                    System.out.println(num);
                }
                num++;
            }
            try {
                // 睡眠会判断中断标示,而且会将中断标志复位,会抛出异常
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(500);
        t.interrupt();
    }
}
  • 1.3 循环阻塞状态即sleep时被中断
public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num < 30000) {
                    if (num % 100 == 0) {
                        System.out.println(num);
                    }
                    num++;
                    // 抛出异常,会把本次的中断信号自动清除
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        };

        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(500);
        t.interrupt();
    }
}

二。中断的传递

被停止方:每次循环或者合适的地方检查中断信号,并在可能抛出interruptException的地方做处理

子方法调用方: 有时在子方法中被中断了,不要吞了异常,

2.1 传递中断:方法申明 public void add() throws InterruptException抛出给最上级的方法处理。

2.2 恢复中断:重新设置中断标示

try {
                while (num < 30000 && !Thread.currentThread().isInterrupted()) {
                    // 抛出异常,会把本次的中断信号自动清除
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }

三。响应中断的方法

这些方法阻塞中会响应中断

Object.wait(), Thread.sleep(), Thread.join()

四。错误的中断方法

4.1 stop会立即停止,程序无法做收尾工作,可能造成脏数据,比如批量转账中10比转了8比突然停止

4.2 volatile变量作为中断标识,某些情况是可行的,但是遇到wait,.sleep阻塞情况就不行了

这种是可行的

/**
 *演示volatile局限性,part1看似可行
 * @author Administrator
 *
 */
public class WrongWayVolatile implements Runnable{
    
    // 中断标志,线程间是透明的可见性
    private volatile boolean cancled = false;
    
    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile w = new WrongWayVolatile();
        Thread t = new Thread(w);
        t.start();
        Thread.sleep(1000);
        w.cancled = true;
    }

    @Override
    public void run() {
        int i = 0;
        while(i<100000 && !cancled) {
            if(i%100 == 0) {
                System.out.println(i);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

下面这种不可行,ArrayBlockingQueue.put会一直阻塞走不出来了,就不能判断cancled标识

/**
 * 陷入阻塞时,volatile是无法关闭线程的 此例中生产者生产过快,消费者消费慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
 * 
 *
 */
public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue queue = new ArrayBlockingQueue(10);
        Producer p = new Producer(queue);
        Thread t = new Thread(p);
        t.start();
        Thread.sleep(1000);
        Consumer c = new Consumer(queue);
        while(c.needMoreore()) {
            System.out.println(c.storage.take()+"被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据");
        p.cancled = true;
    }
}

class Producer implements Runnable {
    BlockingQueue storage;
    // 中断标志,线程间是透明的可见性
    public volatile boolean cancled = false;
    
    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int i = 0;
        try {
            while (i < 100000 && !cancled) {
                if (i % 100 == 0) {
                    // 队列满了时会阻塞,直到队列有空余可以放值了。如果此时interrupt()方法会抛异常,
                    //但是不会处理自定义的cancled标识
                    storage.put(i);
                    System.out.println(i);
                    Thread.sleep(1);
                }
                i++;
            }
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }finally {
            System.out.println("生产结束");
        }
    }
}

class Consumer{
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }
    
    public boolean needMoreore() {
        if(Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}

应该使用interrupt()方法,这样ArrayBlockingQueue.put阻塞时进入waiting状态也可以感知到并抛出异常

 

五。 无法响应中断时如何停止线程

ReetrantLock.lock是不能响应中断,可以使用ReetrantLock.lockinterruptly()方法来响应

socket io是不能响应的, 有些IO是可以响应中断

对特定情况用特定方法,没有固定方式

原文地址:https://www.cnblogs.com/t96fxi/p/12616414.html