Java中final和volatile字段在内存模型语义上的差异
从实践中的Java并发性: 当一个字段被声明为volatile时,编译器和运行时被打开 注意,这个变量是共享的,对它的操作应该 不能与其他内存操作一起重新排序。可变变量是 不缓存在寄存器或缓存中,在这些寄存器或缓存中它们对其他对象隐藏 处理器,因此读取易失性变量总是返回最多 任何线程最近的写入。(p25) 以及 无法修改最终字段(尽管它们引用的对象可以 如果它们是可变的,则可以修改),但它们也有特殊的语义 在Java内存模型下。正是使用final字段使 可能的初始化安全保证(见第3.5.2节) 这使得不可变对象可以自由访问和共享,而无需 同步(第32页) 背诵不安全出版物:Java中final和volatile字段在内存模型语义上的差异,java,multithreading,concurrency,thread-safety,volatile,Java,Multithreading,Concurrency,Thread Safety,Volatile,从实践中的Java并发性: 当一个字段被声明为volatile时,编译器和运行时被打开 注意,这个变量是共享的,对它的操作应该 不能与其他内存操作一起重新排序。可变变量是 不缓存在寄存器或缓存中,在这些寄存器或缓存中它们对其他对象隐藏 处理器,因此读取易失性变量总是返回最多 任何线程最近的写入。(p25) 以及 无法修改最终字段(尽管它们引用的对象可以 如果它们是可变的,则可以修改),但它们也有特殊的语义 在Java内存模型下。正是使用final字段使 可能的初始化安全保证(见第3.5.2节)
public class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n != n) // might be true for other threads.
}
}
令人惊讶的是,n
的值可能会被其他线程视为过时。但是,final
修饰符会起作用。类似于volatile,不是吗?final
字段是否本质上是易变的?(不允许使用final volatile
的可能解释)
这是否可能是不允许使用私有volatile的原因
允许使用私有易失性
你是说最终挥发性
?是的,这些修饰符在性质上是不兼容的-final
var,其值/引用不能更改,不需要额外的volatile
goodies(以及相关的开销),因为final
字段的变异是不可能的,并且跨多个线程的读取是一致的
但是JMM
确实为final
字段提供了初始化易失性样式一致性。AFAIK它是在JSR133
中实现的(包含在JavaSE5.0
中)。在此之前,JSR
init读取在数据竞争期间可能不一致(例如返回null或某个中间值)
PS:我找到了与您的问题有关的问题。强烈推荐(以及第二部分)
这是否可能是不允许使用私有volatile的原因
允许使用私有易失性
你是说最终挥发性
?是的,这些修饰符在性质上是不兼容的-final
var,其值/引用不能更改,不需要额外的volatile
goodies(以及相关的开销),因为final
字段的变异是不可能的,并且跨多个线程的读取是一致的
但是JMM
确实为final
字段提供了初始化易失性样式一致性。AFAIK它是在JSR133
中实现的(包含在JavaSE5.0
中)。在此之前,JSR
init读取在数据竞争期间可能不一致(例如返回null或某个中间值)
PS:我找到了与您的问题有关的问题。强烈推荐它(以及第二部分)
volatile
仅与变量本身的修改相关,而与它所指的对象无关。使用final volatile
字段没有任何意义,因为无法修改final
字段。只需声明字段final
,它就应该可以了。volatile
只与变量本身的修改相关,而与它引用的对象无关。使用final volatile
字段没有任何意义,因为无法修改final
字段。只需声明字段final
,就可以了。不,final
字段本质上不是易变的
如果是的话,那将是不必要的昂贵,因为在大多数情况下,您需要在volatile
写入之后设置一个StoreLoad屏障
这对于final
字段是可以避免的,因为您有一个额外的约束可以帮助您-您知道final
字段必须在相应的类或实例对象完全初始化时初始化
该规范可能有点难以阅读(查看JLS),但请记住,与臭名昭著的JMM因果关系部分一样,主要内容是正式描述大多数人的直觉行为
至于实施,通常需要两件事:
final
字段存储,包括链下游的存储(如果字段是引用),不能使用构造函数之外的存储重新排序。如果底层硬件体系结构具有强大的内存模型(如x86),即使您内联了构造函数,这通常也是不可操作的final
字段加载不能与该字段所属的相应引用的同一线程中的第一个加载一起重新排序。这几乎总是不可操作的,因为所有编译器和大多数硬件架构都遵循负载依赖性final
字段
===
您可以在以下章节中阅读更多关于如何实现final
字段的信息:
- Doug Lea(参见最终字段部分)
- Alexey Shipilev的精彩博客文章
注意:即使存在
final
字段,不安全的发布也是危险的。请参阅以了解一些注意事项。否,final
字段本质上不是易变的
如果是的话,那将是不必要的昂贵,因为在大多数情况下,您需要在volatile
写入之后设置一个StoreLoad屏障
对于final
字段,这是可以避免的,因为