Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ConcurrentHashMap put vs putIfAbsent

Java Docs says that, putIfAbsent is equivalent to

   if (!map.containsKey(key)) 
      return map.put(key, value);
   else
      return map.get(key);

So if the key exists in the map, it doesn't update its value. Is this correct?

What if i want to update a keys value based on some criteria? Say expiration time etc.

Would this be a better impl for adding and updating cache?

public void AddToCache(T key, V value)
{
   V local = _cache.putifabsent(key, value);

   if(local.equals(value) && local.IsExpired() == false){
     return;
   }
   // this is for updating the cache with a new value
   _cache.put(key, value);
}
like image 872
DarthVader Avatar asked May 07 '12 17:05

DarthVader


People also ask

Is ConcurrentHashMap putIfAbsent thread-safe?

ConcurrentHashMap putifAbsent example in Java Of course, you can wrap the whole code in the synchronized block and make it thread-safe but that will only make your code single-threaded.

What is difference between computeIfAbsent and putIfAbsent?

putIfAbsent adds an element with the specified Value whereas computeIfAbsent adds an element with the value computed using the Key.

Why putIfAbsent?

Overview. The putIfAbsent method adds the key-value pair to the map if the key is not present in the map. If the key is already present, then it skips the operation.

Which is better HashMap or ConcurrentHashMap?

HashMap is non-Synchronized in nature i.e. HashMap is not Thread-safe whereas ConcurrentHashMap is Thread-safe in nature. HashMap performance is relatively high because it is non-synchronized in nature and any number of threads can perform simultaneously.


2 Answers

So it doesnt update a key's value. is this correct?

That is correct. It will return the current value that was already in the Map.

would this be a better impl for adding and updating cache?

A couple things would make your implementation better.

1. You shouldn't use putIfAbsent to test if it exists, you should only use it when you want to ensure if one does not exist then putIfAbsent. Instead you should use map.get to test it's existence (or map.contains).

    V local = _cache.get(key);
    if (local.equals(value) && !local.IsExpired()) {
        return;
    }

2. Instead of put you will want to replace, this is because a race condition can occur where the if can be evaluated as false by two or more threads in which one of the two (or more) threads will overwrite the other thread's puts.

What you can do instead is replace

When all is said and done it could look like this

public void AddToCache(T key, V value) {
    for (;;) {

        V local = _cache.get(key);
        if(local == null){
            local = _cache.putIfAbsent(key, value);
            if(local == null)
                return;
        }
        if (local.equals(value) && !local.IsExpired()) {
            return;
        }

        if (_cache.replace(key, local, value))
            return;
    }
}
like image 200
John Vint Avatar answered Sep 20 '22 13:09

John Vint


Your code will throw an NPE if the key was not previously in the map.

Other than that, although this is a reasonable idea, it will not work in a "concurrent" environment. The reason the putIfAbsent() method was added was so that the map could manage the atomicity of the operation using whatever underlying support it is using to make the operations thread-safe. In your implementation, 2 different callers could end of stepping on each other (the first replaces an expired value with a new one, and the second immediately replaces the first new one with a second new one).

like image 33
jtahlborn Avatar answered Sep 21 '22 13:09

jtahlborn