AQS是什么? - 暖暖-木木 - 博客園 (cnblogs.com)
AQS是一個抽象類,主是是以繼承的方式使用。AQS本身是沒有實現任何同步接口的,它僅僅只是定義了同步狀態的獲取和釋放的方法來供自定義的同步組件的使用。從圖中可以看出,在java的同步組件中,AQS的子類(Sync等)一般是同步組件的靜態內部類,即通過組合的方式使用。
抽象的隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架,許多同步類實現都依賴於它,如常用的ReentrantLock/Semaphore/CountDownLatch
它維護了一個volatile int state(代表共享資源)和一個FIFO(雙向隊列)線程等待隊列(多線程爭用資源被阻塞時會進入此隊列)
AQS原理簡介

AQS的實現依賴內部的同步隊列(FIFO雙向隊列),如果當前線程獲取同步狀態失敗,AQS會將該線程以及等待狀態等信息構造成一個Node,將其加入同步隊列的尾部,同時阻塞當前線程,當同步狀態釋放時,喚醒隊列的頭節點。
上面說的有點抽象,來具體看下,首先來看AQS最主要的三個成員變量:
private transient volatile Node head; private transient volatile Node tail; private volatile int state;
上面提到的同步狀態就是這個int型的變量state. head和tail分別是同步隊列的頭結點和尾結點。假設state=0表示同步狀態可用(如果用於鎖,則表示鎖可用),state=1表示同步狀態已被占用(鎖被占用)。
下面舉例說下獲取和釋放同步狀態的過程:
獲取同步狀態
假設線程A要獲取同步狀態(這里想象成鎖,方便理解),初始狀態下state=0,所以線程A可以順利獲取鎖,A獲取鎖后將state置為1。在A沒有釋放鎖期間,線程B也來獲取鎖,此時因為state=1,表示鎖被占用,所以將B的線程信息和等待狀態等信息構成出一個Node節點對象,放入同步隊列,head和tail分別指向隊列的頭部和尾部(此時隊列中有一個空的Node節點作為頭點,head指向這個空節點,空Node的后繼節點是B對應的Node節點,tail指向它),同時阻塞線程B(這里的阻塞使用的是LockSupport.park()方法)。后續如果再有線程要獲取鎖,都會加入隊列尾部並阻塞。
釋放同步狀態
當線程A釋放鎖時,即將state置為0,此時A會喚醒頭節點的后繼節點(所謂喚醒,其實是調用LockSupport.unpark(B)方法),即B線程從LockSupport.park()方法返回,此時B發現state已經為0,所以B線程可以順利獲取鎖,B獲取鎖后B的Node節點隨之出隊。
上面只是簡單介紹了AQS獲取和釋放的大致過程,下面結合AQS和ReentrantLock源碼來具體看下JDK是如何實現的,特別要注意JDK是如何保證同步和並發操作的。



維護一個用voliate修飾的共享變量和一個雙向隊列。









線程堵塞:LockSupport.park(),線程喚醒LockSupport.unpark(B)

對於公平鎖則是讓隊列的第一個線程去獲取鎖,加入了一個判斷。

https://mp.weixin.qq.com/s/PdB_1-C2FGl91vN3SM5ZVg
公平鎖:多個線程按照申請鎖的順序去獲得鎖,線程會直接進入隊列去排隊,永遠都是隊列的第一位才能得到鎖。
- 優點:所有的線程都能得到資源,不會餓死在隊列中。
- 缺點:吞吐量會下降很多,隊列里面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷會很大。
非公平鎖:多個線程去獲取鎖的時候,會直接去嘗試獲取,獲取不到,再去進入等待隊列,如果能獲取到,就直接獲取到鎖。
- 優點:可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點,CPU也不必取喚醒所有線程,會減少喚起線程的數量。
- 缺點:你們可能也發現了,這樣可能導致隊列中間的線程一直獲取不到鎖或者長時間獲取不到鎖,導致餓死。
