ConcurrentMap::compute clarification

Paul Sandoz paul.sandoz at oracle.com
Wed Dec 9 11:21:58 UTC 2015


Hi Jaromir,

Thanks, you found a bug, seems to be a hangover from the Map default implementation.

One fix might be:

if (oldValue != null) {
    // something to remove
    if (remove(key, oldValue)) {
        // removed the old value as expected
        return null;
    }

    // some other value replaced old value. try again.
    oldValue = get(key);
} else if (contains(key)) {
    // a mapping was added after obtaining the old value, try again
    oldValue = get(key);
} else {
    // nothing to do. Leave things as they were.
    return null;
}

Although i am somewhat inclined to do away with the contains check (given nulls are not supported), so things are just left alone.

Do you wanna report the issue here:

  http://bugreport.java.com/

Thanks,
Paul.

> On 8 Dec 2015, at 11:12, Jaromir Hamala <jaromir.hamala at gmail.com> wrote:
> 
> Hi,
> 
> I stumbled upon an interesting issue with default implementation of
> `compute(K key, BiFunction<? super K, ? super V, ? extends V>
> remappingFunction)` in JDK8 `ConcurrentMap`.
> According to its contract the default method implementation assumes map
> implementations do not support null values.
> 
> This is the begin of the default implementation:
> 
> default V compute(K key, BiFunction<? super K, ? super V, ? extends V>
> remappingFunction) {
>        Objects.requireNonNull(remappingFunction);
>        V oldValue = get(key);
>        for(;;) {
>            V newValue = remappingFunction.apply(key, oldValue);
>            if (newValue == null) {
>                // delete mapping
>                if (oldValue != null || containsKey(key)) {
>                    // something to remove
>                    if (remove(key, oldValue)) {
> [...]
> 
> 
> Let's say we have an empty map and 2 threads:
> T1 is calling the `compute('foo', someFunction)`
> T2 is concurrently calling calling `put('foo', 'bar');`
> 
> so the T1 will get `oldValue = null`, but `containsKey()` will return
> `true` - because T2 already created the mapping `foo -> bar`. Hence T1 will
> call `remove('foo', null)` !
> 
> Contract of `remove()` says: `throws NullPointerException if the specified
> key or value is null, and this map does not permit null keys or values
> optional.` -> the T1 will throw NPE.
> Is it a bug in default method impl or do I understand it wrong?
> 
> Cheers,
> Jaromir
> 
> --
> “Perfection is achieved, not when there is nothing more to add, but when
> there is nothing left to take away.”
> Antoine de Saint Exupéry




More information about the core-libs-dev mailing list