如何在Java中实现可临时跳过不符合条件元素的阻塞FIFO队列?
嘿,这个需求和海关排队的类比太形象了!我来给你梳理几个适合Java场景的实现方案,结合你熟悉的并发队列工具来调整:
方案一:基于LinkedBlockingDeque的直接遍历移除(性能优先)
如果你的队列可能存在大量不符合条件的元素,且符合条件的元素可能出现在队列中间,这个方案会更高效。它直接在队列中找到第一个符合条件的元素并移除,其他元素保持原有顺序,同时支持阻塞等待符合条件的元素。
import java.util.Iterator; import java.util.concurrent.LinkedBlockingDeque; import java.util.function.Predicate; import java.util.concurrent.locks.Condition; public class FilteredBlockingQueue<E> { private final LinkedBlockingDeque<E> deque; private final Predicate<E> filter; private final Condition notEmpty; public FilteredBlockingQueue(int capacity, Predicate<E> filter) { this.deque = new LinkedBlockingDeque<>(capacity); this.filter = filter; this.notEmpty = deque.newCondition(); } public void put(E e) throws InterruptedException { synchronized (deque) { // 队列满时阻塞等待 while (deque.remainingCapacity() == 0) { deque.wait(); } deque.addLast(e); notEmpty.signal(); // 通知take方法有新元素了 } } public E take() throws InterruptedException { synchronized (deque) { while (true) { // 遍历队列寻找第一个符合条件的元素 Iterator<E> iterator = deque.iterator(); E matchedElement = null; while (iterator.hasNext()) { E e = iterator.next(); if (filter.test(e)) { matchedElement = e; iterator.remove(); // 原子移除符合条件的元素 deque.notifyAll(); // 通知put方法队列有空闲空间 return matchedElement; } } // 没有找到符合条件的元素,阻塞等待新元素 notEmpty.await(); } } } }
优点:不需要频繁移动元素,找到目标直接移除,性能更优;队列顺序严格保持FIFO,完全贴合你描述的场景。
缺点:需要遍历队列,当队列极大时,遍历会带来一定开销。
方案二:优化你的双端队列思路(贴合场景逻辑)
你原本想到的双端队列方案思路是对的,但需要调整放回元素的顺序,才能保证队列原有的FIFO顺序。具体来说,取出不符合条件的元素后,要逆序放回队首,这样原来的队首元素会回到最前面,下次检查依然从它开始。
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.LinkedBlockingDeque; import java.util.function.Predicate; public class ConditionalBlockingQueue<E> { private final LinkedBlockingDeque<E> deque; private final Predicate<E> filter; public ConditionalBlockingQueue(int capacity, Predicate<E> filter) { this.deque = new LinkedBlockingDeque<>(capacity); this.filter = filter; } public void put(E e) throws InterruptedException { deque.putLast(e); // 保持FIFO入队 } public E take() throws InterruptedException { while (true) { List<E> tempUnmatched = new ArrayList<>(); E matchedElement = null; // 从队首逐个取出元素检查 while (true) { E current = deque.takeFirst(); if (filter.test(current)) { matchedElement = current; break; } else { tempUnmatched.add(current); } } // 逆序放回不符合条件的元素,保证原顺序 Collections.reverse(tempUnmatched); for (E e : tempUnmatched) { deque.putFirst(e); } return matchedElement; } } }
优点:逻辑简单直观,完全匹配你描述的“从队首开始检查,找到符合的放行,其余放回原位”的流程;利用LinkedBlockingDeque的原子操作,无需额外加锁,线程安全。
缺点:当队列中前半部分全是不符合条件的元素时,会有多次取放操作,性能略逊于方案一,但对于大多数业务场景完全够用。
方案三:兼容现有ArrayBlockingQueue的包装方案(最小改动)
如果你不想替换已经用习惯的ArrayBlockingQueue,可以用包装类实现,但这个方案性能较差,仅适合小队列场景:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.function.Predicate; public class FilteredArrayBlockingQueue<E> { private final ArrayBlockingQueue<E> queue; private final Predicate<E> filter; public FilteredArrayBlockingQueue(int capacity, Predicate<E> filter) { this.queue = new ArrayBlockingQueue<>(capacity); this.filter = filter; } public void put(E e) throws InterruptedException { queue.put(e); } public E take() throws InterruptedException { while (true) { int currentSize = queue.size(); List<E> tempUnmatched = new ArrayList<>(); E matchedElement = null; // 取出所有元素检查,直到找到符合条件的 for (int i = 0; i < currentSize; i++) { E e = queue.take(); if (filter.test(e)) { matchedElement = e; break; } else { tempUnmatched.add(e); } } // 放回不符合条件的元素 for (E e : tempUnmatched) { queue.put(e); } if (matchedElement != null) { return matchedElement; } } } }
缺点:当队列元素较多时,会频繁取出和放回所有元素,不仅性能差,还会阻塞其他线程的put()操作,不推荐在高并发或大队列场景使用。
总结推荐
如果追求性能且队列可能较大,选方案一;如果更看重逻辑贴合你的场景描述,选方案二(也是我最推荐的);如果必须兼容现有ArrayBlockingQueue,再考虑方案三。
内容的提问来源于stack exchange,提问作者John Fitzpatrick

