目录
1、使用退出标识(interrupt方法)。其一,使线程正常退出,也就是当run()方法完成后线程终止;其二,使用异常,通过抛出异常来终止操作。
2、暴力终止。使用stop和suspend及resume方法,当都是过期方法,非常容易引发线程安全问题,不推荐使用。
下边将对以上终止方法进行简单的示例。
一、使用interrupt()方法停止线程
使用interrupt()方法并不能使线程马上停止,它只是在当前线程打上了一个停止标记,并不是真正的停止线程。
如果要使线程停止,需要结合isInterrupted()方法进行判断,在进入判断的逻辑中添加中断操作。
1、线程正常终止
测试示例:
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i = 0;i<50000;i++){
if(this.isInterrupted()){
// 获取到中断标记,使用break终止循环,方法执行完毕,正常结束
System.out.println("线程已经打上中断标记,执行停止...");
break;
}
System.out.println("i="+(i+1));
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(1000);
// 给线程打上中断标记
myThread.interrupt();
}
}
注意:
1、如果只是用interrupt()方法是无法终止线程的。
2、在停止线程的操作当中,其实是手动实现中断逻辑的,interrupt()+isInterrupted()判断本身不会进行任何中断操作。
正常终止的失败案例
测试代码:
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0;i<50000;i++){
if(currentThread().isInterrupted()){
// 获取到中断标记,使用break终止循环,方法执行完毕,正常结束
System.out.println("线程已经打上中断标记,执行停止...");
break;
}
System.out.println("第一层循环:i="+(i+1));
}
// 稍微修改方法,增加新的执行逻辑
for(int i = 0;i<5;i++){
System.out.println("第二层循环:i="+(i+1));
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(100);
// 给线程打上中断标记
myThread.interrupt();
}
}
执行结果:
当第一层循环结束后,第二层循环依然从头到尾的执行了。这是因为interrupt()方法+isInterrupted()判断逻辑+break,只控制了第一层循环,并没有影响到后边逻辑的执行。就像if()...elseif()一样,只是给出了判断的条件。因此使用interrupt()方法+isInterrupted()判断逻辑正常中断线程时,一定要考虑到全局,像本例的问题就是典型的控制不足。
2、线程异常终止
在正常终止失控的情况下,我们可以采用抛异常的方式来终止线程(推荐方法),此时,该线程的所有逻辑将不会再执行,线程如果持锁的话,也会对锁进行释放。下边示例是对上边示例的稍微改动,把break方法换成了抛异常。
测试代码:
public class MyThread extends Thread {
@Override
public void run() {
try {
for(int i = 0;i<50000;i++){
if(currentThread().isInterrupted()){
// 获取到中断标记,不再使用break,而是抛出异常
System.out.println("线程已经打上中断标记,抛异常停止...");
throw new InterruptedException();
}
System.out.println("第一层循环:i="+(i+1));
}
// 新的循环
for(int i = 0;i<5;i++){
System.out.println("第二层循环:i="+(i+1));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(100);
// 给线程打上中断标记
myThread.interrupt();
}
}
执行结果:
二、暴力终止
1、stop方法弊端(已废弃)
使用stop()方法能停止线程,但是如果强制让线程停止会带来以下问题:
(1)使程序的一些清理性工作得不到完成。
(2)stop()方法会释放锁,导致数据得不到同步处理。
测试代码:
新建MyService对象
public class MyService {
private String username = "old_ussername";
private String password = "old_password";
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;
}
public synchronized void setValue(String username,String password){
try {
this.username = username;
Thread.sleep(100000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
创建线程
public class MyThread extends Thread {
public MyService myService;
public MyThread(MyService myService){
super();
this.myService = myService;
}
@Override
public void run() {
myService.setValue("new_username","new_Password");
}
public static void main(String[] args) throws InterruptedException {
MyService myService = new MyService();
MyThread myThread = new MyThread(myService);
myThread.start();
Thread.sleep(500);
// 暂停线程-暴力终止,此时会释放对象锁
myThread.stop();
// 打印对象的数据
System.out.println("被破坏后的值——"+myService.getUsername()+":"+myService.getPassword());
}
}
执行结果:
从结果可以看到,MyService对象的数据被修改一半以后,线程停止,main线程读到了该对象的脏值,发生了非常严重的线程安全问题。
2、suspend 与 resume 方法的使用(已废弃)
该方法目前已经被废弃,使用的弊端如下:
(1)如果处理不当,非常容易造成死锁问题。比如,执行该方法时,线程在暂停状态无法释放锁,如果该线程一直得不到恢复,那么其他线程在得不到锁的情况下,也将一直成等待状态,造成系统阻塞和瘫痪。
(2)数据得不到同步处理。如果有多个线程,多个锁对象,当其中一个线程在修改数据时停止,其他线程可能读到该线程修改了一半的脏数据,引发线程安全问题。
替换解决方案:使用wait()/notify(),将在后文详述。