1. 首页
  2. Java

java多线程核心-停止线程

一、 停止线程原理

使用interrupt来通知,而不是强制

二、 java中停止线程的原则

  在Java中,最好的停止线程的方式是使用中断 Interrupt,但是这仅仅是会通知到被终止的线程”你该停止运行了”,被终止的线程自身拥有决定权(决定是否、以及何时停止),这依赖于请求停止方和被停止方都遵守一种约定好的编码规范。
  任务和线程的启动很容易。在大多数时候我们都会让它们运行直到结束或者让它们自行停止。然而有时候我们希望提前结束任务或线程或许是因为用户取消了操作或者服务需要被快速关闭,或者是运行超时或出错了。
  要使任务和线程能安全、快速、可靠地停止下来并不是一件容易的事。Java没有提供任何机制来安全地终止线程。但它提供了中断(interruption),这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
  这种协作式的方法是必要的,我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态。相反在编写任务和服务时可以使用一种协作的方式:当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。
  生命周期结束(End-of-Lifecycle)的问题会使任务、服务以及程序的设计和实现等过程变得复杂,而这个在程序设计中非常重要的要素却经常被忽略。一个在行为良好的软件与勉强运的软件之间的最主要区别就是,行为良好的软件能很完善地处理失败、关闭和取消等过程。

三、 如何正确停止线程

  1. 通常情况下停止线程
    在业务代码适当位置添加Thread.currentThread().isInterrupted()的判断,并通过thread.interrupt()方法停止。
    源码:
public class Thread implements Runnable {
    private native void interrupt0();
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
}
  1. 被阻塞的线程停止
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0 ;
            while (!Thread.currentThread().isInterrupted() && num <= 500){
                if(num % 73 == 0){
                    System.out.println(num+"是73的倍数");
                }
                num++;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
  1. 线程在每次迭代后都阻塞如何停止
 public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0 ;
            try {
                //此处不再需要Thread.currentThread().isInterrupted()的判断
                while (num <= 5000) {
                    if(num % 73 == 0){
                        System.out.println(num+"是73的倍数");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
  1. while内try/catch
    java语言中sleep一旦响应中断就会将线程的Interrupted标记位清除,会导致while内try/catch包裹的sleep无法抛出sleep Interrupted中断线程

四、 停止线程的最佳实践

  1. 传递中断
      优先选择在方法上抛出异常。
      用 throws InterruptedEXception标记你的方法,不采用ty语句块捕获异常,以便于该异常可以传递到顶层,让run方法可以捕获这一异常
      由于run方法内无法抛出 checked Exception(只能用 try catch),顶层方法必须处理该异常,避免了漏掉或者被吞掉的情况,增强了代码的健壮性。
  2. 恢复中断
      如果不想或无法传递 InterruptedEXception(例如用run方法的时候,就不让该方法throws Interrupted Exception),那么应该选择在 catch子句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行依然能够检查到刚才发生了中断。
public class InterruptedReset implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("run 正在执行");
                method();
        }
        System.out.println("线程执行完成");
    }

    public void method() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

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

    }
}

五、可以响应中断的方法

  Object.wait()/wait(long)/wait(long,int)
  Thread.sleep(long)/sleep(long,int)
  Thread.join/join(long)/join(long,int)
  java.util.concurrent.BlockingQueue.take()/put(E)
  java.util.concurrent.lockS.Lock.lockInterruptibly()
  java.util.concurrent.CountDownLatch.await()
  java.util.concurrent.CyclicBarrier.await()
  java.util.concurrent.Exchanger.exchange(V)
  java.nlo.channels.InterruptibleChannel相关方法
  java.nlo.channels.Selector的相关方法

六、 错误停止线程的方法

  1. 被弃用的stop, suspend和resume方法
    用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作,会造成脏数据。
    suspend和resume不会破坏对象,会让线程挂起,带锁休息,容易造成死锁
  2. 用volatile设置boolean标记位
    这种做法是错误的,或者说是不够全面的,在某些情况下虽然可用,但是某些情况下有严重问题。如果我们遇到了线程长时间阻塞(这是一种很常见的情况),就没办法及时唤醒它,或者永远都无法唤醒该线程,而interrupt设计之初就把wait等长期阻塞作为一种特殊情况考虑在内了。
    错误示例:
public class VolatileTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(10);
        producer producer = new producer(arrayBlockingQueue);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);
        Consumer consumer = new Consumer(arrayBlockingQueue);
        while (consumer.need()){
            System.out.println(consumer.storage.take()+"已消费");
            Thread.sleep(100);
        }
        System.out.println("消费者停止消费");
        producer.concurrentFlag = true; //无法停止生产者线程
    }
}
class producer implements Runnable{
    public volatile boolean concurrentFlag = false;
    BlockingQueue storage;

    public producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num < 1000000 && !concurrentFlag){
               if (num %100 == 0 ) {
                       storage.put(num);
                       System.out.println("生产数据"+num);
               }
               num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者停止运行");
        }
    }
}
class Consumer{
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean need(){
        if(Math.random()>0.95){
            return false;
        }
        return true;
    }
}

原创文章,作者:小童子,如若转载,请注明出处:https://www.caobinrg.com/444.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论列表(1条)

联系我们

邮件:caobinrg@163.com