Double checked locking with ConcurrentMap

谁都会走 提交于 2019-12-04 00:28:48

yes it' safe.

If map.containsKey(key) is true, according to doc, map.put(key, resource) happens before it. Therefore getResource(key) happens before resource = map.get(key), everything is safe and sound.

If you can use external libraries, take a look at Guava's MapMaker.makeComputingMap(). It's tailor-made for what you're trying to do.

Why not use the putIfAbsent() method on ConcurrentMap?

if(!map.containsKey(key)){
  map.putIfAbsent(key, getResource(key));
}

Conceivably you might call getResource() more than once, but it won't happen a bunch of times. Simpler code is less likely to bite you.

In general, double-checked locking is safe if the variable you're synchronizing on is marked volatile. But you're better off synchronizing the entire function:


public synchronized Resource getResource(String key) {
  Resource resource = map.get(key);
  if (resource == null) {
    resource = expensiveGetResourceOperation(key);    
    map.put(key, resource);
  }
  return resource;
}

The performance hit will be tiny, and you'll be certain that there will be no sync problems.

Edit:

This is actually faster than the alternatives, because you won't have to do two calls to the map in most cases. The only extra operation is the null check, and the cost of that is close to zero.

Second edit:

Also, you don't have to use ConcurrentMap. A regular HashMap will do it. Faster still.

No need for that - ConcurrentMap supports this as with its special atomic putIfAbsent method.

Don't reinvent the wheel: Always use the API where possible.

The verdict is in. I timed 3 different solutions in nanosecond accuracy, since after all the initial question was about performance:

Fully synching the function on a regular HashMap:

synchronized (map) {

   Object result = map.get(key);
   if (result == null) {
      result = new Object();
      map.put(key, result);
   }                
   return result;
}

first invocation: 15,000 nanoseconds, subsequent invocations: 700 nanoseconds

Using the double check lock with a ConcurrentHashMap:

if (!map.containsKey(key)) {
   synchronized (map) {
      if (!map.containsKey(key)) {
         map.put(key, new Object());
      }
   }
} 
return map.get(key);

first invocation: 15,000 nanoseconds, subsequent invocations: 1500 nanoseconds

A different flavor of double checked ConcurrentHashMap:

Object result = map.get(key);
if (result == null) {
   synchronized (map) {
      if (!map.containsKey(key)) {
         result = new Object();
         map.put(key, result);
      } else {
         result = map.get(key);
      }
   }
} 

return result;

first invocation: 15,000 nanoseconds, subsequent invocations: 1000 nanoseconds

You can see that the biggest cost was on the first invocation, but was similar for all 3. Subsequent invocations were the fastest on the regular HashMap with method sync like user237815 suggested but only by 300 NANO seocnds. And after all we are talking about NANO seconds here which means a BILLIONTH of a second.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!