Volatile 是 Java 虛擬機提供輕量級的同步機制。它有三個特性:緩存
當對非volatile變量進行讀寫的時候,每一個線程先從主內存拷貝變量到CPU緩存中,若是計算機有多個CPU,每一個線程可能在不一樣的CPU上被處理,這意味着每一個線程能夠拷貝到不一樣的CPU cache中。
volatile共享變量則會在修改以後當即刷新到內存中,並使其餘CPU中的變量失效,使得其餘CPU使用時必須從內存中從新加載。(這就保證了可見性)優化
在volatile修飾的共享變量在進行寫操做的時候會多出來一行彙編代碼,這一行彙編代碼在多核處理器下會引起兩件事情:spa
指令重排序是JVM爲了優化指令、提升程序運行效率,在不影響單線程程序執行結果的前提下,儘量地提升並行度。指令重排序包括編譯器重排序和運行時重排序。
在JDK1.5以後,可使用volatile變量禁止指令重排序。針對volatile修飾的變量,在讀寫操做指令先後會插入內存屏障,指令重排序時不能把後面的指令重排序到內存屏.net
首先須要瞭解的是,Java中只有對基本類型變量的賦值和讀取是原子操做,如i = 1的賦值操做,可是像j = i或者i++這樣的操做都不是原子操做,由於他們都進行了屢次原子操做,好比先讀取i的值,再將i的值賦值給j,兩個原子操做加起來就不是原子操做了。線程
因此,若是一個變量被volatile修飾了,那麼確定能夠保證每次讀取這個變量值的時候獲得的值是最新的,可是一旦須要對變量進行自增這樣的非原子操做,就不會保證這個變量的原子性了。3d
舉個栗子:blog
一個變量i被volatile修飾,兩個線程想對這個變量修改,都對其進行自增操做也就是i++,i++的過程能夠分爲三步,首先獲取i的值,其次對i的值進行加1,最後將獲得的新值寫會到緩存中。
線程A首先獲得了i的初始值100,可是還沒來得及修改,就阻塞了,這時線程B開始了,它也獲得了i的值,因爲i的值未被修改,即便是被volatile修飾,主存的變量還沒變化,那麼線程B獲得的值也是100,以後對其進行加1操做,獲得101後,將新值寫入到緩存中,再刷入主存中。根據可見性的原則,這個主存的值能夠被其餘線程可見。
問題來了,線程A已經讀取到了i的值爲100,也就是說讀取的這個原子操做已經結束了,因此這個可見性來的有點晚,線程A阻塞結束後,繼續將100這個值加1,獲得101,再將值寫到緩存,最後刷入主存,因此即使是volatile具備可見性,也不能保證對它修飾的變量具備原子性。
JMM : Java內存模型,不存在的東西,概念!約定!排序
關於JMM的一些同步的約定:內存
內存交互操做有8種,虛擬機實現必須保證每個操做都是原子的,不可在分的(對於double和long類型的變量來講,load、store、read和write操做在某些平臺上容許例外)rem
JMM對這八種指令的使用,制定了以下規則:
參考