Java - энергозависимая ссылка на изменяемый объект - обновления полей объекта будут видны всем потокам
... без дополнительной синхронизации? Класс Tree ниже предназначен для доступа к нескольким потокам (он является одноэлементным, но не реализован через перечисление)
class Tree {
private volatile Node root;
Tree() {
root = new Node();
// the threads are spawned _after_ the tree is constructed
}
private final class Node {
short numOfKeys;
}
}
- Будут ли обновления для
numOfKeys
поле должно быть видимым для потоков читателя без какой-либо явной синхронизации (обратите внимание, что и читатели, и писатели должны получить экземплярReentrantReadWriteLock
- тот же экземпляр для каждого узла - но, кроме этого)? Если не сделаюnumOfKeys
достаточно летучих? - Менять корень так же просто, как
root = new Node()
(только поток записи может изменить корень, кроме основного потока, который вызывает конструктор Tree)
Связанные с:
- несколько полей: volatile или AtomicReference?
- Достаточно ли изменчива для изменения ссылки на список?
- Безопасны ли изменяемые поля в объекте POJO, если они хранятся в concurrentHashMap?
- Использование ключевого слова volatile с изменяемым объектом
РЕДАКТИРОВАТЬ: заинтересованы в пост 5 Java семантики
2 ответа
Это два вопроса. Начнем со второго.
Назначение вновь созданных объектов изменчивым переменным работает хорошо. Каждый поток, который читает переменную volatile, увидит полностью построенный объект. Нет необходимости в дальнейшей синхронизации. Этот паттерн обычно виден в сочетании с неизменяемыми типами.
class Tree {
private volatile Node node;
public void update() {
node = new Node(...);
}
public Node get() {
return node;
}
}
По поводу первого вопроса. Вы можете использовать переменные переменные для синхронизации доступа к энергонезависимой переменной. Следующий листинг показывает пример. Представьте, что две переменные инициализируются, как показано, и что эти два метода выполняются одновременно. Гарантируется, что если второй поток увидит обновление до foo
, он также увидит обновление до bar
,
volatile int foo = 0;
int bar = 0;
void thread1() {
bar = 1;
foo = 1; // write to volatile variable
}
void thread2() {
if (foo == 1) { // read from volatile variable
int r = bar; // r == 1
}
}
Тем не менее, ваш пример отличается. Чтение и письмо могут выглядеть следующим образом. В отличие от приведенного выше примера, оба потока читают из переменной volatile. Однако операции чтения с изменчивыми переменными не синхронизируются друг с другом.
void thread1() {
Node temp = root; // read from volatile variable
temp.numOfKeys = 1;
}
void thread2() {
Node temp = root; // read from volatile variable
int r = temp.numOfKeys;
}
Другими словами: если поток A записывает в энергозависимую переменную x, а поток B читает значение, записанное в x, то после операции чтения поток B увидит все операции записи потока A, которые произошли до записи в x. Но без операции записи в изменчивую переменную, это не повлияет на обновления других переменных.
Это звучит сложнее, чем есть на самом деле. На самом деле, есть только одно правило, которое вы можете рассмотреть, которое вы можете найти в JLS8 §17.4.5:
[..] Если все последовательно согласованные исполнения свободны от гонок данных, [..] тогда все исполнения программы будут казаться последовательно согласованными.
Проще говоря, существует гонка данных, если два потока могут одновременно обращаться к одной и той же переменной, хотя бы одна операция является операцией записи, а переменная является энергонезависимой. Гонки данных можно устранить, объявив общие переменные как изменчивые. Без гонок данных нет проблем с видимостью обновлений.
Нет.
Размещение ссылки на объект в volatile
Поле никак не влияет на сам объект.
После того, как вы загрузите ссылку на объект из изменчивого поля, у вас будет объект, не отличающийся от любого другого объекта, и изменчивость больше не будет действовать.