Volatile для ссылочного типа-всегда ли он избегает публикации ссылок из-за JMM?

предполагаю, что это класс:

public class AmIThreadSafe {

    private int a;
    private int b;

    AmIThreadSafe(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

предполагая, что ссылка этого экземпляра на этот класс (объявлена как volatile) доступен некоторыми потоками (ведущими к состоянию гонки), как только this(ссылка) escapes:

volatile AmIThreadSafe instance = new AmIThreadSafe(1,2);

вот, я уверен, что факт присвоения instance ссылка происходит-перед читать нитями.

а как же AmIThreadSafe's полей?

делает внешний volatile ключевое слово также подразумевается happens-before отношении в отношении a и b полей? Или, скорее, можно закончить с любым потоком, видящим устаревшие значения (значения по умолчанию 0 в этом случае с int) из-за переупорядочивания потенциальных операторов во время конструктора?

другими словами, Я должен объявить a и b final или volatile чтобы предотвратить любые сюрпризы с JMM или просто указывает volatile по ссылке экземпляра достаточно?

----------------ОБНОВЛЕННЫЙ ПОСТ-ХОРОШИЙ ОТВЕТ:----------------------------

следующая статья подтверждает своим образцом, что в моем случае,a и b защищены от оптимизации JMM, которые предотвращают постоянное происходит-перед отношения.

http://jeremymanson.blogspot.fr/2008/11/what-volatile-means-in-java.html

6 ответов


объявления instance as volatile не делает свои поля volatile, но если я правильно понял ваш вопрос, то - да, этого достаточно в вашем случае.

Per §17.4.5 спецификации:

  • a volatile запись в одном потоке происходит-перед все последующие volatile читать в другом потоке.
  • операторы в том же потоке имеют происходит-перед отношения, которые вы бы ожидать.
  • происходит-перед отношения являются транзитивными.

Итак, если нить воспринимает instance Как инициализированный, то инициализация instance случилось-перед это и инициализации instance'поля S случилось-перед что, так нить будет воспринимать instanceполя как инициализированные.


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

предполагая, что public переменные для простоты

volatile AmIThreadSafe instance = new AmIThreadSafe(1,2);
if (instance.x == 0) {
   // instance.x might have changed between checking and assigning
   instance.x = instance.x + 1;
}

volatile применим только к переменной (например, x и y автоматически volatile просто так instance is). Это должно быть ясно из JLS 8.3.1.4


на volatile в вашем случае применяется только к ссылке AmlThreadSafe. Вам все равно нужно сделать переменные экземпляра (a и b) volatile или получить к ним доступ в synchronized блок. В противном случае вы можете получить устаревшие данные.


да.

thread 1                 thread 2

1 write(a)

2 write(instance)

                         3 read(instance)

                         4 read(a)

поскольку экземпляр является изменчивым, [2] происходит-до [3].

Так как происходит-до является транзитивным, мы имеем hb(1,2), hb(2,3), hb(3,4), поэтому hb (1,4)


если a и b изменяются только в конструкторе, то в этом случае вы должны быть в порядке, потому что объект создается (и a и b set) перед назначением ссылки на instance, и любые другие потоки не будут иметь локально кэшированных копий памяти в этих местах, потому что это новый объект, который поток не мог видеть раньше. Другими словами, Я не верю, что возможно, что другой поток когда-либо сможет увидеть значение "по умолчанию" 0 для a и b потому что конструктор будет работать полностью до того, как ссылка на объект будет назначена instance.

если a и b можно изменить после конструктора, тогда другие ответы здесь верны - вам нужна синхронизация вокруг них.

если вы собираетесь предположить, что a и b не будет изменен вне конструктора, тогда нет причин не делать их окончательными в любом случае, просто для безопасности.


пример :

class Something{
  private volatile static Something instance = null;
  private int x;
  private int y;
  private Something(){
    this.x = 1;
    this.y = 2;
  }
  public static Something getInstance() {
    if (instance == null) {
        synchronized (Something.class) {
            if (instance == null)
                instance = new Something();
            }
         }  
     }
   return instance; 
  }

}

объяснение :


допустим, у нас есть приведенный выше код:

теперь предположим, что экземпляр не является изменчивым в течение некоторого времени:

нить#1 :
Входит в метод invoke getInstance, проверяет, например, значение{since null}, будет входить в условие IF, доступ к блокировке теперь снова находит этот экземпляр == null, вызывает что-то конструктор. Теперь идет внутри тела конструктора.

Как только поток#1 входит в тело конструктора, происходит переключение контекста, и теперь поток #2 получает поворот для выполнения.

нить#2 :
Вызывает экземпляр get, но внезапно обнаруживает, что экземпляр не равен null?Почему{разум обсудит сразу после этого}и, следовательно, назначит частично построенный объект ссылке и вернет его.

теперь ситуация такова:нить#1 еще нужно построить объект Полностью{необходимо полностью построить его} и нить#2 получил ссылку на частично построенный объект, и если он использует его, как, скажем, ссылку.x / / будет печатать значение по умолчанию "x", а не"1"

почему частично построенная ссылка на объект возвращается в случае нить#2 ? Причина проста: Заявления Перенумерации. Шаги просты для создания объекта и ссылки ассоциации:

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

но иногда компилятор может выполнить эти инструкции не работает, что означает :
Это может произойти примерно так:

  1. выделить память в куче.
  2. ссылка на вновь созданный объект.
  3. выполнить тело Конструктор, который инициализирует члены класса.

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

если такой сценарий происходит, то ссылка не будет ни нулевой, ни полной, и, следовательно, это нарушит нашу Одноэлементную мотивацию.

А Как летучие спасет нашу жизнь от таких конфузов :
Как известно Летучая работа с двумя принципами: 1)видимость 2) Происходит Перед Отношениями. Теперь Происходит Перед Отношениями входит в картину здесь.

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

  1. выделить память для объекта
  2. инициализировать переменные-члены{тело конструктора}
  3. назначьте ссылку на объект экземпляру переменной volatile.

Шаг 3 и переменная Volatile write и, как происходит раньше .. Все заявления писать гарантированно будут доступны для шага 3. И поскольку он является изменчивым, поэтому никакого переупорядочения не произойдет между изменчивыми и энергонезависимыми заявлениями, чего не было в Старая Модель Памяти Java.
Таким образом, перед выполнением шага 3, Шаг 1 и Шаг 2 гарантированно будут выполнены и доступны для шага 3. {В каком порядке происходит Шаг 1 и Шаг 2, мы не беспокоимся об этом.}

Так что из этой темы будет полностью создан объект или null