Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is ConcurrentHashMap.get() guaranteed to see a previous ConcurrentHashMap.put() by different thread?

Is ConcurrentHashMap.get() guaranteed to see a previous ConcurrentHashMap.put() by different thread? My expectation is that is is, and reading the JavaDocs seems to indicate so, but I am 99% convinced that reality is different. On my production server the below seems to be happening. (I've caught it with logging.)

Pseudo code example:

static final ConcurrentHashMap map = new ConcurrentHashMap(); //sharedLock is key specific.  One map, many keys.  There is a 1:1  //      relationship between key and Foo instance. void doSomething(Semaphore sharedLock) {     boolean haveLock = sharedLock.tryAcquire(3000, MILLISECONDS);      if (haveLock) {         log("Have lock: " + threadId);         Foo foo = map.get("key");         log("foo=" + foo);          if (foo == null) {             log("New foo time! " + threadId);             foo = new Foo(); //foo is expensive to instance             map.put("key", foo);          } else             log("Found foo:" + threadId);          log("foo=" + foo);         sharedLock.release();      } else         log("No lock acquired"); }  

What seems to be happening is this:

Thread 1                          Thread 2  - request lock                    - request lock  - have lock                       - blocked waiting for lock  - get from map, nothing there  - create new foo  - place new foo in map  - logs foo.toString()  - release lock  - exit method                     - have lock                                    - get from map, NOTHING THERE!!! (Why not?)                                    - create new foo                                    - place new foo in map                                    - logs foo.toString()                                    - release lock                                    - exit method 

So, my output looks like this:

Have lock: 1     foo=null New foo time! 1 foo=foo@cafebabe420 Have lock: 2     foo=null New foo time! 2 foo=foo@boof00boo     

The second thread does not immediately see the put! Why? On my production system, there are more threads and I've only seen one thread, the first one that immediately follows thread 1, have a problem.

I've even tried shrinking the concurrency level on ConcurrentHashMap to 1, not that it should matter. E.g.:

static ConcurrentHashMap map = new ConcurrentHashMap(32, 1); 

Where am I going wrong? My expectation? Or is there some bug in my code (the real software, not the above) that is causing this? I've gone over it repeatedly and am 99% sure I'm handling the locking correctly. I cannot even fathom a bug in ConcurrentHashMap or the JVM. Please save me from myself.

Gorey specifics that might be relevant:

  • quad-core 64-bit Xeon (DL380 G5)
  • RHEL4 (Linux mysvr 2.6.9-78.0.5.ELsmp #1 SMP ... x86_64 GNU/Linux)
  • Java 6 (build 1.6.0_07-b06, 64-Bit Server VM (build 10.0-b23, mixed mode))
like image 638
Stu Thompson Avatar asked Nov 20 '09 12:11

Stu Thompson


People also ask

Can multiple threads read ConcurrentHashMap at the same time?

ConcurrentHashMap is divided into different segments based on concurrency level. So different threads can access different segments concurrently in java. Can threads read the segment of ConcurrentHashMap locked by some other thread in java? Yes.

Is ConcurrentHashMap get thread-safe?

ConcurrentHashMap class is thread-safe i.e. multiple threads can operate on a single object without any complications.

What will happen if you add a new mapping in ConcurrentHashMap while one thread is iterating over it?

It is guaranteed that things will not break if you do this (that's part of what the "concurrent" in ConcurrentHashMap means). However, there is no guarantee that one thread will see the changes to the map that the other thread performs (without obtaining a new iterator from the map).

Do ConcurrentHashMap need to synchronize?

Synchronizing those operations has no benefit here - it actually degrades performance if you don't need synchronization. The reason ConcurrentHashMap was created, is that synchronized maps (either implemented by hand like in the question or instantiated in the usual way with Collections.


1 Answers

This issue of creating an expensive-to-create object in a cache based on a failure to find it in the cache is known problem. And fortunately this had already been implemented.

You can use MapMaker from Google Collecitons. You just give it a callback that creates your object, and if the client code looks in the map and the map is empty, the callback is called and the result put in the map.

See MapMaker javadocs ...

 ConcurrentMap<Key, Graph> graphs = new MapMaker()        .concurrencyLevel(32)        .softKeys()        .weakValues()        .expiration(30, TimeUnit.MINUTES)        .makeComputingMap(            new Function<Key, Graph>() {              public Graph apply(Key key) {                return createExpensiveGraph(key);              }            }); 

BTW, in your original example there is no advantage to using a ConcurrentHashMap, as you are locking each access, why not just use a normal HashMap inside your locked section?

like image 125
David Roussel Avatar answered Sep 20 '22 08:09

David Roussel