...没有额外的同步?下面的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构造函数的主线程外)相关:
编辑:对后期Java 5语义感兴趣
答案 0 :(得分:10)
没有
在volatile
字段中放置对象的引用不会以任何方式影响对象本身。
从volatile字段加载对象的引用后,您有一个与任何其他对象没有区别的对象,并且波动性没有进一步的影响。
答案 1 :(得分:4)
这是两个问题。让我们从第二个开始。
将新构造的对象分配给 volatile 变量可以很好地工作。每个读取 volatile 变量的线程都会看到完全构造的对象。无需进一步同步。此模式通常与不可变类型结合使用。
class Tree {
private volatile Node node;
public void update() {
node = new Node(...);
}
public Node get() {
return node;
}
}
关于第一个问题。您可以使用 volatile 变量来同步对非易失性变量的访问。以下清单显示了一个示例。想象一下,如图所示初始化两个变量,并且两个方法同时执行。可以肯定的是,如果第二个线程看到更新为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 变量中读取。但是, 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 写入 volatile 变量 x 而线程 B 读取值写入 x ,然后在读取操作之后,线程 B 将看到线程 A 的所有写操作,这是在写入 X 。但是如果没有对 volatile 变量的写操作,则对其他变量的更新没有影响。
这听起来比实际更复杂。实际上,只有一条规则需要考虑,您可以在JLS8 §17.4.5中找到:
[..]如果所有顺序一致的执行都没有数据争用,[..]那么程序的所有执行都将显示为顺序一致。
简单地说,如果两个线程可以同时访问同一个变量,至少有一个操作是写操作,并且该变量是非易失性<,则存在数据竞争 / em>的。通过将共享变量声明为 volatile ,可以消除数据竞争。如果没有数据竞赛,则可见性的更新没有问题。