- 隨著多進程多線程的出現(xiàn)沸伏,對共享資源(設備糕珊,數(shù)據(jù)等)的競爭往往會導致資源的使用表現(xiàn)為隨機無序
- 例如:一個線程想在控制臺輸出"I am fine",剛寫到"I am"毅糟,就被另一線程搶占控制臺輸出"naughty"红选,導致結(jié)果是"I am naughty";對于資源的被搶占使用留特,我們能怎么辦呢纠脾?當然不是涼拌玛瘸,可使用鎖進行同步管理蜕青,使得資源在加鎖期間,其他線程不可搶占使用
1 鎖的分類
- 悲觀鎖
- 悲觀鎖糊渊,每次去請求數(shù)據(jù)的時候右核,都認為數(shù)據(jù)會被搶占更新(悲觀的想法);所以每次操作數(shù)據(jù)時都要先加上鎖渺绒,其他線程修改數(shù)據(jù)時就要等待獲取鎖贺喝。適用于寫多讀少的場景,synchronized就是一種悲觀鎖
- 樂觀鎖
- 在請求數(shù)據(jù)時宗兼,覺得無人搶占修改躏鱼。等真正更新數(shù)據(jù)時,才判斷此期間別人有沒有修改過(預先讀出一個版本號或者更新時間戳殷绍,更新時判斷是否變化染苛,沒變則期間無人修改);和悲觀鎖不同的是主到,期間數(shù)據(jù)允許其他線程修改
- 自旋鎖
- 一句話茶行,魔力轉(zhuǎn)轉(zhuǎn)圈。當嘗試給資源加鎖卻被其他線程先鎖定時登钥,不是阻塞等待而是循環(huán)再次加鎖
- 在鎖常被短暫持有的場景下畔师,線程阻塞掛起導致CPU上下文頻繁切換,這可用自旋鎖解決牧牢;但自旋期間它占用CPU空轉(zhuǎn)看锉,因此不適用長時間持有鎖的場景
2 synchronized底層原理
- 代碼使用synchronized加鎖,在編譯之后的字節(jié)碼是怎樣的呢
public class Test {
public static void main(String[] args){
synchronized(Test.class){
System.out.println("hello");
}
}
}
截取部分字節(jié)碼塔鳍,如下
4: monitorenter
5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #15 // String hello
10: invokevirtual #17 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
字節(jié)碼出現(xiàn)了4: monitorenter和14: monitorexit兩個指令度陆;字面理解就是監(jiān)視進入,監(jiān)視退出献幔《可以理解為代碼塊執(zhí)行前的加鎖,和退出同步時的解鎖
- 那monitorenter和monitorexit蜡感,又背著我們干了啥呢蹬蚁?
- 執(zhí)行monitorenter指令時恃泪,線程會為鎖對象關(guān)聯(lián)一個ObjectMonitor對象
objectMonitor.cpp
ObjectMonitor() {
_header = NULL;
_count = 0; \\用來記錄獲取鎖的線程數(shù)
_waiters = 0,
_recursions = 0; \\鎖的重入次數(shù)
_object = NULL;
_owner = NULL; \\當前持有ObjectMonitor的線程
_WaitSet = NULL; \\wait()方法調(diào)用后的線程等待隊列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; \\阻塞等待隊列
FreeNext = NULL ;
_EntryList = NULL ; \\synchronized 進來線程的排隊隊列
_SpinFreq = 0 ;
_SpinClock = 0 ; \\自旋計算
OwnerIsThread = 0 ;
}
- 每個線程都有兩個ObjectMonitor對象列表,分別為free和used列表犀斋,如果當前free列表為空贝乎,線程將向全局global list請求分配ObjectMonitor
- ObjectMonitor的owner、WaitSet叽粹、Cxq览效、EntryList這幾個屬性比較關(guān)鍵。WaitSet虫几、Cxq锤灿、EntryList的隊列元素是包裝線程后的對象-ObjectWaiter;而獲取owner的線程辆脸,既為獲得鎖的線程
- monitorenter對應的執(zhí)行方法
void ATTR ObjectMonitor::enter(TRAPS) {
...
//獲取鎖:cmpxchg_ptr原子操作但校,嘗試將_owner替換為自己,并返回舊值
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
...
// 重復獲取鎖啡氢,次數(shù)加1状囱,返回
if (cur == Self) {
_recursions ++ ;
return ;
}
//首次獲取鎖情況處理
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
...
//嘗試自旋獲取鎖
if (Knob_SpinEarly && TrySpin (Self) > 0) {
...
-
monitorexit對應的執(zhí)行方法
void ATTR ObjectMonitor::exit(TRAPS)...
代碼太長,就不貼了倘是。主要是recursions減1亭枷、count減少1或者如果線程不再持有owner(非重入加鎖)則設置owner為null,退鎖的持有狀態(tài)搀崭,并喚醒Cxq隊列的線程
總結(jié)
- 線程遇到synchronized同步時叨粘,先會進入EntryList隊列中,然后嘗試把owner變量設置為當前線程门坷,同時monitor中的計數(shù)器count加1宣鄙,即獲得對象鎖。否則通過嘗試自旋一定次數(shù)加鎖默蚌,失敗則進入Cxq隊列阻塞等待
image -
線程執(zhí)行完畢將釋放持有的owner冻晤,owner變量恢復為null,count自減1绸吸,以便其他線程進入獲取鎖
image - synchronized修飾方法原理也是類似的鼻弧。只不過沒用monitor指令,而是使用ACC_SYNCHRONIZED標識方法的同步
public synchronized void lock(){
System.out.println("world");
}
....
public synchronized void lock();
descriptor: ()V
flags: (0x0029) ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #26 // String world
5: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- synchronized是可重入锦茁,非公平鎖攘轩,因為entryList的線程會先自旋嘗試加鎖,而不是加入cxq排隊等待码俩,不公平
3 Object的wait和notify方法原理
- wait度帮,notify必須是持有當前對象鎖Monitor的線程才能調(diào)用 (對象鎖代指ObjectMonitor/Monitor,鎖對象代指Object)
- 上面有說到,當在sychronized中鎖對象Object調(diào)用wait時會加入waitSet隊列笨篷,WaitSet的元素對象就是ObjectWaiter
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};
調(diào)用對象鎖的wait()方法時瞳秽,線程會被封裝成ObjectWaiter,最后使用park方法掛起
//objectMonitor.cpp
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS){
...
//線程封裝成 ObjectWaiter對象
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT ;
...
//一系列判斷操作率翅,當線程確實加入WaitSet時练俐,則使用park方法掛起
if (node._notified == 0) {
if (millis <= 0) {
Self->_ParkEvent->park () ;
} else {
ret = Self->_ParkEvent->park (millis) ;
}
}
而當對象鎖使用notify()時
- 如果waitSet為空,則直接返回
- waitSet不為空從waitSet獲取一個ObjectWaiter冕臭,然后根據(jù)不同的Policy加入到EntryList或通過
Atomic::cmpxchg_ptr
指令自旋操作加入cxq隊列或者直接unpark喚醒
void ObjectMonitor::notify(TRAPS){
CHECK_OWNER();
//waitSet為空腺晾,則直接返回
if (_WaitSet == NULL) {
TEVENT (Empty-Notify) ;
return ;
}
...
//通過DequeueWaiter獲取_WaitSet列表中的第一個ObjectWaiter
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
ObjectWaiter * iterator = DequeueWaiter() ;
if (iterator != NULL) {
....
if (Policy == 2) { // prepend to cxq
// prepend to cxq
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
iterator->TState = ObjectWaiter::TS_CXQ ;
for (;;) {
ObjectWaiter * Front = _cxq ;
iterator->_next = Front ;
if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {
break ;
}
}
}
}
- Object的notifyAll方法則對應
voidObjectMonitor::notifyAll(TRAPS)
,流程和notify類似辜贵。不過會通過for循環(huán)取出WaitSet的ObjectWaiter節(jié)點悯蝉,再依次喚醒所有線程
4 jvm對synchronized的優(yōu)化
-
先介紹下32位JVM下JAVA對象頭的結(jié)構(gòu)
image -
偏向鎖
- 未加鎖的時候,鎖標志為01念颈,包含哈希值泉粉、年齡分代和偏向鎖標志位(0)
- 施加偏向鎖時连霉,哈希值和一部分無用內(nèi)存會轉(zhuǎn)化為鎖主人的線程信息榴芳,以及加鎖時的時間戳epoch,此時鎖標志位沒變跺撼,偏向鎖標志改為1
- 加鎖時先判斷當前線程id是否與MarkWord的線程id是否一致窟感,一致則執(zhí)行同步代碼;不一致則檢查偏向標志是否偏向歉井,未偏向則使用CAS加鎖柿祈;未偏向CAS加鎖失敗和存在偏向鎖會導致偏向鎖膨脹為輕量級鎖,或者重新偏向
- 偏向鎖只有遇到其他線程競爭偏向鎖時哩至,持有偏向鎖的線程才會釋放鎖躏嚎,線程不會主動去釋放偏向鎖
-
輕量級鎖
- 當發(fā)生多個線程競爭時,偏向鎖會變?yōu)檩p量級鎖菩貌,鎖標志位為00
- 獲得鎖的線程會先將偏向鎖撤銷(在安全點)卢佣,并在棧楨中創(chuàng)建鎖記錄LockRecord,對象的MarkWord被復制到剛創(chuàng)建的LockRecord箭阶,然后CAS嘗試將記錄LockRecord的owner指向鎖對象虚茶,再將鎖對象的MarkWord指向鎖,加鎖成功
- 如果CAS加鎖失敗仇参,線程會自旋一定次數(shù)加鎖嘹叫,再失敗則升級為重量級鎖
image
-
重量級鎖
- 重量級鎖就是上面介紹到synchronized使用監(jiān)視器Monitor實現(xiàn)的鎖機制
- 競爭線程激烈,鎖則繼續(xù)膨脹诈乒,變?yōu)橹亓考夋i罩扇,也是互斥鎖,鎖標志位為10怕磨,MarkWord其余內(nèi)容被替換為一個指向?qū)ο箧iMonitor的指針
-
自旋鎖
- 減少不必要的CPU上下文切換喂饥;在輕量級鎖升級為重量級鎖時寞缝,就使用了自旋加鎖的方式
-
鎖粗化
- 多次加鎖操作在JVM內(nèi)部也是種消耗,如果多個加鎖可以合并為一個鎖仰泻,就可減少不必要的開銷
Test.class
//編譯器會考慮將兩次加鎖合并
public void test(){
synchronized(this){
System.out.println("hello");
}
synchronized(this){
System.out.println("world");
}
}
- 鎖消除
- 刪除不必要的加鎖操作荆陆,如果變量是獨屬一個線程的棧變量,加不加鎖都是安全的集侯,編譯器會嘗試消除鎖
- 開啟鎖消除需要在JVM參數(shù)上設置
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
//StringBuffer的append操作會加上synchronized被啼,
//但是變量buf不加鎖也安全的,編譯器會把鎖消除
public void test() {
StringBuffer buf = new StringBuffer();
buf.append("hello").append("world");
}
- 其他鎖優(yōu)化方法
- 分段鎖棠枉,分段鎖也并非一種實際的鎖浓体,而是一種思想;ConcurrentHashMap是學習分段鎖的最好實踐辈讶。主要是將大對象拆成小對象命浴,然后對大對象的加鎖操作變成對小對象加鎖,增加了并行度
5 CAS的底層原理
- 在
volatile int i = 0; i++
中贱除,volatile類型的讀寫是原子同步的生闲,但是i++卻不能保證同步性,我們該怎么呢月幌? - 可以使用synchronized加鎖碍讯;還有就是用CAS(比較并交換),使用樂觀鎖的思想同步扯躺,先判斷共享變量是否改變捉兴,沒有則更新。下面看看不同步版本的CAS
int expectedValue = 1;
public boolean compareAndSet(int newValue) {
if(expectedValue == 1){
expectedValue = newValue;
return ture;
}
return false;
}
在jdk是有提供同步版的CAS解決方案录语,其中使用了UnSafe.java的底層方法
//UnSafe.java
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x) ..
@HotSpotIntrinsicCandidate
public final native int compareAndExchangeInt(Object o, long offset, int expected, int x)...
我們再來看看本地方法倍啥,Unsafe.cpp中的compareAndSwapInt
//unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
在Linux的x86,Atomic::cmpxchg方法的實現(xiàn)如下
/**
1 __asm__表示匯編的開始澎埠;
2 volatile表示禁止編譯器優(yōu)化虽缕;//禁止指令重排
3 LOCK_IF_MP是個內(nèi)聯(lián)函數(shù),
根據(jù)當前系統(tǒng)是否為多核處理器失暂,
決定是否為cmpxchg指令添加lock前綴 //內(nèi)存屏障
*/
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
到這一步彼宠,可以總結(jié)到:jdk提供的CAS機制,在匯編層級弟塞,會禁止變量兩側(cè)的指令優(yōu)化凭峡,然后使用cmpxchg指令比較并更新變量值(原子性),如果是多核則使用lock鎖定(緩存鎖决记、MESI)
6 CAS同步操作的問題
- ABA問題
- 線程X準備將變量的值從A改為B摧冀,然而這期間線程Y將變量的值從A改為C,然后再改為A;最后線程X檢測變量值是A索昂,并置換為B建车。但實際上,A已經(jīng)不再是原來的A了
- 解決方法椒惨,是把變量定為唯一類型缤至。值可以加上版本號,或者時間戳康谆。如加上版本號领斥,線程Y的修改變?yōu)锳1->B2->A3,此時線程X再更新則可以判斷出A1不等于A3
- 只能保證一個共享變量的原子操作
- 只保證一個共享變量的原子操作沃暗,對多個共享變量同步時月洛,循環(huán)CAS是無法保證操作的原子
7 基于volatile + CAS 實現(xiàn)同步鎖的原理
- CAS只能同步一個變量的修改,我們又應該如何用它來鎖住代碼塊呢孽锥?
- 先說說實現(xiàn)鎖的要素
- 1 同步代碼塊同一時刻只能有一個線程能執(zhí)行
- 2 加鎖操作要happens-before同步代碼塊里的操作嚼黔,而代碼塊里的操作要happens-before解鎖操作
- 3 同步代碼塊結(jié)束后相對其他線程其修改的變量是可見的 (內(nèi)存可見性)
- 要素1:可以利用CAS的原子性來實現(xiàn),任意時刻只有一個線程能成功操作變量
- 先設想CAS操作的共享變量是一個關(guān)聯(lián)代碼塊的同步狀態(tài)變量惜辑,同步開始之前先CAS更新狀態(tài)變量為加鎖狀態(tài)唬涧,同步結(jié)束之后,再CAS狀態(tài)變量為無鎖狀態(tài)
- 如果期間有第二個線程來加鎖韵丑,則會發(fā)現(xiàn)狀態(tài)變量為加鎖狀態(tài)爵卒,則放棄執(zhí)行同步代碼塊
- 要素2:使用volatile修飾狀態(tài)變量虚缎,禁止指令重排
- volatile保證同步代碼里的操作happens-before解鎖操作撵彻,而加鎖操作happens-before代碼塊里的操作
- 要素3:還是用volatile,volatile變量寫指令前后會插入內(nèi)存屏障
- volatile修飾的狀態(tài)變量被CAS為無鎖狀態(tài)前实牡,同步代碼塊的臟數(shù)據(jù)就會被更新陌僵,被各個線程可見
//偽代碼
volatile state = 0 ; // 0-無鎖 1-加鎖;volatile禁止指令重排创坞,加入內(nèi)存屏障
...
if(cas(state, 0 , 1)){ // 1 加鎖成功碗短,只有一個線程能成功加鎖
... // 2 同步代碼塊
cas(state, 1, 0); // 3 解鎖時2的操作具有可見性
}
8 LockSupport了解一下
- LockSupport是基于Unsafe類,由JDK提供的線程操作工具類题涨,主要作用就是掛起線程偎谁,喚醒線程。Unsafe.park纲堵,unpark操作時巡雨,會調(diào)用當前線程的變量parker代理執(zhí)行。Parker代碼
JavaThread* thread=JavaThread::thread_from_jni_environment(env);
...
thread->parker()->park(isAbsolute != 0, time);
class PlatformParker : public CHeapObj {
protected:
//互斥變量類型
pthread_mutex_t _mutex [1] ;
//條件變量類型
pthread_cond_t _cond [1] ;
...
}
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
- 在Linux系統(tǒng)下席函,用的POSIX線程庫pthread中的mutex(互斥量)铐望,condition來實現(xiàn)線程的掛起、喚醒
- 注意點:當park時,counter變量被設置為0正蛙,當unpark時督弓,這個變量被設置為1
- unpark和park執(zhí)行順序不同時,counter和cond的狀態(tài)變化如下
- 先park后unpark; park:counter值不變乒验,但會設置一個cond; unpark:counter先加1愚隧,檢查cond存在,counter減為0
- 先unpark后park锻全;park:counter變?yōu)?奸攻,但不設置cond;unpark:counter減為0(線程不會因為park掛起)
- 先多次unpark虱痕;counter也只設置為為1
9 LockSupport.park和Object.wait區(qū)別
- 兩種方式都有具有掛起的線程的能力
- 線程在Object.wait之后必須等到Object.notify才能喚醒
- LockSupport可以先unpark線程睹耐,等線程執(zhí)行LockSupport.park是不會掛起的,可以繼續(xù)執(zhí)行
- 需要注意的是就算線程多次unpark部翘;也只能讓線程第一次park是不會掛起
10 AbstractQueuedSynchronizer(AQS)
- AQS其實就是基于volatile+cas實現(xiàn)的鎖模板硝训;如果需要線程阻塞等待,喚醒機制新思,則使用LockSupport掛起窖梁、喚醒線程
//AbstractQueuedSynchronizer.java
public class AbstractQueuedSynchronizer{
//線程節(jié)點
static final class Node {
...
volatile Node prev;
volatile Node next;
volatile Thread thread;
...
}
....
//head 等待隊列頭尾節(jié)點
private transient volatile Node head;
private transient volatile Node tail;
// The synchronization state. 同步狀態(tài)
private volatile int state;
...
//提供CAS操作,狀態(tài)具體的修改由子類實現(xiàn)
protected final boolean compareAndSetState(int expect, int update) {
return STATE.compareAndSet(this, expect, update);
}
}
- AQS內(nèi)部維護一個同步隊列夹囚,元素就是包裝了線程的Node
-
同步隊列中首節(jié)點是獲取到鎖的節(jié)點纵刘,它在釋放鎖的時會喚醒后繼節(jié)點,后繼節(jié)點獲取到鎖的時候荸哟,會把自己設為首節(jié)點
image
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 線程會先嘗試獲取鎖假哎,失敗則封裝成Node,CAS加入同步隊列的尾部鞍历。在加入同步隊列的尾部時舵抹,會判斷前驅(qū)節(jié)點是否是head結(jié)點,并嘗試加鎖(可能前驅(qū)節(jié)點剛好釋放鎖)劣砍,否則線程進入阻塞等待
在AQS還存一個ConditionObject的內(nèi)部類惧蛹,它的使用機制和Object.wait、notify類似
//AbstractQueuedSynchronizer.java
public class ConditionObject implements Condition, java.io.Serializable {
//條件隊列;Node 復用了AQS中定義的Node
private transient Node firstWaiter;
private transient Node lastWaiter;
...
- 每個Condition對象內(nèi)部包含一個Node元素的FIFO條件隊列
- 當一個線程調(diào)用Condition.await()方法刑枝,那么該線程將會釋放鎖香嗓、構(gòu)造Node加入條件隊列并進入等待狀態(tài)
//類似Object.wait
public final void await() throws InterruptedException{
...
Node node = addConditionWaiter(); //構(gòu)造Node,加入條件隊列
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//掛起線程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//notify喚醒線程后,加入同步隊列繼續(xù)競爭鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
image
- 調(diào)用Condition.signal時装畅,獲取條件隊列的首節(jié)點靠娱,將其移動到同步隊列并且利用LockSupport喚醒節(jié)點中的線程。隨后繼續(xù)執(zhí)行wait掛起前的狀態(tài)洁灵,調(diào)用acquireQueued(node, savedState)競爭同步狀態(tài)
//類似Object.notify
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
image
- volatile+cas機制保證了代碼的同步性和可見性饱岸,而AQS封裝了線程阻塞等待掛起掺出,解鎖喚醒其他線程的邏輯。AQS子類只需根據(jù)狀態(tài)變量苫费,判斷是否可獲取鎖汤锨,是否釋放鎖成功即可
- 繼承AQS需要選性重寫以下幾個接口
protected boolean tryAcquire(int arg);//嘗試獨占性加鎖
protected boolean tryRelease(int arg);//對應tryAcquire釋放鎖
protected int tryAcquireShared(int arg);//嘗試共享性加鎖
protected boolean tryReleaseShared(int arg);//對應tryAcquireShared釋放鎖
protected boolean isHeldExclusively();//該線程是否正在獨占資源,只有用到condition才需要取實現(xiàn)它
11 ReentrantLock的原理
image
- ReentrantLock實現(xiàn)了Lock接口百框,并使用內(nèi)部類Sync(Sync繼承AbstractQueuedSynchronizer)來實現(xiàn)同步操作
- ReentrantLock內(nèi)部類Sync
abstract static class Sync extends AbstractQueuedSynchronizer{
....
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//直接CAS狀態(tài)加鎖闲礼,非公平操作
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
...
//重寫了tryRelease
protected final boolean tryRelease(int releases) {
c = state - releases; //改變同步狀態(tài)
...
//修改volatile 修飾的狀態(tài)變量
setState(c);
return free;
}
}
- Sync的子類NonfairSync和FairSync都重寫了tryAcquire方法
- 其中NonfairSync的tryAcquire調(diào)用父類的nonfairTryAcquire方法, FairSync則自己重寫tryAcquire的邏輯。其中調(diào)用hasQueuedPredecessors()判斷是否有排隊Node铐维,存在則返回false(false會導致當前線程排隊等待鎖)
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
....
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
....
12 AQS排他鎖的實例demo
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
@Override
public void lockInterruptibly() throws InterruptedException { throw new RuntimeException(""); }
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new RuntimeException("");}
@Override
public Condition newCondition() { return sync.newCondition(); }
@Override
public void lock() { sync.acquireShared(1); }
@Override
public void unlock() { sync.releaseShared(1); } }
@Override
public boolean tryLock() { return sync.tryAcquireShared(1) > -1; }
}
再來看看Sync的代碼
class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must large than zero");
}
setState(count);
}
@Override
public int tryAcquireShared(int reduceCount) {
for (; ; ) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
public boolean tryReleaseShared(int returnCount) {
for (; ; ) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
public Condition newCondition() {
return new AbstractQueuedSynchronizer.ConditionObject();
}
}
13 使用鎖柬泽,能防止線程死循環(huán)嗎
- 答案是不一定的;對于單個資源來說是可以做的嫁蛇;但是多個資源會存在死鎖的情況锨并,例如線程A持有資源X,等待資源Y睬棚,而線程B持有資源Y第煮,等待資源X
- 有了鎖,可以對資源加狀態(tài)控制抑党,但是我們還需要防止死鎖的產(chǎn)生包警,打破產(chǎn)生死鎖的四個條件之一就行
- 1 資源不可重復被兩個及以上的使用者占用
- 2 使用者持有資源并等待其他資源
- 3 資源不可被搶占
- 4 多個使用者形成等待對方資源的循環(huán)圈
14 ThreadLocal是否可保證資源的同步
- 當使用ThreadLocal聲明變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本底靠,每一個線程都可以獨立地改變自己的副本害晦,而不會影響其它線程所對應的副本
- 從上面的概念可知,ThreadLocal其實并不能保證變量的同步性暑中,只是給每一個線程分配一個變量副本