线程停止、中断之最佳实践(上)

一:如何正确停止线程?

使用interrupt来通知,而不是强制。 那么,interrupt()方法该怎么用呢?interrupt()其本身并不是一个强制打断线程的方法,其仅仅会修改线程的interrupt标志位,然后让线程自行去读标志位,自行判断是否需要中断。

二:代码演示

演示一:run方法内没有sleep或wait方法的场景中,停止线程

public class StopThreadWay implements Runnable{

    @Override
    public void run() {
        int num = 0;
        while(num <= Integer.MAX_VALUE / 2){
            if(num % 10000 == 0){
                System.out.println(num+"是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new StopThreadWay());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

运行结果

1073670000是10000的倍数
1073680000是10000的倍数
1073690000是10000的倍数
1073700000是10000的倍数
1073710000是10000的倍数
1073720000是10000的倍数
1073730000是10000的倍数
1073740000是10000的倍数
任务运行结束了

Process finished with exit code 0

为什么没有中断?这是因为这个线程想不想停止取决于人家本身,你强加给它一个中断信号,人家完全有权不做理会。那怎么办?往下看。

public class StopThreadWay implements Runnable{

    @Override
    public void run() {
        int num = 0;
        while(num <= Integer.MAX_VALUE / 2 && !Thread.currentThread().isInterrupted()){
            if(num % 10000 == 0){
                System.out.println(num+"是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new StopThreadWay());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

运行结果

94940000是10000的倍数
94950000是10000的倍数
94960000是10000的倍数
94970000是10000的倍数
94980000是10000的倍数
94990000是10000的倍数
95000000是10000的倍数
95010000是10000的倍数
任务运行结束了

Process finished with exit code 0

在Thread.java类里提供了两种方法判断线程是否为停止的。
this.interrupted(): 测试当前线程是否已经中断(静态方法),如果连续调用该方法,则第二次调用将返回false。在api文档中说明interrupted()方法具有清除状态的功能。执行后具有将状态标识清除为false的功能。
this.isInterrupted(): 测试线程是否已经中断,但是不能清除状态标识。

演示二:线程可能被阻塞的场景中,停止线程

public class StopThreadWay {

    public static void main(String[] args) {
       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 (Exception e){
              e.printStackTrace();
          }
       };
       Thread thread = new Thread(runnable);
       thread.start();
        try {
            Thread.sleep(500);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.demo.stopthreads.StopThreadWay.lambda$main$0(StopThreadWay.java:19)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

为什么会抛出异常了呢?那是因为当线程正在休眠过程中,如果收到中断信号,sleep会响应中断信号,它响应中断信号的方式非常特殊,即抛出一个异常,此时会被catch捕获。

演示三:如果线程在每次迭代后都阻塞的场景中,停止线程

public class StopThreadWay {

    public static void main(String[] args) {
       Runnable runnable = () -> {
          int num = 0;
          try{
              while (num <= 10000){
                  if(num % 100 == 0){
                      System.out.println(num+"是100的倍数");
                  }
                  num++;
                  Thread.sleep(10);
              }
          }catch (Exception e){
              e.printStackTrace();
          }
       };
       Thread thread = new Thread(runnable);
       thread.start();
        try {
            Thread.sleep(5000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.demo.stopthreads.StopThreadWay.lambda$main$0(StopThreadWay.java:18)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

如果在执行过程中,每次循环都会调用sleep或wait等方法,那么不需要每次迭代都检查是否已中断。

演示四:如果while里面放try/catch,会导致中断失效

public class StopThreadWay {

    public static void main(String[] args) {
       Runnable runnable = () -> {
          int num = 0;
           while (num <= 10000){
               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();
        try {
            Thread.sleep(5000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.demo.stopthreads.StopThreadWay.lambda$main$0(StopThreadWay.java:18)
	at java.lang.Thread.run(Thread.java:748)
500是100的倍数
600是100的倍数
.............

为什么会抛出异常之后继续执行了呢?可能有人会说,加上中断状态判断就好了,行,那我们试试。

public class StopThreadWay {

    public static void main(String[] args) {
       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();
        try {
            Thread.sleep(5000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果同上,为什么啊?是不是很惊讶啊。那是因为,sleep状态下停止某一个线程,会进入catch语句,并清除状态值,变成false。所以判断就不生效。那该怎么办呢?现在我们进入解决环节。

三:实际开发中的两种最佳实践

  • 优先选择:传递中断
  public class StopThreadWay implements Runnable {

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

        Thread thread = new Thread(new StopThreadWay());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

    @Override
    public void run() {

        while(true && !Thread.currentThread().isInterrupted()){
            System.out.println("hello");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                //todo 保存日志,停止服务等操作
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
       Thread.sleep(2000);
    }
}
  • 不想或无法传递:恢复中断
public class StopThreadWay implements Runnable {

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

        Thread thread = new Thread(new StopThreadWay());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

    @Override
    public void run() {

        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println("程序运行结束");
                break;
            }
            throwInMethod();

        }
    }

    private void throwInMethod() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

运行结果

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.demo.stopthreads.StopThreadWay.throwInMethod(StopThreadWay.java:32)
	at com.example.demo.stopthreads.StopThreadWay.run(StopThreadWay.java:25)
	at java.lang.Thread.run(Thread.java:748)
程序运行结束

四:响应中断的方法总结列表

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.util.channels.InterrupttibleChannel相关方法
java.util.channels.Selector的相关方法

你可能感兴趣的:(并发编程)