生产者和消费者模型是操作系统中经典的同步问题。其设计要求为:
(1)生产者和消费者共同使用一个缓冲区;
(2)生产者生产资源,使得缓冲区的资源增加,但当缓冲区存放的资源达到最大时,生产者生产的线程会被堵塞,直到消费者消耗了缓冲区的资源后,线程才被重新唤醒。
(3)消费者消耗资源,使得缓冲区的资源减少,但当缓冲区存放的资源为空时,消费者的线程会被堵塞,直到生产者为缓冲区生产了新的资源后,线程才被重新唤醒。
在Java5之前,一般是通过线程中的wait和notify方法来实现线程的调度的。wait()方法将线程置入“睡眠”状态,同时又“积极”地等待条件发生改变.而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒,并检查条件是否有变。
基于wait和notify方法实现生产者和消费者模型的代码如下:
package com.bos.test.concurrent.producer_consumer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 生产者-消费者 基于wait和notify的实现
* @author betty
*
*/
class Constants{
// 最大任务数量
public static final Integer MAX_TASK_NUM = 5;
// 生产者数量
public static final Integer PRODUCER_NUM = 2;
// 消费者数量
public static final Integer CONSUMER_NUM = 3;
}
/**
* 工作任务
* */
class Task{
private String id;
public Task() {
id = UUID.randomUUID().toString();
}
@Override
public String toString() {
return "Task [id=" + id + "]";
}
}
/**
* 生产者
* */
class Producer implements Runnable{
private List<Task> buffer;
public Producer(List<Task> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
synchronized (buffer) {
// 判断当前任务列表中的任务数量是否大于设点的最大任务数量
while(buffer.size()>=Constants.MAX_TASK_NUM) {
try {
System.out.println("tasks are full!!!");
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 创建新的任务
Task task = new Task();
buffer.add(task);
buffer.notifyAll();
try {
Thread.sleep(1000L*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Producer[" + Thread.currentThread().getName() + "] put " + task);
}
}
}
}
/**
* 消费者
* */
class Consumer implements Runnable{
private List<Task> buffer;
public Consumer(List<Task> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
synchronized (buffer) {
// 判断当前任务列表中的任务数量是否为零
while(buffer.isEmpty()) {
try {
System.out.println("tasks are empty!!!");
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消耗当前一个任务
Task task = buffer.remove(0);
buffer.notifyAll();
try {
Thread.sleep(1000L*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Consumer[" + Thread.currentThread().getName() + "] got " + task);
}
}
}
}
public class Wait_Notify {
public static void main(String[] args) {
// 设置列表的最大任务数量
List<Task> buffer = new ArrayList<Task>(Constants.MAX_TASK_NUM);
// 设置线程池
ExecutorService service = Executors.newFixedThreadPool(Constants.CONSUMER_NUM+Constants.PRODUCER_NUM);
for(int i=0;i<Constants.PRODUCER_NUM;i++) {
service.submit(new Producer(buffer));
}
for(int i=0;i<Constants.CONSUMER_NUM;i++) {
service.submit(new Consumer(buffer));
}
service.shutdown();
}
}
前面写了那么多,终于轮到主角登场了
阻塞队列--BlockingQueue,该队列相当于生产者和消费者模型中的缓冲区:
(1)当队列为空时,即缓冲区存放的资源为空,消费者的线程会被堵塞。直到有数据放入队列,线程会自动唤醒。(2)当队列中填满数据时,即缓冲区存放的资源达到最大,生产者的线程会被堵塞,直到队列有空间时,线程会自动唤醒。
其插入元素的方法有:
offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,
则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中
加入BlockingQueue,则返回失败。
put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断
直到BlockingQueue里面有空间再继续。
取出元素的方法有:
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,
取不到时返回null;
poll(long timeout, TimeUnit unit):取出一个队首的对象,如果在指定时间内,队列一旦有数据
可取,则立即返回队列中的数据。否则直到时间超时还没有数据可取,返回null。
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到
BlockingQueue有新的数据被加入;
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),
通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
BlockingQueue常用成员介绍:
(1)ArrayBlockingQueue。基于数组的阻塞队列实现,有界,在ArrayBlockingQueue内部,维护了一个定长数组。
(2)LinkedBlockingQueue。基于链表的阻塞队列,无界,其大小可以任意设置。
基于BlockingQueue实现生产者和消费者模型的代码如下:
package com.bos.test.concurrent.producer_consumer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 生产者-消费者 基于BlockingQueue的实现
* 在调用put方法向队列中插入元素时,如果队列已满,它会让插入元素的线程等待队列腾出空间;
* 在调用take方法从队列中取元素时,如果队列为空,取出元素的线程就会阻塞。
* @author betty
*
*/
class Common{
// 最大任务数量
public static final Integer MAX_TASK_NUM = 5;
// 生产者数量
public static final Integer PRODUCER_NUM = 4;
// 消费者数量
public static final Integer CONSUMER_NUM = 2;
}
/**
* 工作任务
* */
class TaskBlocking{
private String id;
public TaskBlocking() {
id = UUID.randomUUID().toString();
}
@Override
public String toString() {
return "TaskBlocking [id=" + id + "]";
}
}
class ProducerBlocking implements Runnable{
private BlockingQueue<TaskBlocking> buffer;
public ProducerBlocking(BlockingQueue<TaskBlocking> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
try {
TaskBlocking task = new TaskBlocking();
buffer.put(task);
Thread.sleep(1000L*2);
System.out.println("Producer[" + Thread.currentThread().getName() + "] put " + task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ConsumerBlocking implements Runnable {
private BlockingQueue<TaskBlocking> buffer;
public ConsumerBlocking(BlockingQueue<TaskBlocking> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
try {
TaskBlocking task = buffer.take();
Thread.sleep(1000L*2);
System.out.println("Consumer[" + Thread.currentThread().getName() + "] got " + task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class BlockingQueueTest {
public static void main(String[] args) {
BlockingQueue<TaskBlocking> buffer = new LinkedBlockingQueue<TaskBlocking>(Common.MAX_TASK_NUM);
ExecutorService service = Executors.newFixedThreadPool(Common.CONSUMER_NUM+Common.PRODUCER_NUM);
for(int i=0;i<Common.PRODUCER_NUM;i++) {
service.submit(new ProducerBlocking(buffer));
}
for(int i=0;i<Common.CONSUMER_NUM;i++) {
service.submit(new ConsumerBlocking(buffer));
}
service.shutdown();
}
}
参考网址:
http://blog.youkuaiyun.com/shjniu3000/article/details/53487040
http://blog.youkuaiyun.com/jackfrued/article/details/44499227