JUC高并发编程3:线程间通信

news2024/9/28 15:20:42

1 线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析

场景:两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

1.1 synchronized方法

// 第一步 创建资源类,定义属性和操作方法
class Share{

    //初始值
    private int number = 0;

    // +1的方法
    public synchronized void incr() throws InterruptedException {
        // 判断
        while(number != 0) {
            this.wait();
        }
        // 干活
        number++;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        //通知其他线程
        this.notifyAll();
    }

    // -1的方法
    public synchronized void decr() throws InterruptedException {
        // 判断
        while(number != 1) {
            this.wait();
        }
        // 干活
        number--;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        //通知其他线程
        this.notifyAll();
    }

}
public class ThreadDemo1 {

    // 第三步创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        // 创建线程
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();//+1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();//+1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"DD").start();

    }
}

1.2 Lock方案

// 第一步 创建资源类,定义属性和操作方法
class Share{
    //初始值
    private int number = 0;

    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    // +1
    public void incr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while (number != 0){
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName() + " :: " + number);
            //通知
            condition.signalAll();
        }finally {
            // 解锁
            lock.unlock();
        }
    }

    // -1
    public void decr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while (number != 1){
                condition.await();
            }
            //干活
            number--;
            System.out.println(Thread.currentThread().getName() + " :: " + number);
            //通知
            condition.signalAll();
        }finally {
            // 解锁
            lock.unlock();
        }
    }
}
public class ThreadDemo2 {

    // 第三步创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();


        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"DD").start();
    }
}

1.3 虚假唤醒问题

在Java中,线程间通信通常使用 Object 类的 wait()notify()notifyAll() 方法来实现。这些方法与条件变量类似,但同样可能会出现虚假唤醒(Spurious Wakeup)的问题。

1.3.1 什么是虚假唤醒?

虚假唤醒是指一个线程在没有被显式通知的情况下被唤醒。换句话说,即使没有调用 notify()notifyAll() 方法,等待的线程也可能被唤醒。这种现象在某些操作系统或线程库中是允许的,因为它可以简化某些实现。

1.3.2 为什么会出现虚假唤醒?

虚假唤醒的原因可能包括:

  1. 操作系统调度:操作系统可能在某些情况下唤醒线程,即使没有显式的通知。
  2. 多核处理器:在多核处理器上,线程可能在不同的核心上运行,导致某些同步机制不完全可靠。
  3. Java 实现:Java 的线程库实现可能会允许虚假唤醒,以提高性能或简化实现。

1.3.3 如何处理虚假唤醒?

为了避免虚假唤醒带来的问题,通常的做法是在循环中检查条件变量。这样,即使线程被虚假唤醒,它也会在循环中重新检查条件,如果条件不满足,线程会继续等待。

以下是一个使用 wait()notifyAll() 的典型示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SpuriousWakeupExample {
    private static final Lock lock = new ReentrantLock();
    private static final Condition condition = lock.newCondition();
    private static boolean ready = false;

    public static void main(String[] args) throws InterruptedException {
        Thread workerThread = new Thread(new Worker());
        workerThread.start();

        // 主线程设置条件并通知
        Thread.sleep(1000); // 模拟一些工作
        lock.lock();
        try {
            ready = true;
            condition.signalAll(); // 通知等待的线程
        } finally {
            lock.unlock();
        }

        workerThread.join();
    }

    static class Worker implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                // 在循环中检查条件
                while (!ready) {
                    condition.await(); // 等待条件满足
                }
                // 条件满足,执行工作
                System.out.println("Worker thread is processing data");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
    }
}

1.3.4 关键点

  1. 循环检查条件:在 await() 方法周围使用 while 循环来检查条件,而不是 if 语句。这样可以确保即使线程被虚假唤醒,它也会重新检查条件。
  2. 使用 LockCondition:在示例中使用了 ReentrantLockCondition,这是 Java 中更灵活的同步机制。你也可以使用 synchronized 关键字和 Objectwait()notify()notifyAll() 方法,但原理是相同的。
  3. 处理中断:在 await() 方法中捕获 InterruptedException,并处理线程中断的情况。

通过这种方式,可以有效避免虚假唤醒带来的问题,确保线程在条件真正满足时才继续执行。

1.4 多线程编程步骤

  • 第一步创建资源类,在资源类创建属性和操作方法

  • 第二步在资源类操作方法

    • 判断
    • 干活
    • 通知
  • 第三步创建多个线程,调用资源类的操作方法

  • 第四步防止虚假唤醒问题

2 线程间定制化通信

让线程按照指定顺序进行通信。

2.1 案例介绍

启动三个线程,按照如下要求执行:
AA线程打印 5 次 A,BB 线程打印 10 次 B,CC 线程打印 15 次 C,按照此顺序循环 10 轮

2.2 流程分析

在这里插入图片描述

2.3 代码实现

// 第一步 创建资源类,定义属性和操作方法
class ShareResourse{
    // 定义标识位
    private int flag = 1; // 1:AA; 2:BB; 3:CC
    //创建Lock
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    //打印5次,参数第几轮
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            while (flag != 1){
                //等待
                c1.await();
            }
            //干活
            for (int i = 0; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);
            }
            //通知
            flag = 2; //修改标识位2
            c2.signal();//通知BB线程
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    //打印10次,参数第几轮
    public void print10(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            while (flag != 2){
                //等待
                c2.await();
            }
            //干活
            for (int i = 0; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);
            }
            //通知
            flag = 3; //修改标识位3
            c3.signal();//通知CC线程
        }finally {
            // 释放锁
            lock.unlock();
        }
    }


    //打印15次,参数第几轮
    public void print15(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            while (flag != 3){
                //等待
                c3.await();
            }
            //干活
            for (int i = 0; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);
            }
            //通知
            flag = 1; //修改标识位1
            c1.signal();//通知AA线程
        }finally {
            // 释放锁
            lock.unlock();
        }
    }
}
public class ThreadDemo3 {
    // 第三步创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        ShareResourse shareResourse = new ShareResourse();
        new Thread(()->{
            for (int i = 0; i <= 10; i++) {
                try {
                    shareResourse.print5(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i <= 10; i++) {
                try {
                    shareResourse.print10(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i <= 10; i++) {
                try {
                    shareResourse.print15(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();
    }
}

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的class对象。
对于同步方法块,锁是synchonized括号里配置的对象

3 思维导图

在这里插入图片描述

4 参考链接

【【尚硅谷】大厂必备技术之JUC并发编程】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2168536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【智能控制】第15章 智能优化算法,遗传算法

目录 15.1 遗传算法及其应用 15.1.1 遗传算法的基本原理 15.1.2 遗传算法的特点 15.1.3 遗传算法的应用领域 遗传算法的应用 15.1.4 遗传算法的设计 遗传算法的应用步骤 15.1.5 遗传算法求函数极大值 随着优化理论的发展&#xff0c;一些新的智能算法得到了迅速发…

深度学习:ResNet残差神经网络

目录 一、什么是ResNet残差神经网络 二、残差结构 三、18层残差网络 1.最初残差网络变体 2.图片示例 3.表格示例 四、批次归一化&#xff08;Batch Normalization&#xff09; 1.工作过程 2.主要作用 五、ResNet残差神经网络解决了传统神经网络什么问题 1.梯度消失和…

node-red-L3-重启指定端口的 node-red

重启指定端口 目的步骤查找正在运行的Node.js服务的进程ID&#xff08;PID&#xff09;&#xff1a;停止Node.js服务&#xff1a;启动Node.js服务&#xff1a; 目的 重启指定端口的 node-red 步骤 在Linux系统中&#xff0c;如果你想要重启一个正在运行的Node.js服务&#x…

【AI绘画】Midjourney进阶:光影控制详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;为什么要学习光影控制光影控制的作用 &#x1f4af;强化主题hard lighting&#xff08;硬光 &#xff09;soft lighting&#xff08;软光/柔光&#xff09;测试 &…

利用Puppeteer-Har记录与分析网页抓取中的性能数据

引言 在现代网页抓取中&#xff0c;性能数据的记录与分析是优化抓取效率和质量的重要环节。本文将介绍如何利用Puppeteer-Har工具记录与分析网页抓取中的性能数据&#xff0c;并通过实例展示如何实现这一过程。 Puppeteer-Har简介 Puppeteer是一个Node.js库&#xff0c;提供…

Leetcode面试经典150题-322.零钱兑换

给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1 。 你可以认为每种硬币的数量是无限的。 示…

[数据库实验一]数据库和表

目录 一、实验目的与要求 二、实验内容 实验小结 一、实验目的与要求 1.掌握MySQL中如何创建数据库和表的方法 2.熟练掌握MySQL的数据类型、主键实体完整性的设置 3.参照完整性的定义及应用 4.插入数据 5.数据库的备份操作 二、实验内容 &#xff11;、创建名为fruit…

CleanMyMac X 评价、介绍、使用教学|Mac系统最推荐的系统优化和清理软件工具!

本篇文章要带大家看一款知名的Mac系统优化、清理工具– CleanMyMac X &#xff0c;并且也会附上详细的介绍和使用教学。 链接: https://pan.baidu.com/s/1_TFnrIVH1NGsZPsA3lpwAA 提取码: dpjw CleanMyMac X-安装包&#xff1a;https://souurl.cn/QUYb57 为什么Mac电脑需要装系…

Cilium + ebpf 系列文章-什么是ebpf?(一)

前言&#xff1a; 这篇非常非常干&#xff0c;很有可能读不懂。 这里非常非常推荐&#xff0c;建议使用Cilium官网的lab来辅助学习&#xff01;&#xff01;&#xff01;Resources Library - IsovalentExplore Isovalents Resource Library, your one-stop destination for ins…

FocSAM

Dynamic Window简写为Dwin 辅助信息 不建议复现

TCN-Transformer+GRU多变量时间序列预测(Matlab)

超强来袭&#xff01;双路创新&#xff01;TCN-TransformerGRU多变量时间序列预测&#xff08;Matlab&#xff09; 目录 超强来袭&#xff01;双路创新&#xff01;TCN-TransformerGRU多变量时间序列预测&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一…

智慧农业的引擎:高标准农田灌区信息化的探索与实践

在现代农业的广阔图景中&#xff0c;智慧农业作为一股革新力量&#xff0c;正逐步重塑着传统农业的面貌。其中&#xff0c;高标准农田灌区的信息化建设不仅是智慧农业的重要引擎&#xff0c;更是实现农业可持续发展、提高资源利用效率的关键路径。 高标准农田灌区信息化的内涵…

Linux下的基本指令/命令(二)

热键 Tab&#xff1a; 连点两次 对命令进补齐 或者 显式 以目前所需字母 开头的指令。 也可以进行路径补齐 或者 显示所写的文件所处路径上的所有文件。 如果什么也没写&#xff0c;直接按Tab会显示所有命令 Ctrl C&#xff1a; 一旦出现失控的状态&#xff0c;或者任何无法…

刷题计划 day10 栈与队列上【用栈实现队列】【用队列实现栈】【有效的括号】【删除字符串中的所有相邻重复项】

⚡刷题计划day10栈与队列继续&#xff0c;可以点个免费的赞哦~ 往期可看专栏&#xff0c;关注不迷路&#xff0c; 您的支持是我的最大动力&#x1f339;~ 目录 ⚡刷题计划day10继续&#xff0c;可以点个免费的赞哦~ 往期可看专栏&#xff0c;关注不迷路&#xff0c; 您的…

Linux云计算 |【第四阶段】NOSQL-DAY2

主要内容&#xff1a; Redis集群概述、部署Redis集群&#xff08;配置manage管理集群主机、创建集群、访问集群、添加节点、移除节点&#xff09; 一、Redis集群概述 1、集群概述 所谓集群&#xff0c;就是通过添加服务器的数量&#xff0c;提供相同的服务&#xff0c;从而让…

CSDN文章导出md并迁移至博客园

一、获取所有文章地址 1.进csdn首页&#xff0c;点击自己的头像 2.在个人主页界面&#xff0c;按F12打开控制台&#xff0c;并找到network&#xff0c;找到get-business开头的请求&#xff0c;右键copy他的url 3.选择console,输入一下代码&#xff0c;其中fetch里面的url是你刚…

62.【C语言】浮点数的存储

目录 1.浮点数的类型 2.浮点数表示的范围 3.浮点数的特性 《计算机科学导论》的叙述 4.浮点数在内存中的存储 答案速查 分析 前置知识:浮点数的存储规则 推导单精度浮点数5.5在内存中的存储 验证 浮点数取出的分析 1.一般情况:E不全为0或不全为1 2.特殊情况:E全为0…

P335_0334韩顺平Java_零钱通介绍

目录 P335_0334韩顺平Java_零钱通介绍代码过程编程OOP&#xff08;Object-Oriented Project&#xff09; 参考资料 P335_0334韩顺平Java_零钱通介绍 先完成显示菜单&#xff0c;并可以选择。完成零钱通明细。完成收益入账。消费。退出。 PS&#xff1a;判断时尽量用不正确的条…

BEV学习---LSS4-模型训练

主要借鉴如下链接&#xff1a; https://blog.csdn.net/m0_51579041/article/details/140746160 测试命令如下&#xff0c;均已跑通&#xff1a; # 验证集iou计算&#xff1a; python main.py eval_model_iou mini -bsz1 --nworkers0 --gpuid0 --modelf./model/model525000.pt…

【CSS/HTML】圣杯布局和双飞翼布局实现两侧宽度固定,中间宽度自适应及其他扩展实现

前沿简介 圣杯布局和双飞翼布局是前端重要的布局方式。两者的功能相同&#xff0c;都是为了实现一个两侧宽度固定&#xff0c;中间宽度自适应的三栏布局。 圣杯布局来源于文章In Search of the Holy Grail,双飞翼布局来源于淘宝UED。 两者的实现方式有差异&#xff0c;但是都…