Java並發編程 Synchronized及其實現原理

來源:互聯網
上載者:User

標籤:儲存   並發編程   有一個   編譯   com   示範   creat   輕量級   ble   

Synchronized是Java中解決並發問題的一種最常用的方法,也是最簡單的一種方法。Synchronized的作用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共用變數的修改能夠及時可見(3)有效解決重排序問題。

Java中每一個對象都可以作為鎖,這是synchronized實現同步的基礎:

1、普通同步方法,鎖是當前執行個體對象

public class SynchronizedTest { 4     public synchronized void method1(){ 5         System.out.println("Method 1 start"); 6         try { 7             System.out.println("Method 1 execute"); 8             Thread.sleep(3000); 9         } catch (InterruptedException e) {10             e.printStackTrace();11         }12         System.out.println("Method 1 end");13     }14 15     public synchronized void method2(){16         System.out.println("Method 2 start");17         try {18             System.out.println("Method 2 execute");19             Thread.sleep(1000);20         } catch (InterruptedException e) {21             e.printStackTrace();22         }23         System.out.println("Method 2 end");24     }25 26     public static void main(String[] args) {27         final SynchronizedTest test = new SynchronizedTest();28 29         new Thread(new Runnable() {30             @Override31             public void run() {32                 test.method1();33             }34         }).start();35 36         new Thread(new Runnable() {37             @Override38             public void run() {39                 test.method2();40             }41         }).start();42     }43 }

2、靜態同步方法,鎖是當前類的class對象

public class SynchronizedTest { 4      public static synchronized void method1(){ 5          System.out.println("Method 1 start"); 6          try { 7              System.out.println("Method 1 execute"); 8              Thread.sleep(3000); 9          } catch (InterruptedException e) {10              e.printStackTrace();11          }12          System.out.println("Method 1 end");13      }14  15      public static synchronized void method2(){16          System.out.println("Method 2 start");17          try {18              System.out.println("Method 2 execute");19              Thread.sleep(1000);20          } catch (InterruptedException e) {21              e.printStackTrace();22          }23          System.out.println("Method 2 end");24      }25  26      public static void main(String[] args) {27          final SynchronizedTest test = new SynchronizedTest();28          final SynchronizedTest test2 = new SynchronizedTest();29  30          new Thread(new Runnable() {31              @Override32              public void run() {33                  test.method1();34              }35          }).start();36  37          new Thread(new Runnable() {38              @Override39              public void run() {40                  test2.method2();41              }42          }).start();43      }44  }

3、同步方法塊,鎖是括弧裡面的對象

public class SynchronizedTest { 4     public void method1(){ 5         System.out.println("Method 1 start"); 6         try { 7             synchronized (this) { 8                 System.out.println("Method 1 execute"); 9                 Thread.sleep(3000);10             }11         } catch (InterruptedException e) {12             e.printStackTrace();13         }14         System.out.println("Method 1 end");15     }16 17     public void method2(){18         System.out.println("Method 2 start");19         try {20             synchronized (this) {21                 System.out.println("Method 2 execute");22                 Thread.sleep(1000);23             }24         } catch (InterruptedException e) {25             e.printStackTrace();26         }27         System.out.println("Method 2 end");28     }29 30     public static void main(String[] args) {31         final SynchronizedTest test = new SynchronizedTest();32 33         new Thread(new Runnable() {34             @Override35             public void run() {36                 test.method1();37             }38         }).start();39 40         new Thread(new Runnable() {41             @Override42             public void run() {43                 test.method2();44             }45         }).start();46     }47 }

synchronize底層原理:

JAVA 虛擬機器中的同步(Synchronization)基於進入和退出Monitor對象實現, 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此。在 Java 語言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法。同步方法 並不是由 monitorenter 和 monitorexit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法表結構的 ACC_SYNCHRONIZED 標誌來隱式實現的,關於這點,稍後詳細分析。

同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM需要保證每一個monitorenter都有一個monitorexit與之相對應。任何對象都有一個monitor與之相關聯,當且一個monitor被持有之後,他將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試擷取對象所對應的monitor所有權,即嘗試擷取對象的鎖;

在JVM中,對象在記憶體中的布局分為三塊地區:對象頭、執行個體變數和填充資料。如下:

執行個體變數:存放類的屬性資料資訊,包括父類的屬性資訊,如果是數組的執行個體部分還包括數組的長度,這部分記憶體按4位元組對齊。

填充資料:由於虛擬機器要求對象起始地址必須是8位元組的整數倍。填充資料不是必須存在的,僅僅是為了位元組對齊,這點瞭解即可。

對象頭:Hotspot虛擬機器的對象頭主要包括兩部分資料:Mark Word(標記欄位)、Klass Pointer(類型指標)。其中Klass Point是是對象指向它的類別中繼資料的指標,虛擬機器通過這個指標來確定這個對象是哪個類的執行個體,Mark Word用於儲存物件自身的運行時資料,它是實現輕量級鎖和偏向鎖的關鍵。

Mark Word:用於儲存物件自身的運行時資料,如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳記等等。Java對象頭一般佔有兩個機器碼(在32位虛擬機器中,1個機器碼等於4位元組,也就是32bit),但是如果對象是數群組類型,則需要三個機器碼,因為JVM虛擬機器可以通過Java對象的中繼資料資訊確定Java對象的大小,但是無法從數組的中繼資料來確認數組的大小,所以用一塊來記錄數組長度。

Monior:我們可以把它理解為一個同步工具,也可以描述為一種同步機制,它通常被描述為一個對象。與一切皆對象一樣,所有的Java對象是天生的Monitor,每一個Java對象都有成為Monitor的潛質,因為在Java的設計中 ,每一個Java對象自打娘胎裡出來就帶了一把看不見的鎖,它叫做內部鎖或者Monitor鎖。Monitor 是線程私人的資料結構,每一個線程都有一個可用monitor record列表,同時還有一個全域的可用列表。每一個被鎖住的對象都會和一個monitor關聯(對象頭的MarkWord中的LockWord指向monitor的起始地址),同時monitor中有一個Owner欄位存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程佔用。其結構如下:

Owner:初始時為NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖後儲存線程唯一標識,當鎖被釋放時又設定為NULL;
EntryQ:關聯一個系統互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個數。
Nest:用來實現重入鎖的計數。
HashCode:儲存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的環境切換(從阻塞到就緒然後因為競爭鎖失敗又被阻塞)從而導致效能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。

 Java虛擬機器對synchronize的最佳化:

鎖的狀態總共有四種,無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖定擴大到輕量級鎖,再升級的重量級鎖,但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級,關於重量級鎖,前面我們已詳細分析過,下面我們將介紹偏向鎖和輕量級鎖以及JVM的其他最佳化手段。

偏向鎖

偏向鎖是Java 6之後加入的新鎖,它是一種針對加鎖操作的最佳化手段,經過研究發現,在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,因此為了減少同一線程擷取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,如果一個線程獲得了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變為偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即擷取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供者的效能。所以,對於沒有鎖競爭的場合,偏向鎖有很好的最佳化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對於鎖競爭比較激烈的場合,偏向鎖就失效了,因為這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗後,並不會立即膨脹為重量級鎖,而是先升級為輕量級鎖。

輕量級鎖

倘若偏向鎖失敗,虛擬機器並不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的最佳化手段(1.6之後加入的),此時Mark Word 的結構也變為輕量級鎖的結構。輕量級鎖能夠提升程式效能的依據是“對絕大部分的鎖,在整個同步周期內都不存在競爭”,注意這是經驗資料。需要瞭解的是,輕量級鎖所適應的情境是線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹為重量級鎖。

自旋鎖

輕量級鎖失敗後,虛擬機器為了避免線程真實地在作業系統層面掛起,還會進行一項稱為自旋鎖的最佳化手段。這是基於在大多數情況下,線程持有鎖的時間都不會太長,如果直接掛起作業系統層面的線程可能會得不償失,畢竟作業系統實現線程之間的切換時需要從使用者態轉換到核心態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設在不久將來,當前的線程可以獲得鎖,因此虛擬機器會讓當前想要擷取鎖的線程做幾個空迴圈(這也是稱為自旋的原因),一般不會太久,可能是50個迴圈或100迴圈,在經過若干次迴圈後,如果得到鎖,就順利進入臨界區。如果還不能獲得鎖,那就會將線程在作業系統層面掛起,這就是自旋鎖的最佳化方式,這種方式確實也是可以提升效率的。最後沒辦法也就只能升級為重量級鎖了。

鎖消除

消除鎖是虛擬機器另外一種鎖的最佳化,這種最佳化更徹底,Java虛擬機器在JIT編譯時間(可以簡單理解為當某段代碼即將第一次被執行時進行編譯,又稱即時編譯),通過對運行內容相關的掃描,去除不可能存在共用資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節省毫無意義的請求鎖時間,如下StringBuffer的append是一個同步方法,但是在add方法中的StringBuffer屬於一個局部變數,並且不會被其他線程所使用,因此StringBuffer不可能存在共用資源競爭的情景,JVM會自動將其鎖消除。

/** * Created by zejian on 2017/6/4. * Blog : http://blog.csdn.net/javazejian  * 消除StringBuffer同步鎖 */public class StringBufferRemoveSync {    public void add(String str1, String str2) {        //StringBuffer是安全執行緒,由於sb只會在append方法中使用,不可能被其他線程引用        //因此sb屬於不可能共用的資源,JVM會自動消除內部的鎖        StringBuffer sb = new StringBuffer();        sb.append(str1).append(str2);    }    public static void main(String[] args) {        StringBufferRemoveSync rmsync = new StringBufferRemoveSync();        for (int i = 0; i < 10000000; i++) {            rmsync.add("abc", "123");        }    }}

synchronize的可重新進入性:

從互斥鎖的設計上來說,當一個線程試圖操作一個由其他線程持有的對象鎖的臨界資源時,將會處於阻塞狀態,但當一個線程再次請求自己持有對象鎖的臨界資源時,這種情況屬於重入鎖,請求將會成功,在java中synchronized是基於原子性的內部鎖機制,是可重新進入的,因此在一個線程調用synchronized方法的同時在其方法體內部調用該對象另一個synchronized方法,也就是說一個線程得到一個對象鎖後再次請求該對象鎖,是允許的,這就是synchronized的可重新進入性。如下:

public class AccountingSync implements Runnable{    static AccountingSync instance=new AccountingSync();    static int i=0;    static int j=0;    @Override    public void run() {        for(int j=0;j<1000000;j++){            //this,當前執行個體對象鎖            synchronized(this){                i++;                increase();//synchronized的可重新進入性            }        }    }    public synchronized void increase(){        j++;    }    public static void main(String[] args) throws InterruptedException {        Thread t1=new Thread(instance);        Thread t2=new Thread(instance);        t1.start();t2.start();        t1.join();t2.join();        System.out.println(i);    }}

  正如代碼所示範的,在擷取當前執行個體對象鎖後進入synchronized代碼塊執行同步代碼,並在代碼塊中調用了當前執行個體對象的另外一個synchronized方法,再次請求當前執行個體鎖時,將被允許,進而執行方法體代碼,這就是重入鎖最直接的體現,需要特別注意另外一種情況,當子類繼承父類時,子類也是可以通過可重新進入鎖調用父類的同步方法。注意由於synchronized是基於monitor實現的,因此每次重入,monitor中的計數器仍會加1。

線程中斷:正如中斷二字所表達的意義,線上程運行(run方法)中間打斷它,在Java中,提供了以下3個有關線程中斷的方法

//中斷線程(執行個體方法)public void Thread.interrupt();//判斷線程是否被中斷(執行個體方法)public boolean Thread.isInterrupted();//判斷是否被中斷並清除當前中斷狀態(靜態方法)public static boolean Thread.interrupted();

 等待喚醒機制與synchronize:所謂等待喚醒機制本篇主要指的是notify/notifyAll和wait方法,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常,這是因為調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,我們知道monitor 存在於對象頭的Mark Word 中(儲存monitor引用指標),而synchronized關鍵字可以擷取 monitor ,這也就是為什麼notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的原因。

 

本篇參考資料:http://blog.csdn.net/javazejian/article/details/72828483?locationNum=5&fps=1

http://www.cnblogs.com/pureEve/p/6421273.html

http://www.cnblogs.com/paddix/p/5367116.html

Java並發編程 Synchronized及其實現原理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.