Java 对可变对象使用volatile关键字
在Java中,我知道Java 对可变对象使用volatile关键字,java,concurrency,volatile,mutable,Java,Concurrency,Volatile,Mutable,在Java中,我知道volatile关键字提供了变量的可见性。问题是,如果变量是对可变对象的引用,volatile是否也为该对象内的成员提供可见性 在下面的示例中,如果多个线程正在访问volatile Mutable m并更改值,它是否正常工作 范例 class Mutable { private int value; public int get() { return a; } public int set(int value)
volatile
关键字提供了变量的可见性。问题是,如果变量是对可变对象的引用,volatile
是否也为该对象内的成员提供可见性
在下面的示例中,如果多个线程正在访问volatile Mutable m
并更改值
,它是否正常工作
范例
class Mutable {
private int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
class Test {
public volatile Mutable m;
}
volatile
仅提供对声明为volatile的对象的引用的保证。该实例的成员无法同步
根据报告,您有:
- (在所有版本的Java中)读和写都有全局排序 写入易失性变量。这 意味着每个访问 易失性字段将读取其当前值 继续之前的值,而不是 (可能)使用缓存的值。 (但是,不能保证 挥发分的相对排序 使用常规读取进行读取和写入 并写下,这意味着 通常不是一个有用的线程 构造。)
- (在Java5或更高版本中)易失性读写建立了一个 关系,就像获得和 释放互斥锁
因此,基本上您所拥有的是,通过声明字段
volatile
,与它交互创建一个“同步点”,在此之后,任何更改都将在其他线程中可见。但在此之后,使用get()
或set()
是不同步的。有更全面的解释。volatile
不“提供可见性”。它唯一的作用是防止处理器缓存变量,从而在并发读写时提供“发生在之前”关系。它不会影响对象的成员,也不会提供任何同步synchronized
锁定
由于您没有告诉我们代码的“正确”行为是什么,因此无法回答这个问题。在您的示例中,
volatile
关键字仅保证任何线程写入的最后一个引用“m”将对随后读取“m”的任何线程可见
它不能保证关于get()的任何内容
因此,使用以下顺序:
Thread-1: get() returns 2
Thread-2: set(3)
Thread-1: get()
你完全可以得到2而不是3<代码>易失性不会对此进行任何更改
但是如果您将Mutable
类更改为:
class Mutable {
private volatile int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
然后保证来自线程1的第二个get()
将返回3
但是请注意,volatile
通常不是最好的同步方法
在简单的get/set示例(我知道这只是一个示例)中,像
AtomicInteger
这样的类,使用适当的同步并实际提供有用的方法,会更好。使用volatile
而不是完全同步的值本质上是一种优化。与同步访问相比,volatile
值提供的保证较弱,这是优化的原因。过早优化是万恶之源;在这种情况下,邪恶可能很难被追踪,因为它将以种族条件等形式存在。因此,如果您需要询问,您可能不应该使用它。这是关于volatile的一些细节的旁注解释。写这篇文章是因为评论太多了。我想给出一些例子来说明volatile是如何影响可见性的,以及它在JDK1.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+中定义良好,但不是预定义的jdk1.5
在jdk1.5之前的世界中,易失性访问和非易失性访问之间没有定义的关系。因此,该程序的输出可以是:
读:0,0
读:0,5
阅读:5,0
读:5,5
在jdk1.5+世界中,volatile的语义发生了变化,因此volatile访问以与同步完全相同的方式影响非volatile访问。因此,在jdk1.5+世界中,只有某些输出是可能的:
读:0,0
读:0,5
阅读:5,0为了进行比较,您可能想看看AtomicReference,它可以正确地执行此操作,并且是内置的。如果您只阅读Test.m
,则在建立关系之前根本不会发生任何情况。这是不正确的。您忽略了wikipedia中的第二点,java 5使volatile影响非volatile变量。@jtahlborn您是对的,我添加了第二点并重新编写了我自己的解释。您的解释仍然不正确。这似乎仍然暗示volatile不会影响非volatile字段。在m的第一次赋值时,保证了值的可见性。更多细节请参见我在@Gugusee的帖子上的评论。实际上,volatile可能会影响对象的成员(而不是OP如何使用它)。“在关系提供可见性保证之前发生”。@jtahlborn-您能演示一下如何做到这一点吗?关于第二点;结果是写操作对读操作是可见的,但术语“提供变量的可见性”太模糊而没有意义。@OrangeDog:假设您创建了一个可变的本地实例,并用3个不同的值调用了set()方法,然后将本地可变的实例赋给了m。m的所有后续读取都将保证看到第一个线程在分配m之前设置的“最后”值。因此,当分配m时,值的可见性得到保证。但是,正如其他地方正确指出的那样,在赋值给m之后对set()的任何未来调用的影响都不能保证是可见的。(其中“可见”表示保证被另一个线程看到)。@jtahlborn-这描述了一种情况,即只影响对象变量(m),而不影响其成员。@OrangeDog: