Introduction
Suppose I have a ConcurrentHashMap singleton:
public class RecordsMapSingleton {
private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, Record> getInstance() {
return payments;
}
}
Then I have three subsequent requests (all processed by different threads) from different sources.
The first service makes a request, that gets the singleton, creates Record
instance, generates unique ID and places it into Map
, then sends this ID to another service.
Then the second service makes another request, with that ID. It gets the singleton, finds Record
instance and modifies it.
Finally (probably after half an hour) the second service makes another request, in order to modify Record
further.
Problem
In some really rare cases, I'm experiencing heisenbug. In logs I can see, that first request successfully placed Record
into Map
, second request found it by ID and modified it, and then third request tried to find Record by ID, but found nothing (get()
returned null
).
The single thing that I found about ConcurrentHashMap
guarantees, is:
Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread.
from here. If I got it right, it literally means, that get()
could return any value that actually was sometime into Map, as far as it doesn't ruin happens-before
relationship between actions in different threads.
In my case it applies like this: if third request doesn't care about what happened during processing of first and second, then it could read null
from Map
.
It doesn't suit me, because I really need to get from Map
the latest actual Record
.
What have I tried
So I started to think, how to form happens-before
relationship between subsequent Map
modifications; and came with idea. JLS says (in 17.4.4) that:
A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).
So, let's suppose, I'll modify my singleton like this:
public class RecordsMapSingleton {
private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
private static volatile long revision = 0;
public static ConcurrentHashMap<String, Record> getInstance() {
return payments;
}
public static void incrementRevision() {
revision++;
}
public static long getRevision() {
return revision;
}
}
Then, after each modification of Map
or Record
inside, I'll call incrementRevision()
and before any read from Map I'll call getRevision()
.
Question
Due to nature of heisenbugs no amount of tests is enough to tell that this solution is correct. And I'm not an expert in concurrency, so couldn't verify it formally.
Can someone approve, that following this approach guarantees that I'm always going to get the latest actual value from ConcurrentHashMap
? If this approach is incorrect or appears to be inefficient, could you recommend me something else?