После вызова Thread.start() какие гарантии видимости получает новый поток относительно действий создавшего потока до запуска?
Все действия создавшего поток, выполненные до вызова Thread.start(), происходят раньше действий запущенного потока согласно Java Memory Model. Поэтому новый поток обязан видеть результаты этих действий, включая записи в обычные, не volatile поля. Это не распространяется на записи, выполненные после start().
Java Memory Model появилась как формальная модель поведения многопоточной программы на многоядерных системах и при оптимизациях компилятора и процессора. Без правил порядка и видимости одна и та же программа могла бы наблюдать устаревшие значения или разные результаты в зависимости от реализации виртуальной машины.
Правило для Thread.start() решает задачу безопасной передачи начального состояния от создающего потока к новому. Оно позволяет сначала подготовить объект или набор данных, а затем передать управление новому потоку без отдельной синхронизации именно для этой начальной публикации.
Обычные записи в поля сами по себе не создают межпоточного отношения видимости. Если поток изменяет состояние после запуска другого потока, новый поток может не увидеть эти изменения без volatile, блокировки или другого механизма синхронизации.
Важно различать момент передачи начального состояния и последующий обмен данными. Ошибка возникает, когда гарантию запуска ошибочно распространяют на всю дальнейшую работу потоков: записи после start() уже не покрываются этим правилом.
В JMM вызов Thread.start() формирует отношение happens-before: все действия, выполненные до start() в создающем потоке, происходят раньше действий нового потока. Это гарантирует не только видимость значений, но и порядок публикации всей цепочки предшествующих действий.
Например, обычные поля объекта, заполненные до запуска, будут корректно доступны новому потоку:
Здесь новый поток не обязан получать volatile-значения полей Config: отношение happens-before от start() уже обеспечивает видимость выполненной до него инициализации. Однако после запуска оба потока могут одновременно изменять config, и тогда нужны отдельные правила синхронизации.
Гарантия относится к действиям, а не к любым будущим состояниям объектов. Если создающий поток изменит config.port после start(), новый поток не получит специальной гарантии увидеть именно это изменение. Для дальнейшего взаимодействия применяют volatile, synchronized, Lock, атомарные классы или потокобезопасные структуры из java.util.concurrent — в зависимости от требуемой атомарности и модели обмена.
start() также нельзя заменить непосредственным вызовом run(). Вызов run() выполняет метод в текущем потоке и не создает нового участника вычислений, поэтому правило публикации, связанное с запуском нового потока, в таком сценарии неприменимо.
Сервис создает рабочий поток и передает ему неизменяемую после запуска конфигурацию. Возможны три решения: объявить каждое поле volatile, использовать блокировку при каждом чтении или инициализировать конфигурацию до start().
volatile для каждого поля избыточен, если конфигурация действительно не меняется после запуска. Блокировка обеспечивает более сильный механизм, но добавляет ненужные расходы и усложняет код. Выбранная схема — полностью заполнить конфигурацию до start(), затем считать ее только для чтения; гарантия запуска безопасно публикует начальное состояние.
Позже потребовалось менять лимит запросов без остановки потока. Это уже другая задача: для изменяемого лимита применили volatile или атомарную замену целого объекта конфигурации. Одной гарантии start() для такой динамической настройки оказалось недостаточно.
start() для изменений, сделанных после запуска?Нет. Отношение happens-before охватывает действия до вызова start(), но не последующие записи. Если один поток меняет обычное поле после запуска, а другой читает его без синхронизации, возникает гонка данных; видимость и порядок таких операций не гарантируются.
start() атомарность сложного состояния?Он гарантирует видимость уже выполненной инициализации, но не делает последующие операции атомарными. Например, новый поток увидит согласованное состояние, подготовленное до запуска, однако параллельные изменения нескольких полей после запуска могут наблюдаться частично. Для согласованного обновления набора полей нужен общий механизм публикации: блокировка, volatile-ссылка на новый неизменяемый объект или другой подход.
start(), но его конструктор завершился с ошибкой?Объект, конструктор которого завершился исключением, не должен рассматриваться как корректно инициализированный экземпляр. Гарантия start() распространяется на действия, фактически выполненные до запуска, но не превращает неуспешное создание объекта в безопасное состояние. Сначала необходимо успешно завершить инициализацию и передать поток ссылку на корректный объект; только затем запускать новый поток.