【并发编程】(二)如何优雅的中断一个线程

  • 时间:
  • 来源:互联网
  • 文章标签:

文章目录

  • 1.中断线程的几种方式
    • 1.1.stop()方法
    • 1.2.标识字段
  • 2.优雅的中断线程——interrupt
    • 2.1.interrupt相关的方法
    • 2.2.两个interrupt中断线程的例子
      • 2.2.1.用做标识退出while循环
      • 2.2.2.用于打断等待阻塞
    • 2.3.isInterrupted和interrupted使用的区别
      • 2.3.1.使用interrupted静态方法可能遇到的坑
      • 2.3.2.interrupted()线程的复位的验证
  • 3.总结

1.中断线程的几种方式

  • 线程正常或使用标识字段终止线程内的循环,run()方法执行结束后自然终止。
  • 使用stop()方法强行中断。
  • 使用interrupt让线程自行决定是否要中断。

1.1.stop()方法

stop()方法可以强行中断一个正在执行的线程,但是JDK的API中,这是一个已经过时的方法,不建议再使用。

为什么要弃用stop()方法呢?
因为它是不安全的,它的不安全性在于:调用stop()时会释放所有锁, 并且线程停止的时机不可控,我们无法得知这个线程停止时执行到了哪个位置。


举个例子:线程A将一个user对象从name=张三,age=18,修改为name=张三丰,age=108,将这个过程加入到同步代码块中保证线程安全。线程A执行完后,线程B去读取这个user对象,得到的结果就是name=张三丰,age=108。
如果在线程A运行的过程中,main线程调用了线程的stop()方法,在线程A将name修改为张三丰后中断了线程,并释放锁。此时线程B抢到锁后,就得到的结果就是name=张三丰,age=18,这样的结果就是错误的。

1.2.标识字段

在不使用stop()方法的情况下,可以自定义一个标识字段,通过判断标识字段值来决定要不要退出循环。
这种方式一般使用在线程里面用了while(true)这样的的情况下,如果是不耗时的一次异步调用,直接等线程自然结束就可以了。
下面用一个Demo来跑一下这种方式:

public class FlagDemo implements Runnable {

    /**
     * volatile修饰保证可见性
     */
    private static volatile boolean stopFlag = false;

    @Override
    public void run() {
        System.out.println("开始循环");
        while (!stopFlag) {
            // 什么也不做
        }
        System.out.println("停止循环");
    }

    public static void main(String[] args) {
        new Thread(new FlagDemo()).start();
        stopFlag = true;
    }
}

此时控制台打印出,开始循环,停止循环后线程终止,如果不修改stopFlag值,while循环就一直不会停下来。
这种方式可以让线程退出无线循环,但是如果线程是阻塞状态的话,也是中断不了的。例如在while(!stopFlag)中sleep2000秒。

@Override
public void run() {
	System.out.println("开始循环");
	while (!stopFlag) {
	    try {
	        TimeUnit.SECONDS.sleep(2000);
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
	}
	System.out.println("停止循环");
}

此时只会打印出一个开始循环,然后线程就阻塞了,无法进入下一个循环。这种情况可以使用第三种方式来中断,也就是interrupt()。

2.优雅的中断线程——interrupt

2.1.interrupt相关的方法

在Thread类中有三个和中断相关的方法,interrupt() ,interrupted(),isInterrupted()。

  • interrupt:用于改变当前线程的中断标识,如果线程处于等待阻塞状态,则会抛出InterruptedException。需要注意的是,这个方法只修改标识,而不是直接中断一个线程。
  • isInterrupted:检查当前线程状态是否是中断状态,如果是则返回true,不是返回false,调用后不会重置中断状态。
  • interrupted:也是检查当前线程状态是否是中断状态,如果是则返回true,不是返回false,调用后会重置中断状态,这是个静态方法,可以用Thread类调用。

2.2.两个interrupt中断线程的例子

2.2.1.用做标识退出while循环

将1.2中的使用标识字段的例子改写成interrupt。

public class InterruptDemo1 implements Runnable {

    @Override
    public void run() {
        System.out.println("开始循环");
        while (!Thread.currentThread().isInterrupted()) {
			// 什么也不做
        }
        System.out.println("停止循环");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptDemo1());
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

执行后成功打印出停止循环,退出了while无限循环。

2.2.2.用于打断等待阻塞

在1.2的第二个例子中使用了sleep导致线程阻塞,这个时候标识字段就无效了,除此之外还有wait,join也会导致同样的线程阻塞问题。
这个时候可以通过interrupt中断阻塞,并抛出InterruptedException异常,在catch中由当前线程自行选择是否需要结束。

public class InterruptDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println("开始循环");
        while (!Thread.currentThread().isInterrupted()) {
            try {
                TimeUnit.SECONDS.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                /*
                 * 1.在此处由线程自行选择是否中断
                 * 2.抛出InterruptedException后中断状态会重置为false,如果要选择中断循环,需要再次调用interrupt
                 */
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("停止循环");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptDemo2());
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

打印的结果为:

开始循环
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.ls.practice.concurrent.interrupt.InterruptDemo2.run(InterruptDemo2.java:15)
	at java.lang.Thread.run(Thread.java:745)
停止循环

2.3.isInterrupted和interrupted使用的区别

先看一下两者的源码:

public boolean isInterrupted() {
	return isInterrupted(false);
}

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

// 上面两个方法,最终都是调用了本地方法
private native boolean isInterrupted(boolean ClearInterrupted);

传入的布尔值为true的意思是返回当前的中断状态后,将中断状态复位为false,而传入false表示不复位。

2.3.1.使用interrupted静态方法可能遇到的坑

先看一个示例,想一下可能的打印结果。

public class InterruptedDemo1 implements Runnable {

    @Override
    public void run() {
        while (true) {

        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new InterruptedDemo1());
        thread.start();
        // 设置中断状态为true
        thread.interrupt();
        // 获取中断状态
        System.out.println(thread.isInterrupted());
        // 获取中断状态并重置
        System.out.println(thread.interrupted());
        // 获取中断状态并重置
        System.out.println(Thread.interrupted());
        // 获取中断状态
        System.out.println(thread.isInterrupted());
    }

}

输出结果如下:

true
false
false
true

第二次打印的时候就重置了,为什么不是true,false,false,false呢?这就是需要注意的点了,interrupted方法中使用的是currentThread(),也就是说,复位的状态是主线程的状态和此处new的线程没有关系。

2.3.2.interrupted()线程的复位的验证

下面分别使用isInterrupted()和interrupted()做while循环的中断标识,在中断后打印当前线程的中断状态,对比两者的区别。
先测试isInterrupted()

public class InterruptedDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println("开始循环");
        while (!Thread.currentThread().isInterrupted()) {

        }
        System.out.println(Thread.currentThread().isInterrupted());
        System.out.println("停止循环");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptedDemo2());
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

此时打印出的结果为:true


在测试interrupted

public class InterruptedDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println("开始循环");
        while (!Thread.interrupted()) {

        }
        System.out.println(Thread.currentThread().isInterrupted());
        System.out.println("停止循环");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptedDemo2());
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

打印结果为false,表示线程状态已重置。

3.总结

①stop()和interrupt()最根本的区别在于,stop()是“他杀”,不管将要被中断线程的执行状态是什么,直接强制中断。而interrupt()是改变将要被中断的线程标识,被中断的线程中可以根据自身中断标识的变化自行决定要不要中断,并且可以根据其他的条件判断什么时候中断。

②自定义标识字段来结束线程无法解决等待阻塞的问题,这时候可以通过interrupt()让线程抛出InterruptedException来激活线程,打断阻塞的状态。

本文链接http://www.taodudu.cc/news/show-1944715.html