生产者消费者模式
以缓冲区作为生产者和消费者之间沟通的桥梁: 生产者只负责生产, 将生产出来的数据存入缓冲区. 而消费者只负责消费, 不断的从缓冲区中取出数据进行处理.
生产者消费者模式是非常常用的, 因为应用该模式有效的解耦了生产者和消费者. 生产者不需要知道有没有其他生产者在生产, 也不需要知道有多少个消费者在消费, 而消费者不需要知道数据来自哪个生产者. 另外该模式支持并发操作, 如果生产者直接调用消费者的方法, 生产者就必须等到消费者处理完毕才能返回, 万一消费者处理的速度很慢, 就会白白浪费生产者的时间. 而使用模式的话, 生产者只需要将数据存入缓冲区就可以了.
缓冲区是生产者消费者模式的核心. 生产者将数据存入缓冲区的一端, 消费者则负责从缓冲区的另一端取出数据进行处理, 队列非常适用这样的场景. 由于生产者消费者大多处于不同的线程, 队列就必须是线程安全的--java的BlockingQueue可以满足要求.
BlockingQueue
BlockingQueue的put方法用于将数据放入队列, 如果队列已满, put方法所在的线程将阻塞, 直到队列不满. take方法用于从队列中取出数据, 如果队列为空, take方法所在的线程将阻塞, 直到队列不为空.
- public void put(E e) throws InterruptedException {
- if (e == null)
- throw new NullPointerException();
- final E[] items = this.items;
- final ReentrantLock lock = this.lock;
- // 锁定
- lock.lockInterruptibly();
- try {
- try {
- // 如果队列已满, 就阻塞线程
- while (count == items.length)
- notFull.await();
- } catch (InterruptedException ie) {
- notFull.signal(); // propagate to non-interrupted thread
- throw ie;
- }
- insert(e);
- } finally {
- lock.unlock();
- }
- }
- private void insert(E x) {
- items[putIndex] = x;
- putIndex = inc(putIndex);
- ++count;
- // 插入数据后唤醒在非空条件上阻塞的线程
- notEmpty.signal();
- }
- public E take() throws InterruptedException {
- final ReentrantLock lock = this.lock;
- // 锁定
- lock.lockInterruptibly();
- try {
- try {
- // 如果队列为空, 就阻塞线程
- while (count == 0)
- notEmpty.await();
- } catch (InterruptedException ie) {
- notEmpty.signal(); // propagate to non-interrupted thread
- throw ie;
- }
- E x = extract();
- return x;
- } finally {
- lock.unlock();
- }
- }
- private E extract() {
- final E[] items = this.items;
- E x = items[takeIndex];
- items[takeIndex] = null;
- takeIndex = inc(takeIndex);
- --count;
- // 取出数据后唤醒在notFull条件上阻塞的线程
- notFull.signal();
- return x;
- }
offer(E e, long timeout, TimeUnit unit)用于将数据放入队列, 如果队列已满, 将最多等待指定的时间, offer返回true时说明数据成功入队, 否则说明没有成功. poll(long timeout, TimeUnit unit)是与offer配对的方法, 用于从队列中取出数据, 如果队列为空, 最多等待指定的时间, poll返回值为null时说明没有取到数据.
- public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
- if (e == null)
- throw new NullPointerException();
- // 获得阻塞的最大时间
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- for (;;) {
- // 如果队列没有满, 则插入数据并返回true
- if (count != items.length) {
- insert(e);
- return true;
- }
- // 如果剩余的等待时间小于等于0说明等待时间已超过最大值, 此时返回false, 表明插入没有成功
- if (nanos <= 0)
- return false;
- try {
- // awaitNanos方法用于阻塞队列, 并返回剩余的时间值
- nanos = notFull.awaitNanos(nanos);
- } catch (InterruptedException ie) {
- notFull.signal(); // propagate to non-interrupted thread
- throw ie;
- }
- }
- } finally {
- lock.unlock();
- }
- }
- public E poll(long timeout, TimeUnit unit) throws InterruptedException {
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- for (;;) {
- // 如果队列不为空, 就取出数据然后返回
- if (count != 0) {
- E x = extract();
- return x;
- }
- // 如果阻塞时间已过最大时间, 就返回null, 说明没有取到数据
- if (nanos <= 0)
- return null;
- try {
- // awaitNanos方法用于阻塞队列, 并返回剩余的时间值
- nanos = notEmpty.awaitNanos(nanos);
- } catch (InterruptedException ie) {
- notEmpty.signal(); // propagate to non-interrupted thread
- throw ie;
- }
- }
- } finally {
- lock.unlock();
- }
- }
BlockingQueue的容量可以是无限的, 也可以是有限的. 无限容量的BlockingQueue永远也不会发生队列已满的事件.
BlockingQueue的常见实现类有ArrayBlockingQueue, LinkedBlockingQueue, 以及PriorityBlockingQueue等. ArrayBlockingQueue底层使用循环数组实现, LinkedBlockingQueue底层使用链表实现. PriorityBlockingQueue则是一个可排序的阻塞队列, 可以按照元素的自然顺序(元素需要实现Comparable接口)或者指定的Comparator排序.
生产者消费者模式的例子
该例子用于模拟对文件进行索引, 生产者FileCrawler类将待索引的文件放入队列, 消费者Indexer则负责从队列中取出文件进行索引标记.
- /**
- * 生产者, 生产待索引的文件
- */
- public class FileCrawler implements Runnable {
- private final BlockingQueue<File> fileQueue;
- private final FileFilter fileFilter;
- private final File root;
- public FileCrawler(BlockingQueue<File> fileQueue, FileFilter fileFilter, File root) {
- this.fileQueue = fileQueue;
- this.fileFilter = fileFilter;
- this.root = root;
- }
- public void run() {
- try {
- crawl(root);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- private void crawl(File root) throws InterruptedException {
- File[] entries = root.listFiles(fileFilter);
- if (entries != null) {
- for (File entry : entries)
- if (entry.isDirectory()) {
- // 递归调用
- crawl(entry);
- } else if (!alreadyIndexed(entry)) {
- // 向队列中添加文件, 如果队列是BOUND的, 且队列已满, 则put方法将阻塞, 直到队列不满
- System.out.println(entry + ": 等待进行索引 by " + Thread.currentThread().getName());
- fileQueue.put(entry);
- }
- }
- }
- private boolean alreadyIndexed(File entry) {
- return false;
- }
- }
- /**
- * 消费者, 从队列中取出文件进行索引标记
- */
- public class Indexer implements Runnable {
- private final BlockingQueue<File> queue;
- public Indexer(BlockingQueue<File> queue) {
- this.queue = queue;
- }
- public void run() {
- try {
- while (true) {
- // 从队列中取出file标记索引. 如果队列为空, take方法将阻塞, 直到队列重新不为空.
- File file = queue.take();
- indexFile(file);
- System.out.println(file + ": 已进行索引 by " + Thread.currentThread().getName());
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- private void indexFile(File file) {
- }
- }
- /**
- * 测试生产者消费者模式
- */
- public class FileIndexer {
- public static void startIndexing(File[] roots) {
- // 创建一个BOUNDED队列, 队列中最大的元素为10个
- BlockingQueue<File> queue = new LinkedBlockingQueue<File>(10);
- FileFilter filter = new FileFilter() {
- public boolean accept(File file) { return true; }
- };
- for (int i = 0; i < roots.length; i++) {
- File root = roots[i];
- // 启动生产者线程
- new Thread(new FileCrawler(queue, filter, root), "producer " + i).start();
- }
- // 启动3个消费者线程
- for (int i = 0; i < 3; i++) {
- new Thread(new Indexer(queue), "consumer " + i).start();
- }
- }
- public static void main(String[] args) {
- File dir = new File("E:\\TDDOWNLOAD\\mina doc");
- startIndexing(dir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- if (pathname.isDirectory()) {
- return true;
- }
- return false;
- }
- }));
- }
- }