JUC并发编程第五篇,如何优雅的使用线程中断机制和线程等待唤醒机制?
创始人
2024-02-29 04:45:09
0

JUC并发编程第五篇,如何优雅的使用线程中断机制和线程等待唤醒机制?

    • 一、线程中断机制
      • 1. 什么是线程中断?
      • 2. 你知道 interrupt() 方法的含义吗?
      • 3. 如何使用中断标识优雅的停止线程?
        • 第一种:通过volatile变量实现
        • 第二种:通过AtomicBoolean实现
        • 第三种:通过线程自带 interrupt() 方法实现
      • 4. 当前线程的中断标识为true,就立刻停止了吗?
    • 二、线程等待唤醒机制,让线程等待和唤醒的3种方法注意事项
      • 第一种:Object中的 wait() 方法让线程等待,notify() 方法唤醒线程
      • 第二种:JUC包中 Condition 的 await() 方法让线程等待,signal() 方法唤醒线程
      • 第三种:LockSupport类中的park等待和unpark唤醒

一、线程中断机制

1. 什么是线程中断?

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。

  • Java提供了一种用于停止线程的机制——中断。
  • 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
  • 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true,接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。

2. 你知道 interrupt() 方法的含义吗?

在这里插入图片描述

  • public void interrupt()

实例方法interrupt()仅仅是设置线程的中断状态为true,不会停止线程。

  • public boolean isInterrupted()

通过检查中断标志位,判断当前线程是否被中断。

  • public static boolean interrupted()

静态方法,判断线程是否被中断,并清除当前中断状态
也就是说这个方法做了两件事:
1、返回当前线程的中断状态
2、将当前线程的中断状态设为false

3. 如何使用中断标识优雅的停止线程?

第一种:通过volatile变量实现

static volatile boolean isStop = false;public static void volatileDemo() {new Thread(() -> {while(true) {if(isStop) {System.out.println("-----程序结束------");break;}System.out.println("------hello-------");}},"t1").start();try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {isStop = true;},"t2").start();}

第二种:通过AtomicBoolean实现

static AtomicBoolean atomicBoolean = new AtomicBoolean(false);public static void atomicBooleanDemo() {new Thread(() -> {while(true) {if(atomicBoolean.get()) {System.out.println("-----程序结束------");break;}System.out.println("------hello-------");}},"t1").start();try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {atomicBoolean.set(true);},"t2").start();}

第三种:通过线程自带 interrupt() 方法实现

public static void m3() {Thread t1 = new Thread(() -> {while (true) {//当前线程判断中断标志if (Thread.currentThread().isInterrupted()) {System.out.println("-----程序结束------");break;}System.out.println("------hello------");}}, "t1");t1.start();try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {//修改t1线程的中断标志位为truet1.interrupt();},"t2").start();}

4. 当前线程的中断标识为true,就立刻停止了吗?

先说结论:中断只是一种协同机制,只修改中断标识位而已,不会立刻停止线程

  • 代码证明:
public static void stopDemo() {Thread t1 = new Thread(() -> {for (int i = 1; i <= 300; i++) {System.out.println("------i: " + i);}System.out.println("t1.interrupt()调用之后02: "+Thread.currentThread().isInterrupted());}, "t1");t1.start();System.out.println("t1.interrupt()调用之前,t1线程的中断标识默认值: "+t1.isInterrupted());try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }//实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程t1.interrupt();//活动状态,t1线程还在执行中System.out.println("t1.interrupt()调用之后01: "+t1.isInterrupted());try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }//非活动状态,t1线程不在执行中,已经结束执行了。System.out.println("t1.interrupt()调用之后03: "+t1.isInterrupted());}

在这里插入图片描述在这里插入图片描述在这里插入图片描述

还有一种情况:如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

public static void stopDemo2() {Thread t1 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println("-----isInterrupted() = true,程序结束。");break;}try {Thread.sleep(500);} catch (InterruptedException e) {//线程的中断标志位为false,无法停下,需要再次掉interrupt()设置true//Thread.currentThread().interrupt();e.printStackTrace();}System.out.println("------hello Interrupt");}}, "t1");t1.start();try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {t1.interrupt();//修改t1线程的中断标志位为true},"t2").start();}

在这里插入图片描述

上边不能停止的原因是:
抛出了中断异常后,中断标识也被置为了 false ,导致无限循环,想要解决这个bug,需要在异常中再次中断,也就是打开上边的 //Thread.currentThread().interrupt(); 这句代码。

二、线程等待唤醒机制,让线程等待和唤醒的3种方法注意事项

第一种:Object中的 wait() 方法让线程等待,notify() 方法唤醒线程

public static void syncWaitNotify() {new Thread(() -> {//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } 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();//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {synchronized (objectLock){objectLock.notify();System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");}},"t2").start();}

使用总结:
wait和notify方法必须要在同步块或者方法里面,且成对出现使用
需要先 wait 后 notify 才能生效

第二种:JUC包中 Condition 的 await() 方法让线程等待,signal() 方法唤醒线程

public static void lockAwaitSignal() {new Thread(() -> {//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }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();new Thread(() -> {lock.lock();try{condition.signal();System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");}finally {lock.unlock();}},"t2").start();}

使用总结:
Condtion中的线程等待和唤醒方法之前,需要先获取锁
需要先 await 后 signal 才能生效

第三种:LockSupport类中的park等待和unpark唤醒

  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。
public static void parkDemo() {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"+"---发出通知");},"t2").start();}

总结:park() 阻塞线程,unpark(Thread thread) 唤醒线程,不需要像之前那样锁块,也没有先后顺序的要求
注意1:注意多次调用 unpark() 方法,不会累加,permit值最大是1
注意2:一个线程只能发一张通行证,比如 t1 park一次,t2可以 unpark 唤醒,如果 t1 park 了两次,就需要两个线程 t2 park 一次,t3 park 一次,以此类推,一个线程只能唤醒一次阻塞。

相关内容

热门资讯

修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
【前端】‘??‘与‘||‘有什... 0 问题 经常写const data = res.data.a ?? ''或者const d...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
牛客计算器的改良(Python... 文章目录1.题目描述2.输入描述:3.输出描述:4.示例15.分析6.代码7.结语 链接࿱...
正大杯|市调大赛|2023备赛... 关键信息 同时随着精细化养宠趋势的深入,宠物消费类目日渐丰富。 本报告通过 Niuco...
QGIS下载在线地图(Goog... 前言 国内外有很多在线地图下载软件,但功能单一,基本上只能下载数据&#x...