BlockingQueue笔记

BlockingQueue笔记


BlockingQueue是为了实现线程安全的阻塞队列而设计的,在取元素期间,若当前队列为空会阻塞直到有新的元素添加进来;在添加 元素期间,若当前队列满了会阻塞到有空的位置。BlockingQueue提供了四种接口形式:

  • 抛异常
  • 同步返回具体数值
  • 调用时阻塞当前线程
  • 阻塞超时

BlockingQueue的实现有5种:ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlocingQueue和SynchronousQueue。ArrayBlockingQueue&LinkedBlockingQueue是我们最长使用。

BlockingQueue的成员函数主要是以下几种:

1. Insert
 add add(e)     //插入元素,true表示插入成功,如果队列满了则抛出IllegalStateException,如果插入为null也会抛出NPE
 offer offer(e) //插入元素,true表示插入成功,false表示队列满了
 put put(e)     //插入元素,队列满了则线程阻塞等待
 offer(e, time, unit)  //插入元素,队列满了之后会等待直到传入的超时时间
2.Remove
 remove remove() //移除元素,true表示移除成功
 poll poll()     //取队列顶部元素
 take take()     //获取顶部元素,队列为空则阻塞线程
 poll(time, unit)//取队列顶部元素,队列为空则最多等待超市时间time

3.Examine
 drainTo(Collection<? super E> c) //转移当前队列元素到新的队列,返回为转移的元素数量
 peek()      //获取队列顶部元素,但是不会移除
 element()   //获取队列顶部元素,但是不会移除,与peek不同地方在于队列为空时候抛出NoSuchElementException

LinkedBlockingQueue

LinkedBlockingQueue主要是用链表实现的阻塞队列,是一种FIFO队列,其实针对LinkedBlockingQueue的用法非常简单,因为线程安全问题内部都帮你解决了,并且实现了插入移除的线程等待,先看下它的用法:

class Producer implements Runnable {
    private final LinkedBlockingQueue queue;
    Producer(LinkedBlockingQueue q) { queue = q; }
    public void run() {
      try {
        while (true) { queue.put(produce()); }
      } catch (InterruptedException ex) { ... handle ...}
    }
    Object produce() { ... }
  }

  class Consumer implements Runnable {
    private final LinkedBlockingQueue queue;
    Consumer(LinkedBlockingQueue q) { queue = q; }
    public void run() {
      try {
        while (true) { consume(queue.take()); }
      } catch (InterruptedException ex) { ... handle ...}
    }
    void consume(Object x) { ... }
  }

  class Setup {
    void main() {
      LinkedBlockingQueue q = new LinkedBlockingQueue();
      Producer p = new Producer(q);
      Consumer c1 = new Consumer(q);
      Consumer c2 = new Consumer(q);
      new Thread(p).start();
      new Thread(c1).start();
      new Thread(c2).start();
    }

这里只是以LinkedBlockingQueue为例,这样的用法试用于任何一种实现了BlockingQueue的队列。LinkedBlockingQueue实现了一种以链表形式存储的队列结构,其成员变量如下:

    /**
     * 链表的结点类.
     */
    static class Node<E> {
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }
    /** 容量,默认为Integer.MAX_VALUE */
    private final int capacity;
    /** 当前元素数量 */
    private final AtomicInteger count = new AtomicInteger();
    /** 链表头结点 */
    transient Node<E> head;
    /** 链表尾结点 */
    private transient Node<E> last;
    /** take, poll等取操作的锁 */
    private final ReentrantLock takeLock = new ReentrantLock();
    /** takes操作等待 */
    private final Condition notEmpty = takeLock.newCondition();
    /** put、offer等方法的锁 */
    private final ReentrantLock putLock = new ReentrantLock();
    /** puts操作等待 */
    private final Condition notFull = putLock.newCondition();

Condition是条件变量,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。

LinkedBlockingQueue是如何实现阻塞队列的呢,我们以put为例:

public void put(E e) throws InterruptedException {
        //如果传入的元素为null,抛出NPE
        if (e == null) throw new NullPointerException();
        // 预设值为-1
        int c = -1;
        Node<E> node = new Node<E>(e);
        //获取入队锁
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            //即使count没有锁保护也能在等待时使用,主要的原因在于count值只能在这个阶段减少
            //如果当前的队列满了,则等待其他线程唤醒,这里的唤醒主要是在take()方法中
            while (count.get() == capacity) {
                notFull.await();
            }
            //等待结束放入新结点
            enqueue(node);
            //count值增加
            c = count.getAndIncrement();
            //如果发现此时队列有空位置,唤醒入队条件变量
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            //结束之后释放入队锁
            putLock.unlock();
        }
        //如果当前队列count为0,则唤醒出队条件变量
        if (c == 0)
            signalNotEmpty();
    }

这里在调用put方法插入元素主要是依靠入队锁putLock和入队条件变量notFull来实现多线程插入与阻塞队列的,这里的条件变量notFull唤醒则是在take、poll等取元素的线程中调用。至于最后一点为什么要在当前队列count为0的时候唤醒出队条件呢?

我们来设定一个场景,当前队列为空,此时put/take未开始执行,这时候首先调用take会发线程阻塞住,一直停留在notEmpty.await(),当我们插入数据的线程调用了put之后,一旦调用count.getAndIncrement(),当前的count变为1;

    while (count.get() == 0) {
         notEmpty.await();
    }
    x = dequeue();
    c = count.getAndDecrement();
    if (c > 1)
         notEmpty.signal();

这时候take中的出队条件满足,取出了该元素,但是此时获取到的c是0,并没有唤醒出队条件变量notEmpty。因此需要put来执行唤醒。

其他的take、offer等方法的逻辑类似就不多加赘述,关于其他类型的BlockingQueue后续有时间也会总结下。

参考文章

https://www.cnblogs.com/java-zhao/p/5135958.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值