Java基础——BlockingQueue源码分析之LinkedBlockingQueue

BlockingQueue是什么

  • BlockingQueue是一个阻塞队列的接口
  • BlockingQueue是线程安全的
  • BlockingQueue具有先进先出的特点
  • 当队列满的时候进行入队操作会阻塞,当队列空的时候进行出队操作会阻塞

BlockingQueue提供的接口

BlockingQueue提供的接口有四种不同的方法,具体如下表所示

操作 Throws Exception Special Value Blocks Times Out
插入 add(o) offer(o) put(o) offer(o, timeout, timeunit)
删除 remove(o) poll() take() poll(timeout, timeunit)
查询 element() peek() - -

这四种不同的方法对应的特点分别是

  1. ThrowsException:如果操作不能马上进行,则抛出异常
  2. SpecialValue:如果操作不能马上进行,将会返回一个特殊的值,一般是true或者false
  3. Blocks:如果操作不能马上进行,操作会被阻塞
  4. TimesOut:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值

LinkedBlockingQueue是什么

  • LinkedBlockingQueue是一个基于链表的阻塞队列
  • LinkedBlockingQueue可以指定容量大小,默认为Integer.MAX_VALUE
  • LinkedBlockingQueue是线程安全的
  • LinkedBlockingQueue具有先进先出的特点
  • 当队列满的时候进行入队(put)操作会阻塞,当队列空的时候进行出队(take)操作会阻塞
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable

LinkedBlockingQueue成员变量

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

private final int capacity; //容量边界
private final AtomicInteger count = new AtomicInteger(); //当前元素的个数

transient Node<E> head; //队列头指针
private transient Node<E> last; //队列尾指针

private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();//容量不为满
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();//容量不为空

由于LinkedBlockingQueue是基于链表实现的阻塞队列,因此需要个结构体Node来存储链表的数据。其中两个锁是为了保证线程安全的,而两个Condition则是用来阻塞队列的。两个锁的用意是

  • ArrayBlockingQueue只有lock锁,说明入队操作和出队操作不能同时进行
  • LinkedBlockingQueue有put锁和take锁两把锁,说明入队操作和出队操作可以同时进行

LinkedBlockingQueue构造方法

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

构造函数

  1. 创造一个容量为Integer.MAX_VALUE的队列
  2. 创造一个指定容量大小的队列,如果参数小于等于零,则抛异常

LinkedBlockingQueue的存储

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);//根据当前的值构造一个节点
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;//提供原子操作的Integer类型
    putLock.lockInterruptibly();//获取锁
    try {
        while (count.get() == capacity) {
            notFull.await();//容量已满,需要等待
        }
        enqueue(node);//链表的插入操作
        c = count.getAndIncrement();//count自增,返回原来的值
        if (c + 1 < capacity)
            notFull.signal();//容量未满,唤醒等待
    } finally {
        putLock.unlock();//释放锁
    }
    if (c == 0)
        signalNotEmpty();//原来的容量是空的,唤醒等待
}

private void enqueue(Node<E> node) {
    last = last.next = node;//加入链尾
}

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

存储的操作很有逻辑

  1. 存储前,先判断容量是否为满,如果满了就等待take()的通知,不满就执行链表的插入操作
  2. 插入后,需要验证插入后是否已经满了,如果没满就应该通知
  3. 最后,如果原来的容量是空的,那么在插入后,自然就不是空的,需要去唤醒不为空的通知

LinkedBlockingQueue的获取

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();//count自减,返回原来的值
        if (c > 1)
            notEmpty.signal();//容量不为空,唤醒等待
    } finally {
        takeLock.unlock();//释放锁
    }
    if (c == capacity)
        signalNotFull();//原来的容量是满的,唤醒等待
    return x;
}

private E dequeue() {
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h;
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}

private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}

获取的操作也很有逻辑

  1. 获取前,先判断容量是否为空,如果空了就等待put()的通知,不空就执行链表的删除操作
  2. 删除后,需要验证删除后是否已经空了,如果没空就应该通知
  3. 最后,如果原来的容量是满的,那么在删除后,自然就不是满的,需要去唤醒不为满的通知

总结

  1. LinkedBlockingQueue队列是基于链表和Condition类来实现的
  2. LinkedBlockingQueue的存储和获取采用生产消费模式
  3. LinkedBlockingQueue的采用两把锁进行了读写分离,有利于提高并发度
  4. LinkedBlockingQueue的队列中不允许元素为null
  5. LinkedBlockingQueue队列性能好于ArrayBlockingQueue
  6. LinkedBlockingQueue队列在Executors.newFixedThreadPool()被使用

猜你喜欢

转载自blog.csdn.net/qq_30379689/article/details/80643893