• Java并发编程之LinkedBlockingDeque阻塞队列详解


    简介

    LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。

    相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。

    LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。

    LinkedBlockingDeque类有三个构造方法:

    public LinkedBlockingDeque()public LinkedBlockingDeque(int capacity)public LinkedBlockingDeque(Collection<? extends E> c)
    

    LinkedBlockingDeque源码详解

    LinkedBlockingDeque类定义为:

    public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>, java.io.Serializable
    

    该类继承自AbstractQueue抽象类,又实现了BlockingDeque接口,下面介绍一个BlockingDeque接口,该接口定义如下:

    public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E>
    

    BlockingDeque继承自BlockingQueue和Deque接口,BlockingDeque接口定义了在双端队列中常用的方法。

    LinkedBlockingDeque类中的数据都被封装成了Node对象:

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

    LinkedBlockingDeque类中的重要字段如下:

    // 队列双向链表首节点transient Node<E> first;// 队列双向链表尾节点transient Node<E> last;// 双向链表元素个数private transient int count;// 双向链表最大容量private final int capacity;// 全局独占锁final ReentrantLock lock = new ReentrantLock();// 非空Condition对象private final Condition notEmpty = lock.newCondition();// 非满Condition对象private final Condition notFull = lock.newCondition();
    

    LinkedBlockingDeque类的底层实现和LinkedBlockingQueue类很相似,都有一个全局独占锁,和两个Condition对象,用来阻塞和唤醒线程。

    LinkedBlockingDeque类对元素的操作方法比较多,我们下面以putFirst、putLast、pollFirst、pollLast方法来对元素的入队、出队操作进行分析。

    入队

    putFirst(E e)方法是将指定的元素插入双端队列的开头,源码如下:

    public void putFirst(E e) throws InterruptedException {    // 若插入元素为null,则直接抛出NullPointerException异常    if (e == null) throw new NullPointerException();    // 将插入节点包装为Node节点    Node<E> node = new Node<E>(e);    // 获取全局独占锁    final ReentrantLock lock = this.lock;    lock.lock();    try {        while (!linkFirst(node))            notFull.await();    } finally {        // 释放全局独占锁        lock.unlock();    }}
    

    入队操作是通过linkFirst(E e)方法来完成的,如下所示:

    private boolean linkFirst(Node<E> node) {    // assert lock.isHeldByCurrentThread();    // 元素个数超出容量。直接返回false    if (count >= capacity)        return false;    // 获取双向链表的首节点    Node<E> f = first;    // 将node设置为首节点    node.next = f;    first = node;    // 若last为null,设置尾节点为node节点    if (last == null)        last = node;    else        // 更新原首节点的前驱节点        f.prev = node;    ++count;    // 唤醒阻塞在notEmpty上的线程    notEmpty.signal();    return true;}
    

    若入队成功,则linkFirst(E e)方法返回true,否则,返回false。若该方法返回false,则当前线程会阻塞在notFull条件上。

    putLast(E e)方法是将指定的元素插入到双端队列的末尾,源码如下:

    public void putLast(E e) throws InterruptedException {    // 若插入元素为null,则直接抛出NullPointerException异常    if (e == null) throw new NullPointerException();    // 将插入节点包装为Node节点    Node<E> node = new Node<E>(e);    // 获取全局独占锁    final ReentrantLock lock = this.lock;    lock.lock();    try {        while (!linkLast(node))            notFull.await();    } finally {        // 释放全局独占锁        lock.unlock();    }}
    

    该方法和putFirst(E e)方法几乎一样,不同点在于,putLast(E e)方法通过调用linkLast(E e)方法来插入节点:

    private boolean linkLast(Node<E> node) {    // assert lock.isHeldByCurrentThread();    // 元素个数超出容量。直接返回false    if (count >= capacity)        return false;    // 获取双向链表的尾节点    Node<E> l = last;    // 将node设置为尾节点    node.prev = l;    last = node;    // 若first为null,设置首节点为node节点    if (first == null)        first = node;    else        // 更新原尾节点的后继节点        l.next = node;    ++count;    // 唤醒阻塞在notEmpty上的线程    notEmpty.signal();    return true;}
    

    若入队成功,则linkLast(E e)方法返回true,否则,返回false。若该方法返回false,则当前线程会阻塞在notFull条件上。

    pollFirst()方法是获取并移除此双端队列的首节点,若不存在,则返回null,源码如下:

    public E pollFirst() {    // 获取全局独占锁    final ReentrantLock lock = this.lock;    lock.lock();    try {        return unlinkFirst();    } finally {        // 释放全局独占锁        lock.unlock();    }}
    

    移除首节点的操作是通过unlinkFirst()方法来完成的:

    private E unlinkFirst() {    // assert lock.isHeldByCurrentThread();    // 获取首节点    Node<E> f = first;    // 首节点为null,则返回null    if (f == null)        return null;    // 获取首节点的后继节点    Node<E> n = f.next;    // 移除first,将首节点更新为n    E item = f.item;    f.item = null;    f.next = f; // help GC    first = n;    // 移除首节点后,为空队列    if (n == null)        last = null;    else        // 将新的首节点的前驱节点设置为null        n.prev = null;    --count;    // 唤醒阻塞在notFull上的线程    notFull.signal();    return item;}
    

    pollLast()方法是获取并移除此双端队列的尾节点,若不存在,则返回null,源码如下:

    public E pollLast() {    // 获取全局独占锁    final ReentrantLock lock = this.lock;    lock.lock();    try {        return unlinkLast();    } finally {        // 释放全局独占锁        lock.unlock();    }}
    

    移除尾节点的操作是通过unlinkLast()方法来完成的:

    private E unlinkLast() {    // assert lock.isHeldByCurrentThread();    // 获取尾节点    Node<E> l = last;    // 尾节点为null,则返回null    if (l == null)        return null;    // 获取尾节点的前驱节点    Node<E> p = l.prev;    // 移除尾节点,将尾节点更新为p    E item = l.item;    l.item = null;    l.prev = l; // help GC    last = p;    // 移除尾节点后,为空队列    if (p == null)        first = null;    else        // 将新的尾节点的后继节点设置为null        p.next = null;    --count;    // 唤醒阻塞在notFull上的线程    notFull.signal();    return item;}
    

    其实LinkedBlockingDeque类的入队、出队操作都是通过linkFirst、linkLast、unlinkFirst、unlinkLast这几个方法来实现的,源码读起来也比较简单。

    相关博客

    Java并发编程之ArrayBlockingQueue阻塞队列详解

    Java并发编程之ReentrantLock详解

    Java并发编程之Condition详解

    Java并发编程之LinkedBlockingQueue阻塞队列详解

    参考资料

    方腾飞:《Java并发编程的艺术》

    本文转自https://blog.csdn.net/qq_38293564/article/details/80592429

  • 相关阅读:
    多线程 信号量
    sql在不同数据库查询前几条数据
    Office Outlook同步 很奇怪的BUG
    搜索小技巧整理
    想做一个权限管理插件
    ibatis和Castle学习历程
    查找存储过程中的错误位置
    VS2005项目模版丢失解决方案及VS2005项目模版查找原理
    C# 邮件发送接收
    数据库优化整合
  • 原文地址:https://www.cnblogs.com/snake107/p/12035580.html
Copyright © 2020-2023  润新知