本笔记来源于 :尚硅谷JUC并发编程(对标阿里P6-P7)b站视频
文章来自: https://www.yuque.com/gongxi-wssld/csm31d/ln0mq1w7wp1oy99g https://www.yuque.com/liuyanntes/vx9leh/fpy93i https://blog.csdn.net/dolpin_ink/category_11847910.html
脑图 本地:尚硅谷JUC并发编程
在线:尚硅谷JUC并发编程
在线脑图加载时间超长。
1、 线程中断机制 1.1 阿里蚂蚁金服面试题
void
interrupt()
中断此线程
static boolean
interrupted()
测试当前线程是否已被中断
boolean
isInterrupted()
测试此线程是否已经被中断
三个方法了解过吗?用在哪?
如何停止一个运行中的线程?
如何中断一个运行中的线程??
1.2 什么是中断机制? 首先 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。 所以,Thread.stop, Thread.suspend, Thread.resume
都已经被废弃了。
其次 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。 因此,Java提供了一种用于停止线程的协商机制 ——中断 。 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true; 接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
eg.顾客在无烟餐厅中吸烟,服务员希望他别吸烟了,不是强行停止他吸烟,而是给他的标志位打为true,具体的停止吸烟还是要顾客自己停止。(体现了协商机制)
1.3 中断的相关API方法之三大方法说明
public void interrupt()
实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
public static boolean interrupted()
静态方法,Thread.interrupted();
判断线程是否被中断,并清除当前中断状态这个方法做了两件事 :1 返回当前线程的中断状态2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。)
public boolean isInterrupted()
实例方法,判断当前线程是否被中断(通过检查中断标志位)
1.4 大厂面试题:如何使用中断标识停止线程? 1如何停止中断运行中的线程? ① 通过一个volatile变量实现
volatile保证了可见性,t2修改了标志位后能马上被t1看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class interruptDemo { static volatile boolean isStop = false ; 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 volatile" ); } },"t1" ).start(); try {TimeUnit.MILLISECONDS.sleep(20 );} catch (InterruptedException e) {e.printStackTrace();} new Thread (()->{ isStop = true ; },"t2" ).start(); } }
② 通过AtomicBoolean(原子布尔型) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class interruptDemo { static AtomicBoolean atomicBoolean = new AtomicBoolean (false ); public static void main (String[] args) { m1_volatile(); } public static void m1_volatile () { new Thread (()->{ while (true ){ if (atomicBoolean.get()){ System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序终止" ); break ; } System.out.println("t1 ------hello volatile" ); } },"t1" ).start(); try {TimeUnit.MILLISECONDS.sleep(20 );} catch (InterruptedException e) {e.printStackTrace();} new Thread (()->{ atomicBoolean.set(true ); },"t2" ).start(); } }
③ 通过Thread类自带的中断api方法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public static void main (String[] args) { m1_volatile(); }public static void m1_volatile () { Thread t1 = new Thread (() -> { while (true ) { if (Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为true,程序终止" ); break ; } System.out.println("t1 ------hello interrupt " ); } }, "t1" ); t1.start(); try {TimeUnit.MILLISECONDS.sleep(20 );} catch (InterruptedException e) {e.printStackTrace();} new Thread (()->{ t1.interrupt(); },"t2" ).start(); }
API源码分析 实例方法interrupt(),没有返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void interrupt () { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null ) { interrupt0(); b.interrupt(this ); return ; } } interrupt0(); }
1 2 3 4 5 6 7 8 private native void setPriority0 (int newPriority) ; private native void stop0 (Object o) ; private native void suspend0 () ; private native void resume0 () ; private native void interrupt0 () ; private native void setNativeName (String name) ;
实例方法isInterrupted,返回布尔值
1 2 3 4 public boolean isInterrupted () { return isInterrupted(false ); }
1 2 private native boolean isInterrupted (boolean ClearInterrupted) ;
说明 具体来说,当对一个线程,调用 interrupt()
时: 1. 如果线程处于正常活动状态 ,那么会将该线程的中断标志设置为 true
,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt()
并不能真正的中断线程,需要被调用的线程自己进行配合才行。 2. 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt
方法,那么线程将立即退出被阻塞状态 (中断状态将被清除),并抛出一个InterruptedException异常。 3. (中断不活动 的线程不会 产生任何影响,看下面案例)
2 当前线程的中断标识为true,是不是线程就立刻停止?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class InterruptDemo02 { public static void main (String[] args) { Thread t1 = new Thread (()->{ for (int i = 0 ;i < 300 ;i ++){ System.out.println("---------" + i); } System.out.println("after t1.interrupt()---第2次----" +Thread.currentThread().isInterrupted()); },"t1" ); t1.start(); System.out.println("before t1.interrupt()----" +t1.isInterrupted()); t1.interrupt(); try {TimeUnit.MILLISECONDS.sleep(3 );} catch (InterruptedException e) {e.printStackTrace();} System.out.println("after t1.interrupt()---第1次---" +t1.isInterrupted()); try {TimeUnit.MILLISECONDS.sleep(3000 );} catch (InterruptedException e) {e.printStackTrace();} System.out.println("after t1.interrupt()---第3次---" +t1.isInterrupted()); } }
后手案例-深入
在我们基本中断程序的骨架上 + 一个sleep阻塞
中断异常 且 会导致程序无限循环 ,因为其回清楚中断标志位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class InterruptDemo03 { 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) { e.printStackTrace(); } System.out.println("-----hello InterruptDemo03" ); } },"t1" ); t1.start(); try {TimeUnit.MILLISECONDS.sleep(1 );} catch (InterruptedException e) {e.printStackTrace();} new Thread (() -> t1.interrupt()).start(); } }
前文
如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt
方法,那么线程将立即退出被阻塞状态 (中断状态将被清除),并抛出一个InterruptedException异常。
1 sleep方法抛出InterruptedException后,中断标识也被清空置为false ,我们在catch 没有通过th.interrupt()方法再次将中断标志设置为true ,这就导致无限循环了
小总结
中断只是一种协同机制,修改中断标识位仅此而已,而不是立刻stop打断
3 静态方法Thread.interrupted(),谈谈你的理解 api里的第二个
public static boolean interrupted()
静态方法,Thread.interrupted();
判断线程是否被中断,并清除当前中断状态这个方法做了两件事 :
1 返回当前线程的中断状态
2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class InterruptDemo04 { public static void main (String[] args) { System.out.println(Thread.currentThread().getName()+"\t" +Thread.interrupted()); System.out.println(Thread.currentThread().getName()+"\t" +Thread.interrupted()); System.out.println("-----1" ); Thread.currentThread().interrupt(); System.out.println("-----2" ); System.out.println(Thread.currentThread().getName()+"\t" +Thread.interrupted()); System.out.println(Thread.currentThread().getName()+"\t" +Thread.interrupted()); } }
看下源码,interrupted()
对比isInterrupted()
1 2 3 4 5 public static boolean interrupted () { return currentThread().isInterrupted(true ); } private native boolean isInterrupted (boolean ClearInterrupted) ;
1 2 3 4 5 6 public boolean isInterrupted () { return isInterrupted(false ); }private native boolean isInterrupted (boolean ClearInterrupted) ;
2、LockSupport是什么 官方解释:用于创建锁和其他同步类的基本线程阻塞原语。
核心 就是park()
和unpark()
方法
park()
方法是阻塞线程
unpark()
方法是解除阻塞线程
3、线程等待唤醒机制 3种让线程等待和唤醒的方法
使用Object中的wait()
方法让线程等待,使用Object中的notify()
方法唤醒线程
使用JUC包中Condition
的await()
方法让线程等待,使用signal()
方法唤醒线程
LockSupport
类可以阻塞当前线程以及唤醒指定被阻塞的线程
①Object类中的wait和notify方法实现线程等待和唤醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class LockSupportDemo { 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) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"\t" +"---被唤醒了" ); },"t1" ).start(); try { TimeUnit.SECONDS.sleep(3L ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName()+"\t ---发出通知" ); } },"t2" ).start(); } }
异常1 —去掉synchronized
说明要使用wait
和notify
必须加synchronized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class LockSupportDemo { public static void main (String[] args) { Object objectLock = new Object (); new Thread (() -> { System.out.println(Thread.currentThread().getName()+"\t ---- come in" ); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t" +"---被唤醒了" ); },"t1" ).start(); try { TimeUnit.SECONDS.sleep(3L ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { objectLock.notify(); System.out.println(Thread.currentThread().getName()+"\t ---发出通知" ); },"t2" ).start(); } }
异常2 —把notify和wait的执行顺序对换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class LockSupportDemo { public static void main (String[] args) { Object objectLock = new Object (); new Thread (() -> { try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLock) { System.out.println(Thread.currentThread().getName()+"\t ---- come in" ); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"\t" +"---被唤醒了" ); },"t1" ).start(); new Thread (() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName()+"\t ---发出通知" ); } },"t2" ).start(); } }
小总结
wait和notify方法必须要在同步 块或者方法里面,且成对 出现使用
先wait后notify才OK,顺序
②Condition接口中的await后signal方法实现线程的等待和唤醒 正常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class LockSupportDemo { public static void main (String[] args) { Lock lock = new ReentrantLock (); Condition condition = lock.newCondition(); new Thread (() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"\t-----come in" ); condition.await(); System.out.println(Thread.currentThread().getName()+"\t -----被唤醒" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"t1" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName()+"\t" +"我要进行唤醒" ); },"t2" ).start(); } }
异常1原理同上
仍然返回IllegalMonitorStateException
异常2 原理同上
小总结 await
和notify
类似于上面wait
和notify
Condition中的线程等待和唤醒方法,需要先获取锁
一定要先await后signal,不能反了
Object和Condition使用的限制条件
总结
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
③LockSupport类中的park等待和unpark唤醒 是什么 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
官网解释 :
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),
permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
主要方法 API
阻塞
park()/park(Object blocker)
调用LockSupport.park()
时,发现它调用了unsafe类
,并且默认传了一个0
1 2 3 public static void park () { UNSAFE.park(false , 0L ); }
permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。
唤醒
调用LockSupport.unpark();
时,也调用了unsafe类
1 2 3 4 public static void unpark (Thread thread) { if (thread != null ) UNSAFE.unpark(thread); }
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1 (注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class LockSupportDemo { public static void main (String[] args) { Thread t1 = new Thread (()->{ System.out.println(Thread.currentThread().getName()+"\t----------come in" ); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了" ); },"t1" ); t1.start(); new Thread (()->{ LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1" ); },"t2" ).start(); } }
之前错误的先唤醒后等待,LockSupport照样支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class LockSupportDemo { public static void main (String[] args) { Thread t1 = new Thread (()->{ try {TimeUnit.SECONDS.sleep(3 );} catch (InterruptedException e) {e.printStackTrace();} System.out.println(Thread.currentThread().getName()+"\t----------come in" +"\t" +System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了" +"\t" +System.currentTimeMillis()); },"t1" ); t1.start(); new Thread (()->{ LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1" ); },"t2" ).start(); } }
sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下。先执行了unpark(t1)导致上面的park方法形同虚设无效,时间是一样的 类似于高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。
最后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class LockSupportDemo { public static void main (String[] args) { Thread t1 = new Thread (()->{ try {TimeUnit.SECONDS.sleep(3 );} catch (InterruptedException e) {e.printStackTrace();} System.out.println(Thread.currentThread().getName()+"\t----------come in" +"\t" +System.currentTimeMillis()); LockSupport.park(); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了" +"\t" +System.currentTimeMillis()); },"t1" ); t1.start(); new Thread (()->{ LockSupport.unpark(t1); LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1" ); },"t2" ).start(); } }
小总结 Lock Support是用来创建锁和其他同步类的基本线程阻塞原语。 Lock Support是一个线程阻塞工具类, 所有的方法都是静态方法, 可以让线程在任意位置阻塞, 阻塞之后也有对应的唤醒方法。归根结 底, Lock Support调用的Unsafe中的native代码。
Lock Support提供park()
和unpark()
方法实现阻塞线程 和解除线程阻塞 的过程 Lock Support和每个使用它的线程都有一个许可(permit) 关联。 每个线程都有一个相关的permit, permit最多只有一个, 重复调用unpark也不会积累凭证。
形象的理解 线程阻塞需要消耗凭证(permit) , 这个凭证最多只有1 个。
当调用park()
方法时 *如果有凭证,则会直接消耗掉这个凭证然后正常退出; *如果无凭证,就必须阻塞等待凭证可用; 而unpark()
则相反, 它会增加一个凭证, 但凭证最多只能有1 个, 累加无效。
面试题 为什么可以突破wait/notify的原有调用顺序? 因为unpark获得了一个凭证, 之后再调用park方法, 就可以名正言顺的凭证消费, 故不会阻塞。 先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程? 因为凭证的数量最多为1, 连续调用两次un park和调用一次un park效果一样, 只会增加一个凭证; 而调用两次park却需要消费两个凭证, 证不够, 不能放行。