2016-09-09 9 views
1

У меня возникают трудности с пониманием барьеров памяти и когерентности кэша в Java и как эти понятия относятся к массивам.Неустойчивые массивы и барьеры памяти и видимость в Java

У меня есть следующий сценарий, в котором один поток изменяет массив (как ссылку на него, так и одно из его внутренних значений), а другой поток читает из него.

int[] integers; 
volatile boolean memoryBarrier; 

public void resizeAndAddLast(int value) { 
    integers = Arrays.copyOf(integers, integers.size + 1); 
    integers[integers.size - 1] = value; 
    memoryBarrier = true; 
} 

public int read(int index) { 
    boolean memoryBarrier = this.memoryBarrier; 
    return integers[index]; 
} 

Мой вопрос, означает ли это то, что я думаю, что это делает, то делает «издательство» на memoryBarrier и впоследствии многочасовое чтение переменной силы действие кэш-когерентной и убедитесь, что читатель нить действительно получить как Последняя ссылка на массив и правильное базовое значение по указанному индексу?

Я понимаю, что ссылка на массив не должен быть объявлен volatile, оно должно быть достаточно, чтобы заставить действие кэш-когерентности, используя любой летучий поле. Правильно ли это рассуждение?

EDIT: существует ровно одна нить писателя и множество потоков считывателей.

ответ

2

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

void raiseFlag() { 
    if (memoryBarrier == true) 
    throw new IllegalStateException("Flag already raised"); 
    memoryBarrier = true; 
} 

public int read(int index) { 
    if (memoryBarrier == false) 
    throw IllegalStateException("Flag not raised yet"); 
    return integers[index]; 
} 

Вы можете получить только поднять флаг один раз, и вы не можете публиковать более одного integers массива. Однако это было бы бесполезно для вашего случая использования.

Теперь, как к почему ... Вы не гарантируете, что между первой и второй линией read() не было вмешательство записи в integers который наблюдался во второй строке. Отсутствие барьера памяти не предотвращает другой поток от наблюдения за действием. Это делает результат неопределенным.

Существует простая идиома, что бы сделать ваш код потокобезопасного (специализируется на предположение, что один поток вызывает resizeAndAddLast, в противном случае больше коды необходима и AtomicReference):

volatile int[] integers; 

public void resizeAndAddLast(int value) { 
    int[] copy = Arrays.copyOf(integers, integers.length + 1); 
    copy[copy.length - 1] = value; 
    integers = copy; 
} 

public int read(int index) { 
    return integers[index]; 
} 

В этом коде вы никогда не прикасайтесь к массиву после публикации, поэтому независимо от того, что вы разыскиваете с read, будет наблюдаться, как и предполагалось, с обновленным индексом.

+0

Мой сценарий очень специфичен, и я не могу вдаваться в подробности, но на самом деле мне не нужна безопасность потоков в классическом смысле, потому что нить читателя никогда не сможет запросить индекс, который ранее не был опубликован в массив. Что происходит, так это: читатель всегда знает максимальный доступный индекс, но он не знает базового значения этого индекса. Будет ли мой код гарантировать, что читатель всегда получает ** как ** последнюю ссылку на массив ** и ** базовое значение в этом индексе (вместо устаревших значений)? Это проблема видимости, а не безопасность потоков как таковая. – maxim1

+0

Не знаете, каково ваше различие между видимостью и безопасностью потоков; обычно не гарантированная видимость считается нить-небезопасной и наоборот. Ваша программа содержит гонку, как я описал, а это означает, что вам не гарантировано наблюдать как последнюю ссылку на массив, так и значение в индексе. Я добавляю больше кода, чтобы показать вам правильную идиому для безопасной публикации. –

+0

Упс ... другой ответ уже сделал это. Он даже решил использовать одно и то же имя var :) –

0

Если вы не объявите переменную volatile, то нет гарантии, что нить получит правильное значение. Изменчивые гарантии изменения в переменной - это видимое значение, а не использование кэша ЦП, которое он будет записывать/читать из основной памяти. Вам также понадобится synchronization, чтобы поток чтения не читался до завершения записи. Любая причина для перехода с массивом, а не с объектом ArrayList, поскольку вы уже используете Arrays.copyOf и изменяете размер?

+0

Объявление массива как изменчивого означает, что ссылка на массив является изменчивой, но не значениями массива. –

1

Есть несколько причин, почему она не будет работать вообще:

  • Java не говорит ничего о барьерах памяти или о заказе в несвязанного переменных.Глобальные барьеры памяти являются побочным эффектом x86
  • Даже с глобальными барьерами памяти: порядок записи значений массива и индексированного значения массива не определен. Гарантируется, что оба произойдут - перед барьером памяти, но в каком порядке? Несинхронизированное чтение может видеть ссылку, но не значение массива. Ваш барьер чтения не помогает здесь в случае множественного чтения/записи.
  • Остерегайтесь массивов ссылок: Видимость ссылочных значений требует особого внимания

Немного лучше было бы объявить сам массив в качестве летучего и рассматривать его ценности как неизменен:

volatile int[] integers; // volatile (or maybe better AtomicReference) 

public void resizeAndAddLast(int value) { 
    // enforce exactly one volatile read! 
    int[] copy = integers; 
    copy = Arrays.copyOf(copy, copy.size + 1); 
    copy[copy.size - 1] = value; 
    // may lose concurrent updates. Add synchronization or a compareExchange-loop! 
    integers = copy; 
} 

public int read(int index) { 
    return integers[index]; 
} 
+0

Java _ что-то говорит о барьерах памяти несвязанных переменных, потому что 'произойдет-до' закрывается по порядку программы. –

 Смежные вопросы

  • Нет связанных вопросов^_^