blockingdeque java_Java并发编程之LinkedBlockingDeque阻塞队列详解

本文详细介绍了Java中的LinkedBlockingDeque,它是由链表结构组成的双向阻塞队列,可从两端插入和移除元素。文中介绍了其构造方法、类定义、重要字段,还对putFirst、putLast、pollFirst、pollLast等入队、出队操作的源码进行了分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

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 extends AbstractQueue implements BlockingDeque, java.io.Serializable

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

public interface BlockingDeque extends BlockingQueue, Deque

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

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

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

LinkedBlockingDeque类中的重要字段如下:

// 队列双向链表首节点transient Node first;// 队列双向链表尾节点transient Node 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 node = new Node(e); // 获取全局独占锁 final ReentrantLock lock = this.lock; lock.lock(); try { while (!linkFirst(node)) notFull.await(); } finally { // 释放全局独占锁 lock.unlock(); }}

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

private boolean linkFirst(Node node) { // assert lock.isHeldByCurrentThread(); // 元素个数超出容量。直接返回false if (count >= capacity) return false; // 获取双向链表的首节点 Node 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 node = new Node(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 node) { // assert lock.isHeldByCurrentThread(); // 元素个数超出容量。直接返回false if (count >= capacity) return false; // 获取双向链表的尾节点 Node 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 f = first; // 首节点为null,则返回null if (f == null) return null; // 获取首节点的后继节点 Node 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 l = last; // 尾节点为null,则返回null if (l == null) return null; // 获取尾节点的前驱节点 Node 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并发编程的艺术》

本文转自https://blog.youkuaiyun.com/qq_38293564/article/details/80592429

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值