Reputation: 341
I have many services with his own hashmaps, my hashmaps are inmutables and they get initialized at initializers blocks. So all threads will be reading the hashmaps but never writing on them.
My services are Spring's component, so they are "Spring Singletons".
Is there any problem at using HashMap for this? Should I use other class? Why?
Here is an example of what am I talking about:
@Service
public class TrackingServiceOne extends TrackingService {
Map<Integer, String> mapCreditos = new HashMap<Integer, String>();
Map<Integer, String> mapDeudas = new HashMap<Integer, String>();
Map<Integer, String> mapEjemplo = new HashMap<Integer, String>();
{
mapCreditos.put(1, "hi");
mapCreditos.put(2, "example");
// And like this I will populate each map.
}
// My methods will read the maps, never write them.
}
Upvotes: 3
Views: 1872
Reputation: 1587
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:
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 threadCountDownLatch
and writing thread calls countDown()
after the map is filled-in. The readers start using the map.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
Upvotes: 2
Reputation: 339043
Map.copyOf
As the other Answers say, any Map
should be thread-safe for read-only access. To be sure the map is used only for reading and not writing, use a map that cannot be modified.
The modern way to make an unmodifiable map is to use Map.copyOf
in Java 10 and later.
Populate your HashMap
using a temporary object. Then make an unmodifiable copy to be held as a member field on your object.
Map<Integer, String> mapCreditos = Map.copyOf( myTemporaryHashMap ) ;
Upvotes: 2
Reputation: 20604
In principle there is no problem using a HashMap
in a concurrent way as long as you really ensure no modifications!
As code evolves over time you should consider wrapping the map using Collections.unmodifiableMap()
to make sure nobody can modify later. To do so, initialization by the constructor may be the easiest way.
Also make sure you don’t expose neither the map itself nor any parts like the keySet()
or values()
to outside of the class.
Upvotes: 4
Reputation: 51572
HashMap is safe for read-only concurrent access. Make sure the hashmaps are initialized before the threads are started, and as long as no other thread writes to them, they can be used from multiple threads without any race conditions.
Upvotes: 5