内容概要
LinkedTransferQueue类实现了高效的线程间数据传递,支持等待匹配的生产者-消费者模式,基于链表的无界设计使其在高并发场景下表现卓越,且无需担心队列溢出,丰富的方法和良好的可扩展性满足了各种复杂应用场景的需求。
核心概念
LinkedTransferQueue
是一个高效、无界、基于链表的队列,它同时实现了 TransferQueue
接口和 BlockingQueue
接口,这个队列设计主要用于解决以下几类问题:
1、直接匹配生产者与消费者,LinkedTransferQueue 提供了一种机制,使得生产者可以将元素直接传输给等待消费的消费者,这意味着当调用 transfer(E e)
方法时,如果有一个消费者正等待接收元素,那么元素会立即从生产者转移给消费者,并且两个线程之间的交换无需锁或其他同步机制。
2、避免无效通知,在某些其他阻塞队列中,线程可能会由于操作系统或 JVM 的原因而意外地提前唤醒,这称为“虚假唤醒”,LinkedTransferQueue 使用自旋等优化技术来减少这种无效通知,从而提高效率。
3、非阻塞和阻塞操作的混合支持,除了基本的插入(offer)、移除(poll)和检查(peek)等操作外,还提供了额外的方法如 tryTransfer(E e)
和上面提到的 transfer(E e)
。transfer()
方法确保了元素被成功传输前不会释放资源,这对于实现FIFO传递非常有效。
4、高性能低延迟,LinkedTransferQueue 是无界的,但在大多数情况下表现得如同有界队列,因为它会尽力快速地将元素从生产者转移到消费者,避免无限制增长导致的内存溢出,其内部设计通过原子操作和 CAS 算法保证了高度的并发性能和较低的线程上下文切换开销。
总之,LinkedTransferQueue 主要针对那些需要高效、低延迟以及直接 producer-consumer 交互的并发场景,特别适合于工作窃取(work-stealing)算法或者任务传递系统中,它可以简化并发编程模型,降低同步复杂性和提高整体性能。
代码案例
下面是LinkedTransferQueue
的简单代码案例,如下代码:
import java.util.concurrent.LinkedTransferQueue;
public class LinkedTransferQueueDemo {
public static void main(String[] args) {
// 创建一个LinkedTransferQueue实例
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
// 创建一个生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
System.out.println("生产者准备生产:" + i);
// 将生产的数据放入队列,等待消费者接收
queue.transfer(i);
System.out.println("生产者生产完毕:" + i + ",等待消费者消费");
Thread.sleep(500); // 模拟生产过程耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断状态
e.printStackTrace();
}
});
// 创建一个消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
// 从队列中取数据,如果没有数据可取,则该方法会阻塞
Integer item = queue.take();
System.out.println("消费者消费了:" + item);
Thread.sleep(1000); // 模拟消费过程耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断状态
e.printStackTrace();
}
});
// 启动消费者线程和生产者线程(注意启动顺序通常不重要,但这里先启动消费者以避免生产者阻塞)
consumer.start();
producer.start();
}
}
运行输出如下类似结果:
消费者等待消费...
生产者准备生产:0
生产者生产完毕:0,等待消费者消费
消费者消费了:0
生产者准备生产:1
生产者生产完毕:1,等待消费者消费
消费者消费了:1
... (以此类推,直到所有项目都被生产和消费)
核心API
LinkedTransferQueue
实现了一个基于链接节点的、线程安全的 TransferQueue
接口,该队列中的元素可以在生产者线程和消费者线程之间高效传输,LinkedTransferQueue
通常用于需要高效、线程安全的数据传输的场景,尤其是当生产和消费速率不一致时,下面是 LinkedTransferQueue
类中一些重要方法的含义:
transfer(E e)
- 将指定的元素传输给等待的消费者,并立即返回,如果当前没有等待的消费者,则该方法会阻塞,直到有消费者通过
take()
或receive()
方法接收元素。
- 将指定的元素传输给等待的消费者,并立即返回,如果当前没有等待的消费者,则该方法会阻塞,直到有消费者通过
tryTransfer(E e)
- 尝试将指定的元素传输给等待的消费者,并立即返回,如果没有等待的消费者,该方法不会阻塞,而是立即返回
false
。
- 尝试将指定的元素传输给等待的消费者,并立即返回,如果没有等待的消费者,该方法不会阻塞,而是立即返回
tryTransfer(E e, long timeout, TimeUnit unit)
- 尝试在指定的时间内将元素传输给等待的消费者,如果在指定的时间内没有消费者接收元素,则该方法返回
false
。
- 尝试在指定的时间内将元素传输给等待的消费者,如果在指定的时间内没有消费者接收元素,则该方法返回
offer(E e)
- 将指定的元素插入到队列中,如果队列已满,则立即返回
false
,对于LinkedTransferQueue
,由于它是无界的,这个方法实际上永远不会因为队列满而失败,除非内存不足。
- 将指定的元素插入到队列中,如果队列已满,则立即返回
offer(E e, long timeout, TimeUnit unit)
- 将指定的元素插入到队列中,等待指定的时间以使其他线程有机会插入或移除元素。由于
LinkedTransferQueue
是无界的,这个方法通常不会因为队列满而阻塞,除非内存不足,然而,它仍然会等待指定的时间,这可能不是最有效的方法来添加元素到队列中。
- 将指定的元素插入到队列中,等待指定的时间以使其他线程有机会插入或移除元素。由于
put(E e)
- 将指定的元素插入到队列中,等待必要的空间变得可用,对于
LinkedTransferQueue
,由于它是无界的,这个方法实际上永远不会阻塞。
- 将指定的元素插入到队列中,等待必要的空间变得可用,对于
take()
- 检索并移除队列的头部元素,等待必要的元素变得可用,如果队列为空,则该方法会阻塞,直到有元素可用。
poll()
- 检索并移除队列的头部元素,或返回
null
如果队列为空,这个方法不会阻塞。
- 检索并移除队列的头部元素,或返回
poll(long timeout, TimeUnit unit)
- 检索并移除队列的头部元素,等待指定的时间以使元素可用,如果在指定的时间内队列仍然为空,则该方法返回
null
。
- 检索并移除队列的头部元素,等待指定的时间以使元素可用,如果在指定的时间内队列仍然为空,则该方法返回
peek()
- 检索但不移除队列的头部元素,或返回
null
如果队列为空。
- 检索但不移除队列的头部元素,或返回
size()
- 返回队列中的元素数量,由于队列的并发性质,这个值可能立即过时。它主要用于监控,而不是用于同步控制。
isEmpty()
- 如果队列为空,则返回
true
,否则返回false
,和size()
方法一样,由于并发性,这个方法的结果可能立即过时。
- 如果队列为空,则返回
clear()
- 移除队列中的所有元素,这个方法不是线程安全的,通常不建议在并发环境中使用。
remainingCapacity()
- 对于
LinkedTransferQueue
,由于它是无界的,这个方法总是返回Integer.MAX_VALUE
,表示队列的剩余容量非常大。
- 对于
drainTo(Collection<? super E> c)
- 移除队列中的所有元素,并将它们添加到指定的集合中。
drainTo(Collection<? super E> c, int maxElements)
- 移除队列中的最多
maxElements
个元素,并将它们添加到指定的集合中。
- 移除队列中的最多
核心总结
LinkedTransferQueue
是一个高效且线程安全的队列,它实现了 TransferQueue
接口,提供了在生产者和消费者之间直接传递元素的能力,优点在于,它能够在没有消费者时,使生产者线程等待,直到有消费者准备接收元素,从而实现更精细的线程间协作,此外,由于其基于链表的实现,它在高并发环境下表现良好,且不存在队列满的情况(除非内存耗尽)。
LinkedTransferQueue
的缺点在于,相比基于数组的有界队列,它可能会消耗更多的内存,特别是在元素大小较大或队列中元素数量非常多的情况下,此外,虽然它提供了丰富的操作,但在某些简单场景下可能过于复杂。
END!
往期回顾
Java并发基础:LinkedTransferQueue全面解析!
Java并发基础:BlockingQueue和BlockingDeque接口的区别?