Java线程池队列LinkedBlockingDeque的详细原理分析-刘宇

  • 一、 什么是LinkedBlockingDeque?
  • 二、LinkedBlockingDeque类的关系图
  • 三、BlockingDeque介绍
  • 四、LinkedBlockingDeque源码解析
  • 1、字段解析
  • 2、入队操作
  • 2.1、putFirst方法
  • 2.2、offerFirst方法
  • 2.3、入队的具体实现
  • 2.4、入队图解
  • 3、出队操作
  • 3.1、takeLast方法
  • 3.2、pollLast方法
  • 3.3、出队的具体实现
  • 3.4、出队图解
  • 五、总结


CSDN博客地址:
文章来源:转载,原文地址:,感谢这位老哥的辛勤付出,写的非常棒,各位看完别忘了给这位老哥点个赞啊。如有侵权,请联系删除。

一、 什么是LinkedBlockingDeque?

  • LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。
  • 相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法
  • LinkedBlockingDeque是可选容量的,默认容量大小为Integer.MAX_VALUE。

二、LinkedBlockingDeque类的关系图

JAVA线程池使用实例 队列 java线程池队列区别_java

三、BlockingDeque介绍

上一篇文章我们介绍了BlockingQueue接口,既然BlockingDeque接口继承自 BlockingQueue 接口,那我们大致知道有哪些功能了。接下来看看 BlockingQueue 和 BlockingDeque 接口对比,BlockingDeque 提供了哪些功能呢?

提供功能

BlockingQueue

BlockingDeque

头部入队

×


尾部入队



头接出队



尾部部出队

×


如上图,我们看到的从 BlockingDeque 接口比BlockingQueue 接口多了两个功能,分别是头部入队和尾部出队,放入如下:

提供功能

方法名

头部入队

putFirst()、offerFirst()、addFirst()

尾部出队

addLast()、offerLast()、putLast()

四、LinkedBlockingDeque源码解析

1、字段解析

//头结点
transient Node<E> first;
//尾结点
transient Node<E> last;
//队列中个数
private transient int count;
//队列长度,可以使用构造注入,如未设定,默认为无界队列
private final int capacity;
//显示锁
final ReentrantLock lock = new ReentrantLock();
//消费队列(队列为空时,无法消费,线程阻塞)
private final Condition notEmpty = lock.newCondition();
//生产队列(队列满时,无法入队,线程阻塞)
private final Condition notFull = lock.newCondition();

2、入队操作

2.1、putFirst方法

入队操作,该方法是阻塞的

public void putFirst(E e) throws InterruptedException {
	//判空操作
	if (e == null) throw new NullPointerException();
	//节点对象
	Node<E> node = new Node<E>(e);
	//锁
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		//入队操作,如果队列满了,则线程阻塞
	    while (!linkFirst(node))
	        notFull.await();
	} finally {
	    lock.unlock();
	}
}

2.2、offerFirst方法

入队操作,该方法是非阻塞的

public boolean offerFirst(E e) {
	//判空
	if (e == null) throw new NullPointerException();
	//节点对象
	Node<E> node = new Node<E>(e);
	//显示锁
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		//返回是否入队成功 true 为成功,false 为失败
	    return linkFirst(node);
	} finally {
		//解锁,显示锁一定要解锁。
	    lock.unlock();
	}
}

2.3、入队的具体实现

private boolean linkFirst(Node<E> node) {
	/*
	* 判断当前队列中数量是否超过队列允许的边界
	* 如果使用默认构造,长度为 Integer.MAX_VALUE
	* 这长度等于是无界了
	*/
	if (count >= capacity)
	    return false;
	//获取头节点对象
	Node<E> f = first;
	//获取头结点下一个对象
	node.next = f;
	//将下一个对象设为头节点
	first = node;
	//判断尾节点是否为空对象
	if (last == null)
		//尾节点也指向我们设置的节点
	    last = node;
	else
		//将原来的第一个节点的前置节点
		//指向我们新入队的节点
	    f.prev = node;
	//队列长度加 1 
	++count;
	//唤醒堵塞的消费线程(task()操作)
	notEmpty.signal();
	return true;
}

2.4、入队图解

下面是我们两个 Node(data,pre,next 构成一个节点)

JAVA线程池使用实例 队列 java线程池队列区别_并发编程_02

这时我们来了个新的节点:

JAVA线程池使用实例 队列 java线程池队列区别_LinkedBlocking_03

由于我们是从队首入队。这时,新节点的 next 会断掉指向空的节点,next 指向原来的 head 节点对象。 你可以理解为小弟取代自己的原来老大拿到第一把交椅。

JAVA线程池使用实例 队列 java线程池队列区别_JAVA线程池使用实例 队列_04


这时候,旧节点指将自己的前置节点指向新入队的节点。可以理解:新老大上任,宽宏大量,让旧老大叫自己一声 “老大" 好就行了。

JAVA线程池使用实例 队列 java线程池队列区别_Deque_05

全部结束后,将 head 节点指向新节点,游戏结束了。

JAVA线程池使用实例 队列 java线程池队列区别_LinkedBlocking_06

3、出队操作

3.1、takeLast方法

出队方法,该方法是阻塞的

public E takeLast() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        //出队操作 出队成功返回出队的值,失败则为空
        while ( (x = unlinkLast()) == null)
            notEmpty.await();
        return x;
    } finally {
    //解锁
        lock.unlock();
    }
}

3.2、pollLast方法

出队方法,该方法是非阻塞的

public E pollLast() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//出队操作 出队成功返回出队的值,失败则为空
        return unlinkLast();
    } finally {
        lock.unlock();
    }
}

3.3、出队的具体实现

private E unlinkLast() {
	//拿到最后一个节点
	Node<E> l = last;
	//如果最后一个节点是空,则返回空
	if (l == null)
	    return null;
	 //拿到最后一个节点的前置节点
	Node<E> p = l.prev;
	//拿到返回值
	E item = l.item;
	//item 结节点,设空表示已经删除
	l.item = null;
	//将前置节点指向字节,
	l.prev = l; 
	//将前置节点设置为尾节点
	last = p;
	//判断最后一个 last 是否为空
	if (p == null)
	    first = null;
	else
	    p.next = null;
	//减少队列中数量
	--count;
	//唤醒入队的阻塞线程
	notFull.signal();
	return item;
}

3.4、出队图解

准备三个 Node(data,pre,next 构成一个节点) 节点对象:

JAVA线程池使用实例 队列 java线程池队列区别_LinkedBlocking_07

首先先断掉指向上一个节点的前置节点,然后将前置节点指向自己(前置节点指向自己,可以帮助 GC 回收已经无用的节点)

JAVA线程池使用实例 队列 java线程池队列区别_JAVA线程池使用实例 队列_08

接下来,将前一个节点的 next 指向节点设置为空。

JAVA线程池使用实例 队列 java线程池队列区别_java_09

最后,将 last 对象指向将 next 指向空的对象。OK,游戏结束。

JAVA线程池使用实例 队列 java线程池队列区别_LinkedBlocking_10

五、总结

LinkedBlockingDeque 作为一种阻塞双端队列,在 BlockingQueue 的基础上增加队首队尾的出入队方法。在日常开发中需要选择相关业务来选择阻塞队列。当然,打死也不能用无界队列,这玩意不可控,一旦失控就 GG。最后祝大家学习进步,工作愉快。谢谢