Java - volatile ссылка на изменяемый объект - будет обновляться, чтобы поля объекта были видны для всех потоков

... без дополнительной синхронизации? Ниже приведенный ниже класс Tree предназначен для доступа к нескольким потокам (это одноэлементный, но не реализованный через enum).

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 - тот же экземпляр для каждого node - но запрещающий это)? Если бы не было достаточного количества летучих numOfKeys?
  • Изменяет корень так же просто, как root = new Node() (только поток записи будет изменять корень, кроме основного потока, который вызывает конструктор дерева)

по теме:

РЕДАКТИРОВАТЬ: заинтересован в семантике после Java 5

Ответ 1

Это два вопроса. Начните со второго.

Назначение вновь построенных объектов для изменчивых переменных прекрасно работает. Каждый поток, который читает изменчивую переменную, будет видеть полностью построенный объект. Нет необходимости в дальнейшей синхронизации. Этот шаблон обычно рассматривается в сочетании с неизменяемыми типами.

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
    }
}

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

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:

[..] Если все последовательные последовательные исполнения свободны от расчётов данных, [..], то все исполнения программы кажутся последовательно последовательными.

Проще говоря, гонка данных существует, если два потока могут одновременно обращаться к одной и той же переменной, по крайней мере одна операция - операция записи, а переменная является энергонезависимой. Горы данных можно устранить, объявив общие переменные волатильными. Без гонок данных нет проблем с видимостью обновлений.

Ответ 2

Нет.

Размещение ссылки на объект в поле volatile никак не влияет на объект.

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