【多线程】线程间通信 之虚假唤醒和中断

2024-09-02 19:12

本文主要是介绍【多线程】线程间通信 之虚假唤醒和中断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量+1,一个线程对该变量-1,实现交替,来10轮,变量初始值为0,以实现此问题作为引入,简化我们的理解

文章目录

    • 一、两个线程synchronized写法-结果无问题
    • 二(一)、四个线程synchronized写法-问题及解决办法
    • 二(二)、4线程问题 解决办法1:使用while进行条件判断
    • 二(三)、4线程问题 解决办法2:使用Lock和Condition实现线程间通信

一、两个线程synchronized写法-结果无问题

package com.atguigu.signcenter.thread;/*** 线程之间的通信-两个线程synchronized写法* @author: jd* @create: 2024-09-02*/
public class ThreadWaitNotifyDemo {public static void main(String[] args) {AirConditioner airConditioner = new AirConditioner();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();}
}class AirConditioner {  // 资源类private int number = 0;public synchronized void increment() throws InterruptedException {// 1. 判断if (number != 0) {this.wait();}// 2. 干活number++;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}public synchronized void decrement() throws InterruptedException {// 1. 判断if (number == 0) {this.wait();}// 2. 干活number--;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}
}

结果:

A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0

二(一)、四个线程synchronized写法-问题及解决办法

换成4个线程会导致错误,虚假唤醒。
原因:在java多线程判断时,不能用if,程序出事出在了判断上面,突然有一添加的线程进到if了,突然中断了交出控制权,没有进行验证,而是直接走下去了,加了两次,甚至多次。
在这里插入图片描述

中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while。(也就是说在四个线程下,有可能两个increment线程都在if中wait,当其被唤醒时,不会再次判断number是否满足条件,而直接执行number++,因此会导致number大于1的情况,同理也会出现number小于0的情况)

如果使用if判断,则会导致虚假唤醒:代码及现象

package com.atguigu.signcenter.thread;/*** 线程之间的通信-两个、四个线程synchronized写法* @author: jd* @create: 2024-09-02*/
public class ThreadWaitNotifyDemo {public static void main(String[] args) {AirConditioner airConditioner = new AirConditioner();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "D").start();}
}class AirConditioner {  // 资源类private int number = 0;public synchronized void increment() throws InterruptedException {// 1. 判断if (number != 0) {this.wait();}// 2. 干活number++;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}public synchronized void decrement() throws InterruptedException {// 1. 判断if (number == 0) {this.wait();}// 2. 干活number--;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}
}

现象

A	1
B	0
A	1
B	0
C	1
B	0
A	1
B	0
C	1
D	0
B	-1
B	-2
B	-3
B	-4
B	-5
B	-6
A	-5
D	-6
D	-7
D	-8
D	-9
D	-10
D	-11
D	-12
D	-13
D	-14
C	-13
A	-12
C	-11
A	-10
C	-9
A	-8
C	-7
A	-6
C	-5
A	-4
C	-3
A	-2
C	-1

图示为什么会出现问题
在使用if判断两个线程的情况下,阻塞的线程只有两种情况,此时不会出现任何问题;
而使用if在四个线程的情况下,可能存在这种情况:

  1. 最开始+线程进行了增加操作NotifyAll;
  2. 此时+'线程抢占到执行权,进入if判断进入阻塞状态;
  3. +线程又抢到了执行权,同样进入if判断阻塞;
  4. -线程抢占执行权进行减操作,NotifyAll;
  5. +'线程抢占执行权,进行增加操作,NotifyAll;
  6. +线程抢占执行权,进行增加操作 (此时便出现了number=2的情况)
    使用while就不会出现这种问题,因为在NotifyAll线程激活运行后,会进行二次判断!
    在这里插入图片描述

二(二)、4线程问题 解决办法1:使用while进行条件判断

解决此问题:
使用while进行条件判断

  1. 高内聚第耦合的前提下,线程操作资源类
  2. 判断/干活/通知
  3. 多线程交互中,必须要防止多线程的虚假唤醒,也即(在多线程的判断中不许用if只能用while)
    解决代码:
package com.atguigu.signcenter.thread;/*** 线程之间的通信-两个、四个线程synchronized写法* @author: jd* @create: 2024-09-02*/
public class ThreadWaitNotifyDemo {public static void main(String[] args) {AirConditioner airConditioner = new AirConditioner();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "D").start();}
}
/*
class AirConditioner {  // 资源类private int number = 0;public synchronized void increment() throws InterruptedException {// 1. 判断if (number != 0) {this.wait();}// 2. 干活number++;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}public synchronized void decrement() throws InterruptedException {// 1. 判断if (number == 0) {this.wait();}// 2. 干活number--;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}*/class AirConditioner {  // 资源类private int number = 0;public synchronized void increment() throws InterruptedException {// 1. 判断while (number != 0) {this.wait();}// 2. 干活number++;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}public synchronized void decrement() throws InterruptedException {// 1. 判断while (number == 0) {this.wait();}// 2. 干活number--;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知this.notifyAll();}
}

正常结果:

A	1
B	0
A	1
B	0
A	1
B	0
C	1
B	0
A	1
B	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
D	0
A	1
D	0
C	1
D	0
C	1
D	0
C	1
D	0

二(三)、4线程问题 解决办法2:使用Lock和Condition实现线程间通信

通过Java8的Lock和Condition接口(await、signal、signalAll),可以替换synchronized与Object monitor方法(wait、notify、notifyAll)
在这里插入图片描述
这里我们还是使用3.2中的例子,4个线程,两个打印1两个打印0,让其交替打印,分别打印十次

package com.atguigu.signcenter.thread;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author: jd* @create: 2024-09-02*/
public class ThreadWaitNotifyDemo2 {public static void main(String[] args) {AirConditioner2 airConditioner = new AirConditioner2();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {airConditioner.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "D").start();}
}class AirConditioner2 {  // 资源类private int number = 0;// 使用java8 lock 和 condition接口实现private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void increment() throws InterruptedException {lock.lock();try {// 1. 判断while (number != 0) {condition.await(); // this.wait();}// 2. 干活number++;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知condition.signalAll(); // this.notifyAll();}catch (Exception e) {}finally {lock.unlock();}}public void decrement() throws InterruptedException {lock.lock();try {// 1. 判断while (number == 0) {condition.await(); // this.wait();}// 2. 干活number--;System.out.println(Thread.currentThread().getName() + "\t" + number);// 3. 通知condition.signalAll(); // this.notifyAll();}catch (Exception e) {}finally {lock.unlock();}}
}

码字不易,请大家多多指教~

这篇关于【多线程】线程间通信 之虚假唤醒和中断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130819

相关文章

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度

Java 多线程的基本方式

Java 多线程的基本方式 基础实现两种方式: 通过实现Callable 接口方式(可得到返回值):

java线程深度解析(六)——线程池技术

http://blog.csdn.net/Daybreak1209/article/details/51382604 一种最为简单的线程创建和回收的方法: [html]  view plain copy new Thread(new Runnable(){                @Override               public voi

java线程深度解析(五)——并发模型(生产者-消费者)

http://blog.csdn.net/Daybreak1209/article/details/51378055 三、生产者-消费者模式     在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之

java线程深度解析(四)——并发模型(Master-Worker)

http://blog.csdn.net/daybreak1209/article/details/51372929 二、Master-worker ——分而治之      Master-worker常用的并行模式之一,核心思想是由两个进程协作工作,master负责接收和分配任务,worker负责处理任务,并把处理结果返回给Master进程,由Master进行汇总,返回给客

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu