Перейти к основному содержимому

Почему бы не запустить поток в конструкторе?

· 3 мин. чтения

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 .