Java并发编程(十二)阻塞队列LinkedBlockingQueue

129 阅读3分钟

1 LinkedBlockingQueue核心属性

底层基于链表实现的,会将每个元素封装为Node,Node有当前值,还有一个next指针,默认情况下最大长度为Integer.MAX_VALUE,一般被称为无界队列。 LinkedBlockingQueue本质就是一个用Node封装的单向链表。

  • Node
/**
 * 阻塞队列元素会被封装为Node
 */
static class Node<E> {
    E item;

    Node<E> next;

    Node(E x) { item = x; }
}

LinkedBlockingQueue内部提供了读锁和写锁,读写不互斥,而且记录数据条数的属性是AtomicInteger

/** 记录数据条数 */
private final AtomicInteger count = new AtomicInteger();

/** 读锁 */
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();

/** 写锁 */
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

1.1 构造器

LinkedBlockingQueue可以通过构造器传入参数来限制队列的长度,如果不传,则默认为Integer.MAX_VALUE

另外还有一个传入集合类的构造器,capacity为Integer.MAX_VALUE,然后循环将集合内元素全部加入

/** 指定队列的长度,如果不传值,默认为Integer.MAX */
private final int capacity;

// 无参构造
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);  
}

public LinkedBlockingQueue(Collection<? extends E> c) {  
    this(Integer.MAX_VALUE);  
    final ReentrantLock putLock = this.putLock;  
    // 可见行必要
    putLock.lock(); 
    try {  
        int n = 0;  
        for (E e : c) {  
            if (e == null)  
                throw new NullPointerException();  
            // 健壮性判断
            if (n == capacity)  
                throw new IllegalStateException("Queue full");  
            enqueue(new Node<E>(e));  
            ++n;  
        }  
        // 实际加入的个数
        count.set(n);  
    } finally {  
        putLock.unlock();  
    }  
}

2 LinkedBlockingQueue存取源码分析

2.1 存数据

  • enqueue,实际插入链表的方法
// 插入数据到链表
private void enqueue(Node<E> node) {
    last = last.next = node;
}
  • offer,将非null数据插入链表,如果链表已满,则返回false
public boolean offer(E e) {
    // 非空
    if (e == null) throw new NullPointerException();
    // 拿到count(记录当前数据条数)
    final AtomicInteger count = this.count;
    // 如果count达到了最大值
    if (count.get() == capacity)
        // 数据满了。
        return false;
    // 声明c 
    int c = -1;
    // 将当前数据封装为Node
    Node<E> node = new Node<E>(e);
    // 添加写锁~
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        // !!DCL!!
        // 再次拿到条数判断,如果还有空间,enqueue存数据
        if (count.get() < capacity) {
            // 数据放进来
            enqueue(node);
            // 拿到count,再自增
            c = count.getAndIncrement();
            // 添加完数据之后,长度依然小于最大长度,唤醒可能阻塞的写线程  
            // 读写不互斥,可能前面在执行时,队列是满的,但是读操作依然在进行
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    // c == 0,说明添加数据之前,队列是空的,唤醒可能阻塞的读线程
    if (c == 0)
        signalNotEmpty();
    // 返回count >= 0
    return c >= 0;
}
  • offer(E e, long timeout, TimeUnit unit), 将非null数据插入链表,如果链表已满则超时等待,超时后返回false
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    if (e == null) throw new NullPointerException();
    long nanos = unit.toNanos(timeout);
    int c = -1;
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 获取锁,可被中断
    putLock.lockInterruptibly();
    try {
        // 超时等待
        while (count.get() == capacity) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(new Node<E>(e));
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}
  • put,将非null数据插入链表,如果链表已满,阻塞等待
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;
    // 获取锁,可被中断
    putLock.lockInterruptibly();
    try {
        // 链表已满则阻塞等待
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

2.2 取数据

  • dequeue,内部实际的取出方法,取出并改变链表头结点
private E dequeue() {
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // help GC
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}
  • poll,取数据,队列为空则返回null
public E poll() {
    final AtomicInteger count = this.count;
    // 为0,没数据
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    // 读锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 如果队列有数据  DCL
        if (count.get() > 0) {
            x = dequeue();
            // count --
            c = count.getAndDecrement();
            if (c > 1)
                // c > 1,说明还有数据,唤醒读线程
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        // 到这说明还有位置呢,唤醒写线程
        signalNotFull();
    return x;
}

  • poll(long timeout, TimeUnit unit),取数据,队列为空则超时等待,有数据则直接取出,超时后仍为空,返回null
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    final E x;
    final int c;
    long nanos = unit.toNanos(timeout);
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        // 超时等待
        while (count.get() == 0) {
            if (nanos <= 0L)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        // 取数据
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

  • take,取数据,队列为空则阻塞至队列有数据后取出
    public E take() throws InterruptedException {
        final E x;
        final int c;
        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;
    }