如何优雅的停止一个线程
1.为什么要停止线程
- 某个正在进行的线程,用户主动取消的操作
- 服务突然被关闭
- 程序运行超时或者出错
2.为何说要正确的停止线程
- 从设计原则上,Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。
- 举个例子: 银行每天18点下班,我17.55到银行办理存钱业务,然后柜台人员介绍了定期和活期两种存钱方式, 当我选择定期存款,然后把我的100块钱拿给柜台人员,柜台人员拿到钱的时候,正好现在18点了,柜台人员定的下班提醒闹钟到了,你说柜台人员能把钱仍在一边,然后直接关闭窗口下班么?这样是肯定不行的,她要把给你存钱的业务处理完以后,才能关闭窗口下班。
3.使用interrupt()停止线程
- 用interrupt() 方法来通知线程中断,此方法中只是通知,而不是强制。
- 当调用interrupt()方法以后,收到中断信号的线程是不会立即中断的,只是将自己中断标记设置为true,当线程在执行过程中,应定期去检查这个中断标记,如果中断标记是true,说明程序是想停止当前线程。
4.线程在通常三种情况下停止
4.1 普通情况
下面展示一些 内联代码片
。
/**
* 1. 普通情况
*/
public class ThreadDemo2 implements Runnable {
/**
* 计算 10000 的倍数
*/
@Override
public void run() {
int count = 1;
/**
* isInterrupted() 获取当前线程的中断状态 true = 中断,false = 没中断
* 如果程序没有加上这个 isInterrupted() 中断状态判断的话,即使发出中断信号,程序也还是是正常结束
*/
while (count < (Integer.MAX_VALUE / 2) && !Thread.currentThread().isInterrupted()) {
if (count % 10000 == 0) {
System.out.println("count = " + count);
}
count++;
}
System.out.println("程序执行结束,中断信号: " + Thread.currentThread().isInterrupted());
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadDemo2());
t1.start();
try {
// 阻塞两秒后,发出中断信号
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
}
程序响应中断执行结果
程序没有中断执行结果
4.2 线程阻塞情况
/**
* 2. 线程阻塞情况
*/
public class ThreadDemo1 implements Runnable {
public static final SimpleDateFormat FORMAT_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 每隔一秒打印当前的时间
*/
@Override
public void run() {
try {
while (true) {
System.out.println(FORMAT_TIME.format(new Date(System.currentTimeMillis())));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// sleep()方法会响应中断,并且会抛出InterruptedException 异常,但异常抛出以后会清除中断标记
System.out.println("捕捉到中断异常,程序退出. 中断信号: "+ Thread.currentThread().isInterrupted());
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadDemo1());
t1.start();
try {
// 阻塞三秒后,发出中断信号
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
}
执行结果
4.3 传递中断情况
但是我们开发时候会遇到把try/catch 写在循环里面,如果sleep() 期间响应中断抛出一个异常后,会清除当前线程的中断信号。
上述的问题解决办法可以传递中断信号来解决
public class ThreadDemo3 implements Runnable {
public static final SimpleDateFormat FORMAT_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void run() {
// 模仿 try/catch 在循环里面中断情况,
while (true && !Thread.currentThread().isInterrupted()) {
System.out.println(FORMAT_TIME.format(new Date(System.currentTimeMillis())));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// 捕捉到中断异常以后,再次发出中断信号
Thread.currentThread().interrupt();
System.out.println("中断信号: " + Thread.currentThread().isInterrupted());
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadDemo3());
t1.start();
try {
// 阻塞两秒后,发出中断信号
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
}
传递中断执行结果
还有一种方法就是在run方法中调用别的方法
public class ThreadDemo4 implements Runnable {
public static final SimpleDateFormat FORMAT_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 每隔一秒打印当前的时间
*/
@Override
public void run() {
while (true && !Thread.currentThread().isInterrupted()) {
try {
paintTime();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
/**
* 该方法被线程调用,要响应中断异常,把异常向上抛出或者再次中断,而不是自己生吞
*
* @throws InterruptedException
*/
public static void paintTime() throws InterruptedException {
System.out.println(FORMAT_TIME.format(new Date(System.currentTimeMillis())));
Thread.sleep(1000);
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadDemo4());
t1.start();
try {
// 阻塞两秒后,发出中断信号
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
}
5.说说被弃用的stop(),suspend() 和 resume()方法
- stop()方法会直接让线程停止工作。还是那上面的那个例子来说,18点了柜台小姐姐收到下班提醒闹钟了,直接把钱扔到一边,我的存钱业务也不管了,就把窗口关闭了。我存的钱给人家了,但是账户余额没有加,这样搞得话我想这家银行没开几天就会GG吧。在程序中这样做很容易会脏数据或更加严重的后果。
- suspend() 和 resume() ,它们的问题在于如果线程调用 suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,这样就容易导致死锁问题,因为这把锁在线程被 resume() 之前,是不会被释放的。
6.volatile 方式停止线程
public class ThreadDemo6 implements Runnable {
static volatile boolean flag = true;
/**
* 计算 10000 的倍数
*/
@Override
public void run() {
int count = 1;
while (count < (Integer.MAX_VALUE / 2) && flag) {
if (count % 10000 == 0) {
System.out.println("count = " + count);
}
count++;
}
System.out.println("程序执行结束,flag: " + flag);
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadDemo6());
t1.start();
try {
// 阻塞两秒后,发出中断信号
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
}
}
这个方式和interrupt()方式基本上差不多,这是volatile的使用得看情况,像上述的这种比较简单的方式使用是没有问题,但是在线程长时间等待的时候,volatile标记是无法响应中断。
7. 响应线程中断的方法
- Object.wait() 相关方法
- Thread.sleep() 相关方法
- Thread.join() 相关方法
- java.util.concurrent包中 BlockingQueue.take() / put(E)
- java.util.concurrent包中 Lock.lockInterruptibly()
- java.util.concurrent包中 CountDownLatch.await()
- java.util.concurrent包中 CyclicBarrier.await()
- java.util.concurrent包中 Exchanger.exchange(V)
- java.nio.channels包中 InterruptibleChannel相关方法
- java.nio.channels包中 Selector的相关方法
- 以上方法都是可以响应中断的 ,大约这么多。
8.判断是否中断的相关方法
- public static boolean interrupted() // 获取线程中断标识,并且重置 ps:该方法返回的当前线程的标识,就是调用它的那个线程标识
- public boolean isInterrupted() // 获取线程中断标识