前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章《阻塞队列实现生产者消费者模式》。在文中,使用的是Java的concurrent包中的阻塞队列来实现。在看完后,自行实现阻塞队列。

(一)准备

  在本博文中,没有使用concurrent包中提供的阻塞队列,而是基于最近对多线程的学习,使用了ReentrantLock,开发实现了阻塞队列。如果想参考concurrent包中阻塞队列的使用方式,请点击上面的连接。

  在多线程中,生产者-消费者问题是一个经典的多线程同步问题。简单来说就是有两种线程(在这里也可以做进程理解)——生产者和消费者,他们共享一个固定大小的缓存区(如一个队列)。生产者负责产生放入新数据,消费者负责取出缓存区的数据。具体介绍请参考 Producer-consumer problem

(二)设计概述

  详细的代码可以到我的 Github 仓库下载(master分支为详细的代码,输出所有的必要信息;simplification分支为简化的代码)。

  项目代码,分为四个类:Consumer类为消费者线程,Producer类为生产者线程,ProducerConsumer测试的启动类,ProducerConsumerQueue类为阻塞队列。

  在多线程中,生产者不断向队列放入新数据,当队列已满时,生产者将被阻塞,直到消费者从队列中取出部分数据并唤醒生产者;消费者不断从队列取出数据,当队列为空时,消费者将被阻塞,直到生产者再次放入新数据并唤醒消费者。在JAVA的concurrent包中,提供了一系列阻塞队列帮我们完成这一工作。这一工作也恰好是生产者消费者的问题的关键所在。在这里,我将自行实现阻塞队列:ProducerConsumerQueue;

(三)详细实现

  (1)ProducerConsumerQueue类

 public final class ProducerConsumerQueue<E> {
//声明一个重入锁
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
//缓存大小
private final int CAPACITY;
//存储数据的缓存队列
private Queue<E> queue; public ProducerConsumerQueue(int CAPACITY) {
this.CAPACITY = CAPACITY;
this.queue = new LinkedList<E>();
} //put the e into the queue
public void put(E e) throws InterruptedException {
lock.lock(); try {
while (queue.size() == this.CAPACITY)
this.notFull.await();
queue.offer(e);
this.notEmpty.signalAll();
} finally {
lock.unlock();
}
} //get e from queue.
public E take() throws InterruptedException {
lock.lock(); try {
while (queue.size() == 0)
this.notEmpty.await();
this.notFull.signalAll();
return queue.poll();
} finally {
lock.unlock();
}
}
}

  该队列提供put()和take()方法,前者用于生产者放入数据,后者用于消费者取出数据。

  在put方法中,首先要获得锁。然后检测是否队列已满,如果是,阻塞生产者线程,并释放锁;当再次获得锁时,这里使用了while语句,使得线程再次检查队列是否已满,如果未满,放入数据到缓存,并唤醒可能阻塞的消费者线程。在这里使用while,目的是防止其他生产者线程已经唤醒,并且成功放入新数据到队列,然后阻塞,而该线程又再次放入,导致可能缓存溢出。

  在take方法中,同样要先获得锁。检测队列是否已空,如果是,阻塞消费者线程,释放锁;当被唤醒并再次获得锁时,使用while再次检查队列是否仍为空,如果非空,则取出缓存数据。while同样为了防止其他消费者对一个空队列取数据。

  (2)Consumer类

 public class Consumer implements Runnable {
private final ProducerConsumerQueue sharedQueue; public Consumer(ProducerConsumerQueue sharedQueue) {
this.sharedQueue = sharedQueue;
} @Override
public void run() {
while (true) {
try {
synchronized (this){
System.out.println("\tConsumer: " + sharedQueue.take());
}
} catch (InterruptedException ex) {
System.out.println(Thread.currentThread().getName() + " throw a interrupexception.");
//do something.
}
}
}
}

  消费者线程持续从队列中取出数据。

  (3)Producer类

 public class Producer implements Runnable {
private final ProducerConsumerQueue sharedQueue; public Producer(ProducerConsumerQueue sharedQueue) {
this.sharedQueue = sharedQueue;
} @Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
synchronized (this){
System.out.println("Producer: "+i);
sharedQueue.put(i);
}
} catch (InterruptedException ex) {
System.out.println(Thread.currentThread().getName() + " throw a interrupexception. ");
//do something.
}
}
}
}

  生产者线程产生数据并放入队列中。

  (4)ProducerConsumer测试类

public class ProducerConsumer {
public static void main(String[] args) {
ProducerConsumerQueue sharedQueue = new ProducerConsumerQueue(4); Thread prodThread = new Thread(new Producer(sharedQueue));
Thread consThread1 = new Thread(new Consumer(sharedQueue));
Thread consThread2 = new Thread(new Consumer(sharedQueue)); prodThread.start();
consThread1.start();
consThread2.start();
}
}

  创建一个生产者线程和两个消费者线程。

扩展:

1、Wikipedia:http://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem

2、《聊聊并发——生产者和消费者模式》http://www.infoq.com/cn/articles/producers-and-consumers-mode

Java 多线程学习笔记:生产者消费者问题的更多相关文章

  1. Java多线程学习笔记--生产消费者模式

    实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...

  2. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  3. JAVA多线程学习笔记(1)

    JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...

  4. Java多线程学习笔记(一)——多线程实现和安全问题

    1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...

  5. Java多线程学习笔记

    进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...

  6. Java多线程-并发协作(生产者消费者模型)

    对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓 ...

  7. JAVA多线程经典问题 -- 生产者 消费者

    工作2年多来一直也没有计划写自己的技术博客,最近辞职在家翻看<thingking in JAVA>,偶尔看到了生产者与消费者的一个经典的多线程同步问题.本人在工作中很少使用到多线程以及高并 ...

  8. java 多线程 22 :生产者/消费者模式 进阶 利用await()/signal()实现

    java多线程15 :wait()和notify() 的生产者/消费者模式 在这一章已经实现了  wait/notify 生产消费模型 利用await()/signal()实现生产者和消费者模型 一样 ...

  9. java多线程学习笔记(四)

    上一节讲到Synchronized关键字,synchronized上锁的区域:对象锁=方法锁/类锁 本节补充介绍一下synchronized锁重入: 关键字synchronized拥有锁重入的功能,也 ...

随机推荐

  1. 阿里ETL工具datax学习(一)

    阿里云开源离线同步工具DataX3.0介绍 一. DataX3.0概览 ​ DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDFS.Hive.Ma ...

  2. js实现锚点定位

    js实现锚点定位的原理是,算出定位的标签距离顶部的高度,点击触发标签,重新赋值滚动条的高度到达指定位置. <!DOCTYPE html> <html> <head> ...

  3. kindeditor 富文本粘贴 图片

    <script type="text/javascript" src="../../Scripts/Plugins/kindeditor/kindeditor.js ...

  4. (转)可伸缩性最佳实践:来自eBay的经验

    转自:http://www.infoq.com/cn/articles/ebay-scalability-best-practices 在eBay,可伸缩性是我们每天奋力抵抗的一大架构压力.我们所做的 ...

  5. 20145232 韩文浩 《Java程序设计》第8周学习总结

    教材学习内容总结 14 NIO与NIO2 NIO使用频道(channel)来衔接数据节点,对数据区的标记提供了clear(),rewind(),flip(),compact()等高级操作. 想要取得c ...

  6. Java理论学时第一节。课后作业。

    设计思路:用nextFloat()函数将从键盘输入的值分别赋给多个变量,然后直接输出相加结果. 流程图: 源代码: 实验结果:

  7. 将电脑信息上传到中国移动ONENET平台

    用两个小时做的 可以用在服务器远程运维等环境非常方便 需要源码的可以联系NBDX123

  8. restfull和传统http的区别

    摘自 https://bbs.csdn.net/topics/390716580 restfull是一种风格,不是规范,也不是所谓的封装,他只是将http协议用的更彻底了,我们在普通的开发中,虽然说是 ...

  9. Android 响应menu,back键,点击外部消失

    点击外部消失,只需要设置popupWindow.setBackgroundDrawable(new PaintDrawable()); 设置 popupWindow.setFocusable(true ...

  10. shell 命令 --ps aux | grep

    ps aux | grep  要查询的进程名 查询当前进程,如 ps aux | grep python 确认过需要查询的进程,就可以进行 kill -9 进程号等操作了.