Java 多线程学习笔记:生产者消费者问题
前言:最近在学习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 多线程学习笔记:生产者消费者问题的更多相关文章
- Java多线程学习笔记--生产消费者模式
实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...
- java多线程学习笔记——详细
一.线程类 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...
- JAVA多线程学习笔记(1)
JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...
- Java多线程学习笔记(一)——多线程实现和安全问题
1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...
- Java多线程学习笔记
进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...
- Java多线程-并发协作(生产者消费者模型)
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓 ...
- JAVA多线程经典问题 -- 生产者 消费者
工作2年多来一直也没有计划写自己的技术博客,最近辞职在家翻看<thingking in JAVA>,偶尔看到了生产者与消费者的一个经典的多线程同步问题.本人在工作中很少使用到多线程以及高并 ...
- java 多线程 22 :生产者/消费者模式 进阶 利用await()/signal()实现
java多线程15 :wait()和notify() 的生产者/消费者模式 在这一章已经实现了 wait/notify 生产消费模型 利用await()/signal()实现生产者和消费者模型 一样 ...
- java多线程学习笔记(四)
上一节讲到Synchronized关键字,synchronized上锁的区域:对象锁=方法锁/类锁 本节补充介绍一下synchronized锁重入: 关键字synchronized拥有锁重入的功能,也 ...
随机推荐
- Effective C++ 随笔(3)
条款12: 以对象管理资源 两种只能指针: std:auto_ptr<> 当使用copy操作室,原先的智能指针指向为null std:tr1:share_ptr<int> sp ...
- C++指针二(易错模型)
规则一:Main(主调函数)分配的内存(在堆区,栈区.全局区)都可以在被调用函数里使用.如果在被调用函数里面的临时区(栈)分配内存,主调用函数是不能使用的. #include "stdio. ...
- 第70讲:Scala界面GUI编程实战详解
今天又学习了王家林老师的scala学习讲座第70讲,关于scala的界面编程,让我们来初步学习一下scala中界面编程的过程. 信息来源于 DT大数据梦工厂微信公众账号:DT_Spark 关注微信账号 ...
- hdu 1443 Joseph【约瑟夫环】
题目 题意:一共有2k个人,分别为k个好人和k个坏人,现在我们需要每隔m个人把坏人挑出来,但是条件是最后一个坏人挑出来前不能有好人被挑出来..问最小的m是多少 约瑟夫环问题,通常解决这类问题时我们把编 ...
- ASP.NET Web API 框架研究 Web Host模式下的消息处理管道
寄宿的作用是开启一个进程为Web API提供一个运行环境以解决持续监听.请求监听和响应回复,即将接收到的请求转换成HttpRequestMessage对象传入管道,并将管道生成并经过处理后的HttpR ...
- MCU_数码管常用表
共阴极数码管编码(0---F) unsigned char code table[]={ 0x3f,0x06,0x5d,0x4f, 0x66,0x6d,0x77,0x7c, 0x39,0x5e,0x7 ...
- G - Christmas Play
Description My kid's kindergarten class is putting up a Christmas play. (I hope he gets the lead ro ...
- iOS Document Interaction(预览和打开文档) 编程指南
原文:http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/DocumentInteracti ...
- mooctest项目总结 【转载】
原文链接 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3 ...
- C# .NET 获取枚举值的自定义属性
一.定义一个类 using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...