目前我无法理解何时应该使用volatile
声明变量。
我已经做了一些研究并且长时间搜索了一些关于它的材料,并且知道当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,并且它上面的操作不应该被重新排序与其他记忆操作。
然而,我仍然无法理解在什么情况下我们应该使用它。我的意思是,有人可以提供任何示例代码,可以证明使用“volatile”会带来好处或解决问题而不使用它吗?
答案 0 :(得分:15)
以下是为什么需要volatile
的示例。如果删除关键字volatile
,则线程1 可能永不终止。 (当我在Linux上测试Java 1.6 Hotspot时,确实如此 - 您的结果可能会有所不同,因为JVM没有义务对未标记为volatile
的变量进行任何缓存。)
public class ThreadTest {
volatile boolean running = true;
public void test() {
new Thread(new Runnable() {
public void run() {
int counter = 0;
while (running) {
counter++;
}
System.out.println("Thread 1 finished. Counted up to " + counter);
}
}).start();
new Thread(new Runnable() {
public void run() {
// Sleep for a bit so that thread 1 has a chance to start
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
// catch block
}
System.out.println("Thread 2 finishing");
running = false;
}
}).start();
}
public static void main(String[] args) {
new ThreadTest().test();
}
}
答案 1 :(得分:4)
以下是volatile的必要性的典型示例(在这种情况下为str
变量。如果没有它,热点会提升循环外的访问权限(while (str == null)
)和run()
永远不会终止。这将发生在大多数服务器JVM上。
public class DelayWrite implements Runnable {
private String str;
void setStr(String str) {this.str = str;}
public void run() {
while (str == null);
System.out.println(str);
}
public static void main(String[] args) {
DelayWrite delay = new DelayWrite();
new Thread(delay).start();
Thread.sleep(1000);
delay.setStr("Hello world!!");
}
}
答案 2 :(得分:3)
Eric,我已经阅读了你的评论,其中一个特别让我感动
事实上,我可以理解volatile在概念上的用法 水平。但是对于练习,我无法思考 具有并发性的代码 没有使用volatile的问题
你可以遇到的明显问题是编译器重新排序,例如Simon Nickerson提到的更着名的吊装。但是我们假设没有重新排序,评论可以是有效的。
volatile解决的另一个问题是64位变量(long,double)。如果您写入long或double,则将其视为两个单独的32位存储。并发写入会发生什么是一个线程的高32写入寄存器的高32位而另一个线程写入低32位。然后你就可以拥有一个既不是一个又一个的长。
另外,如果你看一下JLS的内存部分,你会发现它是一个放松的内存模型。
这意味着写入可能不会变得可见(可以坐在存储缓冲区中)一段时间。这可能导致陈旧的读取。现在你可能会说这似乎不太可能,而且确实如此,但你的程序是不正确的,有可能失败。
如果你有一个int,你在应用程序的生命周期中递增,并且你知道(或者至少认为)int不会溢出,那么你不会将它升级到long,但它仍然可以。在内存可见性问题的情况下,如果您认为它不会影响您,您应该知道它仍然可以并且可能导致并发应用程序中极难识别的错误。正确性是使用volatile的原因。
答案 3 :(得分:2)
volatile关键字非常复杂,在使用它之前,您需要了解它的作用并且不能很好地完成。我建议阅读this language specification section,这很好地解释了它。
他们强调了这个例子:
class Test {
static volatile int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
这意味着在one()
j
期间永远不会超过i
。但是,另一个运行two()
的线程可能会打印出比j
大得多的i
,因为我们假设two()
正在运行并获取i
的值}。然后one()
运行1000次。然后,运行两个的线程最终再次被安排,然后选择j
,这比i
的值大得多。我认为这个例子完美地证明了volatile和synchronized之间的区别 - 对i
和j
的更新是易变的,这意味着它们发生的顺序与源代码一致。但是,这两个更新是单独发生的,而不是原子的,因此调用者可能会看到(对于该调用者)看起来不一致的值。
简而言之:对volatile
非常小心!
答案 4 :(得分:0)
java 8中的极简主义示例,如果删除volatile关键字,它将永远不会结束。
public class VolatileExample {
private static volatile boolean BOOL = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> { while (BOOL) { } }).start();
TimeUnit.MILLISECONDS.sleep(500);
BOOL = false;
}
}
答案 5 :(得分:0)
要扩展@ jed-wesley-smith的答案,如果将其放到新项目中,请从迭代计数中删除volatile
关键字,然后运行它,它将永远不会停止。将volatile关键字添加到str
或iterationCount
会导致代码成功结束。我还注意到,使用Java 8时,睡眠时间不能小于5,但是您的工作量可能会因其他JVM / Java版本而异。
public static class DelayWrite implements Runnable
{
private String str;
public volatile int iterationCount = 0;
void setStr(String str)
{
this.str = str;
}
public void run()
{
while (str == null)
{
iterationCount++;
}
System.out.println(str + " after " + iterationCount + " iterations.");
}
}
public static void main(String[] args) throws InterruptedException
{
System.out.println("This should print 'Hello world!' and exit if str or iterationCount is volatile.");
DelayWrite delay = new DelayWrite();
new Thread(delay).start();
Thread.sleep(5);
System.out.println("Thread sleep gave the thread " + delay.iterationCount + " iterations.");
delay.setStr("Hello world!!");
}