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

Следует ли синхронизировать геттеры и сеттеры?

private double value;

public synchronized void setValue(double value) {
    this.value = value;
}
public double getValue() {
    return this.value;
}

В приведенном выше примере есть ли какая-либо точка для синхронизации геттера?

4b9b3361

Ответ 1

Я думаю, что лучше всего называть Java Concurrency на практике здесь:

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

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

В отсутствие синхронизации компилятор, процессор и среда выполнения могут делать некоторые совершенно странные вещи в порядке выполнения операций. Попытки рассуждать о порядке, в котором действия памяти "должны" произойти в инсуффально-синхронизированных многопоточных программах, почти наверняка будут неверными.

Обычно вам не нужно быть настолько осторожным с примитивами, поэтому, если это будет int или boolean, возможно, это:

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

Это, однако, неверно для 64-битных операций, например, на long или double, если они не объявлены volatile:

Для модели памяти Java требуется выборка и хранить операции, чтобы быть атомарными, но для энергонезависимой длинной и двойной переменным, JVM разрешено обрабатывать 64-битное чтение или запись как два отдельные 32-битные операции. Если чтение и запись происходят в разных поэтому, можно читать энергонезависимую длину и получить верните 32 бита одного значения и младшие 32 бита другого.

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

Ответ 2

Позвольте мне показать вам пример, что является законным способом для JIT для компиляции вашего кода. Вы пишете:

while (myBean.getValue() > 1.0) {
  // perform some action
  Thread.sleep(1);
}

JIT компилирует:

if (myBean.getValue() > 1.0) 
  while (true) {
    // perform some action
    Thread.sleep(1);
  }

В немногих разных сценариях даже компилятор Java мог бы смириться с аналогичным байт-кодом (он должен был бы исключить возможность динамической отправки в другой getValue). Это учебный пример подъема.

Почему это законно? Компилятор имеет право предположить, что результат myBean.getValue() не может измениться при выполнении кода выше. Без synchronized разрешено игнорировать любые действия других потоков.

Ответ 3

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

Здесь метод get получит внутреннюю блокировку на "this", и, таким образом, любой другой поток, который может попытаться установить/обновить с помощью метода setter, должен будет ждать, чтобы получить блокировку на "this", чтобы ввести метод setter, который уже получен Выполнение потока выполняется.

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

Создание полевого volatile будет работать здесь, поскольку нет составных операторов.


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


При выполнении неатомных 64-битных операций следует учитывать особое внимание. Выдержки из Java Concurrency На практике может помочь здесь понять ситуацию -

"Модель памяти Java требует, чтобы операции извлечения и хранения были атомарными, но для энергонезависимых длинных и двойных переменных JVM разрешено обрабатывать 64-битное чтение или запись как два отдельных 32 бит. Если чтение и запись происходят в разных потоках, поэтому можно прочитать долговременную долговременную информацию и вернуть высокие 32 бита одного значения и младшие 32 бита другого. Таким образом, даже если вам не нужны устаревшие значения, небезопасно использовать общие измененные длинные и двойные переменные в многопоточных программах, если они не объявлены неустойчивым или защищенным блокировкой ".

Ответ 4

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

  private Double value;
  public  void setValue(Double value){
    updateValue(value, true);
  }
  public Double getValue(){
      return updateValue(value, false);
  }
  private double updateValue(Double value,boolean set){
    synchronized(MyClass.class){
      if(set)
        this.value = value;
      return value;
    }
  }