Java面試篇基礎部分-Synchronized關鍵字詳解

Synchronized關鍵字用於對Java物件、方法、程式碼塊等提供執行緒安全操作。Synchronized屬於獨佔式的悲觀鎖機制,同時也是可重入鎖。我們在使用Synchronized關鍵字的時候,可以保證同一時刻只有一個執行緒對該物件進行訪問;也就是說它在同一個JVM中是執行緒安全的。

Java面試篇基礎部分-Synchronized關鍵字詳解

Java中的每個物件都有一個monitor物件,加鎖就是再競爭monitor物件。程式碼塊加鎖是透過在程式碼塊前後分別加上monitorenter和monitorexit指令實現。方法加鎖則是透過一個標記位來進行判斷。

Synchronized 的作用範圍

synchronized 作用於成員變數和非靜態方法的時候,鎖住的是物件例項本身,也就是this物件。

synchronized 作用於靜態方法的時候,鎖住的是Class例項,因為靜態方法屬於Class而不屬於物件。

synchronized 作用於一個程式碼塊的時候,鎖住的是所有程式碼塊中配置的物件。

Synchronized 用法

public class SynchronizedDemo { public static void main(String[] args) { final SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo。generalMethod1(); } })。start(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo。generalMethod2(); } })。start(); } // synchronized 關鍵字修飾普通的同步方法,鎖住的是當前例項物件 public synchronized void generalMethod1(){ try { for (int i = 0; i < 3; i++) { System。out。println(“generalMethod1 execute ”+i+“ time”); Thread。sleep(3000); } }catch (InterruptedException e){ e。printStackTrace(); } } public synchronized void generalMethod2(){ try { for (int i = 0; i < 3; i++) { System。out。println(“generalMethod2 execute ”+i+“ time”); Thread。sleep(3000); } }catch (InterruptedException e){ e。printStackTrace(); } }}

上述程式碼定義了兩個同步方法,在主函式中執行了這兩個同步方法,執行結果如下圖所示。證明在整個的操作過程中,synchronized關鍵字鎖住的是整個的物件

Java面試篇基礎部分-Synchronized關鍵字詳解

如果將程式碼做如下的調整,定義兩個例項物件分別呼叫兩個方法,程式就可以實現併發執行。

public class SynchronizedDemo { public static void main(String[] args) { final SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); final SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo。generalMethod1(); } })。start(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo2。generalMethod2(); } })。start(); } // synchronized 關鍵字修飾普通的同步方法,鎖住的是當前例項物件 public synchronized void generalMethod1(){ try { for (int i = 0; i < 3; i++) { System。out。println(“generalMethod1 execute ”+i+“ time”); Thread。sleep(3000); } }catch (InterruptedException e){ e。printStackTrace(); } } public synchronized void generalMethod2(){ try { for (int i = 0; i < 3; i++) { System。out。println(“generalMethod2 execute ”+i+“ time”); Thread。sleep(3000); } }catch (InterruptedException e){ e。printStackTrace(); } }}

Java面試篇基礎部分-Synchronized關鍵字詳解

Synchronized關鍵字作用於靜態的同步方法,相當於鎖住了當前的Class物件,只需要在方法上加上static關鍵字就可以了。程式碼如下

public class SynchronizedDemo { public static void main(String[] args) { final SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); final SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo。generalMethod1(); } })。start(); new Thread(new Runnable() { @Override public void run() { synchronizedDemo2。generalMethod2(); } })。start(); } // synchronized 關鍵字修飾普通的同步方法,鎖住的是當前例項物件 public static synchronized void generalMethod1(){ try { for (int i = 0; i < 3; i++) { System。out。println(“generalMethod1 execute ”+i+“ time”); Thread。sleep(3000); } }catch (InterruptedException e){ e。printStackTrace(); } } public static synchronized void generalMethod2(){ try { for (int i = 0; i < 3; i++) { System。out。println(“generalMethod2 execute ”+i+“ time”); Thread。sleep(3000); } }catch (InterruptedException e){ e。printStackTrace(); } }}

Java面試篇基礎部分-Synchronized關鍵字詳解

透過測試結果可以很清楚的看到,因為static方法是與Class相關的,並且Class相關的資料是在JVM中全域性共享的,所以靜態方法相當於類本身加鎖。所以就會鎖住整個的Class物件。

Synchroinzed的實現原理

首先呢Synchroinzed內部包含了很多的東西,例如ContentionList、EntryList、WaitSet、OnDeck、Owner、!Owner等六個區域,每個區域的資料都代表鎖的不同狀態。

ContentionList:鎖競爭佇列,所有請求鎖的執行緒都被放在競爭佇列中。

EntryList:競爭候選列表,在ContentList中有資格成為候選者來競爭鎖資源的執行緒都被移動到了EntryList中。

WaitSet:等待集合,呼叫wait方法後被阻塞的執行緒將被放在WaitSet中

OnDeck:競爭候選者,在同一時刻最多隻有一個執行緒在競爭鎖資源,該執行緒的狀態被稱為是OnDeck

Owner:競爭到鎖資源的執行緒被稱為Owner狀態執行緒。

!Owner:在Owner執行緒被釋放之後,會從Owner的狀態程式設計!Owner 。

Synchroinzed在接收到新的鎖請求的時候,首先會自旋等待,如果自旋沒有獲取到鎖資源,則將會被放入到鎖競爭佇列ContentionList中。

這裡為了防止鎖競爭的時候ContentList尾部的元素被大量的併發執行緒CAS方法影響。Owner執行緒會在釋放鎖的時候將ContentList中的一部分執行緒移動到EntryList中,並指定EntryList中的某個執行緒為OnDeck執行緒,一般情況下是以最先進入為最優。Owner執行緒並不會把鎖傳遞到OnDeck執行緒,而是把鎖的競爭權交給OnDeck,讓OnDeck執行緒重新進行鎖競爭。

獲取到鎖資源的OnDeck執行緒會變成Owner執行緒,而沒有獲取到鎖資源的執行緒任然留在EntryList中。

Owner執行緒被wait方法阻塞之後,會轉移到WaitSet佇列中,直到某個時刻被notify方法或者是notifyAll方法喚醒,會再次進入到EntryList中。ContentionList、EntryList、WaitSet中的執行緒均為阻塞狀態,該阻塞是由作業系統來完成的。

Owner執行緒在執行完成之後會釋放鎖的資源並且改變成!Owner的狀態,如下圖所示。

Java面試篇基礎部分-Synchronized關鍵字詳解

在JDK1。6中對Synchroinzed做了很多的最佳化、引入了適應性的自旋、鎖消除、鎖粗化、輕量級鎖以及偏向鎖等等來提高鎖的效率。鎖可以從偏向鎖升級到輕量級鎖、再升級到重量級鎖。這個過程被稱為是鎖膨脹,在JDK1。6中預設開啟了偏向鎖和輕量級鎖,可以提供過引數 -XX:UseBiasedLocking禁用偏向鎖。