Рубрики
Без рубрики

Почему Бы Не Запустить Поток В Конструкторе?

Узнайте, почему вы не должны запускать поток в конструкторе Java

Автор оригинала: 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 .