zoukankan      html  css  js  c++  java
  • 多线程(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是可以响应中断

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

  • 相关阅读:
    Gitee + PicGo搭建图床 & Typora上传图片到图床
    算法思维 ---- 双指针法
    Floyd's cycle-finding algorithm
    Boyer-Moore Voting Algorithm
    youtube-dl 使用小记
    算法思维 ---- 滑动窗口
    Memo
    英语
    BZOJ 3270
    BZOJ 3196
  • 原文地址:https://www.cnblogs.com/t96fxi/p/12616414.html
Copyright © 2011-2022 走看看