Java 8:并行环境下ArrayDeque元素超线程池大小及poll返回null问题
Let's break down exactly what's happening here, and why your ArrayDeque is growing beyond the 10 elements you'd expect given your thread pool size.
The Core Issue: ArrayDeque is Not Thread-Safe
First and foremost, ArrayDeque is a non-thread-safe collection. None of its methods (poll(), add(), size()) are synchronized or designed to handle concurrent access without external coordination. When multiple threads modify and read the queue at the same time, race conditions emerge that break your expected behavior.
Let's Walk Through the Race Condition in Your Code
Your task logic follows this flow:
Integer item = itemsAvailable.poll(); if (item == null) item = new Random().nextInt(); task.run(); itemsAvailable.add(item); System.out.println("Items available: " + itemsAvailable.size());
Here's how the queue size spirals beyond 10:
- Imagine your queue is empty (size 0) and 3 threads start executing this code nearly simultaneously.
- All 3 threads call
itemsAvailable.poll()and getnull—since there's no synchronization, all threads see the empty queue state at the same time. - Each thread creates a new
Integerinstance. - After their tasks finish, all 3 threads add their new
Integerback to the queue. - Suddenly, your queue has 3 new elements instead of just 1—and this pattern repeats across different batches of threads.
Even when the queue has elements, the lack of synchronization causes issues:
- Thread A might call
poll()and remove an element, but before Thread A can add it back, Thread B callspoll()and also getsnull(if the queue was down to 1 element when Thread A removed it, but Thread B hasn't seen the update yet). - Thread B creates another new element, leading to an extra element in the queue once both threads add their items back.
Bonus: Work Stealing Pool Behavior
While you set your work stealing pool to 10 threads with Executors.newWorkStealingPool(10), keep in mind that work stealing pools can adjust thread counts slightly in edge cases—but this isn't the main issue here. Even with a strictly fixed 10-thread pool, the non-thread-safe queue would still cause the size to exceed 10 due to race conditions.
Fixing the Problem
You have two straightforward solutions:
1. Use a Thread-Safe Deque
Replace ArrayDeque<Integer> with ConcurrentLinkedDeque<Integer>, which is purpose-built for concurrent access. All its methods handle synchronization internally:
private ConcurrentLinkedDeque<Integer> itemsAvailable = new ConcurrentLinkedDeque<>();
2. Add Explicit Synchronization
If you need to stick with ArrayDeque, wrap all queue accesses in a synchronized block (or use a Lock for more control):
// Inside your runAsync lambda: Integer item; synchronized (itemsAvailable) { item = itemsAvailable.poll(); if (item == null) item = new Random().nextInt(); } task.run(); synchronized (itemsAvailable) { itemsAvailable.add(item); System.out.println("Items available: " + itemsAvailable.size()); }
Either approach will prevent race conditions and keep your queue size aligned with the number of active threads (or at least prevent it from growing uncontrollably).
内容的提问来源于stack exchange,提问作者Prakhar Mishra

