Synchronized是Java中很是重要的一個關鍵字。java
1. 起源數據庫
事務的產生老是會有特定的緣由,下面這段代碼就做爲引出Synchronized的引子編程
public class SynchronizedDemo implements Runnable { private static int count = 0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new SynchronizedDemo()); thread.start(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("result: " + count); } @Override public void run() { for (int i = 0; i < 1000000; i++) count++; } }
10個線程同時操做count,每一個線程作一百萬次count自增操做,最後出來的結果不必定是一千萬,並且每次運行的結果還不同。安全
2. Synchronized實現原理多線程
實現原理:JVM經過進入、退出對象監視器(Monitor)來實現對方法、同步塊的同步。併發
看下面一段代碼ide
public class SynchronizedDemo { public static void main(String[] args) { synchronized (SynchronizedDemo.class) { } method(); } private static void method() { } }
編譯以後,用javap -v SynchronizedDemo.class 查看字節碼文件:post
該圖能夠看出,任意線程對Object的訪問,首先要得到Object的監視器,若是獲取失敗,該線程就進入同步狀態,線程狀態變爲BLOCKED,當Object的監視器佔有者釋放後,在同步隊列中得線程就會有機會從新獲取該監視器。性能
3. 鎖獲取和鎖釋放的內存語義public class MonitorDemo { private int a = 0; public synchronized void writer() { // 1 a++; // 2 } // 3 public synchronized void reader() { // 4 int i = a; // 5 } // 6 }
從上圖能夠看出,線程A會首先先從主內存中讀取共享變量a=0的值而後將該變量拷貝到本身的本地內存,進行加一操做後,再將該值刷新到主內存,整個過程即爲線程A 加鎖-->執行臨界區代碼-->釋放鎖相對應的內存語義。測試
線程B獲取鎖的時候一樣會從主內存中共享變量a的值,這個時候就是最新的值1,而後將該值拷貝到線程B的工做內存中去,釋放鎖的時候一樣會重寫到主內存中。
從總體上來看,線程A的執行結果(a=1)對線程B是可見的,實現原理爲:釋放鎖的時候會將值刷新到主內存中,其餘線程獲取鎖時會強制從主內存中獲取最新的值。
從橫向來看,這就像線程A經過主內存中的共享變量和線程B進行通訊,A 告訴 B 咱們倆的共享數據如今爲1啦,這種線程間的通訊機制正好吻合java的內存模型正好是共享內存的併發模型結構。
使用鎖時,線程獲取鎖是一種悲觀鎖策略,即假設每一次執行臨界區代碼都會產生衝突,因此當前線程獲取到鎖的時候同時也會阻塞其餘線程獲取該鎖。而CAS操做(又稱爲無鎖操做)是一種樂觀鎖策略,它假設全部線程訪問共享資源的時候不會出現衝突,既然不會出現衝突天然而然就不會阻塞其餘線程的操做。所以,線程就不會出現阻塞停頓的狀態。那麼,若是出現衝突了怎麼辦?無鎖操做是使用**CAS(compare and swap)**又叫作比較交換來鑑別線程是否出現衝突,出現衝突就重試當前操做直到沒有衝突爲止。
CAS比較交換的過程能夠通俗的理解爲CAS(V,O,N),包含三個值分別爲:V 內存地址存放的實際值;O 預期的值(舊值);N 更新的新值。當V和O相同時,也就是說舊值和內存中實際的值相同代表該值沒有被其餘線程更改過,即該舊值O就是目前來講最新的值了,天然而然能夠將新值N賦值給V。反之,V和O不相同,代表該值已經被其餘線程改過了則該舊值O不是最新版本的值了,因此不能將新值N賦給V,返回V便可。當多個線程使用CAS操做一個變量是,只有一個線程會成功,併成功更新,其他會失敗。失敗的線程會從新嘗試,固然也能夠選擇掛起線程
CAS的實現須要硬件指令集的支撐,在JDK1.5後虛擬機纔可使用處理器提供的CMPXCHG指令實現。
1. ABA問題 由於CAS會檢查舊值有沒有變化,這裏存在這樣一個有意思的問題。好比一箇舊值A變爲了成B,而後再變成A,恰好在作CAS時檢查發現舊值並無變化依然爲A,可是實際上的確發生了變化。解決方案能夠沿襲數據庫中經常使用的樂觀鎖方式,添加一個版本號能夠解決。原來的變化路徑A->B->A就變成了1A->2B->3C。java這麼優秀的語言,固然在java 1.5後的atomic包中提供了AtomicStampedReference來解決ABA問題,解決思路就是這樣的。
2. 自旋時間過長
使用CAS時非阻塞同步,也就是說不會將線程掛起,會自旋(無非就是一個死循環)進行下一次嘗試,若是這裏自旋時間過長對性能是很大的消耗。若是JVM能支持處理器提供的pause指令,那麼在效率上會有必定的提高。
3. 只能保證一個共享變量的原子操做
當對一個共享變量執行操做時CAS能保證其原子性,若是對多個共享變量進行操做,CAS就不能保證其原子性。有一個解決方案是利用對象整合多個共享變量,即一個類中的成員變量就是這幾個共享變量。而後將這個對象作CAS操做就能夠保證其原子性。atomic中提供了AtomicReference來保證引用對象之間的原子性。
如圖在Mark Word會默認存放hasdcode,年齡值以及鎖標誌位等信息。
Java SE 1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率。對象的MarkWord變化爲下圖:
3.2 偏向鎖
HotSpot的做者通過研究發現,大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。
下圖線程1展現了偏向鎖獲取的過程,線程2展現了偏向鎖撤銷的過程。
如何關閉偏向鎖