Sometimes I used to do things like this:
@Component class MyBean {
private Map<TypeKey, Processor> processors;
@Autowired void setProcessors(List<Processor> processors) {
this.processors = processors.stream().....toMap(Processor::getTypeKey, x -> x);
}
//some more methods reading this.processors
}
But, strictly speaking, it's buggy code, isn't it?
1) this.processors
is not final, nor is its creation synchronized on the same monitor as every access to it. Thus, every thread - and this singleton can be called from arbitrary thread processing user request - may be observing its own value of this.processors
which might be null.
2) Even though no writes happen after the Map
is initially populated, Javadoc offers no guarantees on which implementation will be used for the Map
, so it might be an implementation not ensuring thread safety when the Map
structure changes, or even if anything is modified, or at all. And initial population is changes, so it may break thread safety for who knows how long. Collectors
even offer the specialized toConcurrentMap()
method, to address that problem - so, at a bare minimum, I should have been using it instead.
But even if I use toConcurrentMap()
in #2, I will not be able to make my field final
, because then I'll not be able to initialize it in a setter. So here are my choices:
a) Initialize and populate the Map
in an autowired constructor, which frankly I prefer. But so few teams do that, so what if we abstain from that solution? What other choices exist?
b) Initialize the Map
to an empty final ConcurrentHashMap
, then populate it in a setter. This is possible, but we'll have to list.forEach()
then map.put()
. This looks like it's still Java 6; or we could definitely do map.addAll(list....toMap())
but its useless duplication of the Map
, even if temporary.
c) Use volatile
on the field. Slightly degrades performance without any need, because after some point the field never gets changed.
d) Use synchronized
to access the field and read its values. Clearly even worse than (c).
Also, any of those methods will make the reader think that the code actually wants some multithreading reads/writes to the Map
, while actually, it's just multithreaded reading.
So, what does a reasonable guru do when they want something like that?
At this point, the best solution seems to be the one with a volatile
field, assigned in a setter by using toConcurrentMap
. Is there anything better? Or maybe I am just making up problems no one ever actually encountered?