2011-06-29 3 views
7

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

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

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

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

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

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

Edit:

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

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

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

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

ответ

8

1.

Основываясь на том, что вы говорите здесь

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

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

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

2.

Летучие чтения/записи на 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, чтобы выйти с Fences API, который вы можете указать, какой стиль чтения/записи вы хотите выполнить с помощью статического утилиты класса, но не выглядит как его отпускания

Edit:

Thkala сделал большой пункт я чувствую, стоит в том числе

, хотя следует отметить, что предварительно JSR133 JVMs (т.е. Java < 5,0) было не имеют ту же семантику

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

+2

+1. Хорошее объяснение семантики барьера памяти 'volatile', хотя следует отметить, что JVM-модели JSR133 (т. Е. Java <5.0) не имели одинаковой семантики ... – thkala

+0

Хорошая точка для включения в качестве примечания. –

+0

Спасибо за отличный ответ. Надеюсь, вы хорошо поняли, что происходит в моем коде. Нет корневой ссылки, только корневой узел. Вот почему я установил два разных следующих поля (один из нового объекта и один из корневого узла). – ciamej

2

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

Это означает, что вам, вероятно, следует попробовать оба и контрольные показатели/профиль, чтобы узнать, что произойдет.

+0

Я не полностью продаю это. Если бы «наложил барьер памяти на все экземпляры класса node», были бы true, то ConcurrentHashMap понес бы одну и ту же стоимость для каждого узла в ведре, не так ли? –

+0

@John V .: Он делает, но это необходимо в этом случае - шаблон использования Карты неизвестен. OP, с другой стороны, необходимо синхронизировать только с корневым узлом. – thkala

+0

Когда вы говорите «все экземпляры», вы имеете в виду, что запись в следующее поле также наложит барьер на все узлы? Или вы только имеете в виду, что каждый узел будет иметь свой собственный барьер при написании или чтении? –

2

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

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

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

+0

Неустойчивое чтение на первом узле не гарантирует ограничений видимости/порядка в энергонезависимых полях Узла. Отдельная запись происходит, скажем, firstNode.next.next не имеет никакого отношения - до отношений и, следовательно, не является поистине потокобезопасным. –

+0

См. OP: узлы неизменяемы. – toto2

+0

Они являются неизменными, когда задано следующее поле. ', а затем установить следующее поле корня в этот новый узел. Если я что-то не упустил, вы не можете использовать все узлы, не подлежащие замене в Linkedlist, где в качестве узла необходимо добавить в список параметры в следующем поле. –

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