java并发3之停止线程

一、原理介绍

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

停止线程的需求:用户取消、运行出错等。

在java中,我们只能告诉线程你应该中断啦,但是最终决定权还是在线程本身。

没有能力去做到强制停止。其实,想要停止线程就是用interrupt来通知那个线程,以及被通知的那个线程如何配合。这就是停止线程的一个核心,而不是强制停止。

二、最佳实践  正确的停止方法:interrupt

①通常线程会在什么情况下停止普通情况

package stopthreads;

/**
 * 描述: 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 % 1000 == 0){
                System.out.println(num + "是10000的倍数");
            }
            num++;
        }
        System.out.println("运行结束!");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        Thread.sleep(1000);//1秒后调用interrupt()
        thread.interrupt();
    }
}

在while语句添加isInterrupt()进行判断。 

java并发3之停止线程_第1张图片java并发3之停止线程_第2张图片

 前后对比!确实调用interrpt()方法后线程停止运行。

②线程可能被阻塞

package stopthreads;

/**
 * 描述:  带有sleep的中断线程的写法
 */
public class RightWayStopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
          int num = 0;
          try {
          while(num <= 300 && !Thread.currentThread().isInterrupted()){
              if(num % 100 == 0){
                  System.out.println(num + "是100的倍数");
              }
              num ++;
          }

                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();

    }
}

 java并发3之停止线程_第3张图片

③如果线程在每次迭代(循环) 后都阻塞

package stopthreads;

/**
 * 描述:    如果在执行过程中,每次循环都会调用sleep或wait等方法,
 *          那么不需要每次迭代都检查是否已中断
 */
public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while(num <= 10000 ){//判断isInterrupt()多余代码,因为我们会在中断的过程中检测到中断状态,并且抛出这个异常。
                    if(num % 100 == 0){
                        System.out.println(num + "是100的倍数");
                    }
                    num ++;
                    Thread.sleep(10);
                }


            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();

    }
}

 java并发3之停止线程_第4张图片

④while内try/catch的问题

package stopthreads;

/**
 * 描述:   如果while里面放try/catch,会导致中断失效
 */
public class CantInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            while(num <= 10000 && !Thread.currentThread().isInterrupted()){//添加检测还是一样
                if(num % 100 == 0){
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try{
                    Thread.sleep(10);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

 java并发3之停止线程_第5张图片

 报错后继续运行。。。

因为java在设计Slee()函数时,当它一旦响应中断,便会立即把interrupt这个标记位给清除。所以当再次运行到while时,由于中断的效果已经被清除,检查不到任何被中断的迹象。

⑤实际开发中的两种最佳实践:1、传递中断 2、恢复中断 3、不应疲敝中断

1)优先选择:传递中断

package stopthreads;

/**
 * 描述:    最佳实践:catch了InterruptedExcetion之后的优先选择:
 *          在方法签名中抛出异常
 *          那么在run()就会强制try/catch
 */
public class RightWayStopThreadInProd implements Runnable{

    @Override
    public void run() {  //较高层次
        while(true){
            System.out.println("go");
            throwInMethod();
        }
    }

    private void throwInMethod() { //低层次 吞掉
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

 通常抛出异常有两种方式,一种是在方法后面直接throw,另一种就是try/catch.

当用try/catch时,

在这个throwInMethod()方法,它是低层次的相对run()方法,在检测到异常时,抛出异常,这时这个异常就会被吞掉。

java并发3之停止线程_第6张图片

这时我们就得上报给更高一级的函数, 在方法后面添加抛出异常的语句。

 @Override
    public void run() {  //较高层次
        while(true){
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

让run()方法(更高级的函数)去处理这个异常,就不会导致这个异常被吞掉。

2)不想或无法传递:恢复中断

 恢复中断的方式也就是在catch子语句中调用Thread.currentThread().interrupt() 来恢复设置中断状态。

package stopthreads;

/**
 * 描述:   最佳实践2:在catch子语句中调用Thread.currentThread().interrupt()
 *                  来恢复设置中断状态,以便于在后续的执行中,依然能够检查到刚才发生的中断
 */
public class RightWayStopThreadInProd2 implements Runnable{
    @Override
    public void run() {
        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println("Interrupt,程序运行结束");
                break;
            }
            System.out.println("go");
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();//恢复中断!
            e.printStackTrace();
        }
    }

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

⑥响应中断的方法总结列表

Object.wait()、Thread.sleep()、Thread.join()、java.util.concurrent.BlockingQueue.take()/put()、

locks.Lock.lockInterruptibly()

三、错误的停止方法

①被弃用的stop,suspend和resume方法

②用volatile设置boolean标记位

四、停止线程的相关函数解析

五、常见面试题

你可能感兴趣的:(java多线程与线程池,java)