1. 程式人生 > >基礎篇:詳解鎖原理,volatile+cas、synchronized的底層實現

基礎篇:詳解鎖原理,volatile+cas、synchronized的底層實現

[TOC](目錄) - 隨著多程序多執行緒的出現,對共享資源(裝置,資料等)的競爭往往會導致資源的使用表現為隨機無序 - 例如:一個執行緒想在控制檯輸出"I am fine",剛寫到"I am",就被另一執行緒搶佔控制檯輸出"naughty",導致結果是"I am naughty";對於資源的被搶佔使用,我們能怎麼辦呢?當然不是涼拌,可使用鎖進行同步管理,使得資源在加鎖期間,其他執行緒不可搶佔使用 # 1 鎖的分類 - 悲觀鎖 - 悲觀鎖,每次去請求資料的時候,都認為資料會被搶佔更新(悲觀的想法);所以每次操作資料時都要先加上鎖,其他執行緒修改資料時就要等待獲取鎖。適用於寫多讀少的場景,synchronized就是一種悲觀鎖 - 樂觀鎖 - 在請求資料時,覺得無人搶佔修改。等真正更新資料時,才判斷此期間別人有沒有修改過(預先讀出一個版本號或者更新時間戳,更新時判斷是否變化,沒變則期間無人修改);和悲觀鎖不同的是,期間資料允許其他執行緒修改 - 自旋鎖 - 一句話,魔力轉轉圈。當嘗試給資源加鎖卻被其他執行緒先鎖定時,不是阻塞等待而是迴圈再次加鎖 - 在鎖常被短暫持有的場景下,執行緒阻塞掛起導致CPU上下文頻繁切換,這可用自旋鎖解決;但自旋期間它佔用CPU空轉,因此不適用長時間持有鎖的場景 # 2 synchronized底層原理 - 程式碼使用synchronized加鎖,在編譯之後的位元組碼是怎樣的呢 ```java public class Test { public static void main(String[] args){ synchronized(Test.class){ System.out.println("hello"); } } } ``` 擷取部分位元組碼,如下 ```c 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 ``` 位元組碼出現了4: monitorenter和14: monitorexit兩個指令;字面理解就是監視進入,監視退出。可以理解為程式碼塊執行前的加鎖,和退出同步時的解鎖 - 那monitorenter和monitorexit,又揹著我們幹了啥呢? - 執行monitorenter指令時,執行緒會為鎖物件關聯一個ObjectMonitor物件 ```c objectMonitor.cpp ObjectMonitor() { _header = NULL; _count = 0; \\用來記錄獲取該鎖的執行緒數 _waiters = 0, _recursions = 0; \\鎖的重入次數 _object = NULL; _owner = NULL; \\當前持有ObjectMonitor的執行緒 _WaitSet = NULL; \\wait()方法呼叫後的執行緒等待佇列 _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這幾個屬性比較關鍵。WaitSet、Cxq、EntryList的佇列元素是包裝執行緒後的物件-ObjectWaiter;而獲取owner的執行緒,既為獲得鎖的執行緒 - **monitorenter對應的執行方法** ```c void ATTR ObjectMonitor::enter(TRAPS) { ... //獲取鎖:cmpxchg_ptr原子操作,嘗試將_owner替換為自己,並返回舊值 cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; ... // 重複獲取鎖,次數加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對應的執行方**法`void ATTR ObjectMonitor::exit(TRAPS)...`程式碼太長,就不貼了。主要是recursions減1、count減少1或者如果執行緒不再持有owner(非重入加鎖)則設定owner為null,退鎖的持有狀態,並喚醒Cxq佇列的執行緒 **總結** - 執行緒遇到synchronized同步時,先會進入EntryList佇列中,然後嘗試把owner變數設定為當前執行緒,同時monitor中的計數器count加1,即獲得物件鎖。否則通過**嘗試自旋一定次數加鎖**,失敗則進入Cxq佇列阻塞等待 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE4LzE3MzYwMDgwMDAyY2Q5NmU?x-oss-process=image/format,png) - 執行緒執行完畢將釋放持有的owner,owner變數恢復為null,count自減1,以便其他執行緒進入獲取鎖 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE4LzE3MzYwNDJkODJiOTJiMjk?x-oss-process=image/format,png) - synchronized修飾方法原理也是類似的。只不過沒用monitor指令,而是使用ACC_SYNCHRONIZED標識方法的同步 ```c 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的執行緒才能呼叫 (物件鎖代指ObjectMonitor/Monitor,鎖物件代指Object) - 上面有說到,當在sychronized中鎖物件Object呼叫wait時會加入waitSet佇列,WaitSet的元素物件就是ObjectWaiter ```java 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); }; ``` **呼叫物件鎖的wait()方法時,執行緒會被封裝成ObjectWaiter,最後使用park方法掛起** ```c //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,然後根據不同的Policy加入到EntryList或通過`Atomic::cmpxchg_ptr`指令自旋操作加入**cxq佇列**或者直接unpark喚醒 ```c 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迴圈取出WaitSet的ObjectWaiter節點,再依次喚醒所有執行緒 # 4 jvm對synchronized的優化 - 先介紹下32位JVM下JAVA物件頭的結構 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE4LzE3MzYxYTFjMDU5ZGFiYTE?x-oss-process=image/format,png) - 偏向鎖 - 未加鎖的時候,鎖標誌為01,包含雜湊值、年齡分代和偏向鎖標誌位(0) - 施加偏向鎖時,雜湊值和一部分無用記憶體會轉化為鎖主人的執行緒資訊,以及加鎖時的時間戳epoch,此時鎖標誌位沒變,偏向鎖標誌改為1 - 加鎖時先判斷當前執行緒id是否與MarkWord的執行緒id是否一致,一致則執行同步程式碼;不一致則檢查偏向標誌是否偏向,未偏向則使用CAS加鎖;**未偏向CAS加鎖失敗**和**存在偏向鎖**會導致偏向鎖膨脹為輕量級鎖,或者重新偏向 - 偏向鎖只有遇到其他執行緒競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖,執行緒不會主動去釋放偏向鎖 - 輕量級鎖 - 當發生多個執行緒競爭時,偏向鎖會變為輕量級鎖,鎖標誌位為00 - 獲得鎖的執行緒會先將偏向鎖撤銷(在安全點),並在棧楨中建立鎖記錄LockRecord,物件的MarkWord被複制到剛建立的LockRecord,然後CAS嘗試將記錄LockRecord的owner指向鎖物件,再將鎖物件的MarkWord指向鎖,加鎖成功 - 如果CAS加鎖失敗,執行緒會**自旋一定次數加鎖**,再失敗則升級為重量級鎖 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE5LzE3MzY1Y2UwNGZkY2RkOTE?x-oss-process=image/format,png) - 重量級鎖 - 重量級鎖就是上面介紹到synchronized使用監視器Monitor實現的鎖機制 - 競爭執行緒激烈,鎖則繼續膨脹,變為重量級鎖,也是互斥鎖,鎖標誌位為10,MarkWord其餘內容被替換為一個指向物件鎖Monitor的指標 - 自旋鎖 - 減少不必要的CPU上下文切換;在輕量級鎖升級為重量級鎖時,就使用了自旋加鎖的方式 - 鎖粗化 - 多次加鎖操作在JVM內部也是種消耗,如果多個加鎖可以合併為一個鎖,就可減少不必要的開銷 ```java Test.class //編譯器會考慮將兩次加鎖合併 public void test(){ synchronized(this){ System.out.println("hello"); } synchronized(this){ System.out.println("world"); } } ``` - 鎖消除 - 刪除不必要的加鎖操作,如果變數是獨屬一個執行緒的棧變數,加不加鎖都是安全的,編譯器會嘗試消除鎖 - 開啟鎖消除需要在JVM引數上設定`-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks` ```java //StringBuffer的append操作會加上synchronized, //但是變數buf不加鎖也安全的,編譯器會把鎖消除 public void test() { StringBuffer buf = new StringBuffer(); buf.append("hello").append("world"); } ``` - 其他鎖優化方法 - 分段鎖,分段鎖也並非一種實際的鎖,而是一種思想;ConcurrentHashMap是學習分段鎖的最好實踐。主要是將大物件拆成小物件,然後對大物件的加鎖操作變成對小物件加鎖,增加了並行度 # 5 CAS的底層原理 - 在`volatile int i = 0; i++`中,volatile型別的讀寫是原子同步的,但是i++卻不能保證同步性,我們該怎麼呢? - 可以使用synchronized加鎖;還有就是用CAS(比較並交換),使用樂觀鎖的思想同步,先判斷共享變數是否改變,沒有則更新。下面看看不同步版本的CAS ```java int expectedValue = 1; public boolean compareAndSet(int newValue) { if(expectedValue == 1){ expectedValue = newValue; return ture; } return false; } ``` 在jdk是有提供同步版的CAS解決方案,其中使用了UnSafe.java的底層方法 ```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 ```c //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方法的實現如下 ```c /** 1 __asm__表示彙編的開始; 2 volatile表示禁止編譯器優化;//禁止指令重排 3 LOCK_IF_MP是個行內函數, 根據當前系統是否為多核處理器, 決定是否為cmpxchg指令新增lock字首 //記憶體屏障 */ 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; } ``` 到這一步,可以總結到:**jdk提供的CAS機制,在彙編層級,會禁止變數兩側的指令優化,然後使用cmpxchg指令比較並更新變數值(原子性),如果是多核則使用lock鎖定(快取鎖、MESI)** # 6 CAS同步操作的問題 - ABA問題 - 執行緒X準備將變數的值從A改為B,然而這期間執行緒Y將變數的值從A改為C,然後再改為A;最後執行緒X檢測變數值是A,並置換為B。但實際上,A已經不再是原來的A了 - 解決方法,是把變數定為唯一型別。值可以加上版本號,或者時間戳。如加上版本號,執行緒Y的修改變為A1->B2->A3,此時執行緒X再更新則可以判斷出A1不等於A3 - 只能保證一個共享變數的原子操作 - 只保證一個共享變數的原子操作,對多個共享變數同步時,迴圈CAS是無法保證操作的原子 # 7 基於volatile + CAS 實現同步鎖的原理 - CAS只能同步一個變數的修改,我們又應該如何用它來鎖住程式碼塊呢? - 先說說實現鎖的要素 - 1 同步程式碼塊同一時刻只能有一個執行緒能執行 - 2 加鎖操作要happens-before同步程式碼塊裡的操作,而程式碼塊裡的操作要happens-before解鎖操作 - 3 同步程式碼塊結束後相對其他執行緒其修改的變數是可見的 (記憶體可見性) - 要素1:可以利用CAS的原子性來實現,任意時刻只有一個執行緒能成功操作變數 - 先設想CAS操作的共享變數是一個關聯程式碼塊的同步狀態變數,同步開始之前先CAS更新**狀態變數**為加鎖狀態,同步結束之後,再CAS**狀態變數**為無鎖狀態 - 如果期間有第二個執行緒來加鎖,則會發現狀態變數為加鎖狀態,則放棄執行同步程式碼塊 - 要素2:使用volatile修飾狀態變數,禁止指令重排 - volatile保證同步程式碼裡的操作happens-before解鎖操作,而加鎖操作happens-before程式碼塊裡的操作 - 要素3:還是用volatile,volatile變數寫指令前後會插入記憶體屏障 - volatile修飾的狀態變數被CAS為無鎖狀態前,同步程式碼塊的髒資料就會被更新,被各個執行緒可見 ```java //虛擬碼 volatile state = 0 ; // 0-無鎖 1-加鎖;volatile禁止指令重排,加入記憶體屏障 ... if(cas(state, 0 , 1)){ // 1 加鎖成功,只有一個執行緒能成功加鎖 ... // 2 同步程式碼塊 cas(state, 1, 0); // 3 解鎖時2的操作具有可見性 } ``` # 8 LockSupport瞭解一下 - LockSupport是基於Unsafe類,由JDK提供的執行緒操作工具類,主要作用就是**掛起執行緒,喚醒執行緒**。Unsafe.park,unpark操作時,會呼叫當前執行緒的變數parker代理執行。Parker程式碼 ```c JavaThread* thread=JavaThread::thread_from_jni_environment(env); ... thread->parker()->park(isAbsolute != 0, time); ``` ```c 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系統下,用的POSIX執行緒庫pthread中的mutex(互斥量),condition來實現執行緒的掛起、喚醒 - 注意點:當park時,counter變數被設定為0,當unpark時,這個變數被設定為1 - unpark和park執行順序不同時,counter和cond的狀態變化如下 - 先park後unpark; park:counter值不變,但會設定一個cond; unpark:counter先加1,檢查cond存在,counter減為0 - 先unpark後park;park:counter變為1,但不設定cond;unpark:counter減為0(執行緒不會因為park掛起) - 先多次unpark;counter也只設置為為1 # 9 LockSupport.park和Object.wait區別 - 兩種方式都有具有掛起的執行緒的能力 - 執行緒在Object.wait之後必須等到Object.notify才能喚醒 - LockSupport可以先unpark執行緒,等執行緒執行LockSupport.park是不會掛起的,可以繼續執行 - 需要注意的是就算執行緒多次unpark;也只能讓執行緒第一次park是不會掛起 # 10 AbstractQueuedSynchronizer(AQS) - AQS其實就是基於volatile+cas實現的鎖模板;如果需要執行緒阻塞等待,喚醒機制,則使用LockSupport掛起、喚醒執行緒 ```java //AbstractQueuedSynchronizer.java public class AbstractQueuedSynchronizer{ //執行緒節點 static final class Node { ... volatile Node prev; volatile Node next; volatile Thread thread; ... } .... //head 等待佇列頭尾節點 private transient volatile Node head; private transient volatile Node tail; // The synchronization state. 同步狀態 private volatile int state; ... //提供CAS操作,狀態具體的修改由子類實現 protected final boolean compareAndSetState(int expect, int update) { return STATE.compareAndSet(this, expect, update); } } ``` - AQS內部維護一個同步佇列,元素就是包裝了執行緒的Node - 同步佇列中首節點是獲取到鎖的節點,它在釋放鎖的時會喚醒後繼節點,後繼節點獲取到鎖的時候,會把自己設為首節點 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE5LzE3MzY1NmFiNzQ5NDgwY2Y?x-oss-process=image/format,png) ```java public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } ``` - 執行緒會先嚐試獲取鎖,失敗則封裝成Node,CAS加入同步佇列的尾部。在加入同步佇列的尾部時,會判斷前驅節點是否是head結點,並嘗試加鎖(可能前驅節點剛好釋放鎖),否則執行緒進入阻塞等待 **在AQS還存一個ConditionObject的內部類,它的使用機制和Object.wait、notify類似** ```java //AbstractQueuedSynchronizer.java public class ConditionObject implements Condition, java.io.Serializable { //條件佇列;Node 複用了AQS中定義的Node private transient Node firstWaiter; private transient Node lastWaiter; ... ``` - 每個Condition物件內部包含一個Node元素的FIFO條件佇列 - 當一個執行緒呼叫Condition.await()方法,那麼該執行緒將會釋放鎖、構造Node加入條件佇列並進入等待狀態 ```java //類似Object.wait public final void await() throws InterruptedException{ ... Node node = addConditionWaiter(); //構造Node,加入條件佇列 int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { //掛起執行緒 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //notify喚醒執行緒後,加入同步佇列繼續競爭鎖 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; ``` ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE5LzE3MzY1ODllOWFmYzVhNTQ?x-oss-process=image/format,png) - 呼叫Condition.signal時,獲取條件佇列的首節點,將其移動到同步佇列並且利用LockSupport喚醒節點中的執行緒。隨後繼續執行wait掛起前的狀態,呼叫acquireQueued(node, savedState)競爭同步狀態 ```java //類似Object.notify private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } ``` ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE5LzE3MzY1OGYzZTRmMDQwNmQ?x-oss-process=image/format,png) - **volatile+cas機制保證了程式碼的同步性和可見性,而AQS封裝了執行緒阻塞等待掛起,解鎖喚醒其他執行緒的邏輯**。AQS子類只需根據狀態變數,判斷是否可獲取鎖,是否釋放鎖成功即可 - 繼承AQS需要選性重寫以下幾個介面 ```java 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才需要取實現它 ``` # 11 ReentrantLock的原理 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE5LzE3MzY1NTUwMGYwOTI1Yjc?x-oss-process=image/format,png) - ReentrantLock實現了Lock介面,並使用內部類Sync(Sync繼承AbstractQueuedSynchronizer)來實現同步操作 - ReentrantLock內部類Sync ```java abstract static class Sync extends AbstractQueuedSynchronizer{ .... final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //直接CAS狀態加鎖,非公平操作 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } ... //重寫了tryRelease protected final boolean tryRelease(int releases) { c = state - releases; //改變同步狀態 ... //修改volatile 修飾的狀態變數 setState(c); return free; } } ``` - Sync的子類NonfairSync和FairSync都重寫了tryAcquire方法 - 其中NonfairSync的tryAcquire呼叫父類的nonfairTryAcquire方法, FairSync則自己重寫tryAcquire的邏輯。其中呼叫hasQueuedPredecessors()判斷是否有排隊Node,存在則返回false(false會導致當前執行緒排隊等待鎖) ```java 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 ```java 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的程式碼 ```java 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 使用鎖,能防止執行緒死迴圈嗎 - 答案是不一定的;對於單個資源來說是可以做的;但是多個資源會存在死鎖的情況,例如執行緒A持有資源X,等待資源Y,而執行緒B持有資源Y,等待資源X - 有了鎖,可以對資源加狀態控制,但是我們還需要防止死鎖的產生,打破產生死鎖的四個條件之一就行 - 1 資源不可重複被兩個及以上的使用者佔用 - 2 使用者持有資源並等待其他資源 - 3 資源不可被搶佔 - 4 多個使用者形成等待對方資源的迴圈圈 # 14 ThreadLocal是否可保證資源的同步 - 當使用ThreadLocal宣告變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本 - 從上面的概念可知,ThreadLocal其實並不能保證變數的同步性,只是給每一個執行緒分配一個變數副本 # 關注公眾號,大家一起交流 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC83LzE5LzE3MzY3Yjk0ZGEwZTlmZDI?x-oss-process=image/format,png) # 參考文章 - [objectMonitor.cpp](https://github.com/openjdk-mirror/jdk7u-hotspot/blob/50bdefc3afe944ca74c3093e7448d6b889cd20d1/src/share/vm/runtime/objectMonitor.cpp) - [Moniter的實現原理](https://www.hollischuang.com/archives/2030) - [JVM原始碼分析之Object.wait/notify實現](https://www.jianshu.com/p/f4454164c017) - [Java物件頭與鎖](https://www.cnblogs.com/ZoHy/p/11313155.html) - [LockSupport中park與unpark基本使用與原理](https://blog.csdn.net/e891377/article/details/104551335/) - [Java併發之Condition](https://www.cnblogs.com/gemine/p/903901