在Java中,我了解volatile
关键字提供对变量的可见性。问题是,如果变量是对可变对象的引用,volatile
是否也提供对该对象内成员的可见性?
在下面的示例中,如果多个线程正在访问volatile Mutable m
并更改value
,那么它是否正常工作?
例如
class Mutable {
private int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
class Test {
public volatile Mutable m;
}
答案 0 :(得分:17)
这是关于volatile的一些细节的侧面说明。在这里写这个是因为评论太多了。我想举一些例子来说明易变性如何影响可见性,以及jdk 1.5中的变化情况。
给出以下示例代码:
public class MyClass
{
private int _n;
private volatile int _volN;
public void setN(int i) {
_n = i;
}
public void setVolN(int i) {
_volN = i;
}
public int getN() {
return _n;
}
public int getVolN() {
return _volN;
}
public static void main() {
final MyClass mc = new MyClass();
Thread t1 = new Thread() {
public void run() {
mc.setN(5);
mc.setVolN(5);
}
};
Thread t2 = new Thread() {
public void run() {
int volN = mc.getVolN();
int n = mc.getN();
System.out.println("Read: " + volN + ", " + n);
}
};
t1.start();
t2.start();
}
}
此测试代码的行为在jdk1.5 +中已明确定义,但在
在pre-jdk1.5世界中,易失性访问和非易失性访问之间没有明确的关系。因此,该计划的输出可能是:
在jdk1.5 +世界中,volatile的语义被改变,因此易失性访问以与同步完全相同的方式影响非易失性访问。因此,在jdk1.5 +世界中只有某些输出是可能的:
输出3.是不可能的,因为从volatile_volN读取“5”会在2个线程之间建立同步点,这意味着在分配给_volN 之前所取的所有操作都必须对t2可见。
进一步阅读:
答案 1 :(得分:8)
在您的示例中,volatile
关键字仅保证任何线程写入“m”的最后一个引用对随后读取“m”的任何线程都可见。
它不保证您的 get()。
所以使用以下序列:
Thread-1: get() returns 2
Thread-2: set(3)
Thread-1: get()
你回到2而不是3是完全合法的。volatile
不会改变任何东西。
但是如果你将Mutable
课改为:
class Mutable {
private volatile int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
然后保证Thread-1中的第二个get()
将返回3.
但请注意,volatile
通常不是最佳同步方法。
在你简单的get / set示例中(我知道它只是一个例子)像AtomicInteger
这样的类,使用适当的同步并实际提供有用的方法,会更好。
答案 2 :(得分:5)
volatile
仅提供对所声明的Object的引用的保证。该实例的成员不会同步。
根据Wikipedia,你有:
- (在所有版本的Java中)读取和存在全局排序 写入易失性变量。这个 暗示每个线程访问一个 volatile字段将读取其当前值 继续前的价值,而不是 (可能)使用缓存值。 (但是,没有保证 易失性的相对排序 读取和写入常规读取 写道,意思是它 通常不是一个有用的线程 构造。)
- (在Java 5或更高版本中)易失性读写会建立先发生的事件 关系,很像收购和 释放互斥锁。
所以基本上你所拥有的是通过声明字段volatile
,与它交互会创建一个“同步点”,之后任何更改都将在其他线程中可见。但在此之后,使用get()
或set()
是未经同步的。 Java Spec有更全面的解释。
答案 3 :(得分:0)
volatile
没有“提供可见性”。它唯一的作用是阻止变量的处理器缓存,从而在并发读写时提供事先发生的关系。它不会影响对象的成员,也不会提供任何同步 synchronized
锁定。
由于您没有告诉我们您的代码的“正确”行为是什么,因此无法回答这个问题。
答案 4 :(得分:0)
使用volatile
而不是完全synchronized
值本质上是一种优化。与volatile
访问相比,优化来自为synchronized
值提供的较弱保证。过早的优化是万恶之源;在这种情况下,邪恶可能很难追查,因为它将以竞争条件等形式出现。所以如果你需要问,你可能不应该使用它。