使用synchronized
的緣由在於:它可以確保多個線程在同一時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性。bash
在JDK 1.6
以前,synchronized
的實現是基於對象上的監視器,這也被稱爲重量鎖。默認狀況下,每個對象都有一個關聯的Monitor
,而每一個Monitor
包含了一個EntryCount
計數器,它是synchronized
實現可重入的關鍵。優化
在JDK 1.6
以後,對鎖進行了一系列優化的措施,經過引入自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減小鎖操做的開銷。this
這些優化措施最終的目的是減小鎖操做的開銷,然而它所改變的只是鎖的實現方式,可是加鎖和解鎖這一基本原則是沒有改變的。這篇文章主要是介紹synchronized
的使用,所以,在後面的介紹中,咱們仍是按照比較容易理解的重量鎖的方式進行分析,在以後的文章中,咱們再來談一下優化後的實現策略。spa
當一個線程執行某個對象的同步方法或者代碼塊時,會先檢查這個對象所關聯的Monitor's EntryCount
是否爲0
:線程
EntryCount
爲0
,那麼該線程就會將Monitor’s EntryCount
設置爲1
,併成爲該Monitor
的全部者,接着執行該方法或者代碼塊中的語句。EntryCount
不爲0
,這時會去檢查對象所關聯的Monitor
的持有者是哪個線程:Monitor
的線程就是當前正在嘗試獲取Monitor
的線程,那麼將EntryCount
的數值加1
,繼續執行方法或者代碼塊中的語句。Monitor
的是其它的線程,那麼該線程進入阻塞狀態,直到EntryCount
的數值變爲0
。當一個線程從同步方法或者代碼塊退出時,會將EntryCount
減1
,若是EntryCount
變爲0
,那麼該線程會釋放它所持有的Monitor
。以前那些阻塞在synchronized
的線程會嘗試去獲取Monitor
,成功獲取Monitor
的線程能夠進入同步方法或者代碼塊。code
對於synchronized
的使用,咱們有兩種分類方法:對象
Monitor
關聯的對象分類。不少介紹synchronized
的文章,都是經過使用場景進行分類的,通常來講能夠分爲以下四種使用場景,而每種場景下根據Monitor
所關聯的對象不一樣,又會衍生出另外的用法:同步
//靜態方法,使用的是Class類鎖
synchronized public static void staticMethod() {}
複製代碼
private static final byte[] mStaticLockByte = new byte[1];
//靜態方法代碼塊1,使用的是Class類鎖
public static void staticBlock1() {
synchronized (SynchronizedObject.class) {}
}
//靜態方法代碼塊2,使用的是內部靜態變量鎖
public static void staticBlock2() {
synchronized (mStaticLockByte) {}
}
複製代碼
//普通方法,使用的是調用該方法的對象鎖
synchronized public void method() {}
複製代碼
private static final byte[] mStaticLockByte = new byte[1];
private final byte[] mLockByte = new byte[1];
//普通方法代碼塊1,使用的是Class類鎖
public void block1() {
synchronized (SynchronizedObject.class) {}
}
//普通方法代碼塊2,使用的是mLockByte的變量鎖
public void block2() {
synchronized (mLockByte) {} //變量須要聲明爲final
}
//普通方法代碼塊3,使用的是mStaticLockByte的變量鎖
public void block3() {
synchronized (mStaticLockByte) {}
}
//普通方法代碼塊4,使用的是調用該方法的對象鎖
public void block4() {
synchronized (this) {}
}
複製代碼
根據使用場景進行分類,主要是爲了讓你們知道如何使用synchronized
關鍵字,然而要真正地理解synchronized
,就須要結合第二節談到的synchronized
原理,其實3.1
中談到的多種場景,都是和Monitor
有關,那麼從和Monitor
關聯的對象來看,咱們從新對3.1
中的8
種場景從新進行分類:it
Class
對象:SynchronizedObject.class
SynchronizedObject.class
this
mStaticLockByte
mStaticLockByte
mLockByte
若是使用場景屬於上面的同一個分類當中,那麼纔有可能產生線程阻塞在synchronized
關鍵字的狀況,舉一個例子,若是A
線程經過靜態方法訪問(分類一)而且沒有從該方法退出:io
B
線程是經過一個對象的普通方法來訪問(分類二),那麼是不會阻塞的,這是由於調用該方法的對象所關聯的Monitor
沒有被持有。B
線程使用的是靜態方法代碼塊來訪問,而該靜態方法代碼塊使用的是SynchronizedObject.class
來修飾(分類一),因爲這兩種使用場景是屬於同一個分類,那麼就會B
線程就會進入阻塞狀態,這是由於SynchronizedObject
類所關聯的Monitor
已經被A
線程持有了。從表面上來看,synchronized
的使用能夠簡單地分爲同步方法和同步代碼塊,可是究竟在什麼狀況下會致使一個線程在synchronized
上阻塞,則須要分析synchronized
方法所嘗試獲取的Monitor
的是否已經被其它線程持有了。