[TOC]
## 三種使用方式
* 修飾普通的實例方法,對于普通的同步方法,鎖式當前實例對象
* 修飾靜態方法,對于靜態同步方法,鎖式當前類的Class對象
* 修飾代碼塊,對于同步方法塊,鎖是Synchronized配置的對象
## java對象頭

”Mark Word"存儲一下消息
~~~
hash:保存對象的哈希碼
age:GC分代年齡
biased_lock:偏向鎖標志
lock:鎖狀態標志
JavaThread* 當前線程
epoch:保存偏向時間戳
//其中關于當前鎖的狀態標志markOopDesc類中也進行了詳細的說明,具體代碼如下:
enum { locked_value = 0,//輕量級鎖 對應[00]
unlocked_value = 1,//無鎖狀態 對應[01]
monitor_value = 2,//重量級鎖 對應[10]
marked_value = 3,//GC標記 對應[11]
biased_lock_pattern = 5//是否是偏向鎖 對應[101] 其中biased_lock一個bit位,lock兩個bit位
};
~~~
## synchronized鎖優化
Java SE 1.6為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。在Java SE 1.6中,鎖一共有4種狀態,級別從低到高依次是:
1. 無鎖狀態
2. 偏向鎖狀態
3. 輕量級鎖狀態
4. 重量級鎖狀態
這幾個狀態會隨著競爭情況逐漸升級,鎖**可以升級但不能降級**,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率
所謂的鎖,本質上就是一個共享變量,這個變量標識了某個共享對象是否被其他線程占用。 線程在訪問共享對象時,要先判斷該共享變量。
### 偏向鎖
理解鎖被同一線程
在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。為了讓線程獲得鎖的代價更低而引入了偏向鎖
當一個線程訪問同步塊并獲取鎖時,會在對象頭中的“Mark word"和棧幀中的鎖記錄里存儲鎖偏向的線程ID。以后該線程在進入和退出同步塊時,不需要進行CAS操作來加鎖和解鎖。只需簡單地測試一下對象頭的”Mark Word“里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下“Mark Word”中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。
### 輕量級鎖
理解鎖被多個線程,但線程之間不存在競爭
* 線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。
* 然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
### 重量級鎖
理解鎖被多個線程,且線程之間存在競爭
重量級鎖的競爭是在objectMonitor.cpp中ObjectMonitor::enter()方法中實現的。
簡述整個過程,可以是根據虛擬機規范的要求,在執行monitorenter指令時:
1. 首先要嘗試獲取對象的鎖。
2. 如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象的鎖,把鎖的計數器加1。相應的,在執行monitorexit指令時會將鎖計數器減1,當計數器為0時,鎖就被釋放。
3. 如果獲取對象鎖失敗,那當前線程就要阻塞等待,直到對象鎖被另外一個線程釋放為止。
虛擬機規范對monitorenter和monitorexit的行為描述中,有兩點是需要特別注意的。
1. synchronized同步塊對同一條線程來說是可重入的,不會出現自己把自己鎖死的問題
2. 同步塊在已進入的線程執行完之前,會阻塞后面其他線程的進入。
**ObjectMonitor結構**
在講解具體的鎖獲取之前,我們需要了解**每個鎖對象(這里指已經升級為重量級鎖的對象)都有一個ObjectMonitor(對象監視器)**。也就是說**每個線程獲取鎖對象都會通過ObjectMonitor**
~~~
class ObjectMonitor {
public:
enum {
OM_OK, // 沒有錯誤
OM_SYSTEM_ERROR, // 系統錯誤
OM_ILLEGAL_MONITOR_STATE, // 監視器狀態異常
OM_INTERRUPTED, // 當前線程已經中斷
OM_TIMED_OUT // 線程等待超時
};
volatile markOop _header; // 線程幀棧中存儲的 鎖對象的mark word拷貝
protected: // protected for JvmtiRawMonitor
void * volatile _owner; // 指向獲得objectMonitor的線程或者 BasicLock對象
volatile jlong _previous_owner_tid; // 上一個獲得objectMonitor的線程id
volatile intptr_t _recursions; // 同一線程重入鎖的次數,如果是0,表示第一次進入
ObjectWaiter * volatile _EntryList; // 在進入或者重進入阻塞狀態下的線程鏈表
protected:
ObjectWaiter * volatile _WaitSet; // 處于等待狀態下的線程鏈表 ObjectWaiter
volatile jint _waiters; //處于等待狀態下的線程個數
}
~~~
重量級級鎖的競爭步驟,主要分為以下幾個步驟:
1. 通過CAS操作嘗試吧monitor的\_owner( 指向獲得objectMonitor的線程或者 BasicLock對象)設置為當前線程,如果CAS操作成功,表示線程獲取鎖成功,直接執行同步代碼塊。
2. 如果是同一線程重入鎖,則記錄當前重入的次數。
3. 如果2,3步驟都不滿足,則開始競爭鎖,走EnterI()方法。
EnterI()方法實現如下:
1. 把當前線程被封裝成ObjectWaiter的node對象,同時將該線程狀態設置為TS\_CXQ(競爭狀態)
2. 在for循環中,通過CAS把node節點push到\_cxq鏈表中,如果CAS操作失敗,繼續嘗試,是因為當期\_cxq鏈表已經發生改變了繼續for循環,如果成功直接返回。
3. 將node節點push到\_cxq鏈表之后,通過自旋嘗試獲取鎖(TryLock方法獲取鎖),如果循環一定次數后,還獲取不到鎖,則通過park函數掛起。(并不會消耗CPU資源)
重量級鎖的釋放可以分為以下步驟:
1. 判斷當前鎖對象中的\_owner沒有指向當前線程,如果\_owner指向的BasicLock在當前線程棧上,那么將\_owner指向當前線程。
2. 如果當前鎖對象中的\_owner指向當前線程,則判斷當前線程重入鎖的次數,如果不為0,那么就重新走ObjectMonitor::exit(),直到重入鎖次數為0為止。
3. 釋放當前鎖,并根據QMode的模式判斷,是否將\_cxq中掛起的線程喚醒。還是其他操作。
## 參考資料
[Java并發編程:synchronized](https://blog.csdn.net/fei20121106/article/details/83268379)
- Java
- Object
- 內部類
- 異常
- 注解
- 反射
- 靜態代理與動態代理
- 泛型
- 繼承
- JVM
- ClassLoader
- String
- 數據結構
- Java集合類
- ArrayList
- LinkedList
- HashSet
- TreeSet
- HashMap
- TreeMap
- HashTable
- 并發集合類
- Collections
- CopyOnWriteArrayList
- ConcurrentHashMap
- Android集合類
- SparseArray
- ArrayMap
- 算法
- 排序
- 常用算法
- LeetCode
- 二叉樹遍歷
- 劍指
- 數據結構、算法和數據操作
- 高質量的代碼
- 解決問題的思路
- 優化時間和空間效率
- 面試中的各項能力
- 算法心得
- 并發
- Thread
- 鎖
- java內存模型
- CAS
- 原子類Atomic
- volatile
- synchronized
- Object.wait-notify
- Lock
- Lock之AQS
- Lock子類
- 鎖小結
- 堵塞隊列
- 生產者消費者模型
- 線程池