Подтвердить что ты не робот

Поток java, доступ к внешнему объекту до его создания

Да, это академический вопрос, я знаю, что люди будут жаловаться, что я не отправляю какой-либо код но я искренне поражен этим вопросом, действительно не знаю с чего начать. Я был бы очень благодарен за объяснение и, возможно, за какой-то пример кода.

Если конструктор объекта запускает новый поток, который выполняет метод запуск анонимного объекта внутреннего класса, возможно, что это новое поток может получить доступ к окружающему внешнему объекту до того, как он полностью построены, и его поля полностью инициализированы. Как бы ты предотвратить это?

4b9b3361

Ответ 1

Это называется "утечка этого". Здесь у вас есть код

public class Test {

  // this is guaranteed to be initialized after the constructor
  private final int val;

  public Test(int v) {
    new Thread(new Runnable() {
      @Override public void run() {
        System.out.println("Val is " + val);
      }
    }).start();
    this.val = v;
  }

}

Угадайте, что он будет (может, поскольку это нить) печатать. Я использовал поле final, чтобы подчеркнуть, что к объекту обращаются, прежде чем он будет полностью инициализирован (окончательные поля должны быть обязательно назначены после последней строки каждого конструктора)

Как восстановить

Вы не хотите передавать this вокруг, когда находитесь в конструкторе. Это также означает, что вы не хотите вызывать нефинальные виртуальные методы в том же классе (нестатические, не частные) и не использовать внутренние классы (анонимные классы - это внутренние классы), которые неявно связаны с окружающим например, таким образом, чтобы они могли получить доступ к this.

Ответ 2

Сначала подумайте об однопоточной ситуации:

Всякий раз, когда вы создаете объект через new, его конструктор вызывается, который (надеюсь) инициализирует поля нового объекта до того, как будет возвращена ссылка на этот объект. То есть, с точки зрения вызывающего, это new почти похоже на атомную операцию:

Перед вызовом new объекта нет. После возврата из new объект существует полностью инициализирован.

Итак, все хорошо.


Ситуация меняется незначительно, когда в игру вступают несколько потоков. Но мы должны внимательно прочитать вашу цитату:

... был полностью построен и его поля полностью инициализированы.

Важнейшим моментом является fully. Строка темы вашего вопроса говорит "до создания", но здесь подразумевается не до создания объекта, а между созданием и инициализацией объекта. В многопоточной ситуации new больше не может считаться (псевдо) атомным из-за этого (время течет слева направо):

Thread1 --> create object --> initialize object --> return from `new`
                           ^
                           |
                           | (messing with the object)
Thread2 ------------------/

Итак, как может Thread2 беспорядок с объектом? Это потребует ссылки на этот объект, но поскольку new будет возвращать объект только после того, как он был создан и инициализирован, это должно быть невозможно, правильно?

Ну, нет - есть один способ, где это возможно, а именно, если в конструкторе объекта создается Thread 2. Тогда ситуация будет такой:

Thread1 --> create object --> create Thread2 --> initialize object --> return from `new`
                                      |       ^
                                      |       |
                                      |       | (messing with the object)
                                       \-----/

Так как Thread2 создается после создания объекта (но до того, как он был полностью инициализирован), уже есть ссылка на объект, который Thread2 может получить. Один из способов - просто, если конструктор Thread2 явно ссылается на объект как параметр. Другой способ заключается в использовании нестатического внутреннего класса объекта для метода Thread2 run.

Ответ 3

Я не полностью согласен с ответом Паблоса, потому что он сильно зависит от вашего метода инициализации.

public class ThreadQuestion {

    public volatile int number = 0;

    public static void main(String[] args) {
        ThreadQuestion q = new ThreadQuestion();
    }

    public ThreadQuestion() {
        Thread t = new Thread(new Runnable() {

        @Override
        public void run() {
            System.out.println(number);             
        }
        });

        try {
        Thread.sleep(500);
        } catch(Exception e) {
            e.printStackTrace();
        }
        number = 1;     
        t.start();
    }
}

Когда вы

  • поместите t.start() в конец, напечатайте правильные данные.
  • поместите t.start() перед командой sleep, он будет печатать 0
  • удалите команду спящего режима и поместите t.start() перед назначением, которое он может распечатать 1 (не определено)

Играйте в умную игру на 3.) вы можете сказать, что "крошечное" присвоение 1 простого типа данных будет работать так, как ожидалось, но если вы создадите соединение с базой данных, он не достигнет надежного результата.

Не стесняйтесь поднимать любой вопрос.

Ответ 4

Я бы изменил заголовок вопроса, поскольку потоки не обращаются к ним, а второй - к первому. Я имею в виду: У вас есть один поток, создающий объект. Внутри конструктора для этого объекта вы объявляете анонимный внутренний класс, который реализует Runnable. В том же конструкторе первого потока вы запускаете новый поток для запуска своего анонимного внутреннего класса. Таким образом, у вас есть два потока. Если вы хотите заверить, что новый поток ничего не делает до того, как конструктор "полностью закончил", я бы использовал некоторые блокировки в конструкторе. Таким образом, второй поток можно запустить, но дождитесь окончания первого потока.

public class A {
    int final number;

    A() {
        new Thread(
            new Runnable() {
                public void run() {
                    System.out.pritnln("Number: " + number);
                }
        }).start();
    number = 2;
    }
}

Ответ 5

Итак, такая ситуация?

public class MyClass  {
    private Object something;
    public MyClass() {
        new Thread() {
            public void run() {
                something = new Object();
            }
        }.start();
    }
}

В зависимости от используемого фактического кода поведение может меняться. Вот почему конструкторы должны быть тщательно сделаны так, чтобы они, например, не вызывали не частные методы (подкласс мог бы переопределить его, позволяя получить доступ к суперклассу this из подкласса до полной инициализации суперкласса). Хотя этот конкретный пример относится к одному классу и потоку, он связан с проблемой утечки ссылок.