线程中断机制
如何终止一个线程
void interrupt()
static boolean interrupt()
boolean isInterrupted()
什么是线程中断
首先
一个线程不应该有其他线程来强制中断或停止,而是应该有线程自己自行停止,自己来决定自己的命运。
所以,Thread.stop ,Thread.suspend , Thread.resume都已经被废弃了
其次
在java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此java提供了一种用于停止线程的协商机制–中断,也即中断标识协商机制
中断只是一种协作协商机制,java没有给中断增加任何语法,中断的过程完全需要程序猿自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
按照你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟该做什么需要你自己写代码实现
每个线程对象都有一个中断标识位,用于表示线程是否被中断;该标识位为ture表示中断,为false表示为中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用
中断相关API
- void interrupt()
实例方法,Just to set the interrupt flag
实例方法interrupt()仅仅设置线程的中断状态为true,发起一个协商而不会立刻停止线程
- static boolean interrupt()
判断线程是否被中断并清除当前中断状态
这个方法做了两件事:
1. 返回当前线程的中断状态,测试当前线程是否已被中断
1. 将当前线程的中断状态清零并重新设置为false,清除线程的中断状态
如果连续两次调用此方法,则第二次调用将返回false,因为连续两次的结果可能不一样
- boolean isInterrupted()
判断当前线程是否被中断(通过检查中断标志位)
面试题
-
如何停止中断运行中的线程
-
volatile变量实现
public class ThreadTest { static volatile boolean isStop = false;// volatile 保证可见性 public static void main(String[] args) { new Thread(() -> { while (true) { if (isStop) { System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止"); break; } System.out.println("t1 ---- hello world"); } }, "t1").start(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { isStop = true; }, "t2").start(); } }
-
AtomicBoolean实现
public class ThreadTest { static AtomicBoolean atomicBoolean = new AtomicBoolean(false); public static void main(String[] args) { new Thread(() -> { while (true) { if (atomicBoolean.get()) { System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止"); break; } System.out.println("t1 ---- hello world"); } }, "t1").start(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { atomicBoolean.set(true); }, "t2").start(); } }
-
通过Thread类自带的中断api实例方法实现
public class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止"); break; } System.out.println("t1 ---- hello world"); } }, "t1"); t1.start(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { t1.interrupt(); System.out.println("发出协商"); }, "t2").start(); } }
总结:
具体来说,当对一个线程,调用interrupt()时
-
如果线程处于正常活动状体,那么该线程的中断标志设置为true,仅此而起
被设置中断标志位的线程将继续正常运行,不受影响
所以,interrupt()并不能真正中断线程,需要被调用的线程自己进行配合才行
-
如果线程处于阻塞状态(例如处于sleep,wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptException异常
-
中断不活动的线程不受任何影响
-
-
-
当前线程的中断标识为true,是不是线程就立刻停止
public static void main(String[] args) { // 实例方法interrupt()仅仅时设置线程的中断状态位置设置为true,不会停止线程 Thread t1 = new Thread(() -> { for (int i = 0; i < 300; i++) { System.out.println("----" + i); } System.out.println("t1线程调用interrupt()后的中断标志 02" + Thread.currentThread().isInterrupted()); }, "t1"); t1.start(); System.out.println("t1线程默认的中断标志 : "+t1.isInterrupted()); // 暂停毫秒 try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);} t1.interrupt(); System.out.println("t1线程 interrupt()后的中断标志 : "+t1.isInterrupted()); try {TimeUnit.MILLISECONDS.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);} System.out.println("t1线程 2000 interrupt()后的中断标志 : "+t1.isInterrupted()); }
public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "\t" + "中断标志位:" + Thread.currentThread().isInterrupted() + " 程序结束"); break; } try { Thread.sleep(200); } catch (InterruptedException e) { // Thread.currentThread().interrupt(); throw new RuntimeException(e); } System.out.println("---- hello "); } }, "t1"); t1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } new Thread(() -> { t1.interrupt(); },"t2").start(); }
sleep()方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过调用th.interrupt()方法在此将中断标识置为true,这就是导致无限循环的原因
- 静态方法Thread.interrupt(),谈谈理解
返回之前状态并重置为false
LockSupport
是什么
LockSupport 用于创建锁和其他同步类的基本线程阻塞原语
park():除非许可证可用,否则禁用当前线程以进行线程调度
unpark(Thread thread):如果给定线程尚不可用,则为其提供许可证
线程等待唤醒机制
-
使用Object的wait()方法线程等待,使用Object中的notify()方法唤醒线程
缺陷
public static void main(String[] args) { Object objectLock = new Object(); new Thread(() -> { synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t --- come in"); try { objectLock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "\t --- 被唤醒"); } }, "t1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "\t --- 发出通知"); } }, "t2").start(); }
wait和notify方法必须要在同步代码块或者方法里面,且成对使用
先wait后notify才ok
-
使用JUC中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
缺陷
public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { // lock.lock(); try { System.out.println("-- come in"); condition.await(); System.out.println("被唤醒"); } catch (Exception e) { throw new RuntimeException(e); }finally { // lock.unlock(); } }).start(); try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { // lock.lock(); try { System.out.println("发出通知"); condition.signal(); } catch (Exception e) { throw new RuntimeException(e); }finally { // lock.unlock(); } }).start(); }
Condition中的线程等待和唤醒方法,需要先获取锁
一定要先await后signal,不要反了
-
LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("-- come in");
LockSupport.park();
System.out.println("被唤醒");
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
System.out.println("-- 发通知");
LockSupport.unpark(t1);
},"t2").start();
}
解决:
可以先唤醒后等待
无锁
许可证最多只有一个,注意成对出现
重点说明
LockSupport使用来创建锁和其他同步类的基本线程阻塞原语
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证
形象的理解:
线程阻塞需要消耗凭证(permit),这个凭证组多只有一个
当调用哦park方法时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出
- 如果无凭证,就必须阻塞等待凭证可用
而unpark则相反,他会增加一个凭证,但凭证最多只能1个,累加无效
面试题
-
为什么可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
先发放了凭证后续可以畅通无阻
-
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消耗两个凭证,证不够,不能放行。