我正在阅读Java中的volatile关键字,并且完全理解它的理论部分。
但是,我正在寻找的是一个很好的例子,它展示了如果变量不是易失性的,以及如果变量是易失性的,会发生什么。
下面的代码片段不能按预期工作(取自here):
class Test extends Thread {
boolean keepRunning = true;
public void run() {
while (keepRunning) {
}
System.out.println("Thread terminated.");
}
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println("keepRunning set to false.");
}
}
理想情况下,如果keepRunning
不是易失性的,线程应该无限期地运行。但是,它确实会在几秒钟后停止。
我有两个基本问题:
发布于 2013-07-19 22:07:05
Volatile -->保证可见性,而不是原子性
同步(锁定) -->保证可见性和原子性(如果处理得当)
不能替代同步Volatile
仅当更新引用且不对其执行某些其他操作时,才使用volatile。
示例:
volatile int i = 0;
public void incrementI(){
i++;
}
如果不使用同步或AtomicInteger,将不是线程安全的,因为递增是一种复合操作。
为什么程序不能无限期运行?
嗯,这取决于不同的情况。在大多数情况下,JVM足够智能,可以刷新内容。
Correct use of volatile讨论了易失性的各种可能的用法。正确地使用易失性是很棘手的,我会说“如果有疑问,就省略它”,使用同步化的块。
另外:
同步块可以用来代替,但反之不是真的volatile。
发布于 2013-07-19 22:24:12
对于您的特定示例:如果没有声明为,服务器JVM可以将keepRunning
变量提升到循环之外,因为它没有在循环中修改(将其变成无限循环),但是客户机JVM不会。这就是为什么你会看到不同的结果。
关于易失性变量的一般解释如下:
当一个字段被声明为volatile
时,编译器和运行时会注意到这个变量是共享的,对它的操作不应该与其他内存操作一起重新排序。易失性变量不会缓存在寄存器或缓存中,因为它们对其他处理器是隐藏的,因此读取易失性变量总是返回任何线程的最新写入。
易失性变量的可见性影响超出了易失性变量本身的值。当线程A写入易失性变量,随后线程B读取该变量时,在写入易失性变量之前对A可见的所有变量的值在读取易失性变量之后对B变为可见。
易失性变量最常见的用途是作为完成、中断或状态标志:
volatile boolean flag;
while (!flag) {
// do something untill flag is true
}
易失性变量可以用于其他类型的状态信息,但在尝试此操作时需要更加小心。例如,易失性的语义不足以使增量操作(count++
)成为原子,除非您能保证变量只从单个线程写入。
锁可以同时保证可见性和原子性;易失性变量只能保证可见性。
只有在满足以下所有条件时,才能使用易失性变量:
调试技巧:确保在调用-server
时指定JVM命令行开关,即使是在开发和测试时也是如此。服务器JVM比客户端JVM执行更多的优化,比如将未在循环中修改的变量提升到循环之外;在开发环境(客户端JVM)中看起来可以工作的代码可能会在部署环境(服务器JVM)中中断。
这是你能找到的关于这个主题的最好的书"Java Concurrency in Practice"的摘录。
发布于 2014-01-05 18:02:52
我稍微修改了一下你的例子。现在使用keepRunning作为易失性和非易失性成员的示例:
class TestVolatile extends Thread{
//volatile
boolean keepRunning = true;
public void run() {
long count=0;
while (keepRunning) {
count++;
}
System.out.println("Thread terminated." + count);
}
public static void main(String[] args) throws InterruptedException {
TestVolatile t = new TestVolatile();
t.start();
Thread.sleep(1000);
System.out.println("after sleeping in main");
t.keepRunning = false;
t.join();
System.out.println("keepRunning set to " + t.keepRunning);
}
}
https://stackoverflow.com/questions/17748078
复制相似问题