synchronized
не работает с переменными, но объекты . Он также не блокирует память, а только обеспечивает взаимное исключение и гарантии видимости памяти для потоков, выполняющих synchronized
на одном и том же объекте .
Это означает, что любая синхронизация с изменяемой переменной не не работает вообще.
Для вашего конкретного случая c вы должны сначала убедиться, что ArrayList
предварительно заполнен необходимым количеством элементов, прежде чем многопоточный доступ даже запустится и никогда не изменяет его размер, так как в противном случае даже видимость в памяти его внутренних полей ссылки на массив и размера не гарантируется.
Затем возникают общие проблемы с синхронизацией с изменяемой переменной.
Код типа
synchronized(variable) {
return variable;
}
содержит две операции чтения variable
, причем первая выполняется до входа в блок synchronized
, чтобы определить, на каком объекте синхронизироваться. Так как это первое чтение выполняется без какого-либо примитива синхронизации, не гарантируется, что вы увидите самое последнее значение. Хотя «последнее» в любом случае не имеет большого значения, так как авторы, выполняющие
synchronized(variable) {
variable = newValue;
}
, синхронизируются на разных объектах и, следовательно, не имеют никакого отношения упорядочения.
Но даже наиболее тривиальный сценарий: один писатель, выполняющий synchronized(variable) { variable = newValue; }
, за которым следует один читатель, выполняющий synchronized(variable) { return variable; }
, не работает, поскольку писатель синхронизировал со старым значением , в то время как считыватель может прочитать любое значение в первом доступе, включая самое последнее значение . Поскольку оба потока синхронизируются на разных объектах, нет никаких гарантий, что делает второе чтение читателя неопределенным. Как бы интуитивно это ни было, второе чтение может даже прочитать старое значение.
Но это не худший результат. Когда вы не используете неизменяемые объекты, такие как String
или Integer
, это редкое чтение может возвращать более свежую ссылку на объект, но не гарантирует видимость его полей-членов, что приводит к совершенно несовместимому состоянию.
Другими словами, ваши проблемы касаются не взаимоисключения, охватывающего только чтение и запись одной переменной, а гарантированного отсутствия памяти.
Когда у вас все в порядке с фиксированным размером, который используется в вашем примере только List.set
и List.get
означает, что вы можете использовать AtomicReferenceArray
:
public class Program {
// provide needed size
final AtomicReferenceArray<Object> data = new AtomicReferenceArray<>(100);
Object readFromList(int index) {
return data.get(index);
}
// returns old object
Object writeToList(int index, Object obj) {
return data.getAndSet(index, obj);
}
}
Если вам нужен изменяемый размер или вам действительно нужно заблокировать место для нетривиальной операции, вы можете использовать, например,
public class Program {
final ConcurrentHashMap<Integer, Object> data = new ConcurrentHashMap<>();
// returns the new index
int addToList(Object o) {
int ix;
do ix = data.size(); while(data.putIfAbsent(ix, o) != null);
return ix;
}
Object readFromList(int index) {
return data.get(index);
}
void updateList(int index, Object input) {
data.compute(index, (ix, old) -> {
// compute based on old and input
return input;// or computed value
});
}
}