SynchronousQueue
是 Java 中一个特殊的线程安全队列,它不存储任何元素,并且每个插入操作必须等待另一个线程的移除操作,反之亦然。这种队列主要用于任务窃取或线程间的直接通信。由于其特殊性质,SynchronousQueue
可以作为线程池工作窃取机制的一部分,或者用于实现无锁的并发设计模式。它适用于元素生命周期非常短,且生产者和消费者几乎同时操作的场景。
1、SynchronousQueue
SynchronousQueue
是 Java 中一个非常特殊的线程安全队列,它实际上不存储任何元素,而是直接将生产者线程和消费者线程进行匹配。
设计思考:
- 需求场景:
- 在某些高并发处理场景中,如无缓冲的消息传递、实时任务处理等,需要一种机制使得生产者线程直接将任务传递给消费者线程,而不需要任何中间缓冲。
- 适用于线程间需要紧密协作的场景,其中生产者生成任务后,消费者应立即处理这些任务,无需等待或延迟。
- 现有技术局限性:
- 传统的队列实现,如
ArrayBlockingQueue
、LinkedBlockingQueue
和PriorityBlockingQueue
,都使用内部缓冲区来存储元素,这可能会导致生产者线程在缓冲区满时阻塞,或者消费者线程在缓冲区空时阻塞。 - 这些队列实现在处理需要即时任务传递的场景时,由于缓冲区的存在,无法实现生产者和消费者之间的直接交互。
- 传统的队列实现,如
- 技术融合:
- SynchronousQueue 结合了阻塞队列的线程同步特性和无缓冲直接传递的需求,它不存储任何元素,而是将每个插入操作与一个移除操作匹配起来,实现了生产者和消费者之间的直接任务传递。
- 设计理念:
- SynchronousQueue 设计为一个无内部容量的队列,它依赖于消费者线程的可用性来插入元素,实现了生产者和消费者之间的同步。
- 这种设计允许任务在生产者和消费者之间直接传递,无需任何中间缓冲,从而减少了延迟并提高了处理效率。
- 实现方式:
- SynchronousQueue 内部使用一组等待队列来管理等待插入和移除操作的线程,当一个元素被插入时,它会被立即传递给一个等待的消费者线程,反之亦然。
- 它使用了
ReentrantLock
和条件变量来控制线程的等待和唤醒,确保了线程间的同步和协调。
2、数据结构
图说明:
- 同步队列 SynchronousQueue:表示
SynchronousQueue
类的实例,是一个线程安全的阻塞队列,不存储元素,直接匹配生产者和消费者线程。 - 虚拟容器 Virtual Container:表示
SynchronousQueue
内部的虚拟容器,用于协调生产者和消费者线程。这个容器实际上并不存储元素,而是作为一个协调点。 - 空队列状态 Empty:表示虚拟容器的初始状态,为空。
- 非空队列状态 NotEmpty:表示虚拟容器在生产者线程添加元素后的状态,不为空。
- 入队线程 Offer Thread:表示生产者线程尝试向虚拟容器添加元素。
- 出队线程 Poll Thread:表示消费者线程尝试从虚拟容器移除元素。
- 锁 Lock:用于控制对虚拟容器访问的锁。
- 等待队列 Wait Queue:存储等待的线程。
- 入队操作 Offer Operation:生产者线程执行的操作,尝试将元素放入虚拟容器。
- 出队操作 Poll Operation:消费者线程执行的操作,尝试从虚拟容器取出元素。
- 线程等待 Thread Waits:如果虚拟容器为空,入队线程将在此等待。
- 线程通知 Thread Signals:当出队线程执行出队操作后,入队线程被通知。
3、 执行流程
图说明:
- 生产者线程到达:表示生产者线程准备向
SynchronousQueue
进行入队操作。 - 消费者线程到达:表示消费者线程准备从
SynchronousQueue
进行出队操作。 - 队列为空:检查
SynchronousQueue
是否为空。 - 队列不为空:检查
SynchronousQueue
是否有元素可供出队。 - 生产者等待:如果队列为空,生产者线程将等待消费者线程的出队操作。
- 消费者等待:如果队列为空,消费者线程将等待生产者线程的入队操作。
- 生产者唤醒消费者:生产者线程完成入队操作后,唤醒等待的消费者线程。
- 消费者唤醒生产者:消费者线程完成出队操作后,唤醒等待的生产者线程。
- 生产者入队操作:生产者线程执行入队操作,将元素添加到队列。
- 消费者出队操作:消费者线程执行出队操作,从队列中移除元素。
4、应用案例
import java.util.concurrent.SynchronousQueue;
public class SynchronousQueueExample {
public static void main(String[] args) {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
// 生产者线程
Thread producer = new Thread(() -> {
try {
Thread.sleep(1000); // 模拟生产耗时
int item = 1; // 生产一个产品
System.out.println("Producer offering item: " + item);
queue.put(item); // 将产品放入队列
System.out.println("Producer offered item: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
Integer item = queue.take(); // 从队列中取出产品
System.out.println("Consumer has taken item: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
5、优点
- 零容量缓冲:
- 不需要内部缓冲区,实现了任务的直接传递,减少了内存使用和延迟。
- 线程同步:
- 通过等待/唤醒机制,实现了生产者和消费者之间的精确同步。
- 适用于实时处理:
- 适用于需要即时任务处理的场景,如实时消息传递系统。
6、缺点
- 性能开销:
- 在高并发环境下,由于频繁的线程等待和唤醒操作,可能会带来性能开销。
- 可能的线程饥饿:
- 在某些情况下,如果消费者线程长时间不可用,生产者线程可能会长时间阻塞。
7、使用场景
- 无缓冲的消息传递:
- 适用于需要即时消息处理的系统,如即时通讯应用。
- 实时任务处理:
- 适用于需要快速响应的任务处理场景,如实时监控系统。
8、类设计
9、应用案例
SynchronousQueue
通常用于实现线程间的直接交接,特别是在需要严格同步的生产者-消费者模式中。这是一个简单的任务队列处理器,用于处理从队列中取出的任务:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
// 任务类,用于表示需要执行的任务
class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
// 执行任务逻辑
System.out.println("Executing task: " + name);
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class TaskScheduler {
private final BlockingQueue<Runnable> taskQueue;
public TaskScheduler() {
taskQueue = new SynchronousQueue<>();
}
// 添加任务到队列
public void addTask(Runnable task) throws InterruptedException {
taskQueue.put(task);
}
// 执行所有任务
public void executeTasks() {
while (!Thread.currentThread().isInterrupted()) {
Runnable task = taskQueue.take();
task.run();
}
}
public static void main(String[] args) {
TaskScheduler scheduler = new TaskScheduler();
// 创建并启动任务处理器线程
Thread taskProcessorThread = new Thread(scheduler::executeTasks);
taskProcessorThread.start();
// 添加任务到处理器
scheduler.addTask(new Task("Task 1"));
scheduler.addTask(new Task("Task 2"));
scheduler.addTask(new Task("Task 3"));
// 等待任务处理器线程完成
taskProcessorThread.join();
}
}