我们知道线程的start方法,那么,很自然地会想到停止一个线程使用stop,然而stop方法是“过时的”,“不安全”。stop()方法,直接终止线程,释放线程所获的资源,但是在释放过程中会造成对象状态不一致,从而使程序进入未知的境地,已经很久不推荐使用了。所以,Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制。在这里要着重介绍的是Thread.interrupt() 方法,也就是要分析清楚java的中断机制。先来看看JDK怎么描述的:
中断线程。
- 如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。
- 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
- 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。
- 如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。
- 如果以前的条件都没有保存,则该线程的中断状态将被设置。
- 中断一个不处于活动状态的线程不需要任何作用。
- 抛出:SecurityException - 如果当前线程无法修改该线程
上述文档详细介绍了在各种情况下调用interrupt方法的结果,所以看不明白不要紧,有些知识会在另外的博文里介绍,这里我先介绍一些基础。
1 中断标志
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。
Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
2 interrupt() 不会中断一个正在运行的线程
在此我先介绍另外两个方法,这两个方法有助于我们利用程序分析interrupt():
(1)public static boolean interrupted:注意,它是一个静态方法,是一个类方法,测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。源码为:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
所以,这是一个命名很不恰当,很有迷惑性的方法,它不仅仅测试了当前线程的是否已经中断,而且会把中断状态清除。
(2)public boolean isInterrupted():注意,这是一个成员方法,是对象的方法。测试线程是否已经中断。线程的中断状态不受该方法的影响。
public class MyThread extends Thread {
private int count = 0;
@Override
synchronized public void run() {
super.run();
// 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了
for (int i = 0; i < 5; i++) {
count++;
System.out.printf(this.getName() + " 第 %d 次打印。\n", count);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt();
System.out.println("线程的中断状态是:"+myThread.isInterrupted());
}
}
线程的中断状态是:true
Thread-0 第 1 次打印。
Thread-0 第 2 次打印。
Thread-0 第 3 次打印。
Thread-0 第 4 次打印。
Thread-0 第 5 次打印。
可见,线程myThread的中断状态已经被设置为true,但是它并没有被停止,好像interrupt()没有起到任何作用。这也就是上面介绍的,主线程main想让myThread中断,但是它没有理会,依然执行。这也是JDK文档中提到的第一种情况。
所以,interrupt() 方法只是将目标线程的中断状态设置为true,至于是否对这种中断进行处理,完全看这个线程本身,也就是我们的代码是否处理这种情况。上述实例代码我们做一点点修改,增加一个判断中断状态的步骤:
public class MyThread extends Thread {
private int count = 0;
@Override
synchronized public void run() {
super.run();
System.out.println("线程已经开始执行了!");
// 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了
for (int i = 0; i < 5; i++) {
if(interrupted()){
System.out.println("线程已经被中断,我们不往下执行了!");
break;
}
count++;
System.out.printf(this.getName() + " 第 %d 次打印。\n", count);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt();
System.out.println("线程的中断状态是:"+myThread.isInterrupted());
}
}
线程的中断状态是:true
线程已经开始执行了!
线程已经被中断,我们不往下执行了!
降到这里,相信大家对于interrupt()方法已经有了一个基本的了解,那么,JDK中提到的第二种情况是什么意思呢?其实很简单,往下看。
3 InterruptedException
当我想使用Thread.sleep() 方法的时候,IDE就会提醒我要用try/catch包裹:
这是因为sleep方法本身就会去检查线程的中断状态,如果现在的中断状态为true,它会抛出InterruptedException。关于这个异常我们来看看JDK文档:
当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。有时候,一种方法可能希望测试当前线程是否已被中断,如果已被中断,则立即抛出此异常。下列代码可以达到这种效果:
if (Thread.interrupted()) // Clears interrupted status!
throw new InterruptedException();
这个代码好眼熟,不就是section 2 中给出的示例代码吗?对的,在java中有很多方法是自带对中断状态的判断的,不用我们像section 2中那样自己去写。比如除了sleep(),wait()和join()等也是一样。还是上面的例子做一点点修改:
public class MyThread1 extends Thread {
@Override
public void run() {
super.run();
System.out.println("线程已经开始执行了!");
// 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了
for (int i = 0; i < 6; i++) {
System.out.printf(this.getName() + " 第 %d 次打印。\n", i+1);
try {
System.out.printf("即将开始第 %d 次sleep\n",i+1);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程已经被中断,不能进入sleep!");
}
}
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
myThread.interrupt();
}
}
线程已经开始执行了!
Thread-0 第 1 次打印。
即将开始第 1 次sleep
线程已经被中断,不能进入sleep!
java.lang.InterruptedException: sleep interrupted
Thread-0 第 at java.lang.Thread.sleep(Native Method)
at easy.MyThread1.run(MyThread1.java:15)
2 次打印。
即将开始第 2 次sleep
Thread-0 第 3 次打印。
即将开始第 3 次sleep
Thread-0 第 4 次打印。
即将开始第 4 次sleep
Thread-0 第 5 次打印。
即将开始第 5 次sleep
Thread-0 第 6 次打印。
即将开始第 6 次sleep
也就是:
- main在启动 myThread 之后,立刻将其中断状态设置为true;
- 结果,在第一次调用sleep方法时,该方法去检查线程的中断状态,发现为true,就抛出了InterruptedException异常;
- 然后该方法还将 myThread 的中断状态改为false,所以接下来的运行没有任何问题。
(再次强调一点,这里的“中断状态”只是为了讲解方便的一种形象的说法,真正的原理在native方法中,我们不去深究。)
而文章的前面贴出的JDK文档的第二种情况是指在sleep、wait、join的状态下调用interrupt方法的情形,也就是说interrupt方法实际上会触发这三个方法中的InterruptedException异常机制。
public class MyThread2 extends Thread {
@Override
public void run() {
super.run();
System.out.printf("线程 %s 已经启动!接下来进入sleep状态", this.getName());
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("\n我睡觉被吵醒会咬人的!");
}
}
public static void main(String[] args) {
final MyThread2 myThread = new MyThread2();
myThread.start();
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("\n中断它!");
myThread.interrupt();
}
}, 2000);// 设定指定的时间time,此处为2000毫秒
}
}
线程 Thread-0 已经启动!接下来进入sleep状态
中断它!
java.lang.InterruptedException: sleep interrupted
我睡觉被吵醒会咬人的!
at java.lang.Thread.sleep(Native Method) at easy.MyThread2.run(MyThread2.java:13)
4 停止一个线程的技巧
section 2 中的代码其实我故意忽略了一个问题,那就是我用interrupted判断后结束进程看似已经结束了,其实不然,该代码只是结束了for循环:break。如果for循环的底下还有代码,该代码会继续执行:
public class MyThread1 extends Thread {
private int count = 0;
@Override
synchronized public void run() {
super.run();
System.out.println("线程已经开始执行了!");
// 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了
for (int i = 0; i < 5; i++) {
if(interrupted()){
System.out.println("线程已经被中断,我们不往下执行了!");
break;
}
count++;
System.out.printf(this.getName() + " 第 %d 次打印。\n", count);
}
System.out.println("快看,我在for循环的底下");
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
myThread.interrupt();
}
}
线程已经开始执行了!
线程已经被中断,我们不往下执行了!
快看,我在for循环的底下
解决办法1:异常法
用try/catch包裹代码,逻辑代码放入try中,通过interrupted判断中断,一旦发现则主动抛出异常,从而进入了catch块中,for循环以下的代码不会执行。
public class MyThread1 extends Thread {
private int count = 0;
@Override
synchronized public void run() {
super.run();
System.out.println("线程已经开始执行了!");
// 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了
try {
for (int i = 0; i < 50000; i++) {
if(interrupted()){
System.out.println("线程已经被中断,我们不往下执行了!");
//break;
throw new InterruptedException("异常法"); // 主动抛出异常,throw关键字不要忘记
}
count++;
System.out.printf(this.getName() + " 第 %d 次打印。\n", count);
}
System.out.println("快看,我在for循环的底下");
} catch (InterruptedException e) {
System.out.println("进入了catch块了");
e.printStackTrace();
}
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.interrupt();
}
}
结果:
解决办法2:return
将break改为return