I have a cache implementation like this:
class X
{
private final Map<String, ConcurrentMap<String, String>> structure = new HashMap...();
public String getValue(String context, String id)
{
// just assume for this example that there will be always an innner map
final ConcurrentMap<String, String> innerStructure = structure.get(context);
String value = innerStructure.get(id);
if(value == null)
{
synchronized(structure)
{
// can I be sure, that this inner map will represent the last updated
// state from any thread?
value = innerStructure.get(id);
if(value == null)
{
value = getValueFromSomeSlowSource(id);
innerStructure.put(id, value);
}
}
}
return value;
}
}
Is this implementation thread-safe? Can I be sure to get the last updated state from any thread inside the synchronized block? Would this behaviour change if I use a java.util.concurrent.ReentrantLock instead of a synchronized block, like this:
...
if(lock.tryLock(3, SECONDS))
{
try
{
value = innerStructure.get(id);
if(value == null)
{
value = getValueFromSomeSlowSource(id);
innerStructure.put(id, value);
}
}
finally
{
lock.unlock();
}
}
...
I know that final instance members are synchronized between threads, but is this also true for the objects held by these members?
Maybe this is a dumb question, but I don’t know how to test it to be sure, that it works on every OS and every architecture.
For starters, this isn’t a dumb question. Synchronization is really hard to get right, and I don’t profess to be an expert in it.
In your program, at the indicated context, yes, you can assume that the
Stringyou’re getting is the most updated version. However, your code is still not safe because you are reading a value from theMapoutside of thesynchronizedblock. If this read occurs at the same time that theMapis having a value inserted into it, you’re not guaranteed to get back a sensible value. I know that on at least some implementations, this can cause an infinite loop due to some weirdness in the implementation.The short version is that you should not have a structure that is read from or written to by multiple threads unless you guard it with a synchronization primitive like
synchronizedor a lock, or unless that structure is specifically designed to be lock-free like theConcurrentHashMap.You could indeed use the
ReentrantLockin this case to guard access to the structure and to do a timed wait, but if you do you’d have to guarantee that any reads of the structure were also guarded by the same lock. Otherwise you risk multiple threads seeing inconsistent or corrupted data.