一、如何正确的停止线程
线程的停止有两种情况,一种是程序运行完毕,线程自然停止;而另一种是用户主动取消。
Java语言的设计,本身并没有一种机制来安全停止线程,停止线程处于一种合作机制。所以,想要停止其他线程其实只是一种通知,而非强制。Java让被中断的线程本身拥有决定权,它不但可以决定何时去响应这个中断,何时去停止,还可以决定停不停止。如果我们想要停止某个线程,但那个线程自己不想被中断,我们对此是无能为力的,这是一种规范。
其实大多数的时候,想要线程停止,至少要让他运行到结束。比如电脑关机,要让它处理完现在做的事情,保存一下当前的状态等。我们对其他线程一无所知,而且不希望对方陷入混乱,所以线程是否要停止,何时应该停止,应该由自己决定。
所以,Java中用了interrupt,中断,来通知其他线程,希望对方可以停止。
那么一下,分情况给出了interrupt方法的使用。
1. 在没有阻塞的情况下:
public class RightWayStopThreadWithoutSleep implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
@Override
public void run() {
int num = 0;
while(!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE /2){
if(num % 10000 == 0){
System.out.println(num + "是10000的倍数");
}
num++;
}
System.out.println("任务运行结束了");
}
}
没有中断的时候,输出 1073740000是10000的倍数,任务运行结束了
执行thread.interrupt(),通知线程停止,并且被中断的线程也理会此次通知,在循环时判断加入Thread.currentThread().isInterrupted()判断,进行响应,所以输出382800000是10000的倍数,任务运行结束了。可以看出,线程直接停止,并没有抛出异常
2.在阻塞情况下
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num <= 300 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
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 stopthreads.RightWayStopThreadWithSleep.lambda0(RightWayStopThreadWithSleep.java:19)
at java.lang.Thread.run(Thread.java:745)
可见,当运行thread.interrupt()的时候,线程正好在阻塞状态下,会抛出InterruptedException异常。
那么当每次循环时,都处于阻塞情况下,如此中断是否可行呢?把上面的代码稍微改一下:
public class RightWayStopThreadWithSleepEveryLoop {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num <= 1000 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
打印结果:
0是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at stopthreads.RightWayStopThreadWithSleepEveryLoop.lambda0(RightWayStopThreadWithSleepEveryLoop.java:17)
at java.lang.Thread.run(Thread.java:745)
100是100的倍数
200是100的倍数
300是100的倍数
还在一直执行下去,可见把try/catch放在while中,只会中断循环中的一次执行,并不会退出循环,更不会停止线程,难道循环条件!Thread.currentThread().isInterrupted()失效了么?
其实,在Java语言的设计中,在响应中断的时候,会再此把响应状态复原,所以想要用Thread.currentThread().isInterrupted()来判断下一次的中断状态是不可行的。
那么我们把try/catch放在循环外面捕获异常情况下,是否可以如预期,终止线程呢?试一下。
public class RightWayStopThreadWithSleepEveryLoop {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 1000 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
执行结果如下:
0是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at stopthreads.RightWayStopThreadWithSleepEveryLoop.lambda0(RightWayStopThreadWithSleepEveryLoop.java:18)
at java.lang.Thread.run(Thread.java:745)
可见线程被中断了,程序没有再运行下去。那么进一步思索,在循环中加入!Thread.currentThread().isInterrupted()判断,是否画蛇添足了呢?Thread.sleep()方法本身可以抛出异常,而且循环条件判断运行时间过短,其实没有必要再在此进行中断状态的判断了。
3.从代码架构角度来讲,很多时候,我们对中断异常的捕获,很可能是捕获一个抛出异常的方法,那么如何来处理呢?
public class RightWayStopThreadInProd implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("go");
throwInMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(600);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
运行结果如下:
go
go
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at
stopthreads.RightWayStopThreadInProd.throwInMethod(RightWayStopThreadInProd.java:28)
at stopthreads.RightWayStopThreadInProd.run(RightWayStopThreadInProd.java:19)
at java.lang.Thread.run(Thread.java:745)
线程可以正常停止,是没有问题的。那么是否有其他的写法?可以把子方法中的中断异常传递到主方法中呢?
public class RightWayStopThreadInProd2 implements Runnable {
@Override
public void run() {
while (true) {
if(Thread.currentThread().isInterrupted()){
System.out.println("程序运行结束");
break;
}
reInterrupt();
}
}
private void reInterrupt() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
可见,这种传递是可行的。而且在主方法中处理异常是更恰当的处理方式。
二、响应中断的方法列表
Object.wait()/wait(long)/wait(long,int)
Thread.sleep(long)/sleep(long int)
Thread.join()/join(long)/join(long,int)
Java.util.concurrent.BlockingQueue.take()/put(E)
Java.util.concurrent.locks.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的相关方法
三、错误停止的方法
1. 被弃用的stop,suspend和resume方法
用stop来停止线程,会导致线程运行一半突然停止,没有办法完成一个基本单位的操作。
public class StopThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("连队" + i + "开始领取武器");
for (int j = 0; j < 10; j++) {
System.out.println(j);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("连队" + i + "已经领取完毕");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new StopThread());
thread.start();
try {
thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
}
}
运行结果:
连队0开始领取武器
0
1
2
3
4
5
6
7
8
9
连队0已经领取完毕
连队1开始领取武器
0
1
2
3
4
5
6
7
8
9
可见,用stop并不能完整执行完,会导致数据错乱。
关于为什么弃用Thread.stop,来看一下Oracle官网上的解释:
因为它本质上是不安全的。停止线程会导致它解锁已锁定的所有监视器。(当ThreadDeath异常传播到堆栈中时,监视器将被解锁。)如果先前受这些监视器保护的任何对象处于不一致状态,则其他线程现在可以以不一致的状态查看这些对象,据说这些物体已被损坏。当线程对受损对象进行操作时,可能会导致任意行为。这种行为可能很微妙并且难以检测,或者可能是明显的。与其他未经检查的异常不同,它会ThreadDeath默默地杀死线程;因此,用户没有警告他的程序可能被损坏。腐败可能在实际损坏发生后的任何时间显现,甚至未来几小时或几天。
suspend并不会破坏对象,它会让一个线程挂起,而且是带着锁挂起的,这样就很容易造成死锁。如果其他线程不及时把它唤醒,或者需要这把锁才能将它唤醒,则会造成死锁。
2.用volatile设置boolean标记位
这种错误的方法,为什么看起来是可行的
public class WrongWayVolatile implements Runnable {
private volatile boolean canceled = false;//可见性
@Override
public void run() {
int num = 0;
try {
while (num <= 10000 && !canceled) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数。");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
WrongWayVolatile wrongWayVolatile = new WrongWayVolatile();
Thread thread = new Thread(wrongWayVolatile);
thread.start();
Thread.sleep(500);
wrongWayVolatile.canceled = true;
}
}
运行结果如下:
0是100的倍数。
100是100的倍数。
200是100的倍数。
300是100的倍数。
400是100的倍数。
运行结果也正确,可以停下来。但是这种方法存在局限性,也就是在非阻塞情况下是可行的。那么阻塞的情况会是怎么样的呢?那么我们先来验证一下最常用的生产者消费者模式。
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
//一旦消费不需要更多数据了,我们应该让生产者也停下来
producer.canceled = true;
System.out.println(producer.canceled);
}
}
class Producer implements Runnable {
public volatile boolean canceled = false;
private BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
运行结果:
0是100的倍数,被放到仓库中了。
100是100的倍数,被放到仓库中了。
200是100的倍数,被放到仓库中了。
300是100的倍数,被放到仓库中了。
400是100的倍数,被放到仓库中了。
500是100的倍数,被放到仓库中了。
600是100的倍数,被放到仓库中了。
700是100的倍数,被放到仓库中了。
800是100的倍数,被放到仓库中了。
900是100的倍数,被放到仓库中了。
0被消费了
1000是100的倍数,被放到仓库中了。
消费者不需要更多数据了。
true
但实际上,线程并没有停止。
原因,当线程在长时间阻塞情况下是无法即使唤醒的。一直阻塞在storage.put(num),而没有再去判断while中的循环条件。看interrupt是否可以停止这种。
public class WrongWayVolatileFixed {
public static void main(String[] args) throws InterruptedException {
WrongWayVolatileFixed body = new WrongWayVolatileFixed();
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = body.new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = body.new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
//一旦消费不需要更多数据了,我们应该让生产者也停下来
// producer.canceled = true;
producerThread.interrupt();
// System.out.println(producer.canceled);
}
class Producer implements Runnable {
public volatile boolean canceled = false;
private BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
class Consumer {
private BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
}
运行结果:
8000被消费了
9000是100的倍数,被放到仓库中了。
8100被消费了
9100是100的倍数,被放到仓库中了。
消费者不需要更多数据了。
生产者结束运行
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizerConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
at stopthreads.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:51)
at java.lang.Thread.run(Thread.java:745)
可以正常停止下来
四、interrupt源码解析
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
interrupt源码.png
可以看出,在阻塞状态下,线程也是可以被唤醒执行中断的。
五、一些相近方法的解析:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public class RightWayInterrupted {
public static void main(String[] args) {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
for(;;){
}
}
});
//启动线程
threadOne.start();
//设置中断标志
threadOne.interrupt();
//获取中断标志
System.out.println("isInterrupted:"+ threadOne.isInterrupted());
//获取中断标志并重置
System.out.println("isInterrupted:"+ threadOne.interrupted());
System.out.println("isInterrupted:"+ Thread.interrupted());
//获取中断标志
System.out.println("isInterrupted:"+ threadOne.isInterrupted());
}
}
运行结果:
isInterrupted:true
isInterrupted:false
isInterrupted:false
isInterrupted:true
说明interrupted的是执行这个方法的线程,和threadOne或者Thread谁执行没有关系没有关系
网友评论