Why LinkedBlockingQueue’s Stream Traversal Can Deadlock in JDK 8 – A Deep Dive
This article explains how LinkedBlockingQueue, normally thread‑safe, can enter an infinite loop when its stream traversal is used under JDK 8 with concurrent remove() calls, analyzes the root cause in the tryAdvance method, and shows how the bug was fixed in later JDK versions.
Demo
The problematic PR is https://github.com/apache/rocketmq/pull/3509 . It contains a minimal reproducible example that isolates the issue from RocketMQ.
public class TestQueue {
public static void main(String[] args) throws Exception {
LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>(1000);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
queue.offer(new Object());
queue.remove();
}
}).start();
}
while (true) {
System.out.println("begin scan, i still alive");
queue.stream()
.filter(o -> o == null)
.findFirst()
.isPresent();
Thread.sleep(100);
System.out.println("finish scan, i still alive");
}
}
}The demo creates ten threads that continuously call offer and remove. The main thread repeatedly performs a stream traversal looking for a null element.
Root Cause
The stream traversal uses LinkedBlockingQueue$LBQSpliterator.tryAdvance. In JDK 8 the method contains a while (current != null) loop that never exits when a node’s item is null and its next reference also becomes null after a concurrent remove() operation.
When remove() (the no‑arg version) removes the head node, it leaves the removed node’s next pointing to itself, creating a self‑referencing node. The iterator then sees current.item == null and current.next == current, causing tryAdvance to loop forever.
at java.util.concurrent.LinkedBlockingQueue$LBQSpliterator.tryAdvance(LinkedBlockingQueue.java:950)
The bug is reproduced only with JDK 8; running the same code on JDK 15 produces the expected alternating output because the implementation was changed.
Debugging
Thread dumps show the main thread in RUNNABLE state, indicating it is not blocked on a lock but stuck inside the infinite loop of tryAdvance. Multiple dumps at different times confirm the thread is continuously executing the same code path.
How It Was Fixed
The issue was logged as JDK‑8171051 and fixed in JDK 9. The fix introduces a succ(Node<E> p) helper that detects self‑referencing nodes and redirects the iterator to head.next, breaking the loop.
Node<E> succ(Node<E> p) {
if (p == (p = p.next))
p = head.next;
return p;
}Practical Recommendations
For Java developers encountering this bug:
Avoid using stream() on LinkedBlockingQueue when other threads may call the no‑arg remove() concurrently.
Prefer explicit iterator loops or synchronized access.
Upgrade to JDK 9 or later where the bug is resolved.
Remember that thread‑safety guarantees apply to individual operations; combining them without proper coordination can still lead to subtle concurrency problems.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
