Java中停止线程的原则是什么?
在Java中,最好的停止线程的方式是使用中断interrupt,但是这仅仅是会通知到被终止的线程“你该停止运行了”,被终止的线程自身拥有决定权(决定是否、以及何时停止),这依赖于请求停止方和被停止方都遵守一种约定好的编码规范。
任务和线程的启动很容易。在大多数时候,我们都会让它们运行直到结束,或者让它们自行停止。然而,有时候我们希望提前结束任务或线程,或许是因为用户取消了操作,或者服务需要被快速关闭,或者是运行超时或出错了。
要使任务和线程能安全、快速、可靠地停止下来,并不是一件容易的事。Java没有提供任何机制来安全地终止线程。但它提供了中断( Interruption),这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
这种协作式的方法是必要的,我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态。相反,在编写任务和服务时可以使用一种协作的方式:当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。
生命周期结束(End-of-Lifecycle)的问题会使任务、服务以及程序的设计和实现等过程变得复杂,而这个在程序设计中非常重要的要素却经常被忽略。一个在行为良好的软件与勉强运的软件之间的最主要区别就是,行为良好的软件能很完善地处理失败、关闭和取消等过程。
停止线程
- 使用interrupt来通知线程停止,而不是强制停止。
a. 注意只是通知,并不是让线程立即停止。
b. 只需要通知线程,你需要停止,线程通过响应interrupt来在合适的地方停止或者退出线程的执行。 - 为什么要这样做呢?
线程在停止时,所使用的资源没有释放造成资源浪费甚至BUG,数据处理没有完成造成数据不一致,这样的问题往往会令我们头疼。而如果使用interrupt来通知它,线程可以进行停止前的释放资源,完成必须要处理的数据任务,诸如此类的事情,就会令我们的程序的健壮性提升,也减少了系统出现问题的几率 - 停止普通线程示例
public class RightStopThreadWithoutSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int num = 0;
long start = System.currentTimeMillis();
while (num <= Integer.MAX_VALUE / 2) {
if (num % 1000 == 0) {
System.out.println(num + " 是10000的倍数!");
}
// 注意 如果不interrupted的响应处理,线程不会处理interrupt
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " was interrupted");
break;
}
num++;
}
long end = System.currentTimeMillis();
System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
• 停止阻塞线程
• 如果线程在阻塞状态,比如调用sleep()方法时,响应interrupt的方式是抛出异常。
• 所以停止阻塞线程使用try-catch来实现
public class RightStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int num = 0;
long start = System.currentTimeMillis();
while (num <= 300) {
if (num % 100 == 0) {
System.out.println(num + " 是100的倍数!");
}
num++;
// 注意 如果不interrupted的响应处理,线程不会处理interrupt
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " was interrupted");
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + " thread was interrupted by sleep!");
}
long end = System.currentTimeMillis();
System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
});
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
0 是100的倍数!
100 是100的倍数!
200 是100的倍数!
300 是100的倍数!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadWithSleep.lambda$main$0(RightStopThreadWithSleep.java:26)
at java.lang.Thread.run(Thread.java:748)
Thread-0 thread was interrupted by sleep!
Task was finished! 0.505s
Process finished with exit code 0
- 每个循环中都有sleep
- 如果每个循环都有阻塞, 我们就可以不用每个循环都判断一次interrupted了,只需要处理catch的异常即可。
public class RightStopThreadWithSleepInLoop {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int num = 0;
long start = System.currentTimeMillis();
try {
while (num <= 10000) {
if (num % 100 == 0) {
System.out.println(num + " 是100的倍数!");
}
Thread.sleep(10);
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + " thread was interrupted by sleep!");
}
long end = System.currentTimeMillis();
System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
});
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
0 是100的倍数!
100 是100的倍数!
200 是100的倍数!
300 是100的倍数!
400 是100的倍数!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadWithSleepInLoop.lambda$main$0(RightStopThreadWithSleepInLoop.java:19)
at java.lang.Thread.run(Thread.java:748)
Thread-0 thread was interrupted by sleep!
Task was finished! 5.005s
Process finished with exit code 0
这个地方需要注意一个地方,try-catch的位置,这个不难看出,如果是下列代码,则不能interrupt,会死循环。
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int num = 0;
long start = System.currentTimeMillis();
while (num <= 10000) {
if (num % 100 == 0) {
System.out.println(num + " 是100的倍数!");
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + " thread was interrupted by sleep!");
}
num++;
}
long end = System.currentTimeMillis();
System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
});
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
0 是100的倍数!
100 是100的倍数!
200 是100的倍数!
300 是100的倍数!
400 是100的倍数!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.imyiren.concurrency.threadcore.stopthread.CantInterrupt.lambda$main$0(CantInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
Thread-0 thread was interrupted by sleep!
500 是100的倍数!
600 是100的倍数!
700 是100的倍数!
800 是100的倍数!
…
InterruptedException处理最佳实践(业务中如何使用?)
• 绝对不应屏蔽中断请求
- 非run()方法直接抛出interruptedException,不做处理
• 首先我们不能在业务方法中直接处理掉异常,不能try-catch,需要直接抛出。
• 那么我们在业务方法中处理了这个异常会怎么样呢?那么如果run()方法中有循环,则无法退出循环。。
• 最佳实践:在业务代码中有InterruptedException 优先选择 在方法签名中抛出异常,不处理。那么就会使InterruptedException在run()方法中强制try-catch。如下代码
public class RightStopThreadInProd implements Runnable {
@Override
public void run() {
try {
while (true) {
System.out.println("business code...");
// 假设调用其他方法
throwInMethod();
System.out.println("business code...");
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("catch interruptedException handle interrupted! ...");
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(1000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightStopThreadInProd());
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
business code…
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadInProd.throwInMethod(RightStopThreadInProd.java:28)
at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadInProd.run(RightStopThreadInProd.java:18)
at java.lang.Thread.run(Thread.java:748)
catch interruptedException handle interrupted! …
Process finished with exit code 0
- 直接在业务方法中恢复中断(当业务方法无法抛出或不想抛出时)
• 就是利用中断机制,调用Thread.currentThread().interrupt() 来恢复中断
public class RightStopThreadInProd2 implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("business code...");
// 假设调用其他方法
reInterrupted();
System.out.println("business code...");
}
}
private void reInterrupted() {
try {
System.out.println("reInterrupted method business! ");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " reInterrupted interrupt");
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightStopThreadInProd2());
thread.start();
Thread.sleep(1500);
thread.interrupt();
}
}
business code…
reInterrupted method business!
business code…
business code…
reInterrupted method business!
Thread-0 reInterrupted interrupt
business code…
Process finished with exit code 0
响应中断的一些方法
- bject.wait(…) Thraed.sleep(…) Thread.join(…)
- java.util.concurrent.BlockingQueue.take()/put(E)
- java.util.concurrent.locks.Lock.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.CyclicBarrier.await()
- java.util.concurrent.Exchanger.exchange(V)
- java.nio.channels.InterruptibleChannel的相关方法
- java.nio.channels.Selector的相关方法
错误停止线程的方式
• 被弃用的方法:stop()、suspend()、resume()
a. stop方法停止
• 由下代码可看到,很有可能,代码在计算过程中,最后一部分数据没被计算进去。
• 代码具有偶然性,可能出错,可能不会出错。
• 可想如果发生在银行转账过程中,那么最终的金额对不上。。。这就是个大故障了。。
public class ThreadStop {
public static void main(String[] args) throws InterruptedException {
final Data data = new Data();
Thread thread = new Thread(() -> {
while (true) {
int randomInt = (int) (Math.random() * 11);
int sum = 0, temp;
for (int i = 1; i < data.nums.length + 1; i++) {
temp = randomInt * i;
sum += temp;
data.nums[i-1] += temp;
System.out.println("i=" + i + ", num=" + temp);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
//...
}
}
data.total -= sum;
}
});
thread.start();
Thread.sleep(931);
thread.stop();
System.out.println(data);
}
}
class Data{
int total = Integer.MAX_VALUE;
int[] nums = new int[5];
@Override
public String toString() {
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return "Data{" +
"total=" + total +
", nums=" + Arrays.toString(nums) +
", sumNums=" + sum +
", sum=" + (sum + total) +
", Integer.MAX_VALUE=" + Integer.MAX_VALUE +
'}';
}
}
a. suspend和resume
• suspend()方法会使得目标线程停下来,但却仍然持有在这之前获得的锁定。这样一来很容造成死锁。
• 而resume()方法则是用于 恢复通过调用suspend()方法而停止运行的线程
• 这两个方法都已被废弃,所以不推荐使用。
• 用volatile设置boolean标志位
b. 案例一:可以停止
public class VolatileWrong implements Runnable{
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
while (!canceled) {
num++;
if (num % 100 == 0) {
System.out.println("num = " + num);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
//...
}
}
}
public static void main(String[] args) throws InterruptedException {
VolatileWrong volatileWrong = new VolatileWrong();
Thread thread = new Thread(volatileWrong);
thread.start();
Thread.sleep(2345);
System.out.println("开始停止线程...");
volatileWrong.canceled = true;
}
}
num = 100
num = 200
开始停止线程…
Process finished with exit code 0