Когда объект Java становится ненулевым во время строительства? - PullRequest
16 голосов
/ 25 марта 2009

Скажем, вы создаете Java-объект примерно так:

SomeClass someObject = null;
someObject = new SomeClass();

В какой момент someObject становится ненулевым? Это до запуска конструктора SomeClass() или после?

Чтобы прояснить немного, скажем, должен ли другой поток проверять, был ли someObject нулевым, в то время как конструктор SomeClass() был на полпути к завершению, будет ли он нулевым или ненулевым?

Кроме того, какая разница, если бы someObject был создан так:

SomeClass someObject = new SomeClass();

Будет ли someObject нулевым?

Ответы [ 6 ]

16 голосов
/ 25 марта 2009

Если бы другой поток проверял переменную someObject "во время" построения, я полагаю, что может (из-за причуд в модели памяти) увидеть частично инициализированный объект. Новая (начиная с Java 5) модель памяти означает, что любые поля final должны быть установлены в их значения, прежде чем объект станет видимым для других потоков (если ссылка на вновь созданный объект не исчезает) от конструктора любым другим способом), но кроме этого, не так много гарантий.

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

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

РЕДАКТИРОВАТЬ: В целях двойной проверки блокировки вы можете обойтись без этого , если ваше поле равно volatile и , если вы используете Java 5 или выше. До Java 5 модель памяти была недостаточно сильной для этого. Вы должны правильно получить шаблон точно . См. Effective Java, 2nd edition, item 71, для получения более подробной информации.

РЕДАКТИРОВАТЬ: Вот мои аргументы против того, чтобы Аарон был видимым в одной теме. Предположим, у нас есть:

public class FooHolder
{
    public static Foo f = null;

    public static void main(String[] args)
    {
        f = new Foo();
        System.out.println(f.fWasNull);
    }
}

// Make this nested if you like, I don't believe it affects the reasoning
public class Foo
{
    public boolean fWasNull;

    public Foo()
    {
        fWasNull = FooHolder.f == null;
    }
}

Я полагаю, что это будет всегда отчет true. С раздел 15.26.1 :

В противном случае необходимо выполнить три шага:

  • Сначала левый операнд вычисляется для создания переменной. Если эта оценка завершена внезапно, то назначение Выражение завершается внезапно для та же самая причина; правый операнд не оценивается и не присваивается происходит.
  • В противном случае вычисляется правый операнд. Если это оценка завершается внезапно, затем выражение присваивания завершается внезапно по той же причине и нет назначение происходит.
В противном случае значение правой руки операнд преобразуется в тип левая переменная, подвергается преобразовать набор значений (§5.1.13) в соответствующий стандартный набор значений (не набор значений расширенного показателя), и результат преобразования сохраняется в переменной.

Затем из раздел 17.4.5 :

Два действия могут быть упорядочены с помощью отношения «до того». Если одно действие происходит раньше другого, то первое видно и упорядочено до второго.

Если у нас есть два действия x и y, мы пишем hb (x, y), чтобы указать, что x происходит до y.

  • Если x и y являются действиями одного потока и x предшествует y в программном порядке, то hb (x, y).
  • Для этого объекта существует крайний случай "до" от конца конструктора объекта до начала финализатора (§12.6).
  • Если действие x синхронизируется со следующим действием y, то у нас также есть hb (x, y).
  • Если hb (x, y) и hb (y, z), то hb (x, z).

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

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

8 голосов
/ 25 марта 2009

someObject станет не- null в какой-то момент во время строительства. Как правило, есть два случая:

  1. Оптимизатор встроил конструктор
  2. Конструктор не встроен.

В первом случае виртуальная машина выполнит этот код (псевдокод):

someObject = malloc(SomeClass.size);
someObject.field = ...
....

Таким образом, в этом случае someObject не является null , а указывает на память, которая не инициализирована на 100%, а именно не весь код конструктора был выполнен! Вот почему двойная проверка блокировки не работает.

Во втором случае будет выполнен код из конструктора, ссылка будет передана обратно (как при обычном вызове метода), а someObject будет установлено значение ссылки после all и каждый код инициализации был запущен.

Проблема в том, что нет способа сказать java не назначать someObject рано. Например, вы можете попробовать:

SomeClass tmp = new SomeClass();
someObject = tmp;

Но поскольку tmp не используется, оптимизатору разрешено игнорировать его, поэтому он будет генерировать тот же код, что и выше.

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

[EDIT] Вот хорошая статья, которая объясняет, что происходит: http://www.ibm.com/developerworks/java/library/j-dcl.html

PS: Книга Джошуа Блоха " Effective Java, Second Edition " содержит решение для Java 5 и выше:

private volatile SomeClass field;
public SomeClass getField () {
    SomeClass result = field;
    if (result == null) { // First check, no locking
        synchronized(this) {
            result = field;
            if (result == null) { // second check with locking
                field = result = new SomeClass ();
            }
        }
    }
    return result;
}

Выглядит странно, но должно работать на каждой виртуальной машине Java. Обратите внимание, что каждый бит важен; если вы опустите двойное назначение, вы либо получите плохую производительность, либо частично инициализированные объекты. Для полного объяснения купите книгу.

2 голосов
/ 25 марта 2009

someObject будет нулевым указателем вплоть до тех пор, пока ему не будет присвоено значение указателя от конструктора типа. Так как присваивание происходит справа налево, возможно для другого потока, чтобы проверить someObject, пока конструктор все еще работает. Это было бы до назначения указателя на переменную, поэтому someObject все равно будет нулевым.

0 голосов
/ 25 марта 2009

Вот некоторый тестовый код, который показывает, что объект равен нулю, пока конструктор не завершит работу :

public class Test {

  private static SlowlyConstructed slowlyConstructed = null;

  public static void main(String[] args) {
    Thread constructor = new Thread() {
      public void run() {
        Test.slowlyConstructed = new SlowlyConstructed();
      }
    };
    Thread checker = new Thread() {
      public void run() {
        for(int i = 0; i < 10; i++) {
          System.out.println(Test.slowlyConstructed);
          try { Thread.sleep(1000); }
          catch(Exception e) {}
        }
      }
    };

    checker.start();
    constructor.start();
  }

  private static class SlowlyConstructed {
    public String s1 = "s1 is unset";
    public String s2 = "s2 is unset";

    public SlowlyConstructed() {
      System.out.println("Slow constructor has started");
      s1 = "s1 is set";
      try { Thread.sleep(5000); }
      catch (Exception e) {}
      s2 = "s2 is set";
      System.out.println("Slow constructor has finished");
    }

    public String toString() {
      return s1 + ", " + s2;
    }
  }
}

Выход:

null
Slow constructor has started
null
null
null
null
null
Slow constructor has finished
s1 is set, s2 is set
s1 is set, s2 is set
s1 is set, s2 is set
s1 is set, s2 is set
0 голосов
/ 25 марта 2009

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

Object o = null;
try {
    o = new CtorTest();
} catch (Exception e) {
    assert(o == null); // i will be null
}

, где

class CtorTest {
    public CtorTest() {
        throw new RuntimeException("Ctor exception.");
    }
}

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

0 голосов
/ 25 марта 2009

Для вашего первого примера: someObject становится ненулевым ПОСЛЕ того, как конструктор завершил работу. Если вы проверите из другого потока, someObject станет ненулевым после завершения конструктора. Осторожно, вы никогда не должны обращаться к несинхронизированным объектам из разных потоков, поэтому ваш пример не должен быть реализован таким образом в реальном коде.

Во втором примере someObject никогда не будет иметь значение null, поскольку он создается ПОСЛЕ того, как создается SomeClass, а someObject создается и инициализируется с вновь созданным объектом. То же самое и для потоков: не обращайтесь к этой переменной из разных потоков без синхронизации!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...