如何避免使用锁原语,保证队列中同一时间仅运行一个任务?
无锁实现单任务串行执行的队列方案
原代码通过synchronized锁控制队列的任务提交与续跑逻辑,虽然能保证同一时刻仅一个任务执行,但锁竞争和线程切换会带来额外性能开销。可以通过原子类+无锁线程安全队列的组合完全替代锁原语,实现相同语义且性能更优。
核心改造思路
- 用
AtomicBoolean替代普通boolean维护running状态,通过CAS操作实现无锁的状态抢占 - 将非线程安全的
ArrayDeque替换为无锁线程安全队列ConcurrentLinkedQueue,避免队列操作的线程安全问题 - 调整
append和resume方法逻辑,用原子操作替代同步块
改造后的代码
import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import lombok.Getter; public class Queue { public static final Executor EXECUTOR = new Executor(16, 16); @Getter private final long id; @Getter private final String name; @Getter private final int capacity; private final Queue<Command> queue; private final AtomicBoolean running = new AtomicBoolean(false); private final AtomicBoolean destroy = new AtomicBoolean(false); public Queue(long id, String name, int capacity) { this.id = id; this.name = name; this.capacity = capacity; this.queue = new ConcurrentLinkedQueue<>(); } public boolean append(Command command) { command.setQueue(this); // 队列已销毁,直接拒绝任务 if (destroy.get()) { return false; } // CAS抢占执行权:当前无任务运行时,标记为运行中并提交任务 if (running.compareAndSet(false, true)) { EXECUTOR.execute(command); return true; } // 抢占失败,尝试将任务加入队列(队列满则返回false) return queue.offer(command); } protected void resume() { // 队列已销毁,标记为无任务运行并返回 if (destroy.get()) { running.set(false); return; } Command command = queue.poll(); if (command != null) { // 队列还有任务,直接提交执行 EXECUTOR.execute(command); } else { // 队列空了,标记为无任务运行,允许新任务直接抢占 running.set(false); } } public boolean isDestroy() { return destroy.get(); } public void setDestroy(boolean destroy) { this.destroy.set(destroy); } } // Executor类保持原逻辑无需修改
逻辑说明
- append方法:通过
running.compareAndSet(false, true)原子性抢占执行权,成功则直接提交任务;失败则将任务加入队列等待 - resume方法:任务执行完成后触发,从队列取出下一个任务提交;队列空时将
running置为false,让后续新任务可以直接抢占执行权 - 线程安全保障:
AtomicBoolean的CAS操作和ConcurrentLinkedQueue的无锁实现,确保所有并发操作线程安全,无需依赖synchronized锁
这种方案完全避免了锁原语的使用,消除了锁竞争带来的线程切换开销,同时严格保证同一队列中仅一个任务运行至完成的语义。
内容的提问来源于stack exchange,提问作者Criwran
相关产品推荐
相关产品推荐

