普通队列下的生产者-消费者模式
生产者-消费模式是经典的并大量运用的设计模式,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。有效的将生产者与消费者解耦,它两互不关心,各自完成自己的任务。生产者往一个地方放东西,消费者从一个地方取东西,所以生产者与消费者就要共用一块资源了。生产者每生产一个产品,就通知消费者来消费产品,当生产的产品达到了最大容量就停止生产。消费者没消费一个产品就通知生产者去继续生产产品,当队列里面没有产品了,消费者就停止消费。所以生产者与消费者是相互唤醒,必然使用的是同一个锁。下面是使用普通的队列实现的生产者-消费模式。
public class QueueDemo {
private final static int MAX_CAPACITY = 1000;
private static Queue<Integer> queue = new ArrayDeque<Integer>(MAX_CAPACITY);
private static Lock lock = new ReentrantLock();
private static Condition producerCondition = lock.newCondition();
private static Condition consumerCondition = lock.newCondition();
private static void producer() {
while (true) {
try {
lock.lock();
if (queue.size() < MAX_CAPACITY) {
try {
queue.add(1);
System.out.println("生产一个,队列大小" + queue.size());
Thread.sleep(1000); //睡眠1s,便于观察
consumerCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
producerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
private static void consumer() {
while (true) {
try {
lock.lock();
if (queue.size() > 0) {
try {
queue.poll();
System.out.println("消费一个, 队列大小" + queue.size());
Thread.sleep(1000);
producerCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
consumerCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 1; i++) {
new Thread(new Runnable() {
@Override
public void run() {
producer();
}
}).start();
}
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
consumer();
}
}).start();
}
}
}
阻塞队列下的生产者-消费模式
从上面的例子可以看出。我们使用了大量代码来维护共享资源queue,在jdk1.5以后,提供了阻塞队列的实现,下面是使用阻塞队列实现的生产者-消费者模式。
public class BlockQueueDemo {
private static BlockingQueue blockingQueue = new ArrayBlockingQueue(1000);
private static void producer() {
while (true){
try {
blockingQueue.put(1);
System.out.println("生产一个, 队列大小" + blockingQueue.size());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void consumer() {
while (true){
try {
blockingQueue.take();
System.out.println("消费一个, 队列大小" + blockingQueue.size());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 1; i++) {
new Thread(new Runnable() {
@Override
public void run() {
producer();
}
}).start();
}
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
consumer();
}
}).start();
}
}
}
阻塞队列
阻塞队列提供了四种处理方法:
方法\处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
- 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
- 返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
- 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
- 超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
ArrayBlockingQueue的实现
看ArrayBlockingQueue的构造方法,里面lock,notEmpty ,notFull 是不是和普通队列的demo中lock,producerCondition ,consumerCondition 很像。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
看offer方法
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();//获取锁
try {
if (count == items.length) //如果当前队列大小等于队列的容量大小返回false
return false;
else {
insert(e);//向队列插入一个值
return true;
}
} finally {
lock.unlock();//释放锁
}
}
进入insert(e)这个方法
private void insert(E x) {
items[putIndex] = x;//赋值
putIndex = inc(putIndex);
++count;//当前队列大小+1
notEmpty.signal();//唤醒其它线程消费
}
可以看到notEmpty.signal();
看take方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//当前队列大小为0时等待添加值进来
return extract();
} finally {
lock.unlock();
}
}
可以看出阻塞队列的设计思想就是消费者-生产者模式。