java内存模型FAQ
1. 什么是内存模型?
在处理器的层面上,内存模型定义了一个充要条件:让当前处理器可以看到其他处理器写入到内存的数据以及其他处理器可以看到当前处理器写入到内存的数据。
有些处理器有很强的内存模型(strong memory model),能够让所有处理器在任何时候任何指定的内存地址上都可以看到完全相同的值。
而另一些处理器则有较弱的内存模型(weaker memory model),在这种处理器中,必须使用内存屏障来刷新本地处理器缓存并使本地处理器缓存无效,目的是为了让当前处理器能够看到其他处理器的写操作或者其他处理器能够看到当前处理器的写操作。
一个线程的写操作对其他线程可见这个问题是因为编译器对代码进行重排序导致的。
java内存模型描述了哪些行为在多线程中是合法的,以及线程如何通过内存进行交互的。它描述了程序中的变量和从内存或者寄存器获取或存储他们的底层细节之间的关系。
2. 其他语言,像C++,也有内存模型吗?
大部分其他的语言,像C和C++,都没有被设计成直接支持多线程。这些语言对于发生在编译器和处理器平台架构的重排序行为的保护机制会严重的依赖于程序中所使用的线程库(例如pthreads),编译器,以及代码所运行的平台所提供的保障。
3. JSR133是什么?
JSR133为java语言定义了一个新的内存模型,它修复了早期内存模型的缺陷。
JSR133的目的是创建一组正式语义,这些正式语义提供了volatile、synchronzied和final如何工作的直观框架。
JSR133的目标:
- 保留已经存在的安全保证以及强化其他的安全保证
- 正确同步的程序的语义应该尽量简单直观
- 应该定义未完成或者未正确同步的程序的语义,主要是为了把潜在的安全危害降到最低
- 程序员应该能够自信的推断多线程是如何同内存进行交互的
- 能够在现在许多流行的硬件框架中涉及正确以及高性能的JVM实现
- 应该尽量不影响现有的代码
4. 重排序意味着什么?
在很多情况下,访问一个变量可能会使用不同的顺序执行,而不是程序语义所指定的顺序执行。
编译器可以以优化的名义去改变指令顺序。在特定环境下,处理器可能会次序颠倒的执行指令。数据可能在寄存器,处理器缓冲区和主内存中以不同的次序移动,而不是按照指定的顺序。
编译器,运行时和硬件被期望一起协力创建好像是顺序执行的语义的假象,这意味着在单线程的程序中,程序应该是不能够观察到重排序的影响的。但是,重排序在没有正确同步了的多线程程序中开始起作用,在这些多线程程序中,一个线程能够观察到其他线程的影响,也可能检测到其他线程将会以一种不同于程序语义所规定的执行顺序来访问变量。
5. 旧的内存模型有什么问题?
旧的内存模型中有几个严重的问题。这些问题很难理解,因此被广泛的违背。例如,旧的存储模型在许多情况下,不允许JVM发生各种重排序行为。
就如final字段,使用了此字段的变量是不可变的,不需要使用同步,但是就在旧的程序中final变量并没有被区别对待,这以为着同步才能保证final字段的唯一性。
旧的内存模型允许volatile变量的写操作和非volaitle变量的读写操作一起进行重排序,这和大多数的开发人员对于volatile变量的直观感受是不一致的,因此会造成迷惑。
6. 没有正确同步的含义是什么?
在Java内存模型这个语义环境下,我们谈到“没有正确同步”,我们的意思是:
- 一个线程中有一个对变量的写操作,
- 另外一个线程对同一个变量有读操作,
- 而且写操作和读操作没有通过同步来保证顺序。
当这些规则被违反的时候,我们就说在这个变量上有一个“数据竞争”(data race)。一个有数据竞争的程序就是一个没有正确同步的程序。
7. 同步会做些什么?
同步有几方面的作用。首先就是互斥。
互斥:一次只有一个线程能够获得一个监视器,因此,在一个监视器上面同步意味着一旦一个线程进入到监视器保护的同步块中,其他的线程都不能进入到同一个监视器保护的块中间,除非第一个线程退出了同步块。
同步的含义比互斥更广
同步保证了一个线程在同步块之前或者在同步块中的内存写入操作以可预知的方式对其他有相同监视器的线程可见。