Если все кроме volatile полей может быть закешировано, то почему используя конструкцию, которую я скину дальше мы считаем, что она thread safe? Разве, storage.get(word) не может вернуть кешированное значение? Возможно, это описано в спеке JVM, но найти не удалось :(
private HashMap<String, Integer> storage = new HashMap<>();
public synchronized void addWord(String word) {
Integer integer = storage.get(word);
if (integer == null) {
storage.put(word, 1);
} else {
storage.put(word, integer + 1);
}
logger.info(storage);
}