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

Как "this" избежать конструктора в Java?

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

4b9b3361

Ответ 1

Действительно простой пример:

public class Test
{
    private static Test lastCreatedInstance;

    public Test()
    {
        lastCreatedInstance = this;
    }
}

Ответ 2

Пример: в конструкторе вы создаете прослушиватель событий внутренний класс (он имеет неявную ссылку на текущий объект) и регистрирует его в списке слушателя.
= > Таким образом, ваш объект может использоваться другим потоком, даже если он не завершил выполнение своего конструктора.

     public class A {

      private boolean isIt;
      private String yesItIs;

      public A() {
        EventListener el = new EventListener() { ....};
        StaticListeners.register(el);
        isIt = true;
        yesItIs = "yesItIs";
      }
     }

Дополнительная проблема, которая может произойти позже: объект A можно полностью создать, сделать доступным для всех потоков, использовать другой поток... за исключением того, что этот поток может видеть экземпляр A как создан, yesItIs с ним "yesItIs" значение, но не isIt! Верьте или нет, это может случиться! Что происходит:

= > синхронизация составляет лишь половину от блокировки потока, другая половина - видимость между потоками.

Причиной такого выбора Java является производительность: видимость между потоками может привести к снижению производительности, если все данные будут доступны для всех потоков, поэтому гарантируется совместное использование только синхронизированных данных...

Ответ 3

По этой причине блокировка с двойной проверкой не работает. Наивный код

if(obj == null)
{
  synchronized(something)
  {
     if (obj == null) obj = BuildObject(...);
  }
} 
// do something with obj

небезопасен, поскольку назначение локальной переменной может происходить до остальной части конструкции (конструктор или метод factory). Таким образом, поток 1 может находиться на этапе BuildObject, когда поток 2 входит в один и тот же блок, обнаруживает ненулевой obj, а затем переходит к работе с неполным объектом (поток 1, который был запланирован в середине вызова).

Ответ 4

public class MyClass{
    String name;    

    public MyClass(String s)
    {
        if(s==null)
        {
            throw new IllegalArgumentException();
        }
        OtherClass.method(this);
        name= s;
    }

    public getName(){ return name; }
}

В приведенном выше коде OtherClass.method() передается экземпляр MyClass, который в этот момент не полностью сконструирован, т.е. еще не выполнил контракт, что свойство name не равно null.

Ответ 5

Стив Гилхам прав в своих оценках того, почему двойная проверенная блокировка нарушена. Если поток A входит в этот метод, а obj - null, этот поток начнет создавать экземпляр объекта и назначить его obj. Thread B может входить в то время, когда поток A все еще создает экземпляр этого объекта (но не завершает его), а затем будет рассматривать объект как не равный null, но это поле объекта, возможно, не было инициализировано. Частично построенный объект.

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

Немного больше объяснений:

Ваш конструктор может инициализировать каждое поле вашего класса, но если вы разрешите 'this' сбегать, прежде чем какой-либо из других объектов будет создан, они могут быть пустыми (или по умолчанию примитивными) при просмотре другими потоками, если 1. Они не объявлены окончательными или 2. Они не объявлены изменчивыми

Ответ 6

public class Test extends SomeUnknownClass{
    public Test(){
        this.addListner(new SomeEventListner(){
            @Override
            void act(){}
        });
    }
}

После этой операции instanse of SomeEventListner будет иметь ссылку на объект Test, как обычный внутренний класс.

Другие примеры можно найти здесь: http://www.ibm.com/developerworks/java/library/j-jtp0618/index.html