首页 > 其他分享 >生产者消费者问题

生产者消费者问题

时间:2023-06-09 12:34:41浏览次数:33  
标签:信号量 消费者 Producer 生产者 问题 static produced Semaphore 缓冲区

生产者消费者问题

其实这个问题在一开始阶段只存在两个问题,但随着多线程的情况下,同步的执行顺序和临界资源的安全性也必须得以保障,之前在信号量(缓冲区槽位和计数器)和互斥锁中有单独地分开去解决生产者消费者问题,现在来去真正的解决一下这个问题:

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

public class ProducerConsumerExample {
    private static final int BUFFER_SIZE = 10;
    private static int[] buffer = new int[BUFFER_SIZE];
    private static int count = 0;
    private static Lock lock = new ReentrantLock();
    private static Condition notFull = lock.newCondition();
    private static Condition notEmpty = lock.newCondition();

    private static Semaphore mutex = new Semaphore(1);
    private static Semaphore emptySlots = new Semaphore(BUFFER_SIZE);
    private static Semaphore filledSlots = new Semaphore(0);

    public static void main(String[] args) {
        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);

        producerThread.start();
        consumerThread.start();
    }

    private static Runnable producer = () -> {
        try {
            int producedValue = 1;
            while (true) {
                emptySlots.acquire(); // 等待缓冲区中的空槽

                lock.lock();
                try {
                    buffer[count] = producedValue;
                    count++;
                    System.out.println("Producer produced: " + producedValue);
                    producedValue++;

                    notEmpty.signal(); // 通知消费者有可用的值
                } finally {
                    lock.unlock();
                }

                filledSlots.release(); // 增加已填充的槽数
                Thread.sleep(1000); // 休眠一段时间,模拟生产者
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    private static Runnable consumer = () -> {
        try {
            while (true) {
                filledSlots.acquire(); // 等待缓冲区中的空槽

                lock.lock();
                try {
                    int consumedValue = buffer[count - 1];
                    count--;
                    System.out.println("Consumer consumed: " + consumedValue);

                    notFull.signal(); // 通知生产者有一个空槽可用
                } finally {
                    lock.unlock();
                }

                emptySlots.release(); // 增加空槽数
                Thread.sleep(2000); // 模拟消费者所做的一些工作
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
}

老规矩,来说明一下每一个变量的作用及其真正含义:

  1. BUFFER_SIZE:这是一个常量,表示缓冲区的大小,即可以存储的产品数量。
  2. buffer:这是一个整型数组,用于存储生产者生产的产品。
  3. count:这是一个整数变量,表示当前缓冲区中已经存储的产品数量。
  4. lock:这是一个互斥锁(Lock接口的实例),用于实现对临界区的互斥访问。
  5. notFull:这是一个条件变量(Condition接口的实例),用于在缓冲区满时阻塞生产者线程。
  6. notEmpty:这是一个条件变量(Condition接口的实例),用于在缓冲区空时阻塞消费者线程。
  7. mutex:这是一个信号量(Semaphore类的实例),用于实现对临界区的互斥访问,相当于互斥锁的概念。/** 这里实际没有用到 ,在之前信号量的代码中通过这样实现的 **/
  8. emptySlots:这是一个信号量(Semaphore类的实例),表示缓冲区中可用的空槽数量。
  9. filledSlots:这是一个信号量(Semaphore类的实例),表示缓冲区中已经被生产者填充的槽数量。
  10. producerThreadconsumerThread:这是用于执行生产者和消费者任务的线程对象。
  11. producerconsumer:这是两个Runnable接口的实例,分别表示生产者和消费者的任务。通过Lambda表达式的方式定义了它们的具体实现。
  • 用简短的语言称述上述操作就是:
    1. 缓冲区的大小为10,计数器count = 0,每次放入数组,意味着生产资料+1,计数器就会+1,同时信号量+1。而每次从数组拿出,意味着生产资料 -1,计数器也会-1,同时信号量 -1。
    2. 为了保证临界资源型问题,即多个线程去竞争冒险同一个资源区或资源而导致数据的覆盖问题,加入了互斥锁,这里是通过Lock实现,之前在进程中有通过wait和synchronized去实现锁的机制.即在进入缓冲区最多只有一个线程.

不是有计数器了,为什么还需要信号量?信号量的作用好像和计数器没什么区别.

因为这段代码的给出是一个简单的情况,仅仅只有一个生产者和消费者的情况,现实场景中往往通过信号量去表示其中的资源数,且单独分开表示更加规范,而且信号量具备阻塞的作用,计数器仅仅只能当作参考去评判,不能有下一步的动作和行为.

因此输出结果会是这样的:

Producer produced: 1
Consumer consumed: 1
....................

这个过程会持续不断的继续下去,因为缓冲区的大小是10,而生产者消费者一直是一进一出的状态,导致只用了一个槽位,而数值就是具体添加的资源.

  • 如果想加入多个线程也可以:

     Thread producerThread2 = new Thread(producer);
    

    这样就会出现以下这种情况,在缓冲区未被加满之前,消费者的消费速度明显更不上生产者,每次生产者都是生产一个资源,但因为两个生产者的缘故,且放置的位置永远不会冲突,导致消费者其实一直在不停的清理货舱

    Producer produced: 1
    Producer produced: 1
    Consumer consumed: 1
    Producer produced: 2
    Producer produced: 2
    Consumer consumed: 2
    Producer produced: 3
    Producer produced: 3
    Producer produced: 4
    Producer produced: 4
    Consumer consumed: 4
    Producer produced: 5
    Producer produced: 5
    Producer produced: 6
    Producer produced: 6
    Consumer consumed: 6
    Producer produced: 7
    Producer produced: 7
    Consumer consumed: 7 ---------在此处其实缓冲区就已经满了
    Producer produced: 8
    Consumer consumed: 8
    Producer produced: 8
    Consumer consumed: 8
    Producer produced: 9
    Consumer consumed: 9
    Producer produced: 9
    Consumer consumed: 9
    Producer produced: 10
    Consumer consumed: 10
    

    而在此之后便是生产者消费者一进一出的缘故了,无论是哪一个生产者去生产,都不可能出现,他们连续生产的情况,这是因为缓冲区已经被用完所以导致了这种情况

总结:来聊一下互斥锁,信号量,条件变量三者的关系和不同

  • 互斥锁与信号量有相似之处,当信号量的初值设置为1,且只允许其值在0和1之间变化时(在信号量时,便是通过这种方式实现).wait和signal的操作其实就是互斥锁中的lock和unlock操作类似,因此我们称这种信号量为二元信号量.他们二者之间的差别就在于:面向对象的不同,拥有锁的人被称为拥有者,而信号量更是一种同步机制,他不单独属于谁,也不绝对可能属于某一进程.因此两者在机制上有所不同.

    Lock

  • 信号量其实是一种抽象的封装,无论是底层的C语言还是java中的Semaphore,他们都基于计数器,他与条件变量的不同是,他们都提供了类似wait和signal的操作,而信号量是一种更高级的封装,他是由条件变量.互斥锁以及计数器共同实现的,因此我们查看Semaphore这个类时会发现有很多抽象的封装

    信号量

  • 条件变量:

    条件变量

为此,学习编程其实是一个自下向上的过程,如果能够理解整体的架构思想,在学习的路上就会方便和高效许多.

标签:信号量,消费者,Producer,生产者,问题,static,produced,Semaphore,缓冲区
From: https://www.cnblogs.com/looktheworld/p/17468927.html

相关文章

  • chrome 跨域问题解决
    1.后端设置了跨域,https下可以,http不可以高版本chrome配置了策略,如果访问私有网络,会出现禁止跨域chrome://flags/#block-insecure-private-network-requestsBlockinsecureprivatenetworkrequests.......
  • Luogu P4577 [FJOI2018] 领导集团问题
    [FJOI2018]领导集团问题题目描述一个公司的组织领导架构可以用一棵领导树来表示。公司的每个成员对应于树中一个结点\(v_i\),且每个成员都有响应的级别\(w_i\)。越高层的领导,其级别值\(w_i\)越小。树中任何两个结点之间有边相连,则表示与结点相应的两个成员属于同一部门。领......
  • 关于redis在我们数据平台升级版本时出现的问题
    redis启动原来我们是用写死的代码后来统一使用了启动脚本这就导致了redis存储的问题 我们知道,redis在默认情况(appendonlyno)下是使用快照存储,然而在写死的代码中,快照存储的位置是rootPath(我们的数据产品的根路径)大概更新了三个版本之后,bat脚本启动的位置是根路径\redis路径......
  • quickfix协议当有中文时校验位错误问题解决
    quickfix校验位计算都是根据ISO-8859-1编码计算,知道这个规则后续我们处理中文就很好处理了。但是如果用ISO-8859-1编码有中文会出现乱码,如果将CharsetSupport.setCharset设置为UTF-8或者GBK时,在发送数据时会报java.nio.bufferoverflowexception:null,或者校验位失败。1、往step网......
  • 【HMS Core】Health Kit云测数据接入相关问题
    ​【问题描述1】1、由于存在IOS、android、微信小程序,计划接入“云侧数据开放服务”,使用模式为我们自己的服务端去同步华为健康数据,终端通过服务端获取最新的数据。2、在接入准备阶段,申请账号时,文档建议申请“服务器应用”,但已经存在“XXX”这个移动端应用,是否需要单独再申请“......
  • 【HMS Core】Health Kit云测数据接入相关问题
    【问题描述1】1、由于存在IOS、android、微信小程序,计划接入“云侧数据开放服务”,使用模式为我们自己的服务端去同步华为健康数据,终端通过服务端获取最新的数据。2、在接入准备阶段,申请账号时,文档建议申请“服务器应用”,但已经存在“XXX”这个移动端应用,是否需要单独再申请“服务器......
  • 奇安信设备问题(初中级)2
    椒图设备1、在椒图平台日志分析中result字段表示的含义是?答:拦截结果;0表示已拦截,1表示未拦截。2、在椒图平台中如何配置针对服务器非白名单账号和登录IP的监控?答:通过威胁检测—异常登录—违规登录—登录规则设置,添加白名单账户和IP。3、在椒图平台下发web类安全策略需要使用哪......
  • 关于项目发到现场部署时缺少hutool工具依赖的问题
    关于hutool工具包使用总结一、问题在内蒙反写项目中,因为是新部署的项目,有些jar包现场并没有,在发包运行后,产生异常,显示cn.hutool:hutool-ali:jar:5.7.21的库无法被解析和下载 二、分析问题没办法我又在网上找了一圈,发现出现这种问题大多就4种情况依赖库信息错......
  • sqlserver存储过程中使用临时表的问题
    2023年6月6日08:52:15因为最近接触的his系统一些存储过程做数据统计,一个存储过程就要使用1-3个临时表,这些存储过程是零几年的写得,和我们这个时代的写的存储过程习惯不太一样,就好奇为什么要使用这么多的临时表临时表的基本概念在深入临时表之前,我们要了解一下会话(Session),......
  • lvreduce缩容导致根分区只读问题
    描述:我这里想扩容swap的大小,发现磁盘空间都分出去了看根分区还有很大空间于是对根分区下手了,看看能否对根分区进行缩容操作本来想从根取2G空间的,一个不小心把根空间变成2G空间[root@localhost~]#lvreduce-L2G/dev/mapper/bigcloud--enterprise--linux--for--euler-root......