while循环轮询阻塞队列场景下的多线程安全优化方案咨询
有两种性能更优的无锁解决方案,均优于直接加synchronized悲观锁:
方案1:调整拉取逻辑,允许多线程并行处理(最高性能)
LinkedBlockingQueue本身的poll()操作是线程安全的原子操作,原代码的问题出在isEmpty()判断和poll()操作拆分执行,两者组合不是原子操作。直接去掉前置的isEmpty()判断,以poll()的返回值作为循环终止条件即可完全规避null问题,全程无锁,性能最优。
修改后的createJob()代码如下:
public CompoundJob createJob() { CompoundJob job = new CompoundJob(); Integer itemId; // 直接循环拉取,poll返回null即代表队列已空 while ((itemId = idsOfItemsToDelete.poll()) != null) { job.addSubAssignment(DeletionAssignment.of(itemId)); } return job; }
该方案适合队列经常有大量积压的场景:多个工作线程可以同时拉取队列元素生成多个作业并行执行,加快积压任务的处理速度,不会出现重复拉取、null入任务的问题。
方案2:CAS独占标记,单线程拉取整批任务
如果业务要求每次定时调度生成的作业是全量待删条目,不希望拆分给多个作业并行处理,可以用AtomicBoolean实现轻量的乐观锁,同一时间仅允许一个线程拉取全量队列生成作业,抢不到锁的线程直接返回空作业即可,没有synchronized锁的线程挂起、上下文切换开销。
修改后的ItemDeletionManager代码如下:
class ItemDeletionManager { private final Queue<Integer> idsOfItemsToDelete; // CAS独占标记,标记是否有线程正在生成作业 private final AtomicBoolean jobCreatingFlag = new AtomicBoolean(false); public ItemDeletionManager() { this.idsOfItemsToDelete = new LinkedBlockingQueue<>(); } public void enqueue(int itemId) { this.idsOfItemsToDelete.add(itemId); } public CompoundJob createJob() { CompoundJob job = new CompoundJob(); // 抢锁失败直接返回空作业,不阻塞 if (!jobCreatingFlag.compareAndSet(false, true)) { return job; } try { Integer itemId; while ((itemId = idsOfItemsToDelete.poll()) != null) { job.addSubAssignment(DeletionAssignment.of(itemId)); } return job; } finally { // 必须在finally中重置标记,避免异常导致标记永久占用 jobCreatingFlag.set(false); } } }
如果你的业务量级不大,其实直接把定时线程池的核心线程数从2改成1,从根源避免多线程竞争,是成本最低的解决方案。
内容的提问来源于stack exchange,提问作者varlog23
相关产品推荐
相关产品推荐

