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 方法来进行分析。

三、入队

1️⃣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();
    }
}

2️⃣入队操作是通过 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 条件上。

3️⃣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();
    }
}

4️⃣该方法和 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 条件上。

四、出队

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

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

2️⃣移除首节点的操作是通过 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;
}

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

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

4️⃣移除尾节点的操作是通过 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 这几个方法来实现的,源码读起来也比较简单。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269