There are fundamental problems with this approach. You’re accessing a HashMap
, which is not thread safe, before ever entering the synchronized
block. If there are updates to the map after its construction, this approach is broken.
It’s crucial to use the same object instance for synchronizing when accessing the same data.
So even if you used a thread safe map here, using values.get(value) == null? value: values.get(value)
means using changing objects for synchronization, when there are map updates, sometimes it uses the key, sometimes the mapped value, depending on whether a mapping is present. Even when the key is always present, it may use different mapped values.
It’s also pertinent to the Check-Then-Act anti-pattern, as you are checking values.get(value) == null
first, and using values.get(value)
afterwards, when the condition could have changed already.
You should never use strings for synchronization, as different string objects may be equal, so they map to the same data when using them as key to a Map
, whereas synchronization fails due to different object identity. On the other hand, strings may get shared freely in a JVM and they are in case of string literals, so unrelated code performing synchronization on strings could block each other.
There’s a simple solution using a tool designed for this purpose. When using
ConcurrentMap<String, String> values = new ConcurrentHashMap<>();
void someMethod(String string) {
values.compute(string, (key,value) -> {
if(value == null) value = key.toUpperCase(); // construct when not present
// update value
return value;
});
}
the string’s equality determines the mutual exclusion while not serving as the synchronization key itself. So equal keys provide the desired blocking, while unrelated code, e.g. using a different ConcurrentHashMap
with similar or even the same key values, is not affected by these operations.