【发布时间】:2017-01-08 14:32:21
【问题描述】:
考虑以下简单示例:
public class Example extends Thread {
private int internalNum;
public void getNum() {
if (internalNum > 1)
System.out.println(internalNum);
else
System.out.println(1000);
}
public synchronized modifyNum() {
internalNum += 1;
}
public void run() {
// Some code
}
}
假设代码执行分为两个线程。假设会发生以下事件序列:
- 第一个线程访问
getNum方法并缓存internalNum,此时为0。 - 同时第二个线程访问
modifyNum方法获取锁,将internalNum更改为1并退出释放锁。 - 现在,第一个线程继续执行并打印
internalNum。
问题是控制台上会打印什么?
我的猜测是,这个假设的示例将导致在控制台上打印 1000,因为读写刷新仅在进入或离开同步块时强制在特定线程上。因此,第一个线程将愉快地使用它的缓存值,而不知道它已被更改。
我知道使internalNum volatile 可以解决可能的问题,但是我只是想知道天气是否真的有必要。
【问题讨论】:
-
Java 内存模型中没有缓存和刷新的概念。正如你所说,不能保证 getNum() 看到的东西与 0 不同。在你的情况下,我会删除同步并改用 AtomicInteger。
-
internalNum 不是静态的,不需要同步访问它,因为它本身就是一个 Thread 对象,每个对象都有自己的 internalNum 副本。
-
@AlexC 你几乎是正确的。你不知道我是如何运行线程的。我本可以写:
Example myExample = new Example(); Thread t1 = new Thread(myExample); Thread t2 = new Thread(myExample); t1.start(); t2.start();当然,我不会那样做。 -
是的,因为您将基类设为 Thread 而不是 Runnable,这使得您不清楚您打算将其作为 Runnable 调用,同时保留从 Thread 继承添加的所有额外权重。现在,由于您的代码中对 internalNum 的访问未同步,因此行为未定义。您需要同步 getter 或使用 AtomicInteger。
-
我认为,如果您只看到 0 之外的任何内容,它将符合规范,但是我想不出任何真实世界的虚拟机/架构会以这种方式运行。有一些 testvm 标志确实表现得那么病态。参见例如stackoverflow.com/a/17830049/13189
标签: java memory synchronized volatile flush