文章目录
线程的结束有几种类型
- 寿终正寝,完成了使命,自然死亡。
- 线程在执行的过程中,因为耗时过长或者各种原因被中断。
中断线程该如何处理呢?
1. 错误的方法
1.1 stop()方法不要使用
public class StopIsNotSafe {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true) {
System.out.println("我在换衣服");
System.out.println("我在脱裤子");
System.out.println("我在穿裤子");
}
});
t1.start();
Thread.sleep(10);
t1.stop();
}
}
运行结果,脱了裤子没有穿,程序运行到一半:
哪怕该线程正在执行某个事务,锁定了某个对象,此时仍然会被粗暴中止,往往此时程序正在做着重要的事情啊,一旦中止本应该完整的逻辑就被破坏了。
Object o = new Object();
Thread t2 = new Thread(()->{
synchronized (o) {
while (true) {
System.out.println("t2 我在换衣服");
System.out.println("t2 我在脱裤子");
System.out.println("t2 我在穿裤子");
}
}
});
t2.start();
Thread.sleep(10);
t2.stop();
运行结果,脱了裤子没有穿,程序运行到一半:
1.2 suspend() 也不建议使用
很简单,因为suspend方法调用后,线程会被暂停,但是锁资源仍然没有释放。
万一忘记resume(),或者调用resume时出现了问题,就会出现死锁。
况且这个方法并不是中断,只是暂停。
public class ResumeIsNotSafe {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread t1 = new Thread(()->{
for(int i=0;i<5;i++) {
synchronized (o) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 :"+ LocalDateTime.now());
}
}
});
t1.start();
Thread.sleep(100);
t1.suspend();
Thread.sleep(10000);
//如果resume失效了。则无法执行t2了。
System.out.println("此时,t1进入暂停状态,但是它仍然持有锁");
//t1.resume();
Thread t2 = new Thread(()->{
for(int i=0;i<5;i++) {
synchronized (o) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 :"+ LocalDateTime.now());
}
}
});
t2.start();
}
}
执行结果,t2线程无法获得锁,死锁:
2.正确的方法
2.1 使用volatile + 标志位
有句话叫做“凯撒的归凯撒,上帝的归上帝”,线程自己的事情,应该由线程自己控制。
我们可以在线程类内部设定一个flag字段,主线程通过该字段能够通知t1线程,线程监控该字段,当该字段为True,则线程正常运行,否则就停下来。
public class UsingFlagToStop {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
//线程t1每秒打印一次
Thread t1 = new Thread(()->{
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1:"+ LocalDateTime.now());
}
});
t1.start();
Thread.sleep(4000);
flag=false;
}
}
执行结果如下,当主线程4秒后设置flag为false后,t1线程检测到标志被设置,于是停止了运行。
不足之处 ,volatile标志位无法及时通知正在wait()和sleep()的线程。
2.2 使用线程的interrupt()方法
了解关于interrupt有关的三个方法。
- void interrupt() ,例如t1.interrupt(),通过设置t1线程的中断标志通知线程。此时线程会接收到InterruptedException异常,从而进行控制。
- boolean isInterrupted() 线程可以通过该方法查询自身的中断标志。
- static boolean interrupted() ,该方法是静态方法,查看线程当前的中断的标志,如果是被打断状态,会重置恢复标志。
2.2.1 方式1、interrupt() 后进入Exception处理。
interrupt的java内置的解决方案,能够在wait()和sleep()时,及时通知,更加优雅有效。
线程t1 一开始运行后,就睡眠,通过调用t1.interrupt(),将t1打断,不用等那么久了。
public class InterruptToStop {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread t1 = new Thread(()->{
synchronized(o) {
System.out.println(LocalDateTime.now()+" t1:等等我先喝口水上个厕所");
try {
Thread.sleep(10000);
//对于正在wait的线程也可以中断哦。
//o.wait();
} catch (InterruptedException e) {
System.out.println(LocalDateTime.now()+" t1:摸鱼的我,被打断了,赶紧善后。");
}
System.out.println(LocalDateTime.now()+" t1:事情忙完了。");
}
});
Thread t2 = new Thread(()->{
synchronized(o) {
System.out.println(LocalDateTime.now()+" t2:终于轮到我上场了");
}
});
t1.start();
//确保t1先启动,拿到锁对象o。
Thread.sleep(50);
t2.start();
t1.interrupt();
}
}
运行结果显示,t1被打断后,进入异常处理环节,在这里实现更加优雅的打断。
2.2.2 方式2、线程不断检查自身中断状态。
也可以通过线程检查自身interrupted状态来控制。
这种方式适用于线程内部没有调用wait、sleep等方法的情况下。因为在这种情况下,是没有对InterruptedException进行特殊处理。所以需要线程不断检查interrupted状态。
public class InterruptToStop2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (!Thread.interrupted()) {
System.out.println("t1 处理事情 "+ LocalDateTime.now());
}
System.out.println("我被打断了,做后面的事情");
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
运行结果,t1检查到被interrupt,被中断。
2.3 如何打断正在抢锁(BLOCKED)状态的线程
interrupt()方法对于正在抢锁的线程是无能为力的。
做一个实验,线程进入blocked状态后,通过interrupt打断,没有效果。
public class InterruptToStop3 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread t1 = new Thread(()->{
synchronized (o) {
System.out.println(LocalDateTime.now()+",t1:我获得了锁,我将占有10秒钟");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
//保证t1先执行
Thread.sleep(50);
Thread t2 = new Thread(()->{
System.out.println(LocalDateTime.now()+",t2 我将争抢锁");
synchronized (o) {
System.out.println(LocalDateTime.now()+",t2:我终于抢到锁了");
}
});
t2.start();
Thread.sleep(50);
System.out.println("t2 状态:"+ t2.getState());
//此时无法打断。进入blocked状态。
t2.interrupt();
}
}
运行结果,t2非要拿到锁后继续执行。感觉是一个很执拗的线程。
解决方案是,使用ReentrantLock锁对象,调用lockInterruptibly()方法加锁。
public class InterruptToStop4 {
public static void main(String[] args) {
ReentrantLock o = new ReentrantLock();
Thread t1 = new Thread(()->{
try {
o.lock();
System.out.println(LocalDateTime.now()+",t1:我获得了锁,我将占有10秒钟");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
} finally {
o.unlock();
}
});
t1.start();
//保证t1先执行
try {
Thread.sleep(50);
} catch (InterruptedException e) {
//e.printStackTrace();
}
Thread t2 = new Thread(()->{
System.out.println(LocalDateTime.now()+",t2 我将争抢锁");
try {
o.lockInterruptibly();
System.out.println(LocalDateTime.now()+",t2 我终于拿到锁啦!!!");
} catch (InterruptedException e) {
System.out.println(LocalDateTime.now()+",t2 我被中断了!");
}finally {
if(o.isHeldByCurrentThread())
o.unlock();
}
});
t2.start();
//保证t1先执行
try {
Thread.sleep(50);
} catch (InterruptedException e) {
//e.printStackTrace();
}
System.out.println("t2 状态:"+ t2.getState());
//此时无法打断。进入blocked状态。
t2.interrupt();
}
}
运行结果显示,此时可以中断正在抢锁的线程。而且该线程其实并没有进入BLOCKED状态,而是WATING状态。也就是说JAVA对这种抢锁的方式,其处理方法是有不同的。
运行结果:
总结
- 对于线程中断的处理要交给线程自身。
- 可以自己定义标志位来处理。
- 也可以使用interrupt方法来处理,它能适应的场景很多,在线程sleep()、wait(),甚至抢锁时都能中断。
多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。