1. Обзор
В этом кратком руководстве мы увидим, почему нам не следует запускать поток внутри конструктора.
Во-первых, мы кратко представим концепцию публикации в Java и JVM. Затем мы увидим, как эта концепция влияет на то, как мы запускаем потоки.
2. Публикация и побег
Каждый раз, когда мы делаем объект доступным для любого другого кода за пределами его текущей области действия, мы фактически публикуем этот объект . Например, публикация происходит, когда мы возвращаем объект, сохраняем его в общедоступной
ссылке или даже передаем другому методу.
Когда мы публикуем объект, которого у нас не должно быть, мы говорим, что объект сбежал .
Есть много способов избежать ссылки на объект, например, опубликовать объект до его полного построения. На самом деле, это одна из распространенных форм экранирования: когда ссылка 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
. Таким образом, только что запущенный поток может увидеть объемлющий объект до того, как будет завершено его полное построение. В параллельных контекстах это может вызвать тонкие ошибки.
Также можно передать ссылку this
неявно :
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 на
файл Thread.
Однако на этот раз мы запускаем поток после возврата конструктора:
SafePublication publication = new SafePublication();
publication.start();
Поэтому ссылка на объект не переходит в другой поток до его полного построения.
4. Вывод
В этом кратком руководстве после краткого ознакомления с безопасной публикацией мы увидели, почему не следует запускать поток внутри конструктора.
Более подробную информацию о публикации и экранировании в Java можно найти в книге Java Concurrency in Practice .
Как обычно, все примеры доступны на GitHub .