Причина, по которой это возможно, заключается в том, что у Java слабая модель памяти. Это не гарантирует порядок чтения и записи.
Эта конкретная проблема может быть воспроизведена с помощью следующих двух фрагментов кода, представляющих два потока.
Тема 1:
someStaticVariable = new Holder(42);
Тема 2:
someStaticVariable.assertSanity(); // can throw
На первый взгляд кажется невозможным, чтобы это могло когда-либо произойти. Чтобы понять, почему это может произойти, вы должны пройти через синтаксис Java и перейти на гораздо более низкий уровень. Если вы посмотрите на код для потока 1, он может быть разбит на серию операций записи и выделения памяти:
- Распределить память по указателю1
- Запись 42 в указатель1 со смещением 0
- Запись указателя 1 в someStaticVariable
Поскольку у Java слабая модель памяти, код вполне может фактически выполняться в следующем порядке с точки зрения потока 2:
- Выделить память для указателя1
- Запись указателя1 в someStaticVariable
- Записать 42 в pointer1 со смещением 0
Страшно? Да, но это может случиться.
Это означает, что поток 2 теперь может вызывать assertSanity
до того, как n
получит значение 42. Возможно, что значение n
будет прочитано дважды в течение assertSanity
, один раз перед операцией # 3 завершается и один раз после и, следовательно, видит два разных значения и выдает исключение.
EDIT
Согласно Джону Скиту , AssertionError
может продолжаться с Java 8 , если поле не является окончательным.