изменчивые переменные и барьер памяти в Java - PullRequest
7 голосов
/ 29 июня 2011

У меня есть структура данных, которая состоит из связанных узлов. Вы можете думать об этом как о простом LinkedList. Каждый узел списка состоит из некоторого значения и следующего поля, указывающего другой узел или ноль, если это последний узел. Первый узел работает как корень, он не имеет значения, он только указывает на следующий узел. Все остальные узлы практически неизменны, то есть после их создания ни их значение, ни их следующее поле не изменяются в течение срока службы, если только структура не располагается, что относится к конкретной ситуации.

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

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

Мой вопрос: достаточно ли сделать следующее поле нестабильным? Из моего понимания модели памяти Java, если основной поток (тот, который добавляет новые узлы) будет выполнять энергозависимую запись при добавлении нового узла, тогда все будет синхронизироваться просто отлично, и никаких несоответствий не будет.

Также правильно ли предполагать, что в архитектуре x86 чтение изменчивой переменной не повлечет за собой снижения производительности? Поскольку другие потоки будут часто просматривать структуру, читая следующее поле, важно, чтобы это можно было делать свободно, без каких-либо барьеров памяти и т. Д.

У меня также есть еще одна проблема. Потоки, которые будут просматривать структуру, также будут содержать некоторые дополнительные узлы. Эти узлы будут полностью локальными для потока, то есть они будут использоваться только тем потоком, который их создал, и не будут использоваться совместно. Для этих дополнительных узлов нет необходимости, чтобы следующее поле было изменчивым. Более того, установка поля volatile next создаст барьер памяти, который приведет к нежелательной потере производительности. Интересно, есть ли способ избежать этого. В идеале было бы идеально, если бы следующее поле работало иногда как изменчивое поле, а иногда как обычное поле;) или если бы я имел полный контроль и мог самостоятельно создавать барьеры памяти, когда мне это нужно.

Изменить:

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

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

Но, возможно, можно придумать такую ​​схему, которая была бы безопасной? Как насчет этого:

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

Ответы [ 3 ]

8 голосов
/ 29 июня 2011

1.

Исходя из того, что вы здесь говорите

создание нового объекта, установка его полей и установка следующего поля для объекта, указанного корнем, затем установкаСледующее поле root для этого нового узла.

Тогда да, установка следующего поля на volatile будет правильно синхронизироваться.Важно понять почему.У вас есть три набора записей, один для объекта узла, один для полей и другой для узлов (хотя я не совсем уверен, зачем вы это делаете, может я что-то не понимаю).

Так что это 2 + (N число полей) записи.На этом этапе отношения «до и после» отсутствуют, и если узел записывается нормально, гарантии нет.Как только вы записываете в поле volatile, все предыдущие записи теперь также будут видны.

2.

Volatile для чтения / записи в операционной системе x86 (или любой другой, связанной с кэшем) операционной системе имеетследующие атрибуты:

 volatile-read: very close to a normal read
 volatile-write: about 1/3 the time of a synchronization write 
         (whether within intrinsic locking or  j.u.c.Lock locking)

3.

Похоже, вам придется создать VolatileNode и Node.Было предложено, чтобы Java 7 выпустила API Fences , который вы можете указать, какой стиль чтения / записи вы хотите выполнить с помощью статического служебного класса, но не похоже на его выпуск

Редактировать:

Thkala сделал замечание, которое, я чувствую, стоит включить

, хотя следует отметить, что JVM до JSR133 (т.е. Java <5.0) не имелита же семантика </p>

То, что я написал, не относится к приложениям, работающим на Java 1.4 или ниже.

2 голосов
/ 30 июня 2011

Ваш корневой узел на самом деле не должен быть узлом. Вам нужна только ссылка на первый «реальный» узел.

public class LinkedList {
  private volatile Node firstNode;
  ...
  addNode(Node node) {
    node.next = firstNode;
    firstNode = node;
  }
}

Так что вам не нужно делать поле next энергозависимым во всех ваших узлах; узлы вообще не синхронизированы. Вы можете использовать этот класс для несинхронизированных связанных списков, если вы не возражаете против стоимости энергозависимого доступа к первому узлу. Или вместо этого вы можете просто переписать класс с энергонезависимой firstNode для несинхронизированной версии.

2 голосов
/ 29 июня 2011

Создание поля next volatile наложит барьер памяти на всех экземпляров класса узла, а не только корневого узла.Я ожидаю, что это будет дороже, чем просто использовать synchronized на корневом узле.Кроме того, JVM может гораздо лучше оптимизировать вызовы методов synchronized.См. Также this и this .

Тем не менее, вам, вероятно, следует попробовать оба метода и тест / профиль, чтобы увидеть, что происходит.

...