...没有额外的同步?下面的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语义感兴趣
这是两个问题。让我们从第二个开始吧。
将新构造的对象分配给 挥发物 变量很好地工作。每个线程,读取 挥发物 变量,会看到一个 完全构造 目的。没有必要 进一步 同步。这种模式通常与 不可变类型。
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;
}
换句话说:如果是线程 一个 写给一个 挥发物 变量 X 和线程 乙 读取写入的值 X,然后在读取操作后,线程 乙 将看到线程的所有写操作 一个,写到之前发生的 X。但没有写入操作 挥发物 变量,对其他变量的更新没有影响。
这听起来比实际更复杂。实际上,只有一条规则需要考虑,您可以在其中找到 JLS8§17.4.5:
[..]如果所有顺序一致的执行没有数据争用,[..]那么程序的所有执行都将显示为顺序一致。
简单地说,一个 数据竞赛 如果两个线程可以同时访问同一个变量,至少有一个操作是写操作,变量是,则存在 非易失性。 数据竞赛 可以通过声明来消除 共享变量 如 挥发物。没有 数据竞赛,没有问题 能见度 更新。
没有。
放置对象中的对象的引用 volatile
字段不会以任何方式影响对象本身。
一旦从volatile字段加载对象的引用,就会有一个与任何其他对象没有区别的对象,并且波动率没有进一步的影响。