一、前言
最近在项目开发中,有使用到java.util.concurrent.LinkedBlockingQueue
类做数据存储。LinkedBlockingQueue
是Java
并发编程中一种常见的阻塞队列,它是线程安全的,采用链表的方式实现,具有先进先出的特性,它几乎可以无限扩展,并且可以阻塞读取和阻塞写入。
在项目中,某一个工具类中,采用了LinkedBlockingQueue
来进行数据存储,需要在使用的时候进行队列进行是否存在数据,否则进行遍历操作,同时操作完成以后,需要及时重置队列,此时LinkedBlockingQueue
类中的drainTo
方法,刚好可以满足需求。
二、使用介绍
drainTo
方法,主要作用是将队列中的集合元素转移到指定的集合中,下面介绍一下常见的使用方式。
1. 方法介绍
可以看到drainTo
方法有两个重载方法,一个是指定集合转移个数的,另一个没有指定,没有指定的话,会默认迁移集合的全部元素。
2. 方法使用
2.1 迁移全部元素
LinkedBlockingQueue
的drainTo
方法默认就是迁移全部函数的,我们可以利用如下代码进行测试:
// 声明一个LinkedBlockingDeque
LinkedBlockingDeque<String> linkedBlockingDeque = new LinkedBlockingDeque<>();
linkedBlockingDeque.add("a");
linkedBlockingDeque.add("b");
linkedBlockingDeque.add("c");
linkedBlockingDeque.add(null); // 添加一个null,看一下是否也会迁移
System.out.println("linkedBlockingDeque 集合迁移前大小: " + linkedBlockingDeque.size());
// 声明一个接受迁移的集合
List<String> tempList = new ArrayList<>();
// 进行迁移操作
linkedBlockingDeque.drainTo(tempList);
// 查看结果
tempList.forEach(item -> {
System.out.println("迁移集合中: " + item);
});
System.out.println("linkedBlockingDeque 集合迁移后大小: " + linkedBlockingDeque.size());
运行结果:
可以看到,集合中的元素都被成功迁移了,并且原有集合进行了清空操作。
2.2 迁移部分元素
drainTo
方法的重载方法可以指定迁移元素的个数,如果我们不想迁移全部元素,也可以只迁移指定个数的元素,参考代码如下:
// 声明一个LinkedBlockingDeque
LinkedBlockingDeque<String> linkedBlockingDeque = new LinkedBlockingDeque<>();
linkedBlockingDeque.add("a");
linkedBlockingDeque.add("b");
linkedBlockingDeque.add("c");
System.out.println("linkedBlockingDeque 集合迁移前大小: " + linkedBlockingDeque.size());
// 声明一个接受迁移的集合
List<String> tempList = new ArrayList<>();
// 进行迁移操作
linkedBlockingDeque.drainTo(tempList, 2); // 只迁移两个元素
// 查看结果
tempList.forEach(item -> {
System.out.println("迁移集合中: " + item);
});
System.out.println("linkedBlockingDeque 集合迁移后大小: " + linkedBlockingDeque.size());
运行结果:
可以看到,此时成功迁移了2个,原来集合中还有一个元素未被迁移。
三、方法原理
上面我们已经学会了怎么使用,下面我们也看一下是怎么实现的,方法实现倒不复杂,核心代码如下:
-
不指定迁移元素个数源码
public int drainTo(Collection<? super E> c) { return drainTo(c, Integer.MAX_VALUE); }
-
指定迁移元素个数源码
public int drainTo(Collection<? super E> c, int maxElements) { Objects.requireNonNull(c); if (c == this) throw new IllegalArgumentException(); if (maxElements <= 0) return 0; final ReentrantLock lock = this.lock; lock.lock(); try { int n = Math.min(maxElements, count); for (int i = 0; i < n; i++) { c.add(first.item); // In this order, in case add() throws. unlinkFirst(); } return n; } finally { lock.unlock(); } }
unlinkFirst
方法源码
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
Node<E> f = first;
if (f == null)
return null;
Node<E> n = f.next;
E item = f.item;
f.item = null;
f.next = f; // help GC
first = n;
if (n == null)
last = null;
else
n.prev = null;
--count;
notFull.signal();
return item;
}
可以看到,drainTo
方法实现逻辑并不复杂,如果是不指定元素个数,默认就是按照Integer.MAX_VALUE
个元素进行迁移,迁移的时候,要求传入的接收结合对象不能为null
,同时不能接收集合对象不能是自己。如果指定的迁移个数小于等于0,那么不会进行迁移操作,否则,会进行加锁操作,从传入的迁移元素个数和集合实际的元素个数中获取最小值,循环向迁移集合中添加元素,并且原LinkedBlockingDeque
集合不断进行头指针下移操作。
unlinkFirst
方法中,主要就是将LinkedBlockingDeque
中的头指针对象进行下移操作,指定到下一个元素,并且将自身节点进行置空操作,方便GC
回收。