高併發程式設計必備基礎
一、前言
借用Java併發程式設計實踐中的話”編寫正確的程式並不容易,而編寫正常的併發程式就更難了”,相比於順序執行的情況,多執行緒的執行緒安全問題是微妙而且出乎意料的,因為在沒有進行適當同步的情況下多執行緒中各個操作的順序是不可預期的,本文算是對多執行緒情況下同步策略的一個簡單介紹。
二、 什麼是執行緒安全問題
執行緒安全問題是指當多個執行緒同時讀寫一個狀態變數,並且沒有任何同步措施時候,導致髒資料或者其他不可預見的結果的問題。Java中首要的同步策略是使用Synchronized關鍵字,它提供了可重入的獨佔鎖。
三、 什麼是共享變數可見性問題
要談可見性首先需要介紹下多執行緒處理共享變數時候的Java中記憶體模型。
Java記憶體模型規定了所有的變數都存放在主記憶體中,當執行緒使用變數時候都是把主記憶體裡面的變數拷貝到了自己的工作空間或者叫做工作記憶體。
如圖是雙核CPU系統架構,每核有自己的控制器和運算器,其中控制器包含一組暫存器和操作控制器,運算器執行算術邏輯運算,並且有自己的一級快取,並且有些架構裡面雙核還有個共享的二級快取。對應Java記憶體模型裡面的工作記憶體,在實現上這裡是指L1或者L2快取或者自己cpu的暫存器。當執行緒操作一個共享變數時候操作流程為:
- 執行緒首先從主記憶體拷貝共享變數到自己的工作空間
- 然後對工作空間裡的變數進行處理
- 處理完後更新變數值到主記憶體
那麼假如執行緒A和B同時去處理一個共享變數,會出現什麼情況那?
首先他們都會去走上面的三個流程,假如執行緒A拷貝共享變數到了工作記憶體,並且已經對資料進行了更新但是還沒有更新會主記憶體(結果可能目前存放在當前cpu的暫存器或者快取記憶體),這時候執行緒B拷貝共享變數到了自己的工作記憶體進行處理,處理後,執行緒A才把自己的處理結果更更新到主記憶體或者快取,可知 執行緒B處理的並不是執行緒A處理後的結果,也就是說執行緒A處理後的變數值對執行緒B不可見,這就是共享變數的不可見性問題。
構成共享變數記憶體不可見原因是因為三步流程不是原子性操作,下面知道使用恰當同步就可以解決這個問題。
我們知道ArrayList是執行緒不安全的,因為他的讀寫方法沒有同步策略,會導致髒資料和不可預期的結果,下面我們就一一講解如何解決。
這是執行緒不安全的
public class ArrayList<E>
{
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
}
四、原子性
4.1 介紹
假設執行緒A執行操作Ao和執行緒B執行操作Bo ,那麼從A看,當B執行緒執行Bo操作時候,那麼Bo操作全部執行,要麼全部不執行,我們稱Ao和Bo操作互為原子性操作,在設計計數器時候一般都是先讀取當前值,然後+1,然後更新會變數,是讀-改-寫的過程,這個過程必須是原子性的操作。
public class ThreadNotSafeCount {
private Long value;
public Long getCount() {
return value;
}
public void inc() {
++value;
}
}
如上程式碼是執行緒不安全的,因為不能保證++value是原子性操作。方法一是使用Synchronized進行同步如下:
public class ThreadSafeCount {
private Long value;
public synchronized Long getCount() {
return value;
}
public synchronized void inc() {
++value;
}
}
注意,這裡不能簡單的使用volatile修飾value進行同步,因為變數值依賴了當前值
使用Synchronized確實可以實現執行緒安全,即實現可見性和同步,但是Synchronized是獨佔鎖,沒有獲取內部鎖的執行緒會被阻塞掉,那麼有沒有剛好的實現那?答案是肯定的。
4.2 原子變數類
原子變數類比鎖更輕巧,比如AtomicLong代表了一個Long值,並提供了get,set方法,get,set方法語義和volatile相同,因為AtomicLong內部就是使用了volatile修飾的真正的Long變數。另外提供了原子性的自增自減操作,所以計數器可以改下為:
public class ThreadSafeCount {
private AtomicLong value = new AtomicLong(0L);
public Long getCount() {
return value.get();
}
public void inc() {
value.incrementAndGet();
}
}
那麼相比使用synchronized的好處在於原子類操作不會導致執行緒的掛起和重新排程
,因為他內部使用的是cas的非阻塞演算法。
常用的原子類變數為:AtomicLong,AtomicInteger,AtomicBoolean,AtomicReference.
五 CAS介紹
CAS 即CompareAndSet,也就是比較並設定,CAS有三個運算元分別為:記憶體位置,舊的預期值,新的值,操作含義是當記憶體位置的變數值為舊的預期值時候使用新的值替換舊的值。通俗的說就是看記憶體位置的變數值是不是我給的舊的預期值,如果是則使用我給的新的值替換他,如果不是返回給我舊值。這個是處理器提供的一個原子性指令。上面介紹的AtomicLong的自增就是使用這種方式實現:
public final long incrementAndGet() {
for (;;) {
long current = get();(1)
long next = current + 1;(2)
if (compareAndSet(current, next))(3)
return next;
}
}
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
假如當前值為1,那麼執行緒A和檢查B同時執行到了(3)時候各自的next都是2,current=1,假如執行緒A先執行了3,那麼這個是原子性操作,會把檔期值更新為2並且返回1,if判斷true所以incrementAndGet返回2.這時候執行緒B執行3,因為current=1而當前變數實際值為2,所以if判斷為false,繼續迴圈,如果沒有其他執行緒去自增變數的話,這次執行緒B就會更新變數為3然後退出。
這裡使用了無限迴圈使用CAS進行輪詢檢查,雖然一定程度浪費了cpu資源,但是相比鎖來說避免的執行緒上下文切換和排程。
六、什麼是可重入鎖
當一個執行緒要獲取一個被其他執行緒佔用的鎖時候,該執行緒會被阻塞,那麼當一個執行緒再次獲取它自己已經獲取的鎖時候是否會被阻塞那?如果不需要阻塞那麼我們說該鎖是可重入鎖,也就是說只要該執行緒獲取了該鎖,那麼可以無限制次數進入被該鎖鎖住的程式碼。
先看一個例子如果鎖不是可重入的,看看會出現什麼問題。
public class Hello{
public Synchronized void helloA(){
System.out.println("hello");
}
public Synchronized void helloB(){
System.out.println("hello B");
helloA();
}
}
如上面程式碼當呼叫helloB函式前會先獲取內建鎖,然後列印輸出,然後呼叫helloA方法,呼叫前會先去獲取內建鎖,如果內建鎖不是可重入的那麼該呼叫就會導致死鎖了,因為執行緒持有並等待了鎖。
實際上內部鎖是可重入鎖,例如synchronized關鍵字管理的方法,可重入鎖的原理是在鎖內部維護了一個執行緒標示,標示該鎖目前被那個執行緒佔用,然後關聯一個計數器,一開始計數器值為0,說明該鎖沒有被任何執行緒佔用,當一個執行緒獲取了該鎖,計數器會變成1,其他執行緒在獲取該鎖時候發現鎖的所有者不是自己所以被阻塞,但是當獲取該鎖的執行緒再次獲取鎖時候發現鎖擁有者是自己會把計數器值+1, 當釋放鎖後計數器會-1,當計數器為0時候,鎖裡面的執行緒標示重置為null,這時候阻塞的執行緒會獲取被喚醒來獲取該鎖。
七、Synchronized關鍵字
7.1 Synchronized介紹
synchronized塊是Java提供的一種強制性內建鎖,每個Java物件都可以隱式的充當一個用於同步的鎖的功能,這些內建的鎖被稱為內部鎖或者叫監視器鎖,執行程式碼在進入synchronized程式碼塊前會自動獲取內部鎖,這時候其他執行緒訪問該同步程式碼塊時候會阻塞掉。拿到內部鎖的執行緒會在正常退出同步程式碼塊或者異常丟擲後釋放內部鎖,這時候阻塞掉的執行緒才能獲取內部鎖進入同步程式碼塊。
7.2 Synchronized同步例項
內部鎖是一種互斥鎖,具體說是同時只有一個執行緒可以拿到該鎖,當一個執行緒拿到該鎖並且沒有釋放的情況下,其他執行緒只能等待。
對於上面說的ArrayList可以使用synchronized進行同步來處理可見性問題。
使用synchronized對方法進行同步
public class ArrayList<E>
{
public synchronized E get(int index) {
rangeCheck(index);
return elementData(index);
}
public synchronized E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
}
如圖當執行緒A獲取內部鎖進入同步程式碼塊後,執行緒B也準備要進入同步塊,但是由於A還沒釋放鎖,所以B現在進入等待,使用同步可以保證執行緒A獲取鎖到釋放鎖期間的變數值對B獲取鎖後都可見。也就是說當B開始執行A執行的程式碼同步塊時候可以看到A操作的所有變數值,這裡具體說是當執行緒B獲取b的值時候能夠保證獲取的值是2。這時因為執行緒A進入同步塊修改變數值後,會在退出同步塊前把值重新整理到主記憶體,而執行緒B在進入同步塊前會首先清空本地記憶體內容,從主記憶體重新獲取變數值,所以實現了可見性。但是要注意一點所有執行緒使用的是同一個鎖。
注意 Synchronized關鍵字會引起執行緒上下文切換和執行緒排程
。
八、 ReentrantReadWriteLock介紹
使用synchronized可以實現同步,但是缺點是同時只有一個執行緒可以訪問共享變數,但是正常情況下,對於多個讀操作操作共享變數時候是不需要同步的,synchronized時候無法實現多個讀執行緒同時執行,而大部分情況下讀操作次數多於寫操作,所以這大大降低了併發性,所以出現了ReentrantReadWriteLock,它可以實現讀寫分離,多個執行緒同時進行讀取,但是最多一個寫執行緒存在。
對於上面的方法現在可以修改為:
public class ArrayList<E>
{
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public E get(int index) {
Lock readLock = readWriteLock.readLock();
readLock.lock();
try {
return list.get(index);
} finally {
readLock.unlock();
}
}
public E set(int index, E element) {
Lock wirteLock = readWriteLock.writeLock();
wirteLock.lock();
try {
return list.set(index, element);
} finally {
wirteLock.unlock();
}
}
}
如程式碼在get方法時候通過 readWriteLock.readLock()獲取了讀鎖,多個執行緒可以同時獲取這讀鎖,set方法通過readWriteLock.writeLock()獲取了寫鎖,同時只有一個執行緒可以獲取寫鎖,其他執行緒在獲取寫鎖時候會阻塞直到寫鎖被釋放。假如一個執行緒已經獲取了讀鎖,這時候如果一個執行緒要獲取寫鎖時候要等待直到釋放了讀鎖,如果一個執行緒獲取了寫鎖,那麼所有獲取讀鎖的執行緒需要等待直到寫鎖被釋放。所以相比synchronized來說執行多個讀者同時存在,所以提高了併發量。
注意 需要使用者顯示呼叫Lock與unlock操作
九、 Volatile變數
對於避免不可見性問題,Java還提供了一種弱形式的同步,即使用了volatile關鍵字。該關鍵字確保了對一個變數的更新對其他執行緒可見。當一個變數被宣告為volatile時候,執行緒寫入時候不會把值快取在暫存器或者或者在其他地方,當執行緒讀取的時候會從主記憶體重新獲取最新值,而不是使用當前執行緒的拷貝記憶體變數值。
volatile雖然提供了可見性保證,但是不能使用他來構建複合的原子性操作,也就是說當一個變數依賴其他變數或者更新變數值時候新值依賴當前老值時候不在適用。與synchronized相似之處在於如圖
如圖執行緒A修改了volatile變數b的值,然後執行緒B讀取了改變數值,那麼所有A執行緒在寫入變數b值前可見的變數值,在B讀取volatile變數b後對執行緒B都是可見的,圖中執行緒B對A操作的變數a,b的值都可見的。volatile的記憶體語義和synchronized有類似之處,具體說是說當執行緒寫入了volatile變數值就等價於執行緒退出synchronized同步塊(會把寫入到本地記憶體的變數值同步到主記憶體),讀取volatile變數值就相當於進入同步塊(會先清空本地記憶體變數值,從主記憶體獲取最新值)。
下面的Integer也是執行緒不安全的,因為沒有進行同步措施
public class ThreadNotSafeInteger {
private int value;
public int get() {
return value;
}
public void set(int value) {
this.value = value;
}
}
使用synchronized關鍵字進行同步如下:
public class ThreadSafeInteger {
private int value;
public synchronized int get() {
return value;
}
public synchronized void set(int value) {
this.value = value;
}
}
等價於使用volatile進行同步如下:
public class ThreadSafeInteger {
private volatile int value;
public int get() {
return value;
}
public void set(int value) {
this.value = value;
}
}
這裡使用synchronized和使用volatile是等價的,但是並不是所有情況下都是等價,一般只有滿足下面所有條件才能使用volatile
- 寫入變數值時候不依賴變數的當前值,或者能夠保證只有一個執行緒修改變數值。
- 寫入的變數值不依賴其他變數的參與。
- 讀取變數值時候不能因為其他原因進行枷鎖。
另外 加鎖可以同時保證可見性和原子性,而volatile只保證變數值的可見性。
注意 volatile關鍵字不會引起執行緒上下文切換和執行緒排程
。另外volatile還用來解決重排序問題,後面會講到。
十、 樂觀鎖與悲觀鎖
10.1 悲觀鎖
悲觀鎖,指資料被外界修改持保守態度(悲觀),在整個資料處理過程中,將資料處於鎖定狀態。 悲觀鎖的實現,往往依靠資料庫提供的鎖機制 。資料庫中實現是對資料記錄進行操作前,先給記錄加排它鎖,如果獲取鎖失敗,則說明資料正在被其他執行緒修改,則等待或者丟擲異常。如果加鎖成功,則獲取記錄,對其修改,然後事務提交後釋放排它鎖。
一個例子:select * from 表 where .. for update;
悲觀鎖是先加鎖再訪問策略,處理加鎖會讓資料庫產生額外的開銷,還有增加產生死鎖的機會,另外在多個執行緒只讀情況下不會產生資料不一致行問題,沒必要使用鎖,只會增加系統負載,降低併發性,因為當一個事務鎖定了該條記錄,其他讀該記錄的事務只能等待。
10.2 樂觀鎖
樂觀鎖是相對悲觀鎖來說的,它認為資料一般情況下不會造成衝突,所以在訪問記錄前不會加排他鎖,而是在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,具體說根據update返回的行數讓使用者決定如何去做。樂觀鎖並不會使用資料庫提供的鎖機制,一般在表新增version欄位或者使用業務狀態來做。
具體可以參考:https://www.atatech.org/articles/79240
樂觀鎖直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。
十一、獨佔鎖與共享鎖
根據鎖能夠被單個執行緒還是多個執行緒共同持有,鎖又分為獨佔鎖和共享鎖。獨佔鎖保證任何時候都只有一個執行緒能讀寫許可權,ReentrantLock就是以獨佔方式實現的互斥鎖。共享鎖則可以同時有多個讀執行緒,但最多隻能有一個寫執行緒,讀和寫是互斥的,例如ReadWriteLock讀寫鎖,它允許一個資源可以被多執行緒同時進行讀操作,或者被一個執行緒 寫操作,但兩者不能同時進行。
獨佔鎖是一種悲觀鎖,每次訪問資源都先加上互斥鎖,這限制了併發性,因為讀操作並不會影響資料一致性,而獨佔鎖只允許同時一個執行緒讀取資料,其他執行緒必須等待當前執行緒釋放鎖才能進行讀取。
共享鎖則是一種樂觀鎖,它放寬了加鎖的條件,允許多個執行緒同時進行讀操作。
十二、公平鎖與非公平鎖
根據執行緒獲取鎖的搶佔機制鎖可以分為公平鎖和非公平鎖,公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的時間多少來決定的,也就是最早加鎖的執行緒將最早獲取鎖,也就是先來先得的FIFO順序。而非公平鎖則執行闖入,也就是先來不一定先得。
ReentrantLock提供了公平和非公平鎖的實現:
公平鎖ReentrantLock pairLock = new ReentrantLock(true);
非公平鎖 ReentrantLock pairLock = new ReentrantLock(false);
如果建構函式不傳遞引數,則預設是非公平鎖。
在沒有公平性需求的前提下儘量使用非公平鎖,因為公平鎖會帶來效能開銷。
假設執行緒A已經持有了鎖,這時候執行緒B請求該鎖將會被掛起,當執行緒A釋放鎖後,假如當前有執行緒C也需要獲取該鎖,如果採用非公平鎖方式,則根據執行緒排程策略執行緒B和C兩者之一可能獲取鎖,這時候不需要任何其他干涉,如果使用公平鎖則需要把C掛起,讓B獲取當前鎖。
十三、 AbstractQueuedSynchronizer介紹
AbstractQueuedSynchronizer提供了一個佇列,大多數開發者可能從來不會直接用到AQS,AQS有個變數用來存放狀態資訊 state,可以通過protected的getState,setState,compareAndSetState函式進行呼叫。對於ReentrantLock來說,state可以用來表示該執行緒獲可重入鎖的次數,semaphore來說state用來表示當前可用訊號的個數,FutuerTask用來表示任務狀態(例如還沒開始,執行,完成,取消)。
十四、CountDownLatch原理
14.1 一個例子
public class Test {
private static final int ThreadNum = 10;
public static void main(String[] args) {
//建立一個CountDownLatch例項,管理計數為ThreadNum
CountDownLatch countDownLatch = new CountDownLatch(ThreadNum);
//建立一個固定大小的執行緒池
ExecutorService executor = Executors.newFixedThreadPool(ThreadNum);
//新增執行緒到執行緒池
for(int i =0;i<ThreadNum;++i){
executor.execute(new Person(countDownLatch, i+1));
}
System.out.println("開始等待全員簽到...");
try {
//等待所有執行緒執行完畢
countDownLatch.await();
System.out.println("簽到完畢,開始吃飯");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
executor.shutdown();
}
}
static class Person implements Runnable{
private CountDownLatch countDownLatch;
private int index;
public Person(CountDownLatch cdl,int index){
this.countDownLatch = cdl;
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("person " + index +"簽到");
//執行緒執行完畢,計數器減一
countDownLatch.countDown();
}
}
}
如上程式碼,建立一個執行緒池和CountDownLatch例項,每個執行緒通過建構函式傳入CountDownLatch的例項,主執行緒通過await等待執行緒池裡面執行緒任務全部執行完畢,子執行緒則執行完畢後呼叫countDown計數器減一,等所有子執行緒執行完畢後,主執行緒的await才會返回。
14.2 原理
先看下類圖:
可知CountDownLatch內部還是使用AQS實現的。
首先通過建構函式初始化AQS的狀態值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
然後看下await方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果執行緒被中斷則拋異常
if (Thread.interrupted())
throw new InterruptedException();
//嘗試看當前是否計數值為0,為0則直接返回,否者進入佇列等待
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
如果tryAcquireShared返回-1則 進入doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//加入佇列狀態為共享節點
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//如果多個執行緒呼叫了await被放入佇列則一個個返回。
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//shouldParkAfterFailedAcquire會把當前節點狀態變為SIGNAL型別,然後呼叫park方法把當先執行緒掛起,
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
呼叫await後,當前執行緒會被阻塞,直到所有子執行緒呼叫了countdown方法,並在計數為0時候呼叫該執行緒unpark方法啟用執行緒,然後該執行緒重新tryAcquireShared會返回1。
然後看下 countDown方法:
委託給sync
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
首先看下tryReleaseShared
protected boolean tryReleaseShared(int releases) {
//迴圈進行cas,直到當前執行緒成功完成cas使計數值(狀態值state)減一更新到state
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
該函式一直返回false直到當前計數器為0時候才返回true。
返回true後會呼叫doReleaseShared,該函式主要作用是呼叫uppark方法啟用呼叫await的執行緒,程式碼如下:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//節點型別為SIGNAL,把型別在通過cas設定回去,然後呼叫unpark啟用呼叫await的執行緒
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
啟用主執行緒後,主執行緒會在呼叫tryAcquireShared獲取鎖。
十五、ReentrantLock獨佔鎖原理
15.1 ReentrantLock結構
先上類圖:
可知ReentrantLock最終還是使用AQS來實現,並且根據引數決定內部是公平還是非公平鎖,預設是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
加鎖程式碼:
public void lock() {
sync.lock();
}
15.2 公平鎖原理
先看Lock方法:
lock方法最終呼叫FairSync重寫的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
//獲取當前執行緒和狀態值
final Thread current = Thread.currentThread();
int c = getState();
//狀態為0說明該鎖未被任何執行緒持有
if (c == 0) {
//為了實現公平,首先看佇列裡面是否有節點,有的話再看節點所屬執行緒是不是當前執行緒,是的話hasQueuedPredecessors返回false,然後使用原子操作compareAndSetState保證一個執行緒更新狀態為1,設定排他鎖歸屬為當前執行緒。其他執行緒通過cass則返回false.
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//狀態不為0說明該鎖已經被執行緒持有,則看是否是當前執行緒持有,是則重入鎖次數+1.
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平性保證程式碼:
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
再看看unLock方法,最終呼叫了Sync的tryRelease方法:
protected final boolean tryRelease(int releases) {
//如果不是鎖持有者呼叫UNlock則丟擲異常。
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果當前可重入次數為0,則清空鎖持有執行緒
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//設定可重入次數為原始值-1
setState(c);
return free;
}
15.3 非公平鎖原理
final void lock() {
//如果當前鎖空閒0,則設定狀態為1,並且設定當前執行緒為鎖持有者
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//呼叫重寫的tryAcquire方法->nonfairTryAcquire方法
}
final boolean