Синхронизация Java-потоков
В приведенном ниже классе используется метод getIt()
thread safe и почему?
public class X {
private long myVar;
public void setIt(long var){
myVar = var;
}
public long getIt() {
return myVar;
}
}
Ответы
Ответ 1
Он не является потокобезопасным. Переменные типа 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
, в результате чего потеряется другое значение потока.
Ответ 2
Это не потокобезопасно. Даже если ваша платформа гарантирует атомную запись long
, отсутствие synchronized
позволяет, чтобы один поток вызывал setIt()
, и даже после завершения этого вызова возможно, что другой поток может вызвать getIt()
, и этот вызов может вернуть старое значение myVar
.
Ключевое слово synchronized
делает больше, чем эксклюзивный доступ одного потока к блоку или методу. Он также гарантирует, что второй поток будет проинформирован об изменении переменной.
Таким образом, вы либо должны отмечать оба метода как synchronized
, либо отмечать элемент myVar
как volatile
.
Там очень хорошее объяснение о синхронизации здесь:
Атомные действия не могут чередоваться, поэтому их можно использовать, не опасаясь вмешательства потока. Однако это не устраняет необходимость синхронизации действий атома, поскольку ошибки согласованности памяти все еще возможны. Использование изменчивых переменных снижает риск ошибок согласованности памяти, поскольку любая запись в изменчивую переменную устанавливает связь между событиями и последующими чтениями этой же переменной. Это означает, что изменения в изменчивой переменной всегда видны для других потоков. Что еще, это также означает, что когда поток читает изменчивую переменную, он видит не только последнее изменение волатильности, но и побочные эффекты кода, которые привели к изменению.
Ответ 3
Нет, нет. По крайней мере, не на платформах, которым не хватает доступа к 64-разрядной памяти Atom.
Предположим, что Thread A вызывает setIt
, копирует 32 бита в память, где находится значение подкачки, и затем предварительно упущен, прежде чем он сможет скопировать другие 32 бита.
Затем поток B вызывает getIt
.
Ответ 4
Нет, это не так, потому что longs не являются атомарными в java, поэтому один поток мог бы записать 32 бита длинного в методе setIt, а затем getIt мог прочитать значение, а затем setIt мог бы установить остальные 32 бита.
Итак, конечный результат: getIt возвращает значение, которое никогда не было действительным.
Ответ 5
Это должно быть и, как правило, но не гарантируется, что оно будет безопасным для потоков. Могут возникнуть проблемы с разными ядрами, имеющими разные версии в кэше CPU, или хранилище/извлечение, не являющееся атомарным для всех архитектур. Используйте класс AtomicLong
.
Ответ 6
Геттер не является потокобезопасным, потому что он не охраняется каким-либо механизмом, который гарантирует самую последнюю видимость. Ваш выбор:
- создание
myVar
final (но тогда вы не можете его мутировать)
- создание
myVar
volatile
- использовать синхронизированный доступ к
myVar
Ответ 7
AFAIK, современные JVM больше не разделяют длинные и двойные операции. Я не знаю никакой ссылки, в которой говорится, что это все еще проблема. Например, см. AtomicLong, который не использует синхронизацию в Sun JVM.
Предполагая, что вы хотите быть уверенным, что это не проблема, вы можете использовать синхронизацию как get(), так и set(). Однако если вы выполняете операцию типа add, т.е. Set (get() + 1), то эта синхронизация не покупает вас много, вам все равно придется синхронизировать объект для всей операции. (Лучше всего это использовать одну операцию для добавления (n), которая синхронизирована)
Однако лучшим решением является использование AtomicLong. Это поддерживает атомные операции, такие как get, set и add и НЕ использует синхронизацию.
Ответ 8
Так как это метод только для чтения. Вы должны синхронизировать метод set
.
EDIT: Я вижу, почему метод get также должен быть синхронизирован. Хорошая работа, объясняющая Фила Росса.