ConcurrentMap.compute is used as delete

As of Java 8, I am implementing a wrapper on an interface java.util.concurrent.ConcurrentMap

, especially the remove(Object key)

. Since I need to do more checks during deletion, I need to implement the functionality using

compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

However, here's the problem: I have to accept Object

, but pass it compute

as K

. With generics I can't even do a runtime check likeif (key instanceof K) ...

I understand why the signature for compute

was chosen this way; if that was easy Object

, in case a computation should create a new record, it can't just use the key. But I'm not sure how to solve this - is there a recommended pattern, but use multiple calls like below?

for (;;) {
    V v = map.get();
    if (check(k, v)) {
        if (map.remove(k, v)) return true;
    } else return false;
}

      

thank

+3


source to share


3 answers


By looking at the default implementation ConcurrentMap.compute

, you can safely use unchecked cast, assuming yours remappingFunction

can handle Object

non-type K

and return null

. Thus, you can use:

public V remove(Object key) {
    @SuppressWarnings("unchecked")
    V result = compute((K)key, (k, v) -> {
        if(v == null) return null;
        ...
    });
    ...
}

      



In implementation it is compute

first used get(key)

(which accepts any object, thus it is safe) and passes the result to remappingFunction

. If the key is of an invalid type, the result will be null

, so yours remappingFunction

should return as well null

. In this case, it will be called containsKey(key)

, which also accepts any object and will return false

for an invalid object, but compute

will return null

.

Note that the behavior is ConcurretMap.compute

well documented (even if there is equivalent code), so it is unlikely that such an implementation will break in the future.

+1


source


The first thought that came to my mind was to use it containsKey

to pre-check if the card can handle a particular key object. If this method returns true

, the map can handle it, so an uncontrolled cast to K

will be fine.

But...

ConcurrentMap<String,Integer> map=new ConcurrentSkipListMap<>();
map.put("foo", 42);
map.containsKey(0);

      

enough to prove that even though a method containsKey

has Object

a parameter type as a parameter, it won't necessarily work on arbitrary argument types - in which case it will issue ClassCastException

.

Nevertheless, even your source cycle using get(Object)

and remove(Object,Object)

not guaranteed to work.

Without an additional type token, there is only one safe way:

return map.entrySet().removeIf(e ->
    Objects.equals(key, e.getKey()) && check(e.getKey(), e.getValue()));

      



but besides performing a linear search, there is a theoretical possibility that it will delete a key more than once if a concurrent insertion of the same key occurs during a traversal.


So the only solution to ensure both atomic and type safety is to use a runtime type marker (usually initialized via a class literal):

ConcurrentMap<K, V> map;
Class<K> keyType;

public boolean remove(Object key) {
    if(!keyType.isInstance(key)) return false;
    K k=keyType.cast(key);
    for(;;) {
        V v=map.get(k);
        if(v==null || !check(k,v)) return false;
        if(map.remove(k, v)) return true;
    }
}

      

Note that you can use something like

if(keyType.isInstance(key))
    map.computeIfPresent(keyType.cast(key), (k,v) -> check(k, v)? null: v);

      

to atomically delete the mapping if it exists and check

succeeds, but you cannot use it to return whether the deletion actually occurred, since the methods compute…

make no difference between missing keys and keys that were deleted as in both cases is returned null

.

+1


source


See if you can use the forEach method on Map and put it in forEach

map.forEach((k, v) -> 
System.out.println(k + "=" + v));

      

0


source







All Articles