一 Java内存模型
1 模型
Java内存逻辑模型如下:
所有变量都存储在主内存中。
每个线程都有自己的工作内存,工作内存中保存了线程使用到的主内存中变量的副本。
线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接操作主内存。
不同线程之间无法访问对方的工作内存。
线程之间的值传递均需通过主内存来完成。
2 协议
操作 | 解释 | 作用域 | 说明 |
lock | 锁定 | 主内存 | 把一个变量表示为一个线程独占的状态 |
unlock | 解锁 | 主内存 | 把一个变量从线程独占的状态释放出来,释放后的变量才能被其他线程锁定 |
read | 读取 | 主内存 | 把一个变量从主内存传输到工作内存中 |
load | 载入 | 工作内存 | 把read操作的变量值放入工作内存的变量副本中。 |
use | 使用 | 工作内存 | 把一个工作内存的变量传递给执行引擎,当虚拟机遇到一个需要使用变量值的字节码指令时会执行此操作 |
assign | 赋值 | 工作内存 | 把从执行引擎收到的值赋给工作内存的变量,当虚拟机遇到一个给变量赋值的字节码指令时会执行此操作。 |
store | 存储 | 工作内存 | 把一个变量的值传到主内存中 |
write | 写入 | 主内存 | 把store操作的值放到主内存的变量中 |
如果需要将一个变量从主内存复制到工作内存,就需要顺序的执行read、load;如果需要讲一个变量从工作内存写回到主内存,就需要顺序的执行store、write。Java内存模型要求了这两对命令的顺序,但不要求其连续,即在read和load之间、store和write允许插入其他指令。
3 规则
不允许read和load、store和write单独出现。即不允许一个变量从主内存读取了但工作内存不接受的情况;不允许从工作内存回写了但主内存不接受的情况。
不允许一个线程丢弃掉它最近的assign操作。即变量在工作内存中修改之后,必须同步回主内存中。
不允许一个线程无原因的把数据从线程的工作内存同步回主内存。即对变量没有执行assgin操作则不能回写到主内存。
一个新的变量只能在主内存中创建,不允许在工作内存中直接使用一个违背初始化过的变量。即对一个变量use前必须load;对一个变量store前必须assign。
一个变量在同一时刻只允许一个线程对其进行lock操作,但一个线程可以多次执行lock操作,多次执行lock操作以后,只有执行相同次数的unlock,变量才被解锁。
如果一个线程没有被lock操作锁定,那么不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁住的变量。
对一个变量执行unlock操作前,必须先把此变量同步回主内存,即先执行store、write操作。
从上面的规则我们可以看到:因为一个变量同一时刻只有一个线程能对其进行lock操作,在unlock前必须将变量同步会主内存,所以使用lock可以保证并发情况下数据安全。
4 long和double的非原子协定
虚拟机允许没有被volatile修饰的64位数据的多些操作划分成2次32位操作进行,即允许虚拟机实现对64位的long、double的read、load、store、write不保证原子性,即long和double的非原子协定。
目前商用虚拟机基本上都对long、double保证原子操作。
二 volatile
1 volatile变量的特性
使用volatile修饰的变量具有两种特性:可见性、禁止指令重排序优化。
1) 可见性
可见性指一个线程修改了这个变量值,新值对其他线程来说是可以立即得知的;普通变量做不到这一点。注意这一点并不意味着使用volatile修饰的变量是线程安全。
2) 禁止指令重排序优化
普通的变量仅仅保证在执行过程中,所有依赖赋值结果的地方都能获取正确的结果,而不保证变量赋值操作的顺序与代码中的执行顺序一致,这就是java内存模型中的“线程内表现为串行的语义”;而使用volatile可以实现此点。
单例模式下,如果不使用volatile修饰,通过双重检查锁创建对象,并发场景中可能出现问题,具体见后面的分析。
2 volatile变量的特殊规则
说明:因为觉得原文中对于volatile规则的描述不好理解,所以我在这里换了一种描述方式,所以如果发现这里的描述和虚拟机规范不同,请不必疑惑。
假设T表示一个线程,V、W表示两个volatile类型的变量,那么拥有以下规则:
Ø 每次使用volatile修饰的变量前,必须先从主内存中获取最新的值
线程T对变量V的use动作和线程T对变量V的read、load的动作可以认为是相关联的,必须连续一起出现。即线程T对V的前一个动作是load时,线程T才能对变量V执行use操作;如果线程T对V的后一个动作是use时,线程T才能对变量V执行load操作。
此规则要求在工作内存中,每次使用V前必须先从主内存中刷新最新的值,用于保证能看到其他线程对变量V修改后的值。
Ø 每次使用volatile修饰的变量后,必须立即同步回主内存
线程T对变量V的assign动作和线程T对变量V的store、write的动作可以认为是相关联的,必须连续一起出现。即线程T对V的前一个动作是assign时,线程T才能对变量V执行store操作;如果线程T对V的后一个动作是store时,线程T才能对变量V执行assign操作。
此规则要求在工作内存中,每次使用V后必须立即同步回主内存,用于保证其他线程能看到当前线程对变量V的值所做的修改。
Ø 代码执行顺序和程序的顺序相同
假定动作UV是线程T对变量V执行的use动作,动作RV是与之相关联的read动作;假定动作UW是线程T对变量W的use动作,动作RW是与之相关联的read动作;如果UV先于UW,那么RV先于RW。
假定动作AV是线程T对变量V执行的assign动作,动作WV是与之相关联的write动作;假定动作AW是线程T对变量W的assign动作,动作WW是与之相关联的write动作;如果AV先于AW,那么WV先于WW。
此规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。
3 示例
a) volalite修饰的变量不是线程安全的
以下示例代码输出的结果不会为100000;基本上都会比此值略小。
<span style="color:#f8f8f2"><code class="language-java"><span style="color:#66d9ef"><span style="color:#f92672">public</span></span> <span style="color:#66d9ef"><span style="color:#f92672">class</span></span> <span style="color:#f8f8f2">VolaliteTester</span> <span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">private</span></span> <span style="color:#66d9ef"><span style="color:#f92672">static</span></span> <span style="color:#66d9ef"><span style="color:#f92672">volatile</span></span> <span style="color:#66d9ef"><span style="color:#f92672">int</span></span> value<span style="color:#f8f8f2">;</span>
<span style="color:#66d9ef"><span style="color:#f92672">private</span></span> <span style="color:#66d9ef"><span style="color:#f92672">static</span></span> <span style="color:#66d9ef"><span style="color:#f92672">void</span></span> <span style="color:#e6db74"><span style="color:#a6e22e">inc</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">(</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">)</span></span> <span style="color:#f8f8f2">{</span>
value<span style="color:#f8f8f2">++</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#66d9ef"><span style="color:#f92672">public</span></span> staticvoid <span style="color:#e6db74"><span style="color:#a6e22e">main</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">(</span></span><span style="color:#f8f8f2">String</span><span style="color:#f8f8f2"><span style="color:#f8f8f2">[</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">]</span></span><span style="color:#f8f8f2"> args</span><span style="color:#f8f8f2"><span style="color:#f8f8f2">)</span></span> <span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">int</span></span> threadCount<span style="color:#f8f8f2">=</span> <span style="color:#ae81ff"><span style="color:#ae81ff">10</span></span><span style="color:#f8f8f2">;</span>
<span style="color:#66d9ef"><span style="color:#f92672">final</span></span> <span style="color:#66d9ef"><span style="color:#f92672">int</span></span> times <span style="color:#f8f8f2">=</span> <span style="color:#ae81ff"><span style="color:#ae81ff">10000</span></span><span style="color:#f8f8f2">;</span>
Thread<span style="color:#f8f8f2">[</span><span style="color:#f8f8f2">]</span> threads<span style="color:#f8f8f2">=</span> <span style="color:#66d9ef"><span style="color:#f92672">new</span></span> Thread<span style="color:#f8f8f2">[</span>threadCount<span style="color:#f8f8f2">]</span><span style="color:#f8f8f2">;</span>
<span style="color:#66d9ef"><span style="color:#f92672">for</span></span> <span style="color:#f8f8f2">(</span><span style="color:#66d9ef"><span style="color:#f92672">int</span></span> i <span style="color:#f8f8f2">=</span> <span style="color:#ae81ff"><span style="color:#ae81ff">0</span></span><span style="color:#f8f8f2">;</span> i <span style="color:#f8f8f2"><</span> threadCount<span style="color:#f8f8f2">;</span>i<span style="color:#f8f8f2">++</span><span style="color:#f8f8f2">)</span> <span style="color:#f8f8f2">{</span>
Threadthread <span style="color:#f8f8f2">=</span> <span style="color:#66d9ef"><span style="color:#f92672">new</span></span> Thread<span style="color:#f8f8f2">(</span><span style="color:#e6db74">newRunnable</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span> <span style="color:#f8f8f2">{</span>
<span style="color:#75715e">@Override</span>
<span style="color:#66d9ef"><span style="color:#f92672">public</span></span> <span style="color:#66d9ef"><span style="color:#f92672">void</span></span> <span style="color:#e6db74"><span style="color:#a6e22e">run</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">(</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">)</span></span> <span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">for</span></span> <span style="color:#f8f8f2">(</span><span style="color:#66d9ef"><span style="color:#f92672">int</span></span> j <span style="color:#f8f8f2">=</span> <span style="color:#ae81ff"><span style="color:#ae81ff">0</span></span><span style="color:#f8f8f2">;</span> j <span style="color:#f8f8f2"><</span> times<span style="color:#f8f8f2">;</span>j<span style="color:#f8f8f2">++</span><span style="color:#f8f8f2">)</span> <span style="color:#f8f8f2">{</span>
<span style="color:#e6db74">inc</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#f8f8f2">}</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
threads<span style="color:#f8f8f2">[</span>i<span style="color:#f8f8f2">]</span> <span style="color:#f8f8f2">=</span> thread<span style="color:#f8f8f2">;</span>
thread<span style="color:#f8f8f2">.</span><span style="color:#e6db74">start</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#66d9ef"><span style="color:#f92672">if</span></span> <span style="color:#f8f8f2">(</span>Thread<span style="color:#f8f8f2">.</span><span style="color:#e6db74">activeCount</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span> <span style="color:#f8f8f2">></span><span style="color:#ae81ff"><span style="color:#ae81ff">1</span></span><span style="color:#f8f8f2">)</span> <span style="color:#f8f8f2">{</span>
Thread<span style="color:#f8f8f2">.</span><span style="color:#e6db74">yield</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
System<span style="color:#f8f8f2">.</span>err<span style="color:#f8f8f2">.</span><span style="color:#e6db74">println</span><span style="color:#f8f8f2">(</span><span style="color:#a6e22e"><span style="color:#e6db74">"value="</span></span> <span style="color:#f8f8f2">+</span> value<span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#f8f8f2">}</span></code></span>
b) 双重检查锁失效
单例模式下创建实例对象时,可能出现双重检查锁失效的情况,即以下示例代码可能会创建多个实例instance对象。
<span style="color:#f8f8f2"><code class="language-java"><span style="color:#66d9ef"><span style="color:#f92672">public</span></span> <span style="color:#66d9ef"><span style="color:#f92672">class</span></span> <span style="color:#f8f8f2">Singleton</span> <span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">private</span></span> <span style="color:#66d9ef"><span style="color:#f92672">static</span></span> <span style="color:#66d9ef"><span style="color:#f92672">volatile</span></span> Singleton instance<span style="color:#f8f8f2">;</span>
<span style="color:#66d9ef"><span style="color:#f92672">private</span></span> <span style="color:#e6db74"><span style="color:#a6e22e">Singleton</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">(</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">)</span></span> <span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">super</span></span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#66d9ef"><span style="color:#f92672">public</span></span> staticSingleton <span style="color:#e6db74"><span style="color:#a6e22e">getInstance</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">(</span></span><span style="color:#f8f8f2"><span style="color:#f8f8f2">)</span></span> <span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">if</span></span> <span style="color:#f8f8f2">(</span>instance <span style="color:#f8f8f2">==</span> <span style="color:#f92672">null</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">{</span>
<span style="color:#66d9ef"><span style="color:#f92672">synchronized</span></span><span style="color:#f8f8f2">(</span>Singleton<span style="color:#f8f8f2">.</span><span style="color:#66d9ef">class</span><span style="color:#f8f8f2">)</span> <span style="color:#f8f8f2">{</span>
<span style="color:slategray"><span style="color:#75715e">// 如果在等待synchronized结束前已经有线程创建Instance则直接忽略。</span></span>
<span style="color:#66d9ef"><span style="color:#f92672">if</span></span><span style="color:#f8f8f2">(</span>instance <span style="color:#f8f8f2">==</span> <span style="color:#f92672">null</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">{</span>
instance <span style="color:#f8f8f2">=</span> <span style="color:#e6db74">newSingleton</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#66d9ef"><span style="color:#f92672">return</span></span> instance<span style="color:#f8f8f2">;</span>
<span style="color:#f8f8f2">}</span>
<span style="color:#f8f8f2">}</span></code></span>
在执行instance = new Singleton()语句时,实际上分为分配内存、调用构造函数、instance指向分配的内存地址三个步骤。如下伪代码所示:
<span style="color:#f8f8f2"><code class="language-java">memery <span style="color:#f8f8f2">=</span><span style="color:#e6db74">allocate</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span> <span style="color:slategray"><span style="color:#75715e">//为对象分配内存</span></span>
<span style="color:#e6db74">ctorSingleton</span><span style="color:#f8f8f2">(</span>memery<span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span> <span style="color:slategray"><span style="color:#75715e">//调用构造函数实例化对象</span></span>
instance <span style="color:#f8f8f2">=</span> memery<span style="color:#f8f8f2">;</span> <span style="color:slategray"><span style="color:#75715e">//将instance指向新分配的内存</span></span></code></span>
但是实际上有些虚拟机进行指令重排序以后会变成如下顺序(虚拟机的内存模型以及协议规则均没有限制不能进行这种操作)。
<span style="color:#f8f8f2"><code class="language-java">memery <span style="color:#f8f8f2">=</span><span style="color:#e6db74">allocate</span><span style="color:#f8f8f2">(</span><span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span> <span style="color:slategray"><span style="color:#75715e">//为对象分配内存</span></span>
instance <span style="color:#f8f8f2">=</span> memery<span style="color:#f8f8f2">;</span> <span style="color:slategray"><span style="color:#75715e">//将instance指向新分配的内存,注意此时instance为not null,但是此时对象并未实例化,如果此时执行非空判断,将返回true。</span></span>
<span style="color:#e6db74">ctorSingleton</span><span style="color:#f8f8f2">(</span>memery<span style="color:#f8f8f2">)</span><span style="color:#f8f8f2">;</span> <span style="color:slategray"><span style="color:#75715e">//调用构造函数实例化对象</span></span>
</code></span>
三 原子性、可见性、有序性
1 原子性(Atomicity)
java内存模型直接对变量的read、load、use、assign、store、write操作的原子性(long、double的非原子协定基本是例外,但基本不会遇到)
通过synchronized关键字实现lock、unlock操作,保证同一时间段内只有一个线程访问同步快,所以可以实现代码块的原子性。
2 可见性(Visibility)
java内存模型是通过变量使用前从主内存读取、变量修改后将值同步回主内存来实现可见性的。
volalite的可见性是由:修改后的新值立即同步到主内存,使用前立即从主内存中读取新值这个规则决定的。volatite保证了多线程操作时变量的可见性,而普通变量却不行。
Synchronized的可见性是由:在unlock前必须将变量先同步到主内存这个规则决定的。
final的可见性是由:在构造函数中初始化后,不会将this的引用传递出去,以后将无法修改此值这个规则决定的。
3 有序性(Ordering)
如果在本线程内观察,所有操作都是有序的;如果在一个线程观察另外一个线程,所有操作都是无序的。前半句指:线程内变形为串行的语义;后半句指:指令重排序闲现象和工作内存与主内存同步延迟现象。
Volatile本身就有禁止指令重排序的语义,所以可以保证有序性。
Synchronized的有序性是由:同一时刻只允许一个线程对其进行lock操作这个规则决定的,这决定了synchronized的语句块只能串行进入,所以可以保证有序性。
四 先行发生原则
以下是java内存模型提供的“天然”的先行发生关系,这些先行发生关系不需任何同步协助就已经存在。如果两个操作之间的关系不在此列,并且无法通过这些规则推导出来,那么他们就没有顺序保证,虚拟机可能对他们随意的进行重排序。
1. 程序次序规则(Program Order Rule)
在一个线程中,按照代码顺序,书写在前的操作先行发生于书写在后的操作。确切的说,应该是控制流顺序而不是书写顺序,例如分支、循环机构。
2. 管程锁定规则(Monitor Lock Rule)
一个unlock操作先行发生于后面对同一个锁的lock操作。后面值的是时间上的先后顺序。
3. volatile变量规则(Volatile Rule)
对一个volatile变量的写操作先行发生于后面对这个变量的读操作。。后面值的是时间上的先后顺序。
4. 线程启动规则(Thread Start Rule)
Thread对象的start方法先行发生于对此线程的每一个动作。
5. 线程终止规则(Thread Termination Rule)
线程中的所有操作都先行发生于对此项承德终止检测.可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到县城已经终止执行。
6. 线程中断规则(Thread Interruption Rule)
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。可以通过Thread.interrupted()方法检测到是否发生中断。
7. 对象终结规则(Finalizer Rule)
一个对象的初始化完成(构造函数执行结束)先行发生于他的finalize()方法的开始。
8. 传递性(Transitivity)
如果操作A先行发生于操作,操作B先行发生于操作C,那么可以得出操作A先行发生于操作C。