Краткая версия заключается в том, что совместно используемые переменные должны ЛИБО быть объявлены как volatile
ИЛИ доступ / обновление переменных должно выполняться с использованием соответствующего механизма синхронизации. Подходящим механизмом синхронизации может быть:
Использование примитивных мониторов; т.е. synchronized
блоки или методы с одним и тем же целевым объектом.
Использование Lock.acquire
и Lock.release
для одного и того же объекта блокировки.
Что-то еще с соответствующими происходит до отношений. (Не беспокойтесь об этом случае. Это сложно. Но если хотите, прочитайте о Java Модель памяти.)
В любом случае, если два потока совместно используют (энергонезависимая) переменная без соответствующей синхронизации, тогда одному потоку не гарантируется 1 , чтобы увидеть значение, записанное другим потоком. Это то, что происходило с вашим примером. Дочерний поток никогда не видит результаты записи родительского потока.
Это нормальное поведение с многопоточностью в Java. И одна из причин, почему параллельное программирование в Java является хитрым.
1 - В некоторых случаях данные будут видны, а в других - нет. Это может зависеть от версии Java, операционной системы, аппаратной платформы, отладки или нет, а также от различных других вещей. Существует как минимум две возможные причины проблем с видимостью. 1) Это часто связано с проблемами кэширования памяти; например, изменения, сделанные одним потоком, не сбрасываемым в основную память, чтобы другой поток мог их видеть. 2) В качестве альтернативы, это (по крайней мере, в теории) может быть связано с JIT-компилятором, оптимизирующим выборки из памяти. Чтобы точно понять, что происходит, вам нужно проанализировать нативный код, генерируемый JIT-компилятором. Но в любом случае, эти поведения разрешены моделью памяти Java ... если требуемый происходит до того, как отношения не существуют.