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 一次,以此类推,一个线程只能唤醒一次阻塞。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...