在併發場景中,保證同一時刻只有一個線程對有併發隱患的代碼進行操做java
需求:兩個線程對 count 變量進行200000次循環增長,預期結果是400000次多線程
public class SynchronizedDemo implements Runnable { private static int count = 0; static SynchronizedDemo synchronizedInstance = new SynchronizedDemo(); public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance); Thread t2 = new Thread(synchronizedInstance); t1.start(); t2.start(); try { t1.join(); t2.join(); System.out.println("count 最終的值爲: " + count); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { synchronized (this) { for (int i = 0; i < 200000; i++) { count++; } } } }
結果 :顯然不等於400000次因此出現了運算錯誤
併發
緣由:異步
count++;
該語句包含三個操做:ide
注意:他們是將本身工做內存中的值進行改變刷回主內存,假設當前count的值爲8,t一、t2將count的值複製到本身的工做內存中進行修改,若是此時t1將count變成九、t2此時也將count的值變成9,當t一、t2兩個線程都將值刷回主內存的時候count值爲9,並非10,這個時候就會形成最後的結果和預期的不一致。性能
@Override public void run() { synchronized (this) { for (int i = 0; i < 200000; i++) { count++; } } }
@Override public synchronized void run() { for (int i = 0; i < 200000; i++) { count++; } }
@Override public void run() { for (int i = 0; i < 200000; i++) { synchronized (SynchronizedDemo.class) { count++; } } }
輸出結果:
優化
後文詳細講解四種加 synchronized 的方式this
2.1.1 方法鎖操作系統
修飾普通方法默認鎖對象爲this當前實例對象線程
public synchronized void method() ;在普通方法上面加synchronized
public class SynchronizedDemo3 implements Runnable { static SynchronizedDemo3 synchronizedDemo3 = new SynchronizedDemo3(); public synchronized void method() { System.out.println("線程名稱" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程名稱" + Thread.currentThread().getName() + "運行完成"); } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedDemo3); t1.setName("我是線程 t1"); Thread t2 = new Thread(synchronizedDemo3); t2.setName("我是線程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出結果: 線程 t1 和線程 t2 執行過程是順序執行的
2.1.2 同步代碼塊
輸出結果:線程 t1 和線程 t2 交叉執行造成了亂序
輸出結果:線程 t1 和線程 t2 執行過程是順序執行的
輸出結果:線程 t1 和線程 t2 執行造成了順序,這種狀況下和this沒有什麼區別,可是若是是多個同步代碼塊的話就須要進行自定義對象鎖了
輸出結果:輸出順序線程t1 和線程t2 代碼進行了交叉執行,出現了亂序
輸出結果:線程 t1 和線程 t2 執行造成了順序
特色:類鎖只能在同一時間被一個對象擁有(不管有多少個實例想訪問也是一個對象持有它)
2.2.1 synchronized修飾靜態的方法
代碼示例: synchronized 加在普通方法上面
public class SynchronizedDemo4 implements Runnable { private static SynchronizedDemo4 synchronizedInstance1 = new SynchronizedDemo4(); private static SynchronizedDemo4 synchronizedInstance2 = new SynchronizedDemo4(); public synchronized void method() { System.out.println("線程名稱" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程名稱" + Thread.currentThread().getName() + "運行完成"); } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance1); t1.setName("我是線程 t1"); Thread t2 = new Thread(synchronizedInstance2); t2.setName("我是線程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出結果:輸出順序線程t1 和線程t2 代碼進行了交叉執行,出現了亂序
public static synchronized void method();使用方式
public class SynchronizedDemo4 implements Runnable { private static SynchronizedDemo4 synchronizedInstance1 = new SynchronizedDemo4(); private static SynchronizedDemo4 synchronizedInstance2 = new SynchronizedDemo4(); public static synchronized void method() { System.out.println("線程名稱" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程名稱" + Thread.currentThread().getName() + "運行完成"); } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance1); t1.setName("我是線程 t1"); Thread t2 = new Thread(synchronizedInstance2); t2.setName("我是線程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出結果:線程 t1 和線程 t2 執行造成了順序
2.2.2 指定鎖對象爲Class對象
*synchronized (**SynchronizedDemo5.class**)*
public class SynchronizedDemo5 implements Runnable { private static SynchronizedDemo5 synchronizedInstance1 = new SynchronizedDemo5(); private static SynchronizedDemo5 synchronizedInstance2 = new SynchronizedDemo5(); void method() { synchronized (SynchronizedDemo5.class) { //類鎖只有一把 System.out.println("線程名稱" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程名稱" + Thread.currentThread().getName() + "運行完成"); } } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance1); t1.setName("我是線程 t1"); Thread t2 = new Thread(synchronizedInstance2); t2.setName("我是線程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出結果: 線程 t1 和線程 t2 執行造成了順序
就是說你已經獲取了一把鎖,等想要再次請求的時候不須要釋放這把鎖和其餘線程一塊兒競爭該鎖,能夠直接使用該鎖
好處:避免死鎖
粒度:線程而非調用
代碼實例:
package synchronizedPage; public class SynchronizedDemo6 { int count = 0; public static void main(String[] args) { SynchronizedDemo6 synchronizedDemo6 = new SynchronizedDemo6(); synchronizedDemo6.method(); } private synchronized void method() { System.out.println(count); if (count == 0) { count++; method(); } } }
輸出結果:
代碼實例:
package synchronizedPage; public class SynchronizedDemo7 { private synchronized void method1() { System.out.println("method1"); method2(); } private synchronized void method2() { System.out.println("method2"); } public static void main(String[] args) { SynchronizedDemo7 synchronizedDemo7 = new SynchronizedDemo7(); synchronizedDemo7.method1(); } }
輸出結果:
代碼實例:
package synchronizedPage; public class SynchronizedDemo8 { public synchronized void doSomething() { System.out.println("我是父類方法"); } } class childrenClass extends SynchronizedDemo8{ public synchronized void doSomething() { System.out.println("我是子類方法"); super.doSomething(); } public static void main(String[] args) { childrenClass childrenClass = new childrenClass(); childrenClass.doSomething(); } }
輸出結果:
當A線程持有這把鎖時,B線程若是也想要A線程持有的鎖時只能等待,A永遠不釋放的話,那麼B線程永遠的等待下去。
public void test() { synchronized(this){ count++; } }
利用 javap -verbose 類的名字查看編譯後的文件
monitorenter:每一個對象都是一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:
monitorexit:執行monitorexit的線程必須是objectref所對應的monitor的全部者。指令執行時,monitor的進入數減1,若是減1後進入數爲0,那線程退出monitor,再也不是這個monitor的全部者。其餘被這個monitor阻塞的線程能夠嘗試去獲取這個 monitor 的全部權
monitorexit指令出現了兩次,第1次爲同步正常退出釋放鎖;第2次爲發生異步退出釋放鎖
public synchronized void test() { count++; }
利用 javap -verbose 類的名字查看編譯後的文件
方法的同步並無經過指令monitorenter和monitorexit來完成,不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:
當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象,其實底層仍是monitor對象鎖。
從JDK6開始,就對synchronized的實現機制進行了較大調整,包括使用JDK5引進的CAS自旋以外,還增長了自適應的CAS自旋、鎖消除、鎖粗化、偏向鎖、輕量級鎖這些優化策略。因此synchronized關鍵字的優化使得性能極大提升,同時語義清晰、操做簡單、無需手動關閉,因此推薦在容許的狀況下儘可能使用此關鍵字,同時在性能上此關鍵字還有優化的空間。
無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態
鎖的膨脹過程:
無鎖狀態 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖
只能從低到高升級,不會出現鎖的降級
所謂自旋鎖,就是指當一個線程嘗試獲取某個鎖時,若是該鎖已被其餘線程佔用,就一直循環檢測鎖是否被釋放,而不是進入線程掛起或睡眠狀態。(減小線程切換)
使用場景: 自旋鎖適用於鎖保護的臨界區很小的狀況,臨界區很小的話,鎖佔用的時間就很短。
缺點:雖然它能夠避免線程切換帶來的開銷,可是它佔用了CPU處理器的時間。若是持有鎖的線程很快就釋放了鎖,那麼自旋的效率就很是好,反之,自旋的線程就會白白消耗掉處理的資源,它不會作任何有意義的工做,因此增長了適應性自選鎖
所謂自適應就意味着自旋的次數再也不是固定的,它是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。
線程若是自旋成功了,那麼下次自旋的次數會更加多,由於上次成功了,那麼這次自旋也頗有可能會再次成功,那麼它就會容許自旋等待持續的次數更多。反之,不多可以成功,那麼之後自旋的次數會減小甚至省略掉自旋過程,以避免浪費處理器資源。
爲了保證數據的完整性,在進行操做時須要對這部分操做進行同步控制,可是在有些狀況下,JVM檢測到不可能存在共享數據競爭,這是JVM會對這些同步鎖進行鎖消除。做爲寫程序的人應該會知道哪裏存在數據競爭,不可能隨便的加鎖。
將多個連續的加鎖、解鎖操做鏈接在一塊兒,擴展成一個範圍更大的鎖。雖然咱們平時倡導把加鎖的片斷儘可能小爲了增長併發效率和性能。可是若是一系列的連續加鎖解鎖操做,可能會致使沒必要要的性能損耗,因此引入鎖粗化。
在大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低,引進了偏向鎖。偏向鎖是在單線程執行代碼塊時使用的機制,若是在多線程併發的環境下(即線程A還沒有執行完同步代碼塊,線程B發起了申請鎖的申請),則必定會轉化爲輕量級鎖或者重量級鎖。
引入偏向鎖主要目的是:爲了在沒有多線程競爭的狀況下儘可能減小沒必要要的輕量級鎖執行路徑。由於輕量級鎖的加鎖解鎖操做是須要依賴屢次CAS原子指令的,而偏向鎖只須要在置換ThreadID的時候依賴一次CAS原子指令。
當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,之後該線程進入和退出同步塊時不須要花費CAS操做來爭奪鎖資源,只須要檢查是否爲偏向鎖、鎖標識爲以及ThreadID便可,處理流程以下:
偏向鎖的獲取和撤銷流程:
引入輕量級鎖的主要目的是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。當關閉偏向鎖功能或者多個線程競爭偏向鎖致使偏向鎖升級爲輕量級鎖,則會嘗試獲取輕量級鎖,其步驟以下:
輕量級鎖的釋放也是經過CAS操做來進行的,主要步驟以下:
問題:
由於在申請對象鎖時須要以該值做爲CAS的比較條件,同時在升級到重量級鎖時,能經過這個比較斷定是否在持有鎖的過程當中此鎖被其餘線程申請過,若是被其餘線程申請了,則在釋放鎖的時候要喚醒被掛起的線程。
Synchronized是經過對象內部的一個叫作監視器鎖(Monitor)來實現的。可是監視器鎖本質又是依賴於底層的操做系統的Mutex Lock來實現的。而操做系統實現線程之間的切換這就須要從用戶態轉換到核心態,這個成本很是高,性能消耗特別嚴重。 所以,這種依賴於操做系統Mutex Lock所實現的鎖咱們稱之爲 「重量級鎖」。