RFR: 8355726: LinkedBlockingDeque fixes and improvements [v6]
Viktor Klang
vklang at openjdk.org
Tue Jun 10 10:20:31 UTC 2025
On Mon, 9 Jun 2025 16:53:07 GMT, kabutz <duke at openjdk.org> wrote:
>> We logged several bugs on the LinkedBlockingDeque. This aggregates them into a single bug report and PR.
>>
>> 1. LinkedBlockingDeque does not immediately throw InterruptedException in put/take
>>
>> The LinkedBlockingDeque does not behave consistently with other concurrency components. If we call putFirst(), putLast(), takeFirst(), or takeLast() with a thread that is interrupted, it does not immediately throw an InterruptedException, the way that ArrayBlockingQueue and LInkedBlockingQueue does, because instead of lockInterruptibly(), we call lock(). It will only throw an InterruptedException if the queue is full (on put) or empty (on take). Since interruptions are frequently used as a shutdown mechanism, this might prevent code from ever shutting down.
>>
>> 2. LinkedBlockingDeque.clear() should preserve weakly-consistent iterators
>>
>> LinkedBlockingDeque.clear() should preserve weakly-consistent iterators by linking f.prev and f.next back to f, allowing the iterators to continue from the first or last respectively. This would be consistent with how the other node-based weakly consistent queues LinkedBlockingQueue LinkedTransferQueue, ConcurrentLinkedQueue/Deque work.
>>
>> The LBD already supports self-linking, since that is done by the unlinkFirst() and unlinkLast() methods, and the iterators and spliterator thus all support self-linking.
>>
>> This can be fixed very easily by linking both f.prev and f.next back to f.
>>
>> 3. LinkedBlockingDeque offer() creates nodes even if capacity has been reached
>>
>> In the JavaDoc of LinkedBlockingDeque, it states: "Linked nodes are dynamically created upon each insertion unless this would bring the deque above capacity." However, in the current implementation, nodes are always created, even if the deque is full. This is because count is non-volatile, and we only check inside the linkFirst/Last() methods whether the queue is full. At this point we have already locked and have created the Node. Instead, the count could be volatile, and we could check before locking.
>>
>> In the current version, calling offer() on a full LinkedBlockingDeque creates unnecessary objects and contention. Similarly for poll() and peek(), we could exit prior to locking by checking the count field.
>>
>> 4. LinkedBlockingDeque allows us to overflow size with addAll()
>>
>> In LinkedBlockingDeque.addAll() we first build up the chain of nodes and then add that chain in bulk to the existing nodes. We count the nodes in "int n" and then whilst hol...
>
> kabutz has updated the pull request incrementally with one additional commit since the last revision:
>
> Whitespace
@kabutz Thanks for the updates, this is really looking good now. I added some suggestions for edits to avoid a couple of volatile reads, which I think makes sense for Aarch64-based systems (as we're already updating the code).
src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java line 215:
> 213: // assert lock.isHeldByCurrentThread();
> 214: if (count >= capacity)
> 215: return false;
Suggestion:
int c;
if ((c = count) >= capacity)
return false;
src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java line 225:
> 223: ++count;
> 224: notEmpty.signal();
> 225: return true;
Suggestion:
count = c + 1;
notEmpty.signal();
return true;
src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java line 236:
> 234: // assert lock.isHeldByCurrentThread();
> 235: if (count >= capacity)
> 236: return false;
Suggestion:
int c;
if ((c = count) >= capacity)
return false;
src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java line 246:
> 244: ++count;
> 245: notEmpty.signal();
> 246: return true;
Suggestion:
count = c + 1;
notEmpty.signal();
return true;
test/jdk/java/util/concurrent/tck/LinkedBlockingDequeTest.java line 1967:
> 1965:
> 1966: public void testWeaklyConsistentIterationWithIteratorRemove() {
> 1967: final LinkedBlockingDeque<Item> q = new LinkedBlockingDeque<>(15);
@kabutz Would 5 suffice?
-------------
PR Review: https://git.openjdk.org/jdk/pull/24925#pullrequestreview-2912857958
PR Review Comment: https://git.openjdk.org/jdk/pull/24925#discussion_r2137473157
PR Review Comment: https://git.openjdk.org/jdk/pull/24925#discussion_r2137473540
PR Review Comment: https://git.openjdk.org/jdk/pull/24925#discussion_r2137471349
PR Review Comment: https://git.openjdk.org/jdk/pull/24925#discussion_r2137472031
PR Review Comment: https://git.openjdk.org/jdk/pull/24925#discussion_r2137478626
More information about the core-libs-dev
mailing list