我想知道私有布尔字段的 getter 方法是否强制其他线程获取最新更新的值?这是易失性场的替代方案吗? 例如:
Class A {
private boolean flag;
public boolean getFlag() {
return this.flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
与
Class B {
public volatile boolean flag;
}
编辑:整个对象是否由线程(包括私有字段)缓存,以便当我调用 getter 时它将返回缓存的私有字段?
不,getter 不会导致字段同步。
在多线程环境中读取和写入基元时,我们有三个问题,由CPU引起
- 原子性 :它可能需要存储或加载多个汇编指令的操作,例如,在 64 位 CPU 上写入 32 位整数。 当前线程可能会作系统在指令序列中间进入睡眠状态。
- 可见性 :在一个内核上运行的线程可能无法读取另一个内核中另一个线程写入的最新值。 仅仅因为 CPU 这么说。
- 重新排序 :为了使程序运行得更快,CPU 会根据需要混合汇编指令的顺序。
Getter不能解决任何这些问题。 即使是这样,JIT 编译器也可能完全优化该函数。 比什么?
易失性是解决上述问题的方法之一。 锁也是如此。 它们确保一个线程读取基元的最新值,或者确保正在写入的值对其他线程可见。 它们还会导致汇编指令完全按照编译时的方式运行,而无需任何混合。
作为旁注,生成的程序集可能看起来与您在代码中编写的内容完全不同。 你问自己"我在我的代码中编写了从flag
中读取,那么为什么程序不从字段本身读取? 编译器可以做任何它认为合适的事情来使程序集尽可能快。 通过不添加任何锁或易失性说明符, 你基本上告诉编译器不涉及多线程访问,编译器(以及随后的CPU)可以自由地假设对象没有被多个线程触及。可能是一开始就没有创建此对象。JIT 编译器可能会说"好吧,在寄存器中声明此布尔值并将其视为整个对象"。这是很有可能的。
编辑:整个对象是否由线程(包括私有字段)缓存,以便当我调用 getter 时它将返回缓存的私有字段?
你不能假设这一点。 这取决于 JVM、底层操作系统和底层 CPU。 它可以完全缓存、部分缓存或根本不缓存。 提醒您,大多数 CPU 都有不止一个缓存行,即使对象被缓存,它缓存在哪里? 在寄存器或缓存行之一中?
我想知道私有布尔字段的 getter 方法是否强制其他线程获取最新更新的值
不。它不会在没有关键字的情况下强制其他线程获取volatile
值。
这是易失性场的替代方案吗?
不。在没有volatile
关键字的情况下,简单的 getter 调用不是获取boolean
值的最新值的替代方法。
解决问题的更好解决方案:使用原子布尔值
可以原子方式更新的布尔值。有关原子变量属性的说明,请参阅 java.util.concurrent.atomic 包规范。
几个更有用的链接:
原子包摘要
原子/易失性/同步有什么区别?
>getter 强制线程获取未缓存的值?这是易失性场的替代方案吗?
不,getter 方法不会强制任何内容,因此您必须需要一个volatile
,以便当前线程看到其他线程更新的最新值,否则,您可能会看到过时的值。
您需要从这里了解以下文本,并清楚地理解原子访问的概念。
易失性变量始终对其他线程可见。它 还意味着当线程读取易失性变量时,它看到的不是 只是对易变性的最新变化
如果您在这两种方法中使用同步关键字,那么是:
public synchronized boolean getFlag() {
return this.flag;
}
public synchronized void setFlag(boolean flag) {
this.flag = flag;
}
那么这两个方法等效于在实例变量上使用易失性