使用ArrayBlockingQueue的removeIf()后,put()为何未触发?
问题分析:ArrayBlockingQueue多线程下removeIf后put阻塞未唤醒
问题场景
在多线程环境下调用ArrayBlockingQueue的removeIf()方法移除元素后,另一个执行put()的线程始终阻塞,无法在队列腾出空间后继续插入元素。程序输出前两个队列大小后,第三个输出一直无法出现,t3线程的put(1)持续等待。
复现代码
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CyclicBarrier; class HelloWorld { static ArrayBlockingQueue<Integer> q = new ArrayBlockingQueue(2); static CyclicBarrier cb = new CyclicBarrier(2); public static void main(String[] args) { try { Thread t1 = new Thread(() -> { try { q.put(2); } catch (Exception e) { e.printStackTrace(); } }); Thread t2 = new Thread(() -> { try { q.put(2); } catch (Exception e) { e.printStackTrace(); } }); Thread t3 = new Thread(() -> { try { q.put(1); } catch (Exception e) { e.printStackTrace(); } }); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); System.out.println(q.size()); q.removeIf(ii -> ii == 2); System.out.println(q.size()); t3.join(); System.out.println(q.size()); } catch (Exception e) { e.printStackTrace(); } } }
问题原因
- removeIf未触发条件通知:
ArrayBlockingQueue的removeIf()方法继承自AbstractQueue,属于通用集合方法,不会触发队列内部的notFull条件变量唤醒机制。而put()方法阻塞时,是等待notFull条件的信号;移除元素后没有通知这个条件,阻塞线程无法感知到队列已腾出空间。 - 执行时序问题:
t1、t2先填满队列(容量2),t3的put()立刻进入阻塞。主线程清空队列后,没有唤醒等待插入的线程,导致t3一直阻塞,t3.join()永远无法完成。
解决方案
方案一:使用队列重写的remove方法(推荐)
替换removeIf()为循环调用ArrayBlockingQueue重写的remove(Object)方法,该方法在移除元素后会自动调用notFull.signal()唤醒等待插入的线程:
// 替换原有的q.removeIf(ii -> ii == 2); while (q.remove(Integer.valueOf(2))) {}
方案二:手动唤醒notFull条件(不推荐,破坏封装)
如果必须使用removeIf(),可以通过反射获取队列的notFull条件变量并手动唤醒,但这种方式依赖内部实现,兼容性差:
q.removeIf(ii -> ii == 2); // 反射唤醒notFull条件 try { Field notFullField = ArrayBlockingQueue.class.getDeclaredField("notFull"); notFullField.setAccessible(true); Condition notFull = (Condition) notFullField.get(q); notFull.signalAll(); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); }
验证结果
使用方案一修复后,程序会依次输出2、0、1,t3线程的put()会正常执行,主线程也能顺利完成t3.join()并输出最终队列大小。
内容的提问来源于stack exchange,提问作者Jia Wen Chin
相关产品推荐
相关产品推荐

