java基础:java.util.concurrent.BlockingQueue

前言

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

介绍

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

java基础:java.util.concurrent.BlockingQueue_第1张图片
1_0.png

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

java基础:java.util.concurrent.BlockingQueue_第2张图片
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数据就会出现线程等待,模型如下


    java基础:java.util.concurrent.BlockingQueue_第3张图片
    Paste_Image.png
  2. 当队列空了,再往队列里take数据,就会出现线程等待,模型如下

java基础:java.util.concurrent.BlockingQueue_第4张图片
Paste_Image.png

实现了BlockingQueue的类

java基础:java.util.concurrent.BlockingQueue_第5张图片
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();
    }
    }
  • 总结

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

你可能感兴趣的:(java基础:java.util.concurrent.BlockingQueue)