You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

面试并发问题:如何设计带互斥且独立并发方法的任务队列?

嘿,这个并发任务队列的需求确实挺考验状态和锁的配合能力的,我来帮你梳理下可行的解决思路!

首先,咱们先把需求再明确一遍,避免遗漏细节:

  • 需求1:多线程能并发往队列加任务
  • 需求2:eventFired()只能被调用一次
  • 需求3:eventFired()调用后,所有方法再调用都抛异常
  • 需求4:如果registerCallback()正在执行时调用eventFired(),必须等前者执行完再触发事件
  • 需求5:如果eventFired()正在执行时调用registerCallback(),直接抛异常

你之前想到用ReentrantReadWriteLock的方向是对的,但缺了状态机的配合——只靠读写锁没法区分「事件正在触发」和「事件已触发」这两种状态,也就处理不了需求5。

核心解决思路:状态机 + 读写锁 + 原子操作

我们可以用一个原子维护的状态枚举,配合读写锁来实现所有约束:

  1. 定义三种状态:INIT(初始状态,允许加任务和第一次触发事件)、FIRING(事件正在触发中,禁止加任务)、FIRED(事件已触发,所有方法禁用)
  2. 用AtomicReference维护状态,保证状态切换的原子性,满足「eventFired()只能调用一次」的要求
  3. 用ReentrantReadWriteLock:
    • registerCallback()获取读锁,允许多线程并发加任务
    • eventFired()获取写锁,会等待所有读锁释放(满足需求4的延迟触发)
  4. 所有方法先做「快速状态检查」,避免不必要的锁竞争,同时满足需求5的即时抛异常

代码示例

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CallbackQueue {
    private enum State {
        INIT, FIRING, FIRED
    }

    private final AtomicReference<State> state = new AtomicReference<>(State.INIT);
    private final ConcurrentLinkedQueue<Runnable> tasks = new ConcurrentLinkedQueue<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

    public void registerCallback(Runnable task) {
        // 快速失败检查:如果已经触发或正在触发,直接抛异常
        State currentState = state.get();
        if (currentState == State.FIRING || currentState == State.FIRED) {
            throw new IllegalStateException("Cannot register callback: event is " + currentState.name().toLowerCase());
        }

        readLock.lock();
        try {
            // 双重检查:防止在获取锁前状态发生变化
            currentState = state.get();
            if (currentState == State.FIRING || currentState == State.FIRED) {
                throw new IllegalStateException("Cannot register callback: event is " + currentState.name().toLowerCase());
            }
            tasks.add(task);
        } finally {
            readLock.unlock();
        }
    }

    public void eventFired() {
        // 原子切换状态到FIRING,保证只执行一次
        if (!state.compareAndSet(State.INIT, State.FIRING)) {
            throw new IllegalStateException("Event already fired or is firing");
        }

        // 获取写锁,等待所有读锁(registerCallback)释放,满足需求4的延迟触发
        writeLock.lock();
        try {
            // 执行所有注册的任务
            Runnable task;
            while ((task = tasks.poll()) != null) {
                task.run();
            }
            // 切换到最终状态FIRED
            state.set(State.FIRED);
        } finally {
            writeLock.unlock();
        }
    }
}

需求匹配验证

咱们逐个看需求是否满足:

  1. 并发加任务:registerCallback()用读锁,多个线程可以同时获取读锁,配合线程安全的ConcurrentLinkedQueue,完美支持并发添加
  2. eventFired仅调用一次:通过state.compareAndSet(State.INIT, State.FIRING)的原子操作,只有第一次调用能成功切换状态,后续调用直接抛异常
  3. 触发后禁用所有方法:状态切换到FIRED后,所有方法的快速检查都会抛异常
  4. registerCallback执行时延迟触发:当registerCallback持有读锁时,eventFired的写锁会阻塞,直到所有读锁释放才会执行任务
  5. eventFired执行时禁止加任务:状态切换到FIRING后,registerCallback的快速检查会直接抛异常,不会走到锁获取步骤,完全符合要求

这个方案把状态机的原子性和读写锁的并发控制结合起来,完美覆盖了所有需求点。

内容的提问来源于stack exchange,提问作者Abhijit Sarkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:26:39