Yes, formally speaking, there may be a problem when you read from a HashMap in a thread other than you use to write. See below...
There is a popular dichotomy: data race and race condition (see, for example https://dzone.com/articles/race-condition-vs-data-race). Since HashMap#get() doesn't modify the state of the HashMap, as other answers say, you don't have a race condition on simultaneous reads. But, you have to avoid a data race as well.
Obviously, you have to guarantee that all the writes are finished before your readers start their reads. This means you need to achieve linearizability between writes and reads. On practice/in terms of java code, this will lead to a kind of HB (https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5) before writes and reads. Just a few examples:
- Save the reference to the map in a final field after the map is filled-in completely (https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5)
Thread#start produces HB edge, so, if you manage threads on your own, pass the filled-in map to the reader threads and only after that call their start(). You should do both pass and start from the writer thread
- Reading threads are awaiting on a
CountDownLatch and writing thread calls countDown() after the map is filled-in. The readers start using the map.
- ForkJoinPool/ExecutorService may be used as well, since if produces required HBs under the hood. Execute/schedule a task to fill-in the map, and only after the task is completed, execute/schedule all the readers.
- and so on...
So, if you really guarantee (from JMM point of view) the last write is finished before any read is started - congrats, you don't have a data race as well.
The first example seems to be like your case... Even if Spring does some invisible magic under the hood which leads to correct visibility of your map (no one can prove, yeah? :)), I'd prefer the following canonical and explicit, in terms of JMM, safe publication:
@Service
public class TrackingServiceOne extends TrackingService {
private final Map<Integer, String> mapCreditos;
private final Map<Integer, String> mapDeudas;
private final Map<Integer, String> mapEjemplo;
{
Map<Integer, String> mapCreditosTemp = new HashMap<Integer, String>();
mapCreditosTemp.put(1, "hi");
mapCreditosTemp.put(2, "example");
mapCreditos = mapCreditosTemp; // publishing to the final field produces the freeze action, which guaranty visibility of the field for readers
}
...
}
The following less explicit form is also OK, since if we have at least one final field defined in the class, its constructor finishes with the freeze action:
@Service
public class TrackingServiceOne extends TrackingService {
private final Map<Integer, String> mapCreditos = new HashMap<Integer, String>();
...
{
mapCreditos.put(1, "hi");
mapCreditos.put(2, "example");
...
}
...
}
Look into https://shipilev.net/blog/2014/all-fields-are-final/ for some interesting HotSpot-related details