java基础:java.util.concurrent.BlockingQueue

前言

在一次项目中,偶遇BlockingQueue,特意查了下用法,使我对它有了强列的兴趣,经过一段时间的学习,将其整理,用图解的方式解释,方便理解。

介绍

在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。图(1_0.png)是其继承关系,可以看出BlockingQueue是继承Queue。

1_0.png

我们先看下BlockQueue的图解,通过图,我们很容易理解,这是一个队列基本图,在图中,我们发现入队有put、add、offer三个方法。出队有poll、remove、take三个方法,就是因为这几个方法,使得blockQueue功能很强大。

1_1.png

put、add、offer 区别

  • put
    把Object加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
  • add
    把Object加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则报异常
  • offer
    将Object加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.
    当队列满了,put阻塞,等有了再加,add直接报错,offer返回状态

poll、remove、take 区别

  • take
    取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止
  • remove
    取走BlockingQueue里排在首位的对象,若不能立即取出,则抛出异常
  • poll
    取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,
        取不到时返回null;
    当队列空了,take取不到就等,remove就抛异常,poll就返回null

阻塞模型

  1. 当队列满了,再往队列里put数据就会出现线程等待,模型如下


    Paste_Image.png
  2. 当队列空了,再往队列里take数据,就会出现线程等待,模型如下

Paste_Image.png

实现了BlockingQueue的类

Paste_Image.png

我们常用的队列就linkedBlockingQueue和ArrayBlockingQueue。

LinkedBlockingQueue 和 ArrayBlockingQueue

  • 区别
    我们区分下LinkedBlockingQueue和ArrayBlockingQueue,凡是linked打头的都是内部维护一个链表,Array打头的是维护一个数组,实现方式不一样,功能也有所区别。
  • LinkedBlockingQueue是内部维护一个链表,当执行put方法时,程序会先获得一个重入锁,防止多线程同时操作产生数据不正确,然后后生成一个数据节点(Node)添加到链表的最后面。如果队列满,则使用java.util.concurrent.locks.Condition的await()方法来阻塞继续添加。
    这是put方法源码:
    public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
    while (count.get() == capacity) {
    notFull.await();
    }
    enqueue(e);
    c = count.getAndIncrement();
    if (c + 1 < capacity)
    notFull.signal();
    } finally {
    putLock.unlock();
    }
    if (c == 0)
    signalNotEmpty();
    }
    take()取数据也用了await()方法,了解原理后,发现取数据时当链表为空时,就阻塞。
    这是take()方法源码:
    public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
    while (count.get() == 0) {
    notEmpty.await();
    }
    x = dequeue();
    c = count.getAndDecrement();
    if (c > 1)
    notEmpty.signal();
    } finally {
    takeLock.unlock();
    }
    if (c == capacity)
    signalNotFull();
    return x;
    }
  • ArrayBlockingQueue是内部维护一个数组,当执行put方法时,程序也会先获得一个重入锁,防止多纯种同时操作数组,然后直接插入到数组上面,这里和linkedBlockingQueue有个区别就是不会产生一个新的对象节点,开销上插入比LinkedBlockQueue节省空间。
    这是put方法源码:
    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();
    }
    }
  • 总结

这两个队列的实现几乎都是相似的,我们可以理解成对应的数组和链表的实现。并且是线程安全的,在特定场景中使用特定的实现类能保证高效和空间的合理性。

推荐阅读更多精彩内容

  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,259评论 1 19
  • 1.如何暂停或恢复线程 在JDK中提供了以下两个方法(类Thread)用来暂停线程和恢复线程。 Øsuspend方...
    pgl2011阅读 1,096评论 0 1
  • 一.线程安全性 线程安全是建立在对于对象状态访问操作进行管理,特别是对共享的与可变的状态的访问 解释下上面的话: ...
    黄大大吃不胖阅读 517评论 0 3
  • 相关概念 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对...
    东经315度阅读 953评论 0 8
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,176评论 0 3
  • 拉起窗帘 打开昨日的书签 给窗前的草 浇点水 然后 看着它 笑上一整天 有时候 突然有点伤感 午后的轻眠 ...
    玩笑的熊阅读 280评论 12 12
  • 欢迎来到小于的王者地盘,小于已经更新了王者峡谷72位英雄全新的推荐出装以及铭文搭配,大家关注小于之后只需回复英雄名...
    王者小于帮阅读 157评论 0 1
  • 地球村里有个一个混得不怎么样的小子,看到大块头邻居家院子里挂着几排腊肉,有几块探出来差不多够到了自己家门口,肉香味...
    丰声阅读 247评论 0 0