停止线程是在进行多线程开发中的重要的知识点,停止线程并不像break那样简单干脆,需要一些技巧性的处理。
本文将讨论如何在一个线程进行的过程中将其停止,停止一个线程意味着在当前线程执行完任务之前停止该线程,也就是放弃该线程正在进行的任务。虽然这看起来很简单,但是要做好防范工作,以避免可能会发生的错误。
如果停止一个线程可以用Thread.stop()方法,但是不建议使用,虽然它可以停止一个线程,但是他是不安全(unsafe)的,会造成一些我们不想看到的后果(关于stop()方法会在后面详细说明),而且这是一个已经被弃用的方法,在未来的java版本中,他可能将会不可用。
这里我们要使用到的是Thread.interrupt()方法,此方法并不能真正的将线程停止,只是为线程设置一个中断标记,我们可以通过Thread.interrupted()和Thread.isInterrupted()方法来查看线程是否处于中断状态,如果返回true,处于中断状态,接下来加入相应的判断操作以完成线程的中断。
1、判断线程是否是停止状态
在对线程进行停止操作之前,首先要判断线程是否处于要停止的状态,Java中提供了两个方法来判断这个状态:
1)this.interrupted():测试当前线程是否已经中断;
2)this.isInterrupted():测试线程是否已经中断;
从上面描述中应该已经能看出来两个方法的不同之处了吧,我们再来看一下两个方法是如何声明的:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
可以看到,interrupted()是一个静态方法,返回当前线程的停止状态;isInterrputed()方法返回的是调用此方法的线程的停止状态。还有一个不同点是,当两次调用interrupted()时,如果第一次返回true,第二次会返回false,这是因为该方法每次被调用的时候会自动清除状态标记,将状态置为默认值false,而isInterrupted()则不会清除标记。
并且只有当线程还未执行完毕时调用isInterrupted()方法才能看到此线程的停止状态,如果线程已经执行完毕,就算使用interrupt()给线程使用了停止标记,使用isInterrupted()查看仍会返回false。
2、停止线程的方法——异常法
我们可以在for循环中增加对于线程停止状态的判断,如果是停止状态,则下面代码不再执行。
创建一个Mythread类
public class Mythread extends Thread{
@Override
public void run() {
super.run();
try {
for(int i=0; i<500000; i++) {
if(this.interrupted()) {
System.out.println("停止状态,退出!");
throw new InterruptedException();
}
System.out.println("i="+(i+1));
}
}
catch(InterruptedException e) {
System.out.println("捕获异常,停止线程");
e.printStackTrace();
}
}
}
Run类包含一个main方法
public class Run {
public static void main(String args[]) {
try {
Mythread mt = new Mythread();
mt.start();
Thread.sleep(1000);
mt.interrupt();
} catch (InterruptedException e) {
System.out.println("main catched");
e.printStackTrace();
}
System.out.println("end!");
}
}
在Mythread类的run()方法中,增加对该线程的停止状态的判断,如果处于停止状态,则输出退出,然后抛出一个InterruptedException异常,再进行捕获和相对应的处理,这样可以保证for循环后面的代码也不会继续执行,保证整个线程停止下来。运行结果如下所示:
/*
i=325813
i=325814
i=325815
i=325816
i=325817
end!
停止状态,退出!
捕获异常,停止线程
java.lang.InterruptedException
at Practice.Mythread.run(Mythread.java:12)
*/
3、在沉睡中停止线程
如果线程在sleep()的状态下被终止,会是什么情况呢?可以理解成一个人在睡觉的时候被杀害了,那么这个人睡觉之后的行为便不会再进行,比如说他不会再醒过来。
这里继续创建Mythread类
- public class Mythread extends Thread{
- @Override
- public void run() {
- super.run();
- try {
- System.out.println("我开始睡觉啦!");
- Thread.sleep(200000);
- System.out.println("我醒来啦!");
- }
- catch(InterruptedException e) {
- System.out.println("在沉睡中被杀掉了!没法醒过来了!"+this.isInterrupted());
- e.printStackTrace();
- }
- }
- }
Run中包含一个main方法
- public class Run {
- public static void main(String args[]) {
- try {
- Mythread mt = new Mythread();
- mt.start();
- Thread.sleep(1000);
- mt.interrupt();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
运行结果
/*
我开始睡觉啦!
在沉睡中被杀掉了!没法醒过来了!false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Practice.Mythread.run(Mythread.java:9)
*/从打印结果来看,如果在sleep的状态下终止某一个线程,会进入catch语句,并且将他的终止状态清除,设为false。
另一种情况,如果是在 sleep()之前就遇到了interrupt()的话一样会直接进入catch语句,并结束线程。
4、使用return停止线程
将方法interrupt()与return结合起来也能终止线程。修改Mythread类与main方法如下
- public class Mythread extends Thread{
- @Override
- public void run() {
- while(true) {
- if(this.isInterrupted()){
- System.out.println("停止!");
- return;
- }
- System.out.println("计时:"+System.currentTimeMillis());
- }
- }
- }
- public class Run {
- public static void main(String args[]) throws InterruptedException {
- Mythread mt = new Mythread();
- mt.start();
- Thread.sleep(2000);
- mt.interrupt();
- }
- }
在检查到线程状态为终止的时候,不再输出时间,返回结果
运行如下:
/*
计时:1525424532412
计时:1525424532412
计时:1525424532412
计时:1525424532412
计时:1525424532412
计时:1525424532412
停止!
*/虽然可以使用interrput()与return结合的方式来终止线程,但是还是推荐使用抛异常的方式终止线程,因为跑出的异常可以继续向上层传递,使得线程终止这一事件得以传播。
上文所说的Thread中的方法stop(),虽然可以用来使线程终止,但是使用此方法是十分暴力的,会造成一些不安全的后果。调用stop()方法后程序会抛出java.lang.ThreadDeath异常,但在通常情况下,此异常不需要显示的捕捉。
在java中stop()方法已经被作废,这是因为如果强制让线程停止,可能会是一些清理工作得不到完成,另外一个情况就是对于锁定的对象进行了解锁,导致数据得不到同步处理,会出现数据不一致的问题。
使用stop释放锁会造成数据不一致的后果,如果出现这种结果,程序处理数据就有可能遭到破坏,最终导致执行流程错误,所以一定要特别注意。
下面来看一个例子,创建类synObject如下:
- public class synObject {
- private String username = "a";
- private String password = "aa";
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- synchronized public void init(String username, String password) {
- try {
- this.username = username;
- Thread.sleep(200000);
- this.password = password;
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
其中username和password初始化为a和aa
Mythread和Run类如下:
- public class Mythread extends Thread{
- private synObject object;
- public Mythread(synObject object) {
- super();
- this.object = object;
- }
- @Override
- public void run() {
- object.init("b", "bb");
- }
- }
- public class Run {
- public static void main(String args[]) throws InterruptedException {
- try {
- synObject object = new synObject();
- Mythread mt = new Mythread(object);
- mt.start();
- Thread.sleep(200);
- mt.stop();
- System.out.println(object.getUsername()+
- " "+object.getPassword());
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
运行后结果为:
/*
b aa
*/
可以看到在init方法执行的过程中,线程被强制stop(),所以被迫释放了锁,而被操作的对象object出现了数据不一致的结果。显然stop()方法在功能上是有缺陷的,所以不建议使用此方法。