上次说了线程的6种状态,这次说说如何中止一个正在运行的线程。
(一)不正确的线程终止 - Stop
- ① stop
中止线程,并且清楚监控锁的信息,但是可能导致线程安全问题,JDK不建议用。
- ② Destroy
JDK从未实现该方法。
- ③ 通过代码示例说明
public class Demo3 { public static void main(String[] args) throws InterruptedException { StopThread thread = new StopThread(); (); // 休眠1秒,确保i变量自增成功 T(1000); // 暂停线程 (); // 错误的终止 // (); // 正确终止 while ()) { // 确保线程已经终止 } // 输出结果 (); } }Demo3.java
public class StopThread extends Thread { private int i = 0, j = 0; @Override public void run() { synchronized (this) { // 增加同步锁,确保线程安全 ++i; try { // 休眠10秒,模拟耗时操作 T(10000); } catch (InterruptedException e) { e.printStackTrace(); } ++j; } } /** * 打印i和j */ public void print() { Sy("i=" + i + " j=" + j); } }S
线程操作其实很简单,在一个同步代码块中,i和j做一个++的操作,但是在执行的过程中,也就是说在 ++i 操作之后,进入10秒的睡眠状态,如果通过stop这样一个api,将我们的线程中止掉的话,这就会导致 i 和 j 线程上的安全问题,由主线程影响到了创建过的stop线程,它的数据不正确性,理想的情况下,i 和 j 要么都添加成功,要么都是全部添加失败,添加同步块的目的就是要保证操作的原子性,或者说这2个变量不受其他线程的干扰,但是使用stop的话,就会导致 i = 1 j = 0,也就是代码 i 执行自增,但是 j 没有完成自增操作,这种就是破坏的线程安全。
因为StopThread有同步块,理论上来讲要么全部成功,要么全部失败,StopThread中有个睡眠10秒,但是在主线程里面使用了stop,强制得让线程中止,导致i=1,j=0,原来使用同步块的目标就是为了保证线程的安全,两个变量的操作保证线程的安全不受其他线程影响,这就是stop方法被禁用的原因,既然有了stop方法被禁用,肯定是有更加优雅的方式,下面继续说。
(二) 正确的线程中止 - interrupt
目前的jdk版本中,推荐的方式是使用interrupt来进行线程中止。如果目标线程在条用Object class的等待(wait),挂起(wait),阻塞,那么调用interrupt会失效,该线程的终端状态将被消除,爬出interruptedException异常。
如果目标线程是被 I/O 或者NIO中的Channel所阻塞,同样,I/O操作会被终端或者返回特殊异常值,达到中止线程的目的。
以上条件都不满足,则会设置此线程的中断状态。
对于上班的Demo3 注释stop,interrupt后,最终输出为 “ i=1 j=1 ”,数据一致。
首先达到了,我们先要的效果,所见为所得,程序编写的目的就是让这两个变量自增可以正确的完成,不会产生强制中止。使用interrupt可以控制程序的正常执行,让程序可以正常的执行,线程安全的状态,会议sleep抛出了一个异常,可以由开发者来控制业务的逻辑,而不是像stop强制的进行中止操作。这就是为了JDK推荐的方式。
(三) 正确的线程中止 - 标志位
- ① 介绍
除了interrupt这种方式之外,还有一种是通过标志位这种方式,如果程序代码里面是一种循环执行的逻辑,可以在程序中,增加一个标志为volatile,当循环的时候通过标志位的判断是否中止。主线程修改了标志位的值,子线程会随之收到标志位变化的通知,共享了同一个变量,通过变量的形式,通知另一个线程,已达到控制是否继续执行,控制线程是否中止的效果,这种方式受限于线程执行的业务逻辑,如果程序中存在有这种可以使用标志位的条件可以使用这种方式来做,这种方式也是一种很优雅的方式。
- ② 代码
PS:上边介绍了三种线程中止的方式,stop(不要用),interrupt(通过抛出异常,方便开发者始终),volatile(标志位,首先业务逻辑可以通过变量才判断可以使用这种方式),下次一起说说内存屏障和CPU缓存。