【JUC基础】06. 生产者和消费者问题

news2024/11/25 5:43:11

1、前言

学习JUC,就不得不提生产者消费者。生产者消费者模型是一种经典的多线程模型,用于解决生产者和消费者之间的数据交换问题。在生产者消费者模型中,生产者生产数据放入共享的缓冲区中,消费者从缓冲区中取出数据进行消费。在这个过程中,生产者和消费者之间需要保持同步,以避免数据出现错误或重复。今天我们就来说说生产者消费者模型,以及JUC中如何解决该模型的同步问题。

2、什么是生产者消费者问题

生产者消费者问题是一种经典的多线程问题,用于描述生产者和消费者之间的数据交换问题。其实本质上就是线程间通信问题,即线程等待唤醒和通知唤醒。

生产者消费者问题通常包含以下三个元素:

  1. 生产者:负责生产数据,并将其放入共享的缓冲区中。
  2. 消费者:负责从缓冲区中取出数据,并进行消费。
  3. 缓冲区:用于存放生产者生产的数据,消费者从中取出数据进行消费。

在实际应用中,生产者和消费者可能存在速度差异,导致缓冲区的数据量不断变化。如果缓冲区满了,生产者需要等待,直到消费者取走了一部分数据。同样,如果缓冲区为空,消费者需要等待,直到生产者生产了一些数据放入缓冲区中。

3、Synchronized解决方案

synchronized解决方案,一般采用wait()(等待唤醒)和notifyAll()(通知唤醒)进行线程的同步通信。

  • wait()方法用于使当前线程等待,直到另一个线程调用相同对象上的notify()方法或notifyAll()方法来唤醒它。wait()方法必须在synchronized块或方法中调用,以确保线程获得对象的监视器锁。
  • notify()方法用于通知等待在相同对象上的某个线程,告诉它们可以继续运行。
  • notifyAll()方法则通知等待在相同对象上的所有线程。

调用wait()方法会释放锁,使当前线程进入等待状态,直到其他线程调用相同对象上的notify()方法或notifyAll()方法唤醒它。而notify()方法则会随机选择一个等待的线程唤醒,而notifyAll()则会唤醒所有等待的线程,让它们竞争锁。

public class ProducerConsumerExample {
    public static void main(String[] args) {
        NumberOper object = new NumberOper();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-1").start();
    }
}

class NumberOper {
    private int number = 0;

    public synchronized void add() {
        if(number != 0) {
            try {
                // 等待唤醒
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
        
        // 通知其他唤醒
        this.notifyAll();
    }

    public synchronized void sub() {
        if(number == 0) {
            try {
                // 等待唤醒
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
        
        // 通知其他唤醒
        this.notifyAll();
    }
}

执行结果:

不过需要注意的是,上面的代码没有考虑到多线程并发的情况,如果多个生产者和多个消费者同时访问缓冲区,就需要使用线程安全的数据结构或加锁来保证线程安全。也就是虚假唤醒问题。

虚假唤醒问题,请参考《wait(),notify()虚假唤醒》篇幅。

4、Lock解决方案

Synchronized解决方案,主要是依赖于wait()和notify()方法解决。相应的JUC中的Lock也是类似的解决手段。

  • Synchronized:(注意wait()和notify()方法是Object的方法)
    • wait():线程等待,直到其他线程将他唤醒
    • notify():唤醒其他等待的线程
    • notifyAll():换新所有等待的线程
  • Lock:
    • await():线程等待,直到其他线程将他唤醒
    • signal():唤醒正在等待的线程
    • signalAll():唤醒正在等待的线程

使用JUC Lock来解决生产者消费者问题,可以使用Condition(条件变量)来实现。

Condition是基于Lock来创建的,每个Condition对象都和一个Lock对象绑定。Condition对象提供了类似wait()和notify()的方法来控制线程的等待和唤醒。Condition对象可以通过Lock对象的newCondition()方法创建。

生产者消费者问题中,我们可以使用两个Condition对象来控制生产者和消费者的等待和唤醒。当缓冲区为空时,消费者线程等待,当缓冲区满时,生产者线程等待。

package com.github.fastdev.waitnotify;

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

/** * @author Shamee loop * @date 2023/4/9 */
public class ProducerConsumerExample {

    public static void main(String[] args) {
        NumberOper object = new NumberOper();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.add();
            }
        }, "thread-add-1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                object.sub();
            }
        }, "thread-sub-1").start();
    }
}


class NumberOper {
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void add() {
        lock.lock();
        try {
            if (number != 0) {
                condition.await();
            }
            number++;
            System.out.println("线程" + Thread.currentThread().getName() + "执行了add(),number====>" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void sub() {
        lock.lock();
        try {
            if (number == 0) {
                condition.await();
            }
            number--;
            System.out.println("线程" + Thread.currentThread().getName() + "执行了sub(),number====>" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

执行结果:

5、Condition

那么既然synchronized就能解决生产者消费者问题,为什么还需要JUC的Lock这种方式呢? 从代码量上看,Lock的方式明显比较繁琐。

当然,存在即合理。JUC实现了Lock的方式,且引入了Condition。肯定是具备了synchronized所没有的特性。

试想一个场景:

synchronized的notify()虽然唤醒了等待的线程。但是如果存在多个等待的线程呢?唤醒后获得执行权的需要取决于分配策略。那么有没有一种可能,我需要指定唤醒某个等待的线程?Condition就来了,他可以指定唤醒某个线程,也就是精准唤醒。

Condition 是 Java 中 Lock 的一个重要组件,可以用于实现更加灵活、高效的线程同步。它提供了类似于 Object.wait() 和 Object.notify() 的等待/通知机制,但相较于传统的 synchronized,它更加灵活,可以实现更多高级特性。

Condition 的主要作用是允许线程在等待某些条件的情况下暂停执行(即阻塞线程),并且当条件满足时,可以重新唤醒这些线程。Condition 与 Lock 一起使用,通常需要创建一个 Lock 对象,然后调用 Lock 的 newCondition() 方法来创建一个 Condition 对象。

Condition 接口中最常用的方法包括:

  • await():当前线程等待,直到被通知或中断;
  • awaitUninterruptibly():当前线程等待,直到被通知,但不会响应中断;
  • signal():唤醒一个等待中的线程;
  • signalAll():唤醒所有等待中的线程。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerExample {

    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity = 10;
    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();

    public void produce() throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await();
            }
            int num = (int) (Math.random() * 100);
            queue.add(num);
            System.out.println("Produced " + num);
            
             // 指定唤醒线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();
            }
            int num = queue.remove();
            System.out.println("Consumed " + num);
            
            // 指定唤醒线程
            notFull.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ProducerConsumerExample example = new ProducerConsumerExample();
        Thread producerThread1 = new Thread(() -> {
            while (true) {
                try {
                    example.produce();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread producerThread2 = new Thread(() -> {
            while (true) {
                try {
                    example.produce();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread consumerThread1 = new Thread(() -> {
            while (true) {
                try {
                    example.consume();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread consumerThread2 = new Thread(() -> {
            while (true) {
                try {
                    example.consume();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        producerThread1.start();
        producerThread2.start();
        consumerThread1.start();
        consumerThread2.start();
    }
}

6、小结

到此,我们学习了生产者和消费者模型,以及他的一些问题,以及如何解决。还接触了Locks中的另一个类Condition的使用。一天进步一点点,一起加油~

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

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

相关文章

MySQL同时In俩个字段,In多个字段,Mybatis多个In查询问题,Mysql多个IN查询多出数据问题,Mysql多个IN查询 数据准确问题

背景&#xff1a; 今天产品验收的时候&#xff0c;导入了大量数据&#xff1b;发现造价项目某个查询列表数据多出了几条数据&#xff1b;看了Mybatis查询&#xff0c;才发现是同时使用了多个IN查询导致的问题&#xff1b;入参是对象列表&#xff0c;In值是分开循环赋值…

【SpringBoot整合RabbitMQ(上)】

一、简单的生产者-消费者 1.1、创建连接工具类获取信道 public class RabbitMqUtils {public static Channel getChannel() throws IOException, TimeoutException {//创建一个链接工厂ConnectionFactory factory new ConnectionFactory();//工厂IP 链接RabbitMQ的队列facto…

google_breakpad库的基本使用

参考链接&#xff1a; windows下捕获dump之Google breakpad_client的理解Google Breakpad&#xff1a;基本介绍和操作方法Breakpad 入门linux下用QT捕获程序异常 简介 github 地址 三大组件 client:读取当前线程的状态、加载的可执行文件、共享库等信息&#xff0c;写入到…

Azure深层防御

深层防御的目的是保护信息&#xff0c;防止未经授权访问信息的人员窃取信息。 深层防御策略使用一系列机制来减缓攻击进度&#xff0c;这些攻击旨在获取对数据的未经授权的访问权限。 深层防御层 可以将深度防御可视化为一组层&#xff0c;并将要保护的数据放在中心&#xf…

一篇文章搞定ftp、dns服务器

一篇文章搞定ftp、dns服务器 1、ftp 安装ftp 挂载centos镜像cd /media/CentOS_6.8_Final/Packages安装命令&#xff1a;[rootlocalhost Packages]# rpm -ivh vsftpd-2.2.2-21.el6.x86_64.rpm Vsftpd配置目录为/etc/vsftpd&#xff0c;其中包含下面几个文件 /var/ftp/&#xf…

awk命令编辑

awk工作原理 逐行读取文本&#xff0c;默认以空格或tab键分隔符进行分隔&#xff0c;将分隔所得的各个字段保存到内建变量中&#xff0c;并按模式或者条件执行编辑命令。 sed命令常用于一整行的处理&#xff0c;而awk比较倾向于将一行分成多个“字段”然后再进行处理。awk信息…

做网工10年,没人在30岁前和我讲这些(一)

晚上好&#xff0c;我是老杨。 23年才刚过几天&#xff0c;我就感觉自己又上了点年纪&#xff0c;时常面对年纪比较小的粉丝&#xff0c;无意识的面露慈爱的笑容。 还是每次小冬提醒我&#xff0c;我才发现我的表情不对劲。 我对年轻人的包容度是很强的&#xff0c;尤其是一…

VMware、CentOS、XShell、Xftp的安装

第 1 章 VMware 1.1 VMware 安装 一台电脑本身是可以装多个操作系统的&#xff0c;但是做不到多个操作系统切换自如&#xff0c;所以我们 需要一款软件帮助我们达到这个目的&#xff0c;不然数仓项目搭建不起来。 推荐的软件为 VMware&#xff0c;VMware 可以使用户在一台计…

DNS正反向解析

正向解析 1.准备工作 关闭Selinux服务和firewalld服务 [rootserver ~]# setenforce 0 [rootserver ~]# systemctl stop firewalld 修改服务器与客户端的IP为静态IP地址 [rootserver ~]# nmcli connection modify ens160 ipv4.method manual ipv4.address …

剑指offer 19. 正则表达式匹配

文章目录 1. 题目描述2. 解题思想3. 设置dp初始值4.代码实现 1. 题目描述 2. 解题思想 定义dp数组 dp[i][j]&#xff1a;表示当字符串长度i&#xff0c;j是&#xff0c;s与p是否匹配 确定递推公式 核心是s[i]要与p[j]进行比较&#xff0c;比较的结果来确定 dp数组的值&#xf…

STM32-ADC多通道输入实验

之前已经介绍了几个ADC的笔记和实验了&#xff0c;链接如下&#xff1a; 关于ADC的笔记1_Mr_rustylake的博客-CSDN博客 STM32-ADC单通道采集实验_Mr_rustylake的博客-CSDN博客 STM32-单通道ADC采集&#xff08;DMA读取&#xff09;实验_Mr_rustylake的博客-CSDN博客 接下来…

NodeJs基础之NRM与NPM

nrm nrm can help you easy and fast switch between different npm registries, now include: npm, cnpm, taobao, nj(nodejitsu). 译文&#xff1a;nrm可以帮助您在不同的npm注册表之间轻松快速地切换&#xff0c;现在包括&#xff1a;npm、cnpm、taobao、nj&#xff08;no…

编译安装及yum安装

一、编译安装 源码包&#xff1a;是由程序员按照特定格式和语法编写的包 二进制包:源码包经过成功编译之后产生的包 1.tar -xf httpd-2.4.29.tar.bz #解压源码包 2.安装依赖环境 3.配置安装路径 4.编译make并安装 5.关闭防火墙&#xff0c;和安全机制 6.开启服务器 7.…

全电发票时代,记账凭证不用再打印了!

—政策通告— 为进一步推进电子发票应用和推广实施工作&#xff0c;助力国家数字经济发展&#xff0c;国家档案局会同财政部、商务部、国家税务总局总结三批增值税电子发票电子化报销、入账、归档试点经验&#xff0c;依据国家相关法律法规和标准规范&#xff0c;编制形成了《…

KMP匹配算法

目录 一、暴力匹配法动画演示代码实现 二、KMP算法的概念三、KMP算法的应用题目代码实现 一、暴力匹配法 动画演示 时间复杂度为&#xff1a; O ( m ∗ n ) O(m * n) O(m∗n) 代码实现 #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std;int…

Revit API:ErrorHandling

前言 本文介绍 Revit 的错误处理机制。 内容 程序员对错误处理的定义和理解 程序的错误处理机制可以分为两种类型&#xff1a;错误返回码和异常捕捉。 错误返回码是指在程序中遇到错误时&#xff0c;通过函数返回值来表明错误的类型和信息。错误返回码可以在程序中被预测和…

分段存储管理方式

目录 一、分段存储管理方式的引入的需求: 1.方便编程 2.信息共享 3.信息保护 4.动态增长 5.动态链接 二、分段系统的基本原理 1.分段 2.段表 3.地址变换机构 4.分页与分段的主要区别 三、信息共享 四、段页式存储管理方式 1.基本原理 2.地址变换过程 分段与分页…

Spring实现IOC和DI入门案例(XML版)

文章目录 1 IOC入门案例(XML版)1.1 思路分析1.2 代码实现步骤1:创建Maven项目步骤2:添加Spring的依赖jar包步骤3:添加案例中需要的类步骤4:添加spring配置文件步骤5:在配置文件中完成bean的配置步骤6:获取IOC容器步骤7:从容器中获取对象进行方法调用步骤8:运行程序 2 DI入门案例…

AltTab for Mac 像Windows一样的窗口快速切换工具

AltTab for Mac AltTab for Mac 是一款非常好用的窗口快速切换工具&#xff0c;AltTab将Windows的 “Alt-Tab” 窗口切换器的功能引入到了macOS。可以让您更快的在各个程序之间自由切换&#xff0c;大大提高您的工作效率。 AltTab for Mac下载 AltTab for Mac AltTab for Ma…

哈工大软件过程与工具作业2

云原生技术云原生技术 哈尔滨工业大学 计算机科学与技术学院/国家示范性软件学院 2022年秋季学期 《软件过程与工具》课程 作业报告 作业 2&#xff1a;需求分析UML建模 姓名 学号 联系方式 石卓凡 120L021011 944613709qq.com/18974330318 目 录 1 需求概述...........…