2010-10-27 2 views
10

Мой вопрос касается безопасных публикаций значений полей в Java (здесь здесь не указано Java multi-threading & Safe Publication).Безопасная публикация, когда значения считываются синхронными методами

Как я понимаю, поле можно безопасно прочитать (то есть доступ из нескольких потоков будет видеть правильное значение), если:

  • чтения и записи синхронизированы на одном мониторе
  • поле является окончательным
  • поле является летучим

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

public class Foo { 

    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("hello"); 
      needsGreeting = false; 
     } 
    } 
} 

Я что-то не хватает? Правильно ли указан код и если да, то почему? Или необходимо в таких случаях сделать firstvolatile или использовать final AtomicBoolean или что-то в этом роде дополнительно для доступа к нему из метода synchronized.

(Просто чтобы прояснить, я знаю, что если начальное значение было написано в методе synchronized было бы потокобезопасной даже без volatile ключевого слова.)

ответ

3

Строго говоря, я не вижу что можно с уверенностью предположить, что needsGreeting имеет значение true, когда вызывается greet.

Для того чтобы это было верно, должно произойти до отношения между начальной записью (возникающей при построении объекта) и первым чтением (в методе greet). Chapter 17 Threads and Locks в JLS однако, говорится следующее о происходит, прежде (ХБА) ограничения:

17.4.5 Случается-перед заказом Два действия могут быть упорядочены происходит, прежде чем отношения. Если происходит одно действие - перед другим, то первое видно и упорядочивается до второго.

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

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

Кроме того, единственный способ ввести синхронизирована-с отношением, то есть порядоксинхронизации сделать что-то из следующего:

действия Синхронизация вызвать синхронизированный - с отношением по действиям, определяемым следующим образом:

  • Действие разблокировки на мониторе m синхронизируется с все последующие действия блокировки на m (где последующее определяется в соответствии с порядком синхронизации).
  • Запись в изменчивую переменную (§8.3.1.4) v синхронизируется со всеми последующими чтениями v любым потоком (где последующее определяется в соответствии с порядком синхронизации).
  • Действие, которое запускает поток, синхронизируется с первым действием в потоке, которое оно запускает.
  • Запись значений по умолчанию (ноль, ложь или нуль) для каждой переменной синхронизируется с первым действием в каждом потоке. Хотя может показаться немного странным писать значение по умолчанию для переменной до выделения объекта, содержащего эту переменную, концептуально каждый объект создается в начале программы с ее инициализированными значениями по умолчанию.
  • Окончательное действие в потоке T1 синхронизируется с любым действием в другом потоке T2, который обнаруживает, что T1 завершен. T2 может выполнить это, вызвав T1.isAlive() или T1.join().
  • Если поток T1 прерывает поток T2, прерывание от T1 синхронизируется с любой точкой, где любой другой поток (включая T2) определяет, что T2 был прерван (путем исключения InterruptedException или путем вызова Thread.interrupted или Thread.isInterrupted) ,

Он нигде не говорит, что «строительство объекта происходит перед любыми вызовами методов на объекте. Происходит, прежде чем отношение, однако, утверждает, что есть происходит, прежде чем край с конца конструктора объекта к началу финализатора (§12.6) для этого объекта., который может быть подсказкой о том, что существует не Происходит до края от конца конструктора объекта до начала произвольного метода!

+0

«[...], из которого вытекает, что не происходит - до края от конца конструктора объекта до начала произвольного метода!». Эта «импликация» неверна. – Grodriguez

+0

О, конечно, это не формальное значение. Однако тот факт, что они утверждают, что конец конструктора происходит до начала метода финализатора, будем говорить «намеки на», что это может быть неверно для произвольных методов. – aioobe

+0

@aioobe no, это просто для таких случаев, как следующий код: 'new SomeObject()', т. Е. Вызывает конструктор, но не сохраняет ссылку. Затем объект может сразу же быть собрано в мусор, и финализатор может быть немедленно вызван. Предложение, о котором вы говорите, просто гарантирует, что конструктор все еще закончит работу до завершения финализатора. –

4

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

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

public class Foo { 
    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("Hello."); 
      needsGreeting = false; 
     } 
    } 
} 

class FooUser { 
    private static Foo foo; 

    public static Foo getFoo() { 
     if (foo == null) { 
      synchronized (FooUser.class) { 
       if (foo == null) { 
        foo = new Foo(); 
       } 
      } 
     } 
     return foo; 
    } 
} 

Если несколько потоков называют FooUser.getFoo().greet() в то же время, один поток может создавать экземпляр Foo, но другой поток может найти ненулевую ссылку Foo преждевременно, а вызов greet() и find needsGreeting все равно false.

Пример этого упоминается в Java Concurrency in Practice (3.5).

+0

Итак, это правда, хотя присваивание 'foo' выполняется * после того, как' 'new Foo()' была полностью оценена? У вас есть ссылка, подтверждающая это? – aioobe

+0

Назначение foo выполняется после того, как новый Foo() вызывается * только * в том потоке, в котором он выполняется. С точки зрения других потоков, это может быть (или, возможно, не похоже). Компиляторам и процессорам разрешено изменять порядок выполнения, а не сбрасывать изменения, если они согласованы в потоке, в котором выполняется код. В конце концов, это видимость. И поэтому DCLP нарушен: видимость. Раздел «Модель памяти Java» спецификации языка будет хорошей ссылкой. Правильная видимость обеспечивается только с надлежащим случаем - до отношения. – sjlee

+0

Это также может быть полезно: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html – sjlee