【转】-Java实现生产者和消费者的5种方式
Java实现生产者和消费者的5种方式
该博客转载自掘金的Java实现生产者和消费者的5种方式
1. 前言
生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

现在用四种方式来实现生产者消费者模型
2. wait()和notify()方法的实现
这也是最简单最基础的实现,缓冲区满和为空时都调用wait()方法等待,当生产者生产了一个产品或者消费者消费了一个产品之后会唤醒所有线程。
/**
* 生产者和消费者,wait()和notify()的实现
* @author ZGJ
* @date 2017年6月22日
*/
public class Test1 {
private static Integer count = 0;
private static final Integer FULL = 10;
private static String LOCK = "lock";
public static void main(String[] args) {
Test1 test1 = new Test1();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (LOCK) {
while (count == FULL) {
try {
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "生产者生产,目前总共有" + count);
LOCK.notifyAll();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK) {
while (count == 0) {
try {
LOCK.wait();
} catch (Exception e) {
}
}
count--;
System.out.println(Thread.currentThread().getName() + "消费者消费,目前总共有" + count);
LOCK.notifyAll();
}
}
}
}
}
结果:
Thread-0生产者生产,目前总共有1
Thread-4生产者生产,目前总共有2
Thread-3消费者消费,目前总共有1
Thread-1消费者消费,目前总共有0
Thread-2生产者生产,目前总共有1
Thread-6生产者生产,目前总共有2
Thread-7消费者消费,目前总共有1
Thread-5消费者消费,目前总共有0
Thread-0生产者生产,目前总共有1
Thread-4生产者生产,目前总共有2
Thread-3消费者消费,目前总共有1
Thread-6生产者生产,目前总共有2
Thread-1消费者消费,目前总共有1
Thread-7消费者消费,目前总共有0
Thread-2生产者生产,目前总共有1
Thread-5消费者消费,目前总共有0
Thread-0生产者生产,目前总共有1
Thread-4生产者生产,目前总共有2
Thread-3消费者消费,目前总共有1
Thread-7消费者消费,目前总共有0
Thread-6生产者生产,目前总共有1
Thread-2生产者生产,目前总共有2
Thread-1消费者消费,目前总共有1
Thread-5消费者消费,目前总共有0
Thread-0生产者生产,目前总共有1
Thread-4生产者生产,目前总共有2
Thread-3消费者消费,目前总共有1
Thread-1消费者消费,目前总共有0
Thread-6生产者生产,目前总共有1
Thread-7消费者消费,目前总共有0
Thread-2生产者生产,目前总共有1
3. 可重入锁ReentrantLock的实现
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,通过对lock的lock()方法和unlock()方法实现了对锁的显示控制,而synchronize()则是对锁的隐性控制。
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响,简单来说,该锁维护这一个与获取锁相关的计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,函数调用结束计数器就减1,然后锁需要被释放两次才能获得真正释放。已经获取锁的线程进入其他需要相同锁的同步代码块不会被阻塞。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 生产者和消费者,ReentrantLock的实现
*
* @author ZGJ
* @date 2017年6月22日
*/
public class Test2 {
private static Integer count = 0;
private static final Integer FULL = 10;
//创建一个锁对象
private Lock lock = new ReentrantLock();
//创建两个条件变量,一个为缓冲区非满,一个为缓冲区非空
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
Test2 test2 = new Test2();
new Thread(test2.new Producer()).start();
new Thread(test2.new Consumer()).start();
new Thread(test2.new Producer()).start();
new Thread(test2.new Consumer()).start();
new Thread(test2.new Producer()).start();
new Thread(test2.new Consumer()).start();
new Thread(test2.new Producer()).start();
new Thread(test2.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
//获取锁
lock.lock();
try {
while (count == FULL) {
try {
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName()
+ "生产者生产,目前总共有" + count);
//唤醒消费者
notEmpty.signal();
} finally {
//释放锁
lock.unlock();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
lock.lock();
try {
while (count == 0) {
try {
notEmpty.await();
} catch (Exception e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName()
+ "消费者消费,目前总共有" + count);
notFull.signal();
} finally {
lock.unlock();
}
}
}
}
}
4. 阻塞队列BlockingQueue的实现
BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:
- 当队列满了的时候进行入队列操作
- 当队列空了的时候进行出队列操作
因此,当一个线程对已经满了的阻塞队列进行入队操作时会阻塞,除非有另外一个线程进行了出队操作,当一个线程对一个空的阻塞队列进行出队操作时也会阻塞,除非有另外一个线程进行了入队操作。
从上可知,阻塞队列是线程安全的。
下面是BlockingQueue接口的一些方法:
| 操作 | 抛异常 | 特定值 | 阻塞 | 超时 |
|---|---|---|---|---|
| 插入 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
| 移除 | remove(o) | poll(o) | take(o) | poll(timeout, timeunit) |
| 检查 | element(o) | peek(o) |
这四类方法分别对应的是:
- ThrowsException:如果操作不能马上进行,则抛出异常
- SpecialValue:如果操作不能马上进行,将会返回一个特殊的值,一般是true或者false
- Blocks:如果操作不能马上进行,操作会被阻塞
- TimesOut:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false
下面来看由阻塞队列实现的生产者消费者模型,这里我们使用take()和put()方法,这里生产者和生产者,消费者和消费者之间不存在同步,所以会出现连续生成和连续消费的现象
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 使用BlockingQueue实现生产者消费者模型
* @author ZGJ
* @date 2017年6月29日
*/
public class Test3 {
private static Integer count = 0;
//创建一个阻塞队列
final BlockingQueue blockingQueue = new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
Test3 test3 = new Test3();
new Thread(test3.new Producer()).start();
new Thread(test3.new Consumer()).start();
new Thread(test3.new Producer()).start();
new Thread(test3.new Consumer()).start();
new Thread(test3.new Producer()).start();
new Thread(test3.new Consumer()).start();
new Thread(test3.new Producer()).start();
new Thread(test3.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
try {
blockingQueue.put(1);
count++;
System.out.println(Thread.currentThread().getName()
+ "生产者生产,目前总共有" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
blockingQueue.take();
count--;
System.out.println(Thread.currentThread().getName()
+ "消费者消费,目前总共有" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
5. 信号量Semaphore的实现
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源,在操作系统中是一个非常重要的问题,可以用来解决哲学家就餐问题。Java中的Semaphore维护了一个许可集,一开始先设定这个许可集的数量,可以使用acquire()方法获得一个许可,当许可不足时会被阻塞,release()添加一个许可。在下列代码中,还加入了另外一个mutex信号量,维护生产者消费者之间的同步关系,保证生产者和消费者之间的交替进行
import java.util.concurrent.Semaphore;
/**
* 使用semaphore信号量实现
* @author ZGJ
* @date 2017年6月29日
*/
public class Test4 {
private static Integer count = 0;
//创建三个信号量
final Semaphore notFull = new Semaphore(10);
final Semaphore notEmpty = new Semaphore(0);
final Semaphore mutex = new Semaphore(1);
public static void main(String[] args) {
Test4 test4 = new Test4();
new Thread(test4.new Producer()).start();
new Thread(test4.new Consumer()).start();
new Thread(test4.new Producer()).start();
new Thread(test4.new Consumer()).start();
new Thread(test4.new Producer()).start();
new Thread(test4.new Consumer()).start();
new Thread(test4.new Producer()).start();
new Thread(test4.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
notFull.acquire();
mutex.acquire();
count++;
System.out.println(Thread.currentThread().getName()
+ "生产者生产,目前总共有" + count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.release();
notEmpty.release();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
notEmpty.acquire();
mutex.acquire();
count--;
System.out.println(Thread.currentThread().getName()
+ "消费者消费,目前总共有" + count);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.release();
notFull.release();
}
}
}
}
}
6. 管道输入输出流PipedInputStream和PipedOutputStream实现
在java的io包下,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流。
它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。
使用方法:先创建一个管道输入流和管道输出流,然后将输入流和输出流进行连接,用生产者线程往管道输出流中写入数据,消费者在管道输入流中读取数据,这样就可以实现了不同线程间的相互通讯,但是这种方式在生产者和生产者、消费者和消费者之间不能保证同步,也就是说在一个生产者和一个消费者的情况下是可以生产者和消费者之间交替运行的,多个生成者和多个消费者者之间则不行
/**
* 使用管道实现生产者消费者模型
* @author ZGJ
* @date 2017年6月30日
*/
public class Test5 {
final PipedInputStream pis = new PipedInputStream();
final PipedOutputStream pos = new PipedOutputStream();
{
try {
pis.connect(pos);
} catch (IOException e) {
e.printStackTrace();
}
}
class Producer implements Runnable {
@Override
public void run() {
try {
while(true) {
Thread.sleep(1000);
int num = (int) (Math.random() * 255);
System.out.println(Thread.currentThread().getName() + "生产者生产了一个数字,该数字为: " + num);
pos.write(num);
pos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
pos.close();
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
try {
while(true) {
Thread.sleep(1000);
int num = pis.read();
System.out.println("消费者消费了一个数字,该数字为:" + num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
pos.close();
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Test5 test5 = new Test5();
new Thread(test5.new Producer()).start();
new Thread(test5.new Consumer()).start();
}
}
【转】-Java实现生产者和消费者的5种方式的更多相关文章
- 多生产者多消费者(第二种方式2.1)基于BlockingQueue
public class Producer implements Runnable { //静态变量只初始化一次 private static AtomicInteger count = new At ...
- 多生产者多消费者(第一种方式),基于synchronized,wait,notifyAll
生产者消费者模式描述的是协调与协作关系.比如一个人正在准备食物(生产者),而另一个人正在吃(消费者),他们使用一个共用 的桌子用于放置盘子和取走盘子,生产者准备食物,如果桌子上已经满了就等待,消费者( ...
- Windows下RabbitMQ 的下载、配置、Java实现生产者和消费者例子
RabbitMQ是一个轻量级的消息代理中间件,支持多种消息通信协议,支持分布式部署,支持运行于多个操作系统,具有灵活.高可用等特性.RabbitMQ支持多种协议,其中最为重要的是高级消息队列协议(AM ...
- java实现生产者和消费者问题
Java实现生产者和消费者问题 欢迎访问我的个人博客,获取更多有用的东西 链接一 链接二 也可以关注我的微信订阅号:CN丶Moti
- Java反射获取class对象的三种方式,反射创建对象的两种方式
Java反射获取class对象的三种方式,反射创建对象的两种方式 1.获取Class对象 在 Java API 中,提供了获取 Class 类对象的三种方法: 第一种,使用 Class.forName ...
- Java实现生产者和消费者
生产者和消费者问题是操作系统的经典问题,在实际工作中也常会用到,主要的难点在于协调生产者和消费者,因为生产者的个数和消费者的个数不确定,而生产者的生成速度与消费者的消费速度也不一样,同时还要实现生产者 ...
- java之生产者与消费者
package com.produce; import java.util.LinkedList; import java.util.Queue; /*@author shijin * 生产者与消费者 ...
- java线 生产者和消费者
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGlhbmdydWkxOTg4/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...
- java实现生产者/消费者的三种方式
package com.wenki.thread; import java.util.LinkedList; import java.util.concurrent.LinkedBlockingQue ...
- Java实现生产者与消费者模式
生产者不断向队列中添加数据,消费者不断从队列中获取数据.如果队列满了,则生产者不能添加数据:如果队列为空,则消费者不能获取数据.借助实现了BlockingQueue接口的LinkedBlockingQ ...
随机推荐
- Dify 架构全景:从模块设计到部署实践的完整指南
项目概述 Dify 是一个开源的 LLM 应用开发平台,提供直观的界面,结合了AI Agent工作流.RAG 管道.智能体能力.模型管理和可观察性功能等,使用户能够快速从原型转向生产环境.Dify 允 ...
- WPF学习问题汇集:
WPF中ItemsSource改变,DataGrid中不更新 需要将ItemsSource先赋值为null,而后再赋值为新的值. 例如: gridBeamInfo.ItemsSource = null ...
- QEMU挂载硬盘巨大的坑(ntldr is missing )
众所周知挂载硬盘只需要加上 -hdb 你的镜像.img 就可以了,注意此时的img是没有格式化的 然后我使用 qemu-system-x86_64 -m 4096 -enable-kvm -hdb c ...
- 几种简单的springboot启动后启动一条死循环线程方式
前言 之前有测试 # 启动类加 @EnableAsync # 方法上加注解 @Async @PostConstruct 但是依旧会卡主主线程,所有另辟蹊径 第一种 在启动类上加注解 @EnableAs ...
- Kubernetes初学乍练(资源管理)
1.资源管理介绍 在Kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理Kubernetes.怎么理解,就好比我们常说的面向对象语言,一切皆对象. Kubernetes的本质就是 ...
- EasyMR 基于国产化信创的适配实践技术详解
国产化信创,即采用国产信息技术产品和服务,构建自主可控的信息技术体系.近年来,随着国家对网络安全和信息安全的重视程度不断提高,国产化信创已经成为国家战略的重要组成部分,并呈现出以下大趋势: ● 政策驱 ...
- 3.Java SDK源码分析系列笔记-LinkedList
目录 1. 是什么 2. 如何使用 3. 原理分析 3.1. uml 3.2. 构造方法 3.2.1. 队列的节点Node 3.3. add方法 3.3.1. 插入到链表尾部 3.3.2. 构造新节点 ...
- 打砖块小游戏html小游戏
这里提供一个打砖块小游戏html代码,有需要的小伙伴可以自己试试. body内容 点击查看代码 <select id="difficulty"> <option ...
- JQ原生 Ajax请求
现在的前端请求.真的是百花齐放,但是老古董ajax 还是挺好用的,主要是简单的 $.ajax({ type: "Post", ...
- C# 泛型对象转Get请求参数
/// <summary> /// 对象转Get请求参数 /// </summary> /// <returns>< ...