синхронизация потоков Java - PullRequest
14 голосов
/ 15 февраля 2010

В классе ниже метод getIt() безопасен для потоков и почему?

public class X { 
  private long myVar; 
  public void setIt(long  var){ 
    myVar = var; 
   }  
   public long getIt() { 
     return myVar; 
  } 
}

Ответы [ 8 ]

22 голосов
/ 15 февраля 2010

Это не потокобезопасно. Переменные типа long и double в Java обрабатываются как две отдельные 32-битные переменные. Один поток может писать и записать половину значения, когда другой поток читает обе половины. В этой ситуации читатель увидит значение, которое никогда не должно было существовать.

Чтобы сделать этот потокобезопасным, вы можете либо объявить myVar как volatile (Java 1.5 или новее), либо сделать setIt и getIt synchronized.

Обратите внимание, что даже если myVar был 32-разрядным int, вы все равно можете столкнуться с проблемами потоков, когда один поток может считывать устаревшее значение, если другой поток изменился. Это может произойти, потому что значение было кэшировано ЦП. Чтобы решить эту проблему, вам снова нужно объявить myVar как volatile (Java 1.5 или более поздняя версия) или сделать оба setIt и getIt synchronized.

Стоит также отметить, что если вы используете результат getIt в последующем вызове setIt, например, x.setIt(x.getIt() * 2), тогда вы, вероятно, захотите synchronize для обоих вызовов:

synchronized(x)
{
  x.setIt(x.getIt() * 2);
}

Без дополнительной синхронизации другой поток может изменить значение между вызовами getIt и setIt, что приведет к потере значения другого потока.

9 голосов
/ 15 февраля 2010

Это не потокобезопасно. Даже если ваша платформа гарантирует атомарную запись long, отсутствие synchronized делает возможным, чтобы один поток вызывал setIt(), и даже после того, как этот вызов завершился, возможно, что другой поток может вызвать getIt(), и этот вызов мог бы вернуть старое значение myVar.

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

Таким образом, вы должны пометить оба метода как synchronized или пометить элемент myVar как volatile.

Здесь очень хорошее объяснение о синхронизации здесь :

Атомарные действия не могут чередоваться, поэтому их можно использовать, не опасаясь вмешательства потока. Однако это не устраняет всей необходимости синхронизировать атомарные действия, поскольку ошибки согласованности памяти все еще возможны. Использование энергозависимых переменных снижает риск ошибок согласованности памяти, потому что любая запись в энергозависимую переменную устанавливает отношение «происходит до» с последующим чтением этой же переменной. Это означает, что изменения в изменчивой переменной всегда видны другим потокам. Более того, это также означает, что когда поток читает переменную volatile, он видит не только последнее изменение volatile, но и побочные эффекты кода, который привел к изменению.

6 голосов
/ 15 февраля 2010

Нет, это не так. По крайней мере, не на платформах, где отсутствует атомарный 64-битный доступ к памяти.

Предположим, что поток A вызывает setIt, копирует 32 бита в память, где находится значение поддержки, и затем имеет приоритет перед тем, как сможет скопировать другие 32 бита.

Тогда поток B вызывает getIt.

3 голосов
/ 15 февраля 2010

Нет, это не так, поскольку long не являются атомарными в java, поэтому один поток мог записать 32 бита long в методе setIt, а затем getIt мог бы прочитать значение, а затем setIt мог бы установить остальные 32 бита .

Таким образом, конечный результат заключается в том, что getIt возвращает значение, которое никогда не было действительным.

2 голосов
/ 15 февраля 2010

Должно быть, и, как правило, есть, но не гарантированно , чтобы быть потокобезопасным.Могут быть проблемы с разными ядрами, имеющими разные версии в кэше ЦП, или с тем, что сохранение / получение не является атомарным для всех архитектур.Используйте класс AtomicLong.

0 голосов
/ 20 февраля 2010

AFAIK, Современные JVM больше не разделяют длинные и двойные операции. Я не знаю ни одной ссылки, в которой говорится, что это все еще проблема. Например, смотрите AtomicLong, который не использует синхронизацию в JVM от Sun.

Если вы хотите быть уверены, что это не проблема, тогда вы можете использовать synchronize как get (), так и set (). Однако, если вы выполняете операцию, такую ​​как add, то есть set (get () + 1), тогда эта синхронизация не очень выгодна, вам все равно придется синхронизировать объект для всей операции. (Лучший способ обойти это - использовать одну операцию для add (n), которая синхронизируется)

Однако лучшее решение - использовать AtomicLong. Это поддерживает элементарные операции, такие как get, set и add, и НЕ использует синхронизацию.

0 голосов
/ 16 февраля 2010

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

  • делает myVar финальным (но вы не можете изменить его)
  • делает myVar летучим
  • использовать синхронизированный доступ к myVar
0 голосов
/ 15 февраля 2010

Так как это метод только для чтения. Вы должны синхронизировать метод set.

РЕДАКТИРОВАТЬ : Я понимаю, почему метод get также должен быть синхронизирован. Хорошая работа, объясняя Фил Росс.

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