鎖狀態(tài)
鎖信息存儲在java對象的markword內(nèi)容中
markword數(shù)據(jù)的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit,它的最后2bit是鎖狀態(tài)標志位,用來標記當前對象的狀態(tài),對象的所處的狀態(tài),決定了markword存儲的內(nèi)容,如下表所示:
狀態(tài) 標志位 存儲內(nèi)容
未鎖定 01 對象哈希碼、對象分代年齡
輕量級鎖定 00 指向鎖記錄的指針
膨脹(重量級鎖定) 10 執(zhí)行重量級鎖定的指針
GC標記 11 空(不需要記錄信息)
可偏向 01 偏向線程ID、偏向時間戳、對象分代年齡

偏向鎖
偏向鎖是jdk底層的優(yōu)化,當只有一個線程會訪問同步代碼的時候,啟用偏向鎖
若遇到競爭(另一個線程去訪問同步代碼),會先通過CAS方式嘗試獲取偏向鎖,若偏向鎖未獲取到存在競爭則升級成輕量級鎖
升級輕量級鎖前會釋放偏向鎖,釋放偏向鎖時會先進入安全點,發(fā)生stop the world的事情
輕量級鎖
一開始線程進入同步代碼時,拷貝對象的markdown信息,如果同步對象鎖狀態(tài)為無鎖狀態(tài)(鎖標志位為“01”狀態(tài),是否為偏向鎖為“0”)
線程會在當前棧幀中建立鎖記錄Lock Record,并通過CAS更新對象對象頭信息,將鎖設(shè)置為00輕量鎖
若設(shè)置輕量鎖失敗,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經(jīng)擁有了這個對象的鎖,那就可以直接進入同步塊繼續(xù)執(zhí)行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標志的狀態(tài)值變?yōu)椤?0”,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進入阻塞狀態(tài)。
阻塞
線程阻塞狀態(tài)需要操作系統(tǒng)的介入,在JVM和操作系統(tǒng)中切換線程需要耗費大量的系統(tǒng)資源(用戶線程和內(nèi)核的切換的消耗),因此在進入阻塞狀態(tài)前線程會嘗試通過自旋獲取鎖
自旋鎖
如果持有鎖對象的線程能在短時間內(nèi)釋放鎖,則線程通過自選的方式避免用戶線程與內(nèi)核線程之間的切換。
自旋鎖會消耗cpu資源,在自旋期間線程會一直占用CPU資源,所以需要設(shè)置自旋最大等待時間。
注意:自旋鎖是非公平競爭
線程優(yōu)化
1.根據(jù)線程競爭激烈程度,適當?shù)拈_啟或者關(guān)閉自旋鎖、偏向鎖、輕量級鎖
2.減少鎖的時間:不必要的方法不要放在同步代碼中執(zhí)行
3.減少鎖的粒度:將一個鎖拆成多個邏輯上的鎖,增加并行度,從而降低鎖的競爭
4.使用ConcurrentHashMap:通過多個Segment細分鎖的粒度,put時先獲取需要的Segment
5.LongAdder:LongAdder在AtomicLong的基礎(chǔ)上將單點的更新壓力分散到各個節(jié)點,在低并發(fā)的時候通過對base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并發(fā)的時候通過分散提高了性能
6.LinkedBlockingQueue:對頭和隊尾分別加鎖
拆鎖的粒度不能無限拆,最多可以將一個鎖拆為當前cup數(shù)量個鎖即可;
7.使用讀寫鎖:讀寫分離
8.使用cas(樂觀鎖)
9.消除緩存行的偽共享
Synchonized同步鎖
- 普通同步方法--鎖:當前實例對象
- 靜態(tài)同步方法--鎖:當前類的class對象
- 同步方法快--鎖:Synchonized括號里的對象