您的位置:首页 > 其它

Thread详解2:停止与中断

2016-03-27 13:41 190 查看
我们知道线程的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 <
4000
span class="hljs-keyword">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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息