Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型

时间:2022-12-11 09:58:27

Queue是什么

队列,是一种数据结构。除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的。无论使用哪种排序方式,队列的头都是调用remove()或poll()移除元素的。在FIFO队列中,所有新元素都插入队列的末尾。

Queue中的方法

Queue中的方法不难理解,6个,每2对是一个也就是总共3对。看一下JDK API就知道了:

Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型

注意一点就好,Queue通常不允许插入Null,尽管某些实现(比如LinkedList)是允许的,但是也不建议。

BlockingQueue

1、BlockingQueue概述

只讲BlockingQueue,因为BlockingQueue是Queue中的一个重点,并且通过BlockingQueue我们再次加深对于生产者/消费者模型的理解。其他的Queue都不难,通过查看JDK API和简单阅读源码完全可以理解他们的作用。

BlockingQueue,顾名思义,阻塞队列。BlockingQueue是在java.util.concurrent下的,因此不难理解,BlockingQueue是为了解决多线程中数据高效安全传输而提出的。

多线程中,很多场景都可以使用队列实现,比如经典的生产者/消费者模型,通过队列可以便利地实现两者之间数据的共享,定义一个生产者线程,定义一个消费者线程,通过队列共享数据就可以了。

当然现实不可能都是理想的,比如消费者消费速度比生产者生产的速度要快,那么消费者消费到 一定程度上的时候,必须要暂停等待一下了(使消费者线程处于WAITING状态)。BlockingQueue的提出,就是为了解决这个问题的,他不用程序员去控制这些细节,同时还要兼顾效率和线程安全。

阻塞队列所谓的"阻塞",指的是某些情况下线程会挂起(即阻塞),一旦条件满足,被挂起的线程又会自动唤醒。使用BlockingQueue,不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,这些内容BlockingQueue都已经做好了

2、BlockingQueue中的方法

BlockingQueue既然是Queue的子接口,必然有Queue中的方法,上面已经列了。看一下BlockingQueue中特有的方法:

(1)void put(E e) throws InterruptedException

把e添加进BlockingQueue中,如果BlockingQueue中没有空间,则调用线程被阻塞,进入等待状态,直到BlockingQueue中有空间再继续

(2)void take() throws InterruptedException

取走BlockingQueue里面排在首位的对象,如果BlockingQueue为空,则调用线程被阻塞,进入等待状态,直到BlockingQueue有新的数据被加入

(3)int drainTo(Collection<? super E> c, int maxElements)

一次性取走BlockingQueue中的数据到c中,可以指定取的个数。通过该方法可以提升获取数据效率,不需要多次分批加锁或释放锁

3、ArrayBlockingQueue

基于数组的阻塞队列,必须指定队列大小。比较简单。ArrayBlockingQueue中只有一个ReentrantLock对象,这意味着生产者和消费者无法并行运行(见下面的代码)。另外,创建ArrayBlockingQueue时,可以指定ReentrantLock是否为公平锁,默认采用非公平锁。

/** Main lock guarding all access */
private final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;

4、LinkedBlockingQueue

基于链表的阻塞队列,和ArrayBlockingQueue差不多。不过LinkedBlockingQueue如果不指定队列容量大小,会默认一个类似无限大小的容量,之所以说是类似是因为这个无限大小是Integer.MAX_VALUE,这么说就好理解ArrayBlockingQueue为什么必须要制定大小了,如果ArrayBlockingQueue不指定大小的话就用Integer.MAX_VALUE,那将造成大量的空间浪费,但是基于链表实现就不一样的,一个一个节点连起来而已。另外,LinkedBlockingQueue生产者和消费者都有自己的锁(见下面的代码),这意味着生产者和消费者可以"同时"运行。

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

5、SynchronousQueue

比较特殊,一种没有缓冲的等待队列。什么叫做没有缓冲区,ArrayBlocking中有:

/** The queued items  */
private final E[] items;

数组用以存储队列。LinkedBlockingQueue中有:

/**
* Linked list node class
*/
static class Node<E> {
/** The item, volatile to ensure barrier separating write and read */
volatile E item;
Node<E> next;
Node(E x) { item = x; }
}

将队列以链表形式连接。

生产者/消费者操作数据实际上都是通过这两个"中介"来操作数据的,但是SynchronousQueue则是生产者直接把数据给消费者(消费者直接从生产者这里拿数据),好像又回到了没有生产者/消费者模型的老办法了。换句话说,每一个插入操作必须等待一个线程对应的移除操作。SynchronousQueue又有两种模式:

1、公平模式

采用公平锁,并配合一个FIFO队列(Queue)来管理多余的生产者和消费者

2、非公平模式

采用非公平锁,并配合一个LIFO栈(Stack)来管理多余的生产者和消费者,这也是SynchronousQueue默认的模式

利用BlockingQueue实现生产者消费者模型

上一篇我们写的生产者消费者模型有局限,局限体现在:

  • 缓冲区内只能存放一个数据,实际生产者/消费者模型中的缓冲区内可以存放大量生产者生产出来的数据
  • 生产者和消费者处理数据的速度几乎一样

OK,我们就用BlockingQueue来简单写一个例子,并且让生产者、消费者处理数据速度不同。子类选择的是ArrayBlockingQueue,大小定为10:

public static void main(String[] args)
{
final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(10);
Runnable producerRunnable = new Runnable()
{
int i = 0;
public void run()
{
while (true)
{
try
{
System.out.println("我生产了一个" + i++);
bq.put(i + "");
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
};
Runnable customerRunnable = new Runnable()
{
public void run()
{
while (true)
{
try
{
System.out.println("我消费了一个" + bq.take());
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
};
Thread producerThread = new Thread(producerRunnable);
Thread customerThread = new Thread(customerRunnable);
producerThread.start();
customerThread.start();
}

代码的做法是让生产者生产速度快于消费者消费速度的,看一下运行结果:

 我生产了一个0
我消费了一个1
我生产了一个1
我生产了一个2
我消费了一个2
我生产了一个3
我生产了一个4
我生产了一个5
我消费了一个3
我生产了一个6
我生产了一个7
我生产了一个8
我消费了一个4
我生产了一个9
我生产了一个10
我生产了一个11
我消费了一个5
我生产了一个12
我生产了一个13
我生产了一个14
我消费了一个6
我生产了一个15
我生产了一个16
我消费了一个7
我生产了一个17
我消费了一个8
我生产了一个18

分两部分来看输出结果:

1、第1行~第23行。这块BlockingQueue未满,所以生产者随便生产,消费者随便消费,基本上都是生产3个消费1个,消费者消费速度慢

2、第24行~第27行,从前面我们可以看出,生产到16,消费到6,说明到了ArrayBlockingQueue的极限10了,这时候没办法,生产者生产一个ArrayBlockingQueue就满了,所以不能继续生产了,只有等到消费者消费完才可以继续生产。所以之后的打印内容一定是一个生产者、一个消费者

这就是前面一章开头说的"通过平衡生产者和消费者的处理能力来提高整体处理数据的速度",这给例子应该体现得很明显。另外,也不要担心非单一生产者/消费者场景下的系统假死问题,缓冲区空、缓冲区满的场景BlockingQueue都是定义了不同的Condition,所以不会唤醒自己的同类。

Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型的更多相关文章

  1. java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

     *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时 ...

  2. 如何在 Java 中正确使用 wait&comma; notify 和 notifyAll – 以生产者消费者模型为例

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  3. java多线程系类:基础篇:10生产者消费者的问题

    概要 本章,会对"生产/消费者问题"进行讨论.涉及到的内容包括:1. 生产/消费者模型2. 生产/消费者实现 转载请注明出处:http://www.cnblogs.com/skyw ...

  4. 03:进程Queue --- 生产者消费者模型

    1 进程Queue介绍 1 进程间数据隔离,两个进程进行通信,借助于Queue​2 进程间通信:IPC -借助于Queue实现进程间通信    -借助于文件        -借助于数据库    -借助 ...

  5. Java多线程(十):BlockingQueue实现生产者消费者模型

    BlockingQueue BlockingQueue.解决了多线程中,如何高效安全"传输"数据的问题.程序员无需关心什么时候阻塞线程,什么时候唤醒线程,该唤醒哪个线程. 方法介绍 ...

  6. java多线程15 :wait&lpar;&rpar;和notify&lpar;&rpar; 的生产者&sol;消费者模式

    什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: ...

  7. Java多线程-并发协作&lpar;生产者消费者模型&rpar;

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

  8. 结合生活,剖析《生产者消费者模型》-java多线程(一)

    博客园的园友们好,看博客园上各位大佬的文章,已陪伴了我程序员职业的三年, 如今自己同样希望能把自己从小白到菜鸟的成长过程分享给大家.不定期更新!!! 首先我本人智商不高,理解问题十分吃力,完全不属于天 ...

  9. java多线程:线程间通信——生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...

随机推荐

  1. DG gap sequence修复一例

    环境:Oracle 11.2.0.4 DG 故障现象: 客户在备库告警日志中发现GAP sequence提示信息: Mon Nov 21 09:53:29 2016 Media Recovery Wa ...

  2. bootstrap组件 2

    bootstrap组件2 <!DOCTYPE html> <html lang="zh-cn"> <head > <meta charse ...

  3. 批量Ping IP

    刚刚接触Python 想做点什么 听说Python 在网络方便很厉害 后来总结如下: 第一:发现公司都固定IP 每次新来同事都要猜一个没有人用的IP  很费劲 第二:我们公司有的IP可以上QQ 有的不 ...

  4. KDTree

    学习链接:http://www.cnblogs.com/eyeszjwang/articles/2429382.html 下面实现的kdtree支持以下操作:(1) 插入一个节点(2) 插入n个节点( ...

  5. linux C高手成长过程---书籍推荐

    建议学习路径: 首先先学学编辑器,vim, emacs什么的都行. 然后学make file文件,只要知道一点就行,这样就可以准备编程序了. 然后看看<C程序设计语言>K&R,这样 ...

  6. 『奇葩问题集锦』Zepto 页面唤醒拨号功能点透

    不废话直接上代码: HTML: <a class="js-tel tel" data-tel="1312414"></a> JS: // ...

  7. Threejs 的场景查看 - 几个交互事件库助你方便查看场景

    Threejs 的场景查看 - 几个交互事件库助你方便查看场景 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致&q ...

  8. PHP fwrite 函数:将字符串写入文件(追加与换行)

    PHP fwrite() fwrite() 函数用于向文件写入字符串,成功返回写入的字符数,否则返回 FALSE . 语法: int fwrite( resource handle, string s ...

  9. 黑an网络---an网入门

    我认为学网络的会上an网也是一项必备的技能,我不是鼓励你去犯罪而是保护自己的个人隐私免遭被侵害.an网和shen网实际上都是从英文翻译来的,an网即Dark Net或Dark Web,shen网则译为 ...

  10. Redis详解(六)------ RDB 持久化

     前面我们说过,Redis 相对于 Memcache 等其他的缓存产品,有一个比较明显的优势就是 Redis 不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,has ...