Автор оригинала: Mona Mohamadinia.
1. Обзор
В этом кратком руководстве мы увидим, почему мы не должны запускать поток внутри конструктора.
Во-первых, мы кратко представим концепцию публикации в Java и JVM. Затем мы посмотрим, как эта концепция влияет на то, как мы запускаем потоки.
2. Публикация и побег
Каждый раз, когда мы делаем объект доступным для любого другого кода за пределами его текущей области, мы в основном публикуем этот объект . Например, публикация происходит, когда мы возвращаем объект, сохраняем его в public reference или даже передаем его другому методу.
Когда мы публикуем объект, которого у нас не должно быть, мы говорим, что объект сбежал .
Существует много способов, которыми мы можем позволить ссылке на объект избежать, например, опубликовать объект до его полного построения. На самом деле, это одна из распространенных форм побега: когда эта ссылка убегает во время строительства объекта.
Когда ссылка this ускользает во время построения, другие потоки могут видеть этот объект в неправильном и не полностью построенном состоянии. Это, в свою очередь, может привести к странным осложнениям безопасности потоков.
3. Побег с помощью потоков
Один из наиболее распространенных способов разрешить экранирование ссылки this – запустить поток в конструкторе. Чтобы лучше понять это, давайте рассмотрим пример:
public class LoggerRunnable implements Runnable { public LoggerRunnable() { Thread thread = new Thread(this); // this escapes thread.start(); } @Override public void run() { System.out.println("Started..."); } }
Здесь мы явно передаем ссылку this конструктору Thread . Таким образом, вновь запущенный поток может увидеть заключающий объект до завершения его полного построения. В параллельных контекстах это может привести к незначительным ошибкам.
Также можно передать эту ссылку неявно :
public class ImplicitEscape { public ImplicitEscape() { Thread t = new Thread() { @Override public void run() { System.out.println("Started..."); } }; t.start(); } }
Как показано выше, мы создаем анонимный внутренний класс, производный от Thread . Поскольку внутренние классы поддерживают ссылку на свой заключающий класс, ссылка this снова ускользает из конструктора.
Нет ничего изначально неправильного в создании Поток внутри конструктора. Тем не менее, крайне не рекомендуется начинать его немедленно , так как в большинстве случаев мы получаем экранированную ссылку this , явно или неявно.
3.1. Альтернативы
Вместо того, чтобы запускать поток внутри конструктора, мы можем объявить специальный метод для этого сценария:
public class SafePublication implements Runnable { private final Thread thread; public SafePublication() { thread = new Thread(this); } @Override public void run() { System.out.println("Started..."); } public void start() { thread.start(); } };:
Как показано выше, мы по-прежнему публикуем ссылку this на поток . Однако на этот раз мы запускаем поток после возвращения конструктора:
SafePublication publication = new SafePublication(); publication.start();
Таким образом, ссылка на объект не переходит в другой поток до его полного построения.
4. Заключение
В этом кратком руководстве, после краткого введения в безопасную публикацию, мы увидели, почему мы не должны запускать поток внутри конструктора.
Более подробную информацию о публикации и экранировании на Java можно найти в книге Параллелизм Java на практике .
Как обычно, все примеры доступны на GitHub .