面试并发问题:如何设计带互斥且独立并发方法的任务队列?
嘿,这个并发任务队列的需求确实挺考验状态和锁的配合能力的,我来帮你梳理下可行的解决思路!
首先,咱们先把需求再明确一遍,避免遗漏细节:
- 需求1:多线程能并发往队列加任务
- 需求2:
eventFired()只能被调用一次 - 需求3:
eventFired()调用后,所有方法再调用都抛异常 - 需求4:如果
registerCallback()正在执行时调用eventFired(),必须等前者执行完再触发事件 - 需求5:如果
eventFired()正在执行时调用registerCallback(),直接抛异常
你之前想到用ReentrantReadWriteLock的方向是对的,但缺了状态机的配合——只靠读写锁没法区分「事件正在触发」和「事件已触发」这两种状态,也就处理不了需求5。
核心解决思路:状态机 + 读写锁 + 原子操作
我们可以用一个原子维护的状态枚举,配合读写锁来实现所有约束:
- 定义三种状态:
INIT(初始状态,允许加任务和第一次触发事件)、FIRING(事件正在触发中,禁止加任务)、FIRED(事件已触发,所有方法禁用) - 用
AtomicReference维护状态,保证状态切换的原子性,满足「eventFired()只能调用一次」的要求 - 用
ReentrantReadWriteLock:registerCallback()获取读锁,允许多线程并发加任务eventFired()获取写锁,会等待所有读锁释放(满足需求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(); } } }
需求匹配验证
咱们逐个看需求是否满足:
- 并发加任务:
registerCallback()用读锁,多个线程可以同时获取读锁,配合线程安全的ConcurrentLinkedQueue,完美支持并发添加 - eventFired仅调用一次:通过
state.compareAndSet(State.INIT, State.FIRING)的原子操作,只有第一次调用能成功切换状态,后续调用直接抛异常 - 触发后禁用所有方法:状态切换到
FIRED后,所有方法的快速检查都会抛异常 - registerCallback执行时延迟触发:当
registerCallback持有读锁时,eventFired的写锁会阻塞,直到所有读锁释放才会执行任务 - eventFired执行时禁止加任务:状态切换到
FIRING后,registerCallback的快速检查会直接抛异常,不会走到锁获取步骤,完全符合要求
这个方案把状态机的原子性和读写锁的并发控制结合起来,完美覆盖了所有需求点。
内容的提问来源于stack exchange,提问作者Abhijit Sarkar
相关产品推荐
相关产品推荐

