在ibm中有关于volatile使用的article,并且解释让我困惑,下面是本文的示例及其解释:
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
// do lots of stuff
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
}
}
如果theFlooble引用不是volatile,那么doWork()中的代码将有可能看到部分构造的Flooble,因为它取消引用了Flaloble引用。
如何理解这一点?为什么没有volatile,我们可能会使用部分构造的Flooble
对象?谢谢!
答案 0 :(得分:2)
如果没有volatile
,您可以看到部分构造的对象。例如。考虑这个Flooble
对象。
public class Flooble {
public int x;
public int y;
public Flooble() {
x = 5;
y = 1;
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
public void doSomething(Flooble flooble) {
System.out.println(flooble.x / flooble.y);
}
}
}
如果没有volatile
方法,则无法保证doSomething会看到5
和1
的值x
和y
。它可以看到例如x == 5
但y == 0
,导致除以零。
执行此操作theFlooble = new Flooble()
时,会发生三次写入:
tmpFlooble.x = 5
tmpFlooble.y = 1
theFlooble = tmpFlooble
如果这些写入按此顺序发生,一切正常。但是如果没有volatile
,编译器可以自由地重新排序这些写入并按照自己的意愿执行它们。例如。第一点3,然后点1和2.
这实际上一直都在发生。编译器确实对写入进行了重新排序。这样做是为了提高性能。
错误很容易发生在以下方面:
线程A
从类initInBackground()
执行BackgroundFloobleLoader
方法。编译器在执行Flooble()
(正在设置x
和y
的主体)之前重新排序写入,线程A
首先执行theFlooble = new Flooble()
。现在,theFlooble
指向一个flooble实例,其x
和y
为0
。在线程A
继续之前,其他一些线程B
执行类doWork()
的方法SomeOtherClass
。此方法使用当前值doSomething(floobleLoader.theFlooble)
调用方法theFlooble
。在此方法中,theFlooble.x
除以theFlooble.y
,导致除以零。由于未捕获的异常,线程B
完成。线程A
继续并设置theFlooble.x = 5
和theFlooble.y = 1
。
这种情况当然不会在每次运行时发生,但根据Java规则,可以发生。
答案 1 :(得分:0)
当不同的线程访问您的代码时,任何线程都可以对对象的状态执行修改,这意味着当其他线程访问它时,状态可能不是它应该的。
来自oracle文档:
Java编程语言允许线程访问共享 变量。通常,确保共享变量 一致且可靠地更新,线程应确保它具有 通过获得锁定来独占使用这些变量, 通常,对这些共享变量实施互斥。
Java编程语言提供了第二种机制,即volatile 字段,这比某些目的的锁定更方便。
字段可以声明为volatile,在这种情况下是Java Memory Model 确保所有线程都能看到变量的一致值。
这意味着此变量的值永远不会在线程本地缓存,所有读取和写入将直接进入"主内存"
例如图片thread1和thread2访问对象:
答案 2 :(得分:0)
从执行此操作的代码的角度来看:
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
显然,您需要保证在new Flooble()
可能测试为theFlooble
之前,!= null
执行的所有写操作都对此代码可见。没有volatile
的代码中没有任何内容提供此保证。所以你需要一个你没有的保证。失败。
Java提供了几种获得所需保证的方法。一种是使用volatile
变量:
...对volatile变量的任何写入都会建立与之后读取同一变量的先发生关系。这意味着对volatile变量的更改始终对其他线程可见。更重要的是,它还意味着当线程读取volatile变量时,它不仅会看到volatile的最新更改,还会看到导致更改的代码的副作用。 - Docs
因此,在一个线程中对volatile
进行写入,在另一个线程中对volatile
进行读取,准确地确定了我们需要的事先关系。
答案 3 :(得分:-2)
我怀疑在Java中存在部分构造的对象这样的事情。易失性保证每个线程都能看到构造的对象。由于volatile就像引用对象上的一个微小同步块一样,如果theFlobble == null
,你最终会得到一个NPE。也许这就是他们的意思。
答案 4 :(得分:-2)
对象封装了很多东西:变量,方法等等,这些需要时间才能在计算机中存在。在Java中,如果任何变量被声明为volatile,则对它的所有读取和写入都是原子的。因此,如果引用对象的变量被声明为volatile,则只有在系统中完全加载时才允许访问其成员(如何读取或写入那些根本不存在的内容?)