В моем сценарии у меня есть DirtyArray
объекты, которые в основном представляют собой примитивные обертки массивов, которые устанавливают логический флаг «грязный», когда происходит доступ для записи.
public class DirtyArray {
private byte[] data;
public DirtyArray(byte[] data) {
this.data = data;
}
private boolean dirty = false;
public void setValue(int index, byte value) {
dirty = true;
data[index] = value;
}
public boolean isDirty() {
return dirty;
}
}
Флаг грязных данных всегда идет только от * От 1005 * до true
.
Мне нужно сделать это безопасным для одновременного использования: есть один или несколько потоков, которые могут изменять массив (setValue
). Есть один или несколько потоков, которые перехватывают DirtyArray
перед сборкой мусора и должны записывать его на диск, если он был изменен (isDirty
).
Теперь, если я правильно понимаю, это небезопасно , чтобы сделать это, как указано выше: Фактически, с точки зрения потока isDirty
, хранилище data[index]=value
может быть переупорядочено до хранилища dirty=true
. Следовательно, вид isDirty()==false
не гарантирует, что data
не был изменен.
Это правильно?
Если да, то установка флага dirty
volatile
должна исправить Эта проблема. Однако в следующем тесте я вижу замедление в ~ 50-100 раз при этом.
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void touchAll()
{
for (int i = 0; i < numEntities; i++)
bytes.setValue(i, ( byte ) 2);
}
Используя вместо этого AtomicBoolean с вариантами получения / установки порядка памяти, представленными в Java 9, у меня есть этот вариант :
public class DirtyArray {
private byte[] data;
public DirtyArray(byte[] data) {
this.data = data;
}
private AtomicBoolean dirty = new AtomicBoolean();
public void setValue(int index, byte value) {
if (!dirty.getPlain())
dirty.setRelease(true);
data[index] = value;
}
public boolean isDirty() {
return dirty.getAcquire();
}
}
с такой же производительностью (в приведенном выше тесте), что и исходная энергонезависимая версия.
Это безопасно? То есть гарантирует, что при изменении data
я увижу isDirty()==true
? (Насколько я понимаю, это должно быть, но только потому, что dirty
только всегда изменяется с false
до true
, и никогда не возвращается.)
Существуют ли другие варианты для достижения этой гарантии, возможно, даже позволяющие сбросить dirty
- false
, и в идеале без отрицательного воздействия на производительность?
Обновление
Я согласен с общей оценкой ответов до сих пор, что единственный способ гарантировать согласованность между измененным массивом data
и флагом dirty
- синхронизировать как setValue
, так и isDirty
. Условия гонки, на которые указал Пак Уула , являются настоящей проблемой, а не тем, чтобы сделать видимым флаг грязи. Так что в основном вопрос, который я задал выше, является неправильным вопросом ...
Для большего контекста: речь идет о хранении пикселей для прозрачно кэшированных изображений в https://github.com/imglib. Он используется в очень узких циклах, и учесть удар от синхронизации на самом деле не вариант. Типичный сценарий использования:
- Несколько потоков изменяют изображение (которое поддерживается многими
DirtyArrays
). - Проверка
isDirty()
происходит в другом потоке, который перехватывает DirtyArray
перед сборкой мусора (PhantomReference
на держателе DirtyArray
), а если он грязный, записать его на диск.
Сейчас я считаю, что к этому следует подходить более грубый уровень, чем отдельные setValue()
звонки. Есть своего рода «естественные» точки синхронизации, которые возникают из-за того, что потоки переключаются между DirtyArray
s, получая их из ConcurrentHashMap
(конечно, не обращая внимания на детали), потоки находятся в пуле потоков и берут задания из общей очереди, или потоки как-то иначе ждут друг друга. В этих точках синхронизации должны стать видимыми эффекты более ранних (в программном порядке) setValue()
s. Поэтому я предпочитаю использовать простую несинхронизированную версию и полагаться на синхронизацию на более грубом уровне.
Единственное, что доставляет мне легкую головную боль, это то, что очистка запускается сборкой мусора, и я должен сделать убедитесь, что (держатель) DirtyArray
не собран до точки синхронизации грубого уровня. Но я думаю, что смогу убедиться в этом, сохранив сильные ссылки и добавив при необходимости заборы достижимости .