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?