Обратите внимание на synchonized при декларации метода: каждый раз, когда понадобится ссылка на Singleton, потоку придется захватить монитор, а это весьма дорогостоящая операция.
Если обращение к Singleton'у происходит часто, хотелось бы отказаться от захвата монитора в том случае, если объект уже создан и требуется только чтение.
Неправильное, но интуитивное решение:
Почему же оно неправильное? Причина кроется в специфике Java Memory Model, а именно в том, что обычные (не-volatile) переменные не являются точкой синхронизации.
Пусть у Singletona есть важное для нас поле value:
В худшем случае может происходить следующее:
NN | первый поток | второй поток |
---|---|---|
1 | - | getSingleton() |
2 | - | singleton = new Singleton() и value=42 |
3 | - | переменная singleton скидывается в основную память |
4 | getSingleton() | - |
5 | так как переменная singleton не null, в synchronized блок не заходим | |
6 | singleton.value==0 (значение по умолчанию, так как новое значение не было записано еще в основную память) | - |
Все потому, что для не-volatile переменных не гарантируется, что "happens-before order". Нужна некая точка синхронизации. Так как мы хотим отказаться от использования мониторов, то решение: пометить как volatile переменную singleton.