了解了线程的生命周期之后,我们知道,一个线程执行完run方法,或者发生异常之后都会自动终止。
但是,这里要说的是在一个线程A中去终止另一个线程B的情况。
在Thread类里面有2个方法可以达到此目的。stop()和interrupt()
- stop()方法:用stop() 方法会真的杀死线程,不给线程喘息的机会,如果线程持有 ReentrantLock 锁,被 stop() 的线程并不会自动调用 ReentrantLock 的 unlock() 去释放锁,那其他线程就再也没机会获得 ReentrantLock 锁,这实在是太危险了。所以该方法就不建议使用了,类似的方法还有 suspend() 和 resume() 方法,这两个方法同样也都不建议使用了
比如说下方的i++的例子,我们是通过Lock的方式实现原子操作的。
public void addI() {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
这样会出现什么问题呢?当线程A执行时,线程B终止了A的操作,如果A恰好持有共享锁lock,那么它并不会执行unlock操作,导致想获得锁的线程B永远也不能获得锁(不过有的同学可能说给lock加个超时,这个就另说了)
ps:如果是synchronized实现的原子操作,是会主动释放锁的。synchronized原理参考之前写的这篇:大彻大悟synchronized原理,锁的升级
- interrupt():从线程的生命周期了解到,一个线程要终止,必须要从runnable状态到terminated。而线程可能处于休眠状态,interrupt方法恰好提供了此功能,它可以将休眠状态的线程转换到runnable状态,并置标志位为true(此时Thread.currentThread().isInterrupted()==true)。
那么线程B调用了线程A的interrupt方法,就能终止线程A吗?
有2种情况:
- 线程处于waiting或者time_waiting状态:当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果线程B调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException 异常。转换到 WAITING、TIMED_WAITING 状态的触发条件,都是调用了类似 wait()、join()、sleep() 这样的方法,我们看这些方法的签名,发现都会 throws InterruptedException 这个异常。这个异常的触发条件就是:其他线程调用了该线程的 interrupt() 方法。
有一点需要注意的是,抛出异常之后JVM 的异常处理会清除线程的中断状态,我们需要用Thread.currentThread().interrupt() 重新设置了线程的中断状态 - 非waiting状态:这种状态下,线程B调用线程A的interrupt方法,线程A通过Thread.currentThread().isInterrupted()能判断到自己已被中断,
然后自己结束。
所以结合上面2中情况,我们有了下面的例子:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class StopInterruptTest {
private int i = 0;
Lock lock = new ReentrantLock();
public static void main(String[] args) {
StopInterruptTest test = new StopInterruptTest();
Thread t1 = new Thread(() -> {
// 中断标志判断
while (!Thread.currentThread().isInterrupted()) {
test.addI();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 重置标志位
Thread.currentThread().isInterrupted();
}
}
});
t1.start();
// main线程打断线程t1
t1.interrupt();
}
public void addI() {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
但是这样可能有其他问题,就是:如果在t1线程里面,执行的是其他的业务逻辑,其他的业务逻辑并没有正确的处理中断标志。这样就可能出现其他诡异问题。
我们要做的就是,设置自己的标志位,封装自己的终止方法等。比如下面的interruptFlag。
public class StopInterruptTest {
volatile boolean interruptFlag = false;
public static void main(String[] args) {
StopInterruptTest test = new StopInterruptTest();
test.testInterrupt();
}
public void testInterrupt() {
Thread t1 = new Thread(() -> {
// 中断标志判断
while (!interruptFlag) {
// 其它业务
// test.addI();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 重置标志位
Thread.currentThread().isInterrupted();
}
}
});
t1.start();
// main线程打断线程t1
myInterrupt(t1);
}
public synchronized void myInterrupt(Thread t) {
interruptFlag = true;
t.interrupt();
}
}
多线程连载:
Java内存模型-volatile的应用(实例讲解)
synchronized的三种应用方式(实例讲解)
可重入锁-synchronized是可重入锁吗?
大彻大悟synchronized原理,锁的升级
一文弄懂Java的线程池
公平锁和非公平锁-ReentrantLock是如何实现公平、非公平的
一图全面了解Java线程的生命周期
守护线程和用户线程的真正区别(实例讲解)
搞定等待通知机制-wait/notify/notifyall的2个经典面试题(实例详解)