图解SynchronousQueue数据结构设计与应用案例

0 阅读6分钟

image.png

SynchronousQueue 是 Java 中一个特殊的线程安全队列,它不存储任何元素,并且每个插入操作必须等待另一个线程的移除操作,反之亦然。这种队列主要用于任务窃取或线程间的直接通信。由于其特殊性质,SynchronousQueue 可以作为线程池工作窃取机制的一部分,或者用于实现无锁的并发设计模式。它适用于元素生命周期非常短,且生产者和消费者几乎同时操作的场景。

1、SynchronousQueue

SynchronousQueue 是 Java 中一个非常特殊的线程安全队列,它实际上不存储任何元素,而是直接将生产者线程和消费者线程进行匹配。

设计思考:

  1. 需求场景
    • 在某些高并发处理场景中,如无缓冲的消息传递、实时任务处理等,需要一种机制使得生产者线程直接将任务传递给消费者线程,而不需要任何中间缓冲。
    • 适用于线程间需要紧密协作的场景,其中生产者生成任务后,消费者应立即处理这些任务,无需等待或延迟。
  2. 现有技术局限性
    • 传统的队列实现,如 ArrayBlockingQueueLinkedBlockingQueue 和 PriorityBlockingQueue,都使用内部缓冲区来存储元素,这可能会导致生产者线程在缓冲区满时阻塞,或者消费者线程在缓冲区空时阻塞。
    • 这些队列实现在处理需要即时任务传递的场景时,由于缓冲区的存在,无法实现生产者和消费者之间的直接交互。
  3. 技术融合
    • SynchronousQueue 结合了阻塞队列的线程同步特性和无缓冲直接传递的需求,它不存储任何元素,而是将每个插入操作与一个移除操作匹配起来,实现了生产者和消费者之间的直接任务传递。
  4. 设计理念
    • SynchronousQueue 设计为一个无内部容量的队列,它依赖于消费者线程的可用性来插入元素,实现了生产者和消费者之间的同步。
    • 这种设计允许任务在生产者和消费者之间直接传递,无需任何中间缓冲,从而减少了延迟并提高了处理效率。
  5. 实现方式
    • SynchronousQueue 内部使用一组等待队列来管理等待插入和移除操作的线程,当一个元素被插入时,它会被立即传递给一个等待的消费者线程,反之亦然。
    • 它使用了 ReentrantLock 和条件变量来控制线程的等待和唤醒,确保了线程间的同步和协调。

2、数据结构

image.png

图说明:
  • 同步队列 SynchronousQueue:表示 SynchronousQueue 类的实例,是一个线程安全的阻塞队列,不存储元素,直接匹配生产者和消费者线程。
  • 虚拟容器 Virtual Container:表示 SynchronousQueue 内部的虚拟容器,用于协调生产者和消费者线程。这个容器实际上并不存储元素,而是作为一个协调点。
  • 空队列状态 Empty:表示虚拟容器的初始状态,为空。
  • 非空队列状态 NotEmpty:表示虚拟容器在生产者线程添加元素后的状态,不为空。
  • 入队线程 Offer Thread:表示生产者线程尝试向虚拟容器添加元素。
  • 出队线程 Poll Thread:表示消费者线程尝试从虚拟容器移除元素。
  • 锁 Lock:用于控制对虚拟容器访问的锁。
  • 等待队列 Wait Queue:存储等待的线程。
  • 入队操作 Offer Operation:生产者线程执行的操作,尝试将元素放入虚拟容器。
  • 出队操作 Poll Operation:消费者线程执行的操作,尝试从虚拟容器取出元素。
  • 线程等待 Thread Waits:如果虚拟容器为空,入队线程将在此等待。
  • 线程通知 Thread Signals:当出队线程执行出队操作后,入队线程被通知。

3、 执行流程

image.png

图说明:
  • 生产者线程到达:表示生产者线程准备向 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、优点

  1. 零容量缓冲
    • 不需要内部缓冲区,实现了任务的直接传递,减少了内存使用和延迟。
  2. 线程同步
    • 通过等待/唤醒机制,实现了生产者和消费者之间的精确同步。
  3. 适用于实时处理
    • 适用于需要即时任务处理的场景,如实时消息传递系统。

6、缺点

  1. 性能开销
    • 在高并发环境下,由于频繁的线程等待和唤醒操作,可能会带来性能开销。
  2. 可能的线程饥饿
    • 在某些情况下,如果消费者线程长时间不可用,生产者线程可能会长时间阻塞。

7、使用场景

  • 无缓冲的消息传递
    • 适用于需要即时消息处理的系统,如即时通讯应用。
  • 实时任务处理
    • 适用于需要快速响应的任务处理场景,如实时监控系统。

8、类设计

image.png

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();
    }
}