LinkedBlockingQueue

LinkedBlockingQueue继承体系  

  

与ArrayBlockingQueue一样,LinkedBlockingQueue实现了Queue、BlockingQueue接口,继承AbstractCollection类。

源码分析

LinkedBlockingQueue的成员变量

// 队列的容量,若不指定,默认 Integer.MAX_VALUE
private final int capacity;
// 队列中元素的数量
private final AtomicInteger count = new AtomicInteger();
// 队列头节点
transient Node<E> head;
// 队列尾节点
private transient Node<E> last;

// 读锁   take() 等获取元素用到的可重入排他锁
private final ReentrantLock takeLock = new ReentrantLock();

// 阻塞获取数据,用于线程挂起、线程唤醒
private final Condition notEmpty = takeLock.newCondition();

// put() 等添加元素用到的可重入排它锁
private final ReentrantLock putLock = new ReentrantLock();

// 写锁  阻塞添加数据,用于线程挂起、线程唤醒
private final Condition notFull = putLock.newCondition();

LinkedBlockingQueue的链表节点静态类:

static class Node<E> {
    // 元素
    E item;
    // 指向下一节点的Node
    Node<E> next;
    // 构造函数
    Node(E x) { item = x; }
}

LinkedBlockingQueue基于Node节点实现元素的存储,通过next属性将添加的元素构成一个单向链表,在成员变量中声明了链表的头节点head、链表的尾节点last。

添加元素

LinkedBlockingQueue#enqueue(node) 详情如下:
// 元素添加进队列
private void enqueue(Node<E> node) {
     // 将node添加进队列 - 单向链表的尾部
    last = last.next = node;
}

offer(e) - 添加元素

// 添加元素至队列尾
public boolean offer(E e) {
    // 添加元素为null,抛出异常
    if (e == null) throw new NullPointerException();
    // 获取当前队列中元素总数
    final AtomicInteger count = this.count;
    // 队列已满,添加元素失败,返回1false
    if (count.get() == capacity)
        return false;
    // 添加元素e前,队列中的元素总数
    int c = -1;
    // 创建Node节点,封装添加的e元素
    Node<E> node = new Node<E>(e);
    // 获取锁对象
    final ReentrantLock putLock = this.putLock;
    // 加锁
    putLock.lock();
    try {
        // 队列元素总数 小于 队列容量
        if (count.get() < capacity) {
            // 元素添加进队列中
            enqueue(node);
            // 队列元素总数 + 1
            c = count.getAndIncrement();
            // 当前元素添加进队列后,队列未满
            if (c + 1 < capacity)
                // 唤醒其他添加元素线程
                notFull.signal();
        }
    } finally {
        // 释放锁
        putLock.unlock();
    }
    // c = count.getAndIncrement() => 先获取队列中的元素总数,若之前队列为空,即count = 0
    // 添加元素后,队列元素总数为1,此时需要唤醒take阻塞获取元素的线程
    if (c == 0)
        signalNotEmpty();
    // 返回添加结果
    return c >= 0;
}

offer(e)在队列尾部添加元素,若队列中的数组元素已满,返回false;否则将元素添加进队列中,返回false。

若添加元素后队列未满,唤醒其他添加元素的线程(put);

若添加元素前,队列为空,在元素添加成功后,唤醒获取元素的线程(take)。

与ArrayBlockingQueue基于数组添加不同的是,LinkedBlockingQueue基于链表添加元素。

offer(e, timeout, unit)

LinkedBlockingQueue#offer(e, timeout, unit) 详情如下:

// 添加元素至队列尾
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    // 添加元素为null,抛出异常
    if (e == null) throw new NullPointerException();
    // 阻塞时间转化为纳秒
    long nanos = unit.toNanos(timeout);
    // 添加元素e前,队列中的元素总数
    int c = -1;
    // 获取锁对象
    final ReentrantLock putLock = this.putLock;
    // 获取当前队列中元素总数
    final AtomicInteger count = this.count;
    // 加锁
    putLock.lockInterruptibly();
    try {
        // 队列已满
        while (count.get() == capacity) {
            // 阻塞时间已达到
            if (nanos <= 0)
                // 返回false
                return false;
            // 阻塞时间未达到,继续阻塞,并返回阻塞的剩余时间
            nanos = notFull.awaitNanos(nanos);
        }
        // 添加进队列中
        enqueue(new Node<E>(e));
        // 获取添加元素前的总数 c 并获取添加元素后的总数 count + 1
        c = count.getAndIncrement();
        // 当前元素添加进队列后,队列未满
        if (c + 1 < capacity)
            // 唤醒其他添加元素线程
            notFull.signal();
    } finally {
        // 释放锁资源
        putLock.unlock();
    }
    // c = count.getAndIncrement() => 先获取队列中的元素总数,若之前队列为空,即count = 0
    // 添加元素后,队列元素总数为1,此时需要唤醒take阻塞获取元素的线程
    if (c == 0)
        signalNotEmpty();
    // 添加成功返回false
    return true;
}

offer(e, timeout, unit)在队列尾部添加元素,若队列中的数组元素已满,阻塞timeout时间,若阻塞时间已达到,仍未添加成功,返回false。

阻塞时间未到达添加成功,若添加元素后队列未满,唤醒其他添加元素的线程(put);

若添加元素前,队列为空,在元素添加成功后,唤醒获取元素的线程(take)。

put(e) - 添加元素

LinkedBlockingQueue#put(e) 详情如下:

// 添加元素至队列尾
public void put(E e) throws InterruptedException {
    // 添加元素为null,抛出异常
    if (e == null) throw new NullPointerException();
    // 添加元素e前,队列中的元素总数
    int c = -1;
    // 创建Node节点,封装添加的e元素
    Node<E> node = new Node<E>(e);
     // 获取锁对象
    final ReentrantLock putLock = this.putLock;
    // 获取当前队列中元素总数
    final AtomicInteger count = this.count;
    // 加锁
    putLock.lockInterruptibly();
    try {
        // 当前队列已满,put操作挂起线程
        while (count.get() == capacity) {
            notFull.await();
        }
        // 元素添加进队列
        enqueue(node);
        // 获取添加元素前的总数 c 并获取添加元素后的总数 count + 1
        c = count.getAndIncrement();
        // 添加元素后,队列未满,唤醒put添加元素线程操作
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        // 释放锁资源
        putLock.unlock();
    }
    // c = count.getAndIncrement() => 先获取队列中的元素总数,若之前队列为空,即count = 0
    // 添加元素后,队列元素总数为1,此时需要唤醒take阻塞获取元素的线程
    if (c == 0)
        signalNotEmpty();
}

put(e)阻塞添加元素至队列尾部,若当前队列已满,当前put线程挂起。当获取元素线程从队列中拿走元素,队列中有可取空间时,唤醒挂起的put线程,将元素添加进队列中。

若添加元素后队列未满,唤醒其他添加元素的线程(put);

若添加元素前,队列为空,在元素添加成功后,唤醒获取元素的线程(take)。

总结

方法

不同点

offer(e)

队列容量已满,添加失败,直接返回false

offer(e,timeout,unit)

队列容量已满,阻塞等待timeout时间,若阻塞时间到达,仍添加失败,直接返回false

put(e)

队列容量已满,阻塞等待直到队列中有可添加的空间

 

出队元素

LinkedBlockingQueue#dequeue() 详情如下:

// 从队列头元素中移除并返回元素
private E dequeue() {
    // 获取队列头节点
    Node<E> h = head;
    // 获取队列头节点的next
    Node<E> first = h.next;
    // 将当前队列头节点的next作为新的头节点,原头节点通过GC回收
    h.next = h;
    head = first;
    // 获取并记录新头节点的元素值
    E x = first.item;
    // 当前节点的元素值设置为null
    first.item = null;
    // 返回当前节点的元素值
    return x;
}

peek() - 获取元素

LinkedBlockingQueue#peek() 详情如下

// 获取队列头节点
public E peek() {
    // 队列中无可取的元素
    if (count.get() == 0)
        // 返回null
        return null;
    // 获取锁对象
    final ReentrantLock takeLock = this.takeLock;
    // 加锁
    takeLock.lock();
    try {
        // 获取队列元素头节点
        Node<E> first = head.next;
        // 头节点为null,返回null
        if (first == null)
            return null;
        // 头节点不为null,返回节点中的元素值
        else
            return first.item;
    } finally {
        // 释放锁
        takeLock.unlock();
    }
}

peek()获取队列中的头元素,若队列中无可取的元素,返回null;获取head的next节点,若该节点为null,则返回null,否则返回节点中的item值。

poll() - 获取元素

LinkedBlockingQueue#poll() 详情如下:

// 获取队列头节点
public E poll() {
    // 获取队列中的元素总数
    final AtomicInteger count = this.count;
    // 队列中无可取的元素,返回null
    if (count.get() == 0)
        return null;
    // 返回元素变量
    E x = null;
    // 本次获取元素操作前,队列中元素的总数
    int c = -1;
    // 获取 take 的锁对象
    final ReentrantLock takeLock = this.takeLock;
    // 加锁
    takeLock.lock();
    try {
        // 队列中有可取元素
        if (count.get() > 0) {
            // 获取队列头元素
            x = dequeue();
            // 得到本次获取元素前的总数 c 并对获取元素后的总数 count - 1
            c = count.getAndDecrement();
            // 本次获取元素前,队列非空,至少有两个可取元素
            if (c > 1)
                // 唤醒其他获取队列元素的线程
                notEmpty.signal();
        }
    } finally {
        // 释放锁资源
        takeLock.unlock();
    }
    // 本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,
    // 队列中有可用空间,唤醒put线程添加元素
    if (c == capacity)
        signalNotFull();
    // 返回元素值
    return x;
}

poll()获取队列中的头元素,若队列中无可取的元素,返回null;若队列中有多个可取元素,获取队列头节点并唤醒其他获取元素线程,若在本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,队列中有可用空间,唤醒put线程

添加元素。

poll(timeout, unit)

LinkedBlockingQueue#poll(timeout, unit) 详情如下:

// 获取队列头节点(阻塞指定时间)
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    // 返回元素变量
    E x = null;
    // 本次获取元素操作前,队列中元素的总数
    int c = -1;
    // 转换为纳秒
    long nanos = unit.toNanos(timeout);
    // 当前队列中元素总数
    final AtomicInteger count = this.count;
    // 获取锁对象
    final ReentrantLock takeLock = this.takeLock;
    // 加锁
    takeLock.lockInterruptibly();
    try {
        // 当前队列可取元素为空
        while (count.get() == 0) {
            // 阻塞时间已达到
            if (nanos <= 0)
                // 返回null
                return null;
            // 阻塞时间未达到,继续阻塞,并返回剩余阻塞时间
            nanos = notEmpty.awaitNanos(nanos);
        }
        // 获取队列头元素
        x = dequeue();
        // 得到本次获取元素前的总数 c 并对获取元素后的总数 count - 1
        c = count.getAndDecrement();
        // 本次获取元素前,队列非空,至少有两个可取元素
        if (c > 1)
            // 唤醒其他获取队列元素的线程
            notEmpty.signal();
    } finally {
        // 释放锁资源
        takeLock.unlock();
    }
    // 本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,
    // 队列中有可用空间,唤醒put线程添加元素
    if (c == capacity)
        signalNotFull();
    // 返回元素值
    return x;
}

poll(timeout, unit)获取队列中的头元素,若队列中无可取的元素,阻塞timeout时间,阻塞时间已达到仍未获取到头元素,返回null;若获取到队列头元素,并且队列中有多个可取元素,获取队列头节点并唤醒其他获取元素线程(poll、take),若在本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,队列中有可用空间,唤醒put线程添加元素。

take() - 阻塞获取元素

LinkedBlockingQueue#take() 详情如下:

// 获取队列头节点(阻塞)
public E take() throws InterruptedException {
    // 返回元素变量
    E x;
    // 本次获取元素操作前,队列中元素的总数
    int c = -1;
    // 当前队列中元素总数
    final AtomicInteger count = this.count;
    // 获取锁对象
    final ReentrantLock takeLock = this.takeLock;
    // 加锁
    takeLock.lockInterruptibly();
    try {
        // 当前队列可取元素为空,挂起线程,待put、offer添加元素成功后,唤醒线程
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 获取队列头元素
        x = dequeue();
       // 得到本次获取元素前的总数 c 并对获取元素后的总数 count - 1
        c = count.getAndDecrement();
        // 本次获取元素前,队列非空,至少有两个可取元素
        if (c > 1)
            // 唤醒其他获取队列元素的线程
            notEmpty.signal();
    } finally {
        // 释放锁
        takeLock.unlock();
    }
    // 本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,
    // 队列中有可用空间,唤醒put线程添加元素
    if (c == capacity)
        signalNotFull();
    // 返回元素值
    return x;
}

take()获取队列中的头元素,若队列中无可取的元素,挂起线程,等到put/offer添加元素成功后唤醒put线程;获取队列头元素,若队列中有多个可取元素,获取队列头节点并唤醒其他获取元素线程,若在本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,队列中有可用空间,唤醒put线程添加元素。

总结

方法
不同点
peek()
队列无可取元素,获取失败,直接返回null
poll()
队列无可取元素,直接返回null
poll(timeout,unit)
队列无可取元素,阻塞等待timeout时间,若阻塞时间到达,仍获取失败,直接返回null
take()
队列无可取元素,阻塞等待直到队列中有可取元素

 

总结

LinkedBlockingQueue基于单向链表实现元素存取,链表中的节点用Node表示,Node中包含当前元素值及指向下一节点的指向next。LinkedBlockingQueue内部维护Atomic原子类count成员变量,记录当前数组中的元素数量,维护当前队列头节点head、尾节点last。LinkedBlockingQueue内部提供了读锁和写锁,读写不互斥。

LinkedBlockingQueue数据结构详情如下:

0

添加元素时,优先判断count值是否等于队列容量,即队列是否满了,若队列满了,调用不同的添加方法有不同的结果。若在添加元素前队列为空,在添加元素后,唤醒(take)获取元素线程;

获取元素时,优先判断count值是否等于0,即队列是否为空,对队列为空,直接返回null;poll/take在获取元素前,队列中有多个元素,唤醒(take)获取线程,若在获取队列前队列已满,在获取元素后,队列中有可用空间,唤醒(put)添加元素线程。

LinkedBlockingQueue阻塞添加、获取队列元素是基于AQS中的ConditionObject实现的。

posted on 2024-03-02 10:50  zhengbiyu  阅读(28)  评论(0编辑  收藏  举报