生产者消费者问题_多种方式解决生产者消费者问题(java)

前言

生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中,然后重复此过程;与此同时,消费者也在缓冲区消耗这些数据。生产者和消费者之间必须保持同步,要保证生产者不会在缓冲区满时放入数据,消费者也不会在缓冲区空时消耗数据。不够完善的解决方法容易出现死锁的情况,此时进程都在等待唤醒

示意图如下

8af3baa3ff976c8068f61ea891f09242.png

如何解决这个问题

1.既然是消费者和生产者同时访问该内存,那么肯定是多线程

2.消费者和生产者拿到的肯定是同一种商品,那么就要采用某种机制保护生产者和消费者之间的同步

保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。

这里我们用多种方法来实现,首先是最常见的wait/notify

1.wait() / notify()方法

思路:

以我们去店里买包子为例

当店家蒸好包子了,就不会再蒸,而是进行吆喝,通知消费者来购买

当消费者把包子买完了,就不会在购买,而是在店里等待,通知店家再蒸一笼

换成多线程即:

当生产者生产商品满时,停止执行,放弃锁进入等待,并通知消费者进行消费

当消费者消费商品完时,停止执行,放弃锁进入等待,并通知生产者进行生产

代码如下:

商品类(包子)

78fb8038aa1b35cae1fc241085a5a0a3.png

生产者(包子铺)

745e612653cfea653f1d0bbf0b14206b.png

消费者(吃货)

2b77cdb6f22cdd3286c03d99cb63342a.png

主函数

96ceddd619d92dda000531bad592127a.png

运行结果

526a952a0d1baa63579a62e84348d733.png

本次测试中,有3个吃货,3个包子铺,包子铺生产一个包子,休眠1s,吃货吃一个包子,休息3s,结果显示包子铺生产速度明显快于吃货吃包子速度,符合预想

2. BlockingQueue阻塞队列方法

BlockingQueue是1.5新增的内容,是一个同步队列,它在内部实现了线程同步,实现方式采用await() / signal()方法。它可以在生成对象时指定容量大小,用于阻塞操作的是put()和take()方法。

put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。

take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞

3834a9a439352f11ac53ab328f6ac293.png

运行结果:

fd9db315fe542ebeb1e0e7cf19d2ffdf.png

结果发现System.out.println()输出明显有问题,会多次输出相同值,但整体来看总量确没有问题,那是因为put()或take()对list来说是同步的,但System.out.println()输出并不同步,当两个线程put()成功时,这时list.size() = 2;此时两个线程输出结果都为2。

3.await() / signal()方法

上面我们说过BlockingQueue底层是实现await() / signal()方法,下面我们来看一下

在JDK1.5中,用ReentrantLock和Condition可以实现等待/通知模型,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全

c232fbc23c482aa3c76bdff26a3f8579.png

运行结果与wait()/notify()类似

4. 信号量

Semaphore是一个计数的信号量。设置一个阈值,多个线程竞争获取信号,执行完毕后归还,当超过设定值时,线程申请许可信号被阻塞,常用来构造资源池如数据库连接池,对象池等。当阈值为1时,因只有两个状态,也叫二元信号量,可作为互斥锁使用。

6e7135da5e48f5e17ece10683361254f.png

5. 管道

一种特殊的流,用于不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道中读数据。

inputStream.connect(outputStream)或outputStream.connect(inputStream)作用是使两个Stream之间产生通信链接,这样才可以将数据进行输出与输入。

这种方式只适用于两个线程之间通信,不适合多个线程之间通信。

PipedInputStream / PipedOutputStream (操作字节流)

Producer

0e3a95f37ab183a71a4f4e4bc3b41ffd.png

consumer

b6401492426cd63ea4c1d782dc78d3b4.png

Main

50f5ded0f5826ea8a3bb5ea939475baf.png
PipedReader / PipedWriter (操作字符流)与以上字节流操作一致,代码就不放了

以上就是生产者消费者问题线程同步常见的几种方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值