Java LinkedTransferQueue中的size()方法
LinkedTransferQueue是Java的并发集合之一,它是一种可阻塞的队列,支持先进先出的元素排列。根据Java的文档,LinkedTransferQueue中的size()方法返回当前队列中元素的数量。但是,在实际的使用中,LinkedTransferQueue的size()方法会返回一个不准确的值,可能比实际值小,也可能比实际值大。本文将探讨LinkedTransferQueue中的size()方法的实现细节,并介绍一些解决方案。
LinkedTransferQueue简介
LinkedTransferQueue是Java提供的线程安全的队列,它实现了BlockingQueue接口和TransferQueue接口。LinkedTransferQueue的元素按照先进先出的顺序排列。和其他阻塞队列不同的是,LinkedTransferQueue不仅可以被阻塞的线程take或put元素,它还可以被另外一个线程transfer元素,即把元素直接交给另外一个正在等待的线程。LinkedTransferQueue还支持双向队列的功能,即可以从队列的头部或尾部操作元素。
下面是一个简单的LinkedTransferQueue使用示例:
import java.util.concurrent.LinkedTransferQueue;
public class LinkedTransferQueueExample {
public static void main(String[] args) {
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
new Thread(() -> {
try {
queue.transfer(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
try {
int result = queue.take();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代码中,我们使用LinkedTransferQueue来传递一个整数,在另外一个线程中使用take方法取出这个数据并打印。
size()方法的实现细节
LinkedTransferQueue内部使用了一个类似于链表的数据结构来存储元素。在LinkedTransferQueue中,我们可以使用peek和poll方法获取队列头部的元素,也可以使用offer和put方法向队尾添加元素。LinkedTransferQueue内部还实现了TransferQueue接口,它允许一个线程等待另外一个线程直接发送元素。
在LinkedTransferQueue的实现中,并没有一个明确的计数器变量来存储当前队列中元素的数量。因此,在LinkedTransferQueue的size()方法内部,需要进行一些复杂的计算才能得到当前队列中元素的数量。具体实现细节如下:
public int size() {
// 定义一个计数器
int count = 0;
// 获取头部节点
for (Node p = head; p != null; p = succ(p)) {
// 如果p是数据节点
if (p.isData)
// 增加计数器
++count;
// 如果p是Sync节点
else {
// 强制转换成Sync节点
Node.Tryer n = (Node.Tryer)p;
// 如果n.a为null,则该Sync节点还未被匹配,增加计数器
if (n.a == null) {
++count;
continue;
}
// 如果n.a为Thread.WAITING,则等待线程默认被计算在count之内
if (n.a != n)
// 对应submissions字段的的元素 - 1
--count;
}
}
return count;
}
在size()方法内部,首先定义了一个count计数器。然后,遍历队列中的每一个节点,如果该节点是数据节点,则将计数器加1;如果该节点是Sync节点,则需要进行进一步的判断:
- 如果Sync节点还未被匹配(即Sync节点的a为null),则将计数器加1。
- 如果Sync节点已经被匹配,则需要判断是否有等待线程(即a不为当前节点n),如果有等待线程,则默认将其计算在count之内;否则将count减1,因为匹配操作会将Sync节点从队列中移除,且对应submissions字段的元素也会减1。最终返回count作为当前队列中元素的数量。
从实现细节可以看出,LinkedTransferQueue的size()方法不是一个简单的计数器操作,而是需要遍历整个队列,对Data节点和Sync节点进行分类处理后得到数量。由于遍历整个队列需要一定的时间,因此无法保证size()方法的返回值是精确的。
size()方法的不准确性
Due to the implementation details of LinkedTransferQueue, its size() method may return an inaccurate value. Below we will discuss some reasons that cause the size() method to be inaccurate.
转移操作
当LinkedTransferQueue中发生转移操作时,size()方法返回的值可能会发生变化。因为Transfer操作会立即把元素传递给等待的线程,这意味着在转移之前,该元素已经被移除。但是,在size()方法在遍历队列获取元素数量的过程中,这个操作已经被计算在了count之内。因此,在实际发生转移之后,count值会比实际值少1。
import java.util.concurrent.LinkedTransferQueue;
public class TransferExample {
public static void main(String[] args) {
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
queue.add(1);
new Thread(() -> {
try {
int result = queue.transfer(2);
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
System.out.println(queue.size()); // 1
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(queue.size()); // 0
}
}
代码中,我们向LinkedTransferQueue中添加了一个整数1,然后在另外一个线程中使用transfer方法将数据变成2并传递给等待的线程。在transfer操作之后,我们使用size()方法获取队列中元素的数量。结果发现,第一个size()操作返回1,第二个size()操作返回0,说明在transfer操作之后,队列中的元素数量被减少了1。
并发操作
LinkedTransferQueue是一个线程安全的队列,因此可能有多个线程同时对其进行操作。由于size()方法的实现过程需要遍历整个队列,因此当其他线程并发修改队列的同时,size()方法返回的值可能已经过期。
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
public class ConcurrentExample {
public static void main(String[] args) {
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.offer(i);
}
}).start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
TimeUnit.MILLISECONDS.sleep(110);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.take();
}
}).start();
while (true) {
int size = queue.size();
if (size != 0)
System.out.println(size);
}
}
}
在代码中,我们创建了两个线程来并发修改队列中的元素,并且使用一个while循环来不断使用size()方法获取队列中元素数量,直到队列为空。当我们运行代码时,可以看到控制台输出了一系列大于0的数字,这是因为在遍历队列获取size值的过程中,队列的元素发生了改变,导致size()方法的返回值已经过期。
解决方案
由于LinkedTransferQueue中的size()方法不是返回精确的元素数量,因此我们需要使用一些解决方案来获取更加准确的结果。
使用迭代器遍历
一个简单的解决方案是使用迭代器来遍历LinkedTransferQueue中的元素。迭代器的遍历方式是通过指针和元素节点来遍历队列,因此不会受到Transfer或并发操作的影响,可以获得更加准确的结果。
import java.util.Iterator;
import java.util.concurrent.LinkedTransferQueue;
public class IteratorExample {
public static void main(String[] args) {
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
for (int i = 1; i <= 10; i++) {
queue.offer(i);
}
Iterator<Integer> iterator = queue.iterator();
while (iterator.hasNext()) {
int value = iterator.next();
System.out.println(value);
}
}
}
代码中,我们使用offer方法向LinkedTransferQueue中添加了10个整数,然后使用迭代器遍历整个队列,并打印每一个元素。这个遍历过程不会受到其他线程的影响,因此可以获得更加准确的结果。
自定义计数器
另外一个解决方案是自定义一个计数器来统计队列中的元素数量。具体实现方式是使用add、offer、transfer或put操作时,同时将计数器加1,并在take或poll操作时将计数器减1。这种方式仅适用于不修改TransferQueue的操作,如果需要使用Transfer操作来交换元素,则需要使用其他方案。
import java.util.concurrent.LinkedTransferQueue;
public class CountExample {
static int count = 0;
public static void main(String[] args) {
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
for (int i = 1; i <= 10; i++) {
queue.offer(i);
count++;
}
System.out.println("count: " + count); // 10
queue.take();
count--;
System.out.println("count: " + count); // 9
}
}
代码中,我们首先使用offer方法向队列中添加了10个整数,并且同时将计数器加1。然后,使用take方法将队列中的一个元素取出,并将计数器减1。通过这个自定义的计数器,我们可以获得更加准确的队列中元素数量。
结论
LinkedTransferQueue是一个高效且功能强大的线程安全队列,它支持Transfer操作和双向队列等特性。然而,LinkedTransferQueue中的size()方法并不是一个精确的元素数量计数器,而是需要遍历整个队列才能够计算得到的不准确的值。因此,在实际的应用过程中,我们需要使用其他方法来获取更加准确的队列中元素数量。