AtomicXXX.lazySet(...) в терминах происходит до краев
Что означает метод AtomicXXX.lazySet(значение) в терминах "случается" - до ребер, используемых в большинстве рассуждений JMM? Javadocs чист на нем, а ошибка Sun 6275329 утверждает:
Семантика заключается в том, что запись гарантированно не должна быть переупорядочена с любой предыдущей записью, но может быть переупорядочена с последующими операциями (или, что то же самое, может не отображаться для других потоков) до тех пор, пока не произойдет какое-то другое летучее действие записи или синхронизации).
Но это не рассуждение о границах HB, поэтому меня это смущает. Означает ли это, что семантика lazySet() не может быть выражена через границы HB?
ОБНОВЛЕНИЕ. Я постараюсь конкретизировать мой вопрос. Я могу использовать обычное нестабильное поле в следующем сценарии:
//thread 1: producer
...fill some data structure
myVolatileFlag = 1;
//thread 2: consumer
while(myVolatileFlag!=1){
//spin-wait
}
...use data structure...
В этом случае использование "структуры данных" в потребителе является правильным, так как волатильный флаг для чтения записи делает границу HB, предоставляя гарантию того, что все записи в "структуру данных" производителя будут завершены и видны потребителю. Но что, если я буду использовать AtomicInteger.lazySet/get вместо volatile write/read в этом сценарии?
//thread 1: producer
...fill some data structure
myAtomicFlag.lazySet(1);
//thread 2: consumer
while(myAtomicFlag.get()!=1){
//spin-wait
}
...use data structure...
будет ли он по-прежнему правильным? Могу ли я по-прежнему наглядно видеть значения "структуры данных" в потребительском потоке?
Это не вопрос "из воздуха" - я видел такой метод в коде LMAX Disruptor именно в этом сценарии, и я не понимаю, как доказать, что это правильно...
Ответы
Ответ 1
Операции lazySet
не создаются, происходит до краев и поэтому не гарантируется, что они будут немедленно видны. Это низкоуровневая оптимизация, которая имеет лишь несколько вариантов использования, которые в основном связаны с параллельными структурами данных.
Пример сборки мусора для обнуления указателей связанных списков не имеет видимых пользователем побочных эффектов. Нулевое значение предпочтительнее, так что если узлы в списке находятся в разных поколениях, это не заставляет более дорогостоящую коллекцию выполнять, чтобы отбросить цепочку ссылок. Использование lazySet поддерживает гибридную семантику, не вызывая неустойчивых накладных расходов на запись.
Другим примером является использование летучих полей, защищенных блокировкой, например, в ConcurrentHashMap
. Поля являются неустойчивыми, чтобы обеспечить чтение без блокировки, но записи должны выполняться под блокировкой, чтобы обеспечить строгое согласование. Поскольку блокировка гарантирует, что это происходит раньше, чем до релиза, оптимизация заключается в использовании lazySet при записи в поля и сбросе всех обновлений при разблокировке. Это помогает сократить критический разрез, избегая ненужных киосков и трафика на шинах.
Если вы пишете параллельную структуру данных, тогда lazySet
- хороший трюк, о котором нужно знать. Его низкоуровневая оптимизация, поэтому его стоит только учитывать при настройке производительности.
Ответ 2
На основе Javadoc of Unsafe (putOrderedInt используется в AtomicInteger.lazySet)
/**
* Version of {@link #putObjectVolatile(Object, long, Object)}
* that does not guarantee immediate visibility of the store to
* other threads. This method is generally only useful if the
* underlying field is a Java volatile (or if an array cell, one
* that is otherwise only accessed using volatile accesses).
*/
public native void putOrderedObject(Object o, long offset, Object x);
/** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */
public native void putOrderedInt(Object o, long offset, int x);
Заполняющие поля в классах AtomicXXX нестабильны. Кажется, что lazySet пишет в эти поля, как если бы они не были волатильными, что бы устранить случившиеся до того края, которые вы ожидаете. Как указано в вашей ссылке, это было бы полезно для того, чтобы обнулить значения для GC, не требуя волатильной записи.
Edit:
Это ответ на ваше обновление.
Если вы посмотрите на цитату, предоставленную вами по ссылке, вы потеряете все гарантии памяти, которые у вас были с изменчивой записью.
lazySet не будет заказываться выше, где он записывается, но без какой-либо другой фактической синхронизации вы теряете гарантию того, что потребитель увидит любые изменения, которые были написаны перед ним. Совершенно законно откладывать запись myAtomicFlag и там для любых записей перед ним до тех пор, пока не произойдет какая-то другая форма синхронизации.