并发编程学习笔记(三、如何安全地终止线程)
目录:
- 设置退出标识
- interrupt()方法
- 废弃的终止方式
- 总结
设置退出标识:
1 public class FlagThread extends Thread { 2 /** 3 * 退出标识 4 */ 5 public volatile boolean exit = false; 6 7 @Override 8 public void run() { 9 while (!exit) { 10 } 11 System.out.println("ThreadFlag线程退出"); 12 } 13 14 public static void main(String[] args) throws Exception { 15 FlagThread threadFlag = new FlagThread(); 16 threadFlag.start(); 17 // 主线程延迟3秒 18 sleep(3000); 19 // todo 终止线程thread 20 threadFlag.exit = true; 21 // main线程放弃cpu使用权 22 // 让threadFlag线程继续执行,直到threadFlag运行完 23 threadFlag.join(); 24 System.out.println("线程退出!"); 25 } 26 }
通过第5行的exit属性来表示线程是否应该退出;但这种方法有一个弊端,若线程阻塞时标识便不起作用了。
在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是保证exit内存可见性,也就是对exit的修改会立刻对其他线程可见。
interrupt()方法:
1 public class InterruptThread extends Thread { 2 /** 3 * 退出标识 4 */ 5 volatile boolean exit = false; 6 7 @Override 8 public void run() { 9 while (!exit) { 10 System.out.println(getName() + " is running"); 11 try { 12 Thread.currentThread().join(); 13 } catch (InterruptedException e) { 14 System.out.println("week up from block..."); 15 // 在异常处理代码中修改共享变量的状态 16 exit = true; 17 } 18 } 19 System.out.println(getName() + " is exiting..."); 20 } 21 22 public static void main(String[] args) throws InterruptedException { 23 InterruptThread interruptThread = new InterruptThread(); 24 System.out.println("Starting thread..."); 25 interruptThread.start(); 26 Thread.sleep(3000); 27 System.out.println("Interrupt thread...: " + interruptThread.getName()); 28 // 设置退出标识为true 29 interruptThread.exit = true; 30 interruptThread.interrupt(); 31 // 主线程休眠3秒以便观察线程interruptThread的中断情况 32 Thread.sleep(3000); 33 System.out.println("Stopping application..."); 34 } 35 }
若第30行注释,则在执行到第25行,运行到第12行时,线程一直在等待自己执行完,所以线程阻塞了,导致29行的标识无用。
此种情况只需要主动的中断线程即可(第30行代码)。
废弃的终止方式
1、Thread.stop:
- Thread.stop()来强行终止线程,但是stop方法是很危险的,就像突然拔掉计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果。
- Thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeath这个Error强行释放子线程持有的锁,导致被保护的资源出现线程安全问题。
2、Thread.suspend / Thread.resume:
- Thread.suspend
- 使线程暂停。
- 不会释放类似锁这样的资源。
- Thread.resume
- 使线程恢复。
- 如果之前没有使用suspend暂停线程,则不起作用。
- suspend()和resume()必须要成对出现,否则非常容易发生死锁。因为suspend方法并不会释放锁,如果使用suspend的目标线程对一个重要的系统资源持有锁,那么没任何线程可以使用这个资源,直到要suspend的目标线程被resumed,如果一个线程在resume目标线程之前尝试持有这个重要的系统资源锁再去resume目标线程,这两条线程就相互死锁了。
3、Runtime.runFinalizersOnExit:
- 这个方法本身就是不安全的。
- 它可能导致终结器(finalizers)被在活跃对象上被调用,而其他线程正在并发操作这些对象。而且,这个调用不是“线程安全”的,因为它设置了一个VM全局标志。