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:
Linux mysvr 2.6.9-78.0.5.ELsmp #1 SMP
... x86_64 GNU/Linux
)build 1.6.0_07-b06
, 64-Bit Server VM (build 10.0-b23, mixed mode)
)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.
ConcurrentHashMap class is thread-safe i.e. multiple threads can operate on a single object without any complications.
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).
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.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With