Потокобезопасность массива Java
Есть ли проблемы параллелизма с одним потоком, читающим из одного индекса массива, в то время как другой поток записывает в другой индекс массива, пока индексы отличаются?
например (этот пример не обязательно рекомендуется для реального использования, только чтобы проиллюстрировать мою точку зрения)
class Test1
{
static final private int N = 4096;
final private int[] x = new int[N];
final private AtomicInteger nwritten = new AtomicInteger(0);
// invariant:
// all values x[i] where 0 <= i < nwritten.get() are immutable
// read() is not synchronized since we want it to be fast
int read(int index) {
if (index >= nwritten.get())
throw new IllegalArgumentException();
return x[index];
}
// write() is synchronized to handle multiple writers
// (using compare-and-set techniques to avoid blocking algorithms
// is nontrivial)
synchronized void write(int x_i) {
int index = nwriting.get();
if (index >= N)
throw SomeExceptionThatIndicatesArrayIsFull();
x[index] = x_i;
// from this point forward, x[index] is fixed in stone
nwriting.set(index+1);
}
}
edit: критика этого примера - не мой вопрос, я буквально просто хочу знать, может ли доступ к массиву к одному индексу одновременно с доступом к другому индексу создавать проблемы параллелизма, не может придумать простой пример.
5 ответов
Хотя вы не получите недопустимое состояние путем изменения массивов, как вы упомянули, у вас будет та же проблема, которая возникает, когда два потока просматривают энергонезависимое целое число без синхронизации (см. Раздел Учебное руководство по Java об ошибках согласованности памяти). По сути, проблема в том, что Поток 1 может записать значение в пространство i, но нет гарантии, когда (или если) Поток 2 увидит изменение.
Класс java.util.concurrent.atomic.AtomicIntegerArray
делает то, что вы хотите сделать.
В примере есть много вещей, которые отличаются от прозаического вопроса.
Ответ на этот вопрос заключается в том, что к отдельным элементам массива обращаются независимо, поэтому вам не нужна синхронизация, если два потока изменяют разные элементы.
Тем не менее, модель памяти Java не гарантирует (что мне известно), что значение, записанное одним потоком, будет видимо другому потоку, если вы не синхронизируете доступ.
В зависимости от того, что вы действительно пытаетесь достичь, вполне вероятно, что java.util.concurrent
уже есть класс, который сделает это за вас. И если это не так, я все еще рекомендую взглянуть на исходный код для ConcurrentHashMap
, поскольку ваш код, кажется, делает то же самое, что и для управления хэш-таблицей.
Я не совсем уверен, если синхронизировать только write
метод, оставляя read
метод несинхронизированный будет работать. Не совсем, каковы все последствия, но, по крайней мере, это может привести к read
метод, возвращающий некоторые значения, которые были только что переопределены write
,
Да, поскольку плохое чередование кэша все еще может происходить в среде с несколькими процессорами и ядрами. Есть несколько вариантов, чтобы избежать этого:
- Используйте небезопасную библиотеку Sun-private для атомарной установки элемента в массиве (или добавленной функции jsr166y в Java7
- Использовать массив AtomicXYZ[]
- Используйте пользовательский объект с одним изменяемым полем и получите массив этого объекта.
- Вместо этого используйте ParallelArray из дополнения jsr166y в своем алгоритме
Поскольку read() не синхронизирован, у вас может быть следующий сценарий:
Thread A enters write() method
Thread A writes to nwriting = 0;
Thread B reads from nwriting =0;
Thread A increments nwriting. nwriting=1
Thread A exits write();
Поскольку вы хотите гарантировать, что ваши переменные адреса никогда не конфликтуют, как насчет чего-либо подобного (исключая проблемы с индексами массива):
int i;
synchronized int curr(){ return i; }
synchronized int next(){ return ++i;}
int read( ) {
return values[curr()];
}
void write(int x){
values[next()]=x;
}