Java: как именно синхронизированные операции связаны с волатильностью?

Извините, что такой длинный вопрос.

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

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

  1. Если даКакова цель синхронизации простого метода получения? (см. пример 1 )Кроме того, ВСЕ ли изменения отправляются в основную память, пока поток синхронизируется с чем-либо? например, если он отправляется для выполнения большого количества работы повсюду в рамках синхронизации очень высокого уровня, будет ли каждое отдельное изменение, сделанное затем, в основной памяти, и ничего не будет кэшироваться, пока оно снова не будет разблокировано?
  2. Если нетДолжны ли изменения быть явно внутри синхронизированного блока, или может ли java фактически уловить, например, использование объекта Lock? (см. пример 3)
  3. Если либо Должен ли синхронизированный объект каким-либо образом быть связан с изменяемой ссылкой/примитивом (например, с непосредственным объектом, который его содержит)? Могу ли я писать, синхронизируя один объект, и читать с другим, если в остальном это безопасно? (см. пример 2)

(обратите внимание на следующие примеры, которые я знаю, что синхронизированные методы и синхронизированные (это) не одобряются и почему, но обсуждение этого выходит за рамки моего вопроса)

Пример 1:

class Counter{
  int count = 0;

  public synchronized void increment(){
    count++;
  }

  public int getCount(){
    return count;
  }
}

В этом примере increment() необходимо синхронизировать, поскольку ++ не является атомарной операцией. Таким образом, одновременное увеличение двух потоков может привести к общему увеличению счетчика на 1.Примитив count должен быть атомарным (например, не длинным/двойным/ссылочным), и это нормально.

Нужно ли здесь синхронизировать getCount() и почему именно? Объяснение, которое я слышал чаще всего, заключается в том, что у меня не будет гарантии, будет ли возвращенный счет до или после приращения. Однако это похоже на объяснение чего-то немного другого, которое оказалось не в том месте. Я имею в виду, что если бы я синхронизировал getCount(), то я все еще не вижу никакой гарантии - теперь все сводится к незнанию порядка блокировки, вместо того, чтобы не знать, происходит ли фактическое чтение до/после фактической записи.

Пример 2:

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

class Counter{
  private final Object lock1 = new Object();
  private final Object lock2 = new Object();
  private final Object lock3 = new Object();
  int count = 0;

  public void increment1(){
    synchronized(lock1){
      count++;
    }
  }

  public void increment2(){
    synchronized(lock2){
      count++;
    }
  }

  public int getCount(){
    synchronized(lock3){
      return count;
    }
  }

}

Пример 3:

Является ли отношение «происходит до» просто концепцией Java, или это реальная вещь, встроенная в JVM? Несмотря на то, что я могу гарантировать концептуальное отношение «происходит до» для следующего примера, достаточно ли умна java, чтобы подобрать его, если это встроенная вещь? Я предполагаю, что это не так, но действительно ли этот пример потокобезопасен? Если это потокобезопасно, как насчет того, чтобы getCount() не блокировался?

class Counter{
  private final Lock lock = new Lock();
  int count = 0;

  public void increment(){
    lock.lock();
    count++;
    lock.unlock();
  }

  public int getCount(){
    lock.lock();
    int count = this.count;
    lock.unlock();
    return count;
  }
}
11
задан Numeron 28 May 2012 в 07:46
поделиться