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

前言

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

示意图如下

f65a2ae45ac48e153b50c3eb8bae47be.png

如何解决这个问题

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

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

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

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

1.wait() / notify()方法

思路:

以我们去店里买包子为例

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

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

换成多线程即:

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

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

代码如下:

商品类(包子)

c96a04f6031e2ec4eefc6f14b6a86f74.png

生产者(包子铺)

ca61faac4e458905423ba7d170b466ce.png

消费者(吃货)

1c2293243820d33e69d323ed6dd7777f.png

主函数

5cf336135badd3c321e7c4baa0488d0c.png

运行结果

7ebb11e86356f5b445269abdfc4b5693.png

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

2. BlockingQueue阻塞队列方法

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

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

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

6ba5de0f75d5f09bfd3e9c01cc84f13e.png

运行结果:

bf1693c0102d8910a5d77159dcbccd5b.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()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全

8083b2135dc7500fafdb2ae15513a370.png

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

4. 信号量

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

fd17ac9c741f75e7d568d4d488d020fa.png

5. 管道

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

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

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

PipedInputStream / PipedOutputStream (操作字节流)

Producer

bebe462f64ca3882f8ccffef83b23bd9.png

consumer

b5b4d68cae005164b11e26a07c181edb.png

Main

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

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值