ReentrantLock源碼分析全

目錄

 

圖片分析過程

源碼分析

一、t1線程拿鎖源碼分析過程

二、t2線程拿鎖失敗進入隊列阻塞源碼分析過程

三、t3線程拿鎖失敗進入隊列阻塞源碼分析過程

四、t1釋放鎖源碼分析過程

五、t2線程被喚醒後拿鎖源碼分析過程

六、t2線程釋放鎖源碼分析過程

七、t3線程被喚醒後拿到鎖源碼分析過程

八、t3線程釋放鎖源碼分析過程


下面的源碼分析將圍繞三個線程的使用來介紹源碼,先圖解,後源碼解釋,這裏只講公平鎖!!!

藉助代碼分析源碼

import java.util.concurrent.locks.ReentrantLock;

/**
 * 三個線程進來,阻塞兩個,進行ReentrantLock源碼分析
 * @author xuexue
 *
 */
public class Test {
	//創建ReentrantLock對象
	private static ReentrantLock reentrantLock = new ReentrantLock(true);
	public static void main(String[] args) throws InterruptedException {
		//創建線程t1
		Thread t1 = new Thread("t1"){
			public void run() {
				//給t1加鎖
				reentrantLock.lock();
				try {
					//打印線程名
					System.out.println(getName());
					//讓線程t1睡眠,讓線程t2、t3進入阻塞,進入隊列
					sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} finally {//釋放t1鎖
					reentrantLock.unlock();
				}
			}
		};
		
		//創建線程t2
		Thread t2 = new Thread("t2"){
			public void run() {
				//給t2加鎖
				reentrantLock.lock();
				try {
					//打印線程名
					System.out.println(getName());
				} finally {//釋放t2鎖
					reentrantLock.unlock();
				}
			}
		};
		//創建線程t3
		Thread t3 = new Thread("t3"){
			public void run() {
				//給t3加鎖
				//reentrantLock.lock();
				try {
					//打印線程名
					System.out.println(getName());
				} finally {//釋放t3鎖
					//reentrantLock.unlock();
				}
			}
		};
		
		//分別啓動三個線程
		t1.start();
		t2.start();
		t3.start();
		
	}

}

 

圖片分析過程

1、當t1進來拿到鎖之後,緊接着t2上來拿鎖,進行初始化隊列

2、隊列初始化成功,線程t2入隊

3、t3入隊

4、t1釋放鎖後,t2拿到鎖出隊

5、t2釋放鎖後,t3拿到鎖出隊

 

源碼分析

一、t1線程拿鎖源碼分析過程

1、進入lock()方法

2、繼續sync進入lock()方法-----(sync是Sync類,繼承了AQS的內部類)

3、進入acquire(1)方法-----參數1是cas嘗試拿鎖操作用的

4、進入tryAcquire方法-----t1是第一個線程,拿鎖成功,方法結束,返回ture

5、全部返回,代碼繼續向下執行,執行代碼輸出t1

但是遇到sleep(2000)睡眠2s,這裏故意設置睡眠,從而達到讓t1/t2/t3線程存在競爭,讓t2/t3進入隊列並阻塞

 

二、t2線程拿鎖失敗進入隊列阻塞源碼分析過程

7、因爲t1休眠,此時cpu分配給了t2(或者t3這裏假設分配給了t2)

8、繼續1、2、3步操作,繼續分析

此時state=1,拿鎖失敗,並且t2不是鎖持有線程t1,所以返回false

9、執行入隊操作,進入方法addWaiter() 參數Node.EXCLUSIVE=null是一個Node節點

10、進入addWaiter 方法-----創建線程t2的節點,獲取隊尾節點,隊尾節點爲null,所以進入end()方法

11、進入end()方法-----初始化隊列過程

第一次循環,tail=null,new Node() 創建了一個新的節點cas操作,將head設置爲這個新節點,並將tail也設置爲這個節點

第二次循環,將t2線程的Node節點前驅指向head,進行cas操作,將t2線程的Node設置爲tail 就是tail=node,將head.next=node 鏈表關係建立好,隊列就是維護鏈表的關係

aqs爲什麼不一開始初始化隊列?因爲如果是單線程或者線程交替執行的時候就不需要用到隊列,當有競爭的時候才需要

此過程對應的圖解(但是ws還沒設值,在13步設值)

12、將t2線程的Node節點返回,進入acquireQueued()方法-----

拿到t2 node的前驅head,進入判斷,進入自旋,t1未釋放鎖,自旋拿鎖失敗

第一次自旋拿鎖失敗進入shouldParkAfterFailedAcquire(參數1:head,參數2:t2線程的Node節點)

下次還會自旋一次,這裏設置自旋,爲了儘量不讓線程park進入阻塞,儘量在java級別實現,不調用os的api,提高效率

13、進入shouldParkAfterFailedAcquire(p, node)方法----

perd.waitStatus就是head.waitStatus,初始化值爲0,2線程 直接進入 cas操作 將head的waitStatus置爲-1

返回false,結束方法,進入第二次循環,第二次自旋

每次入隊都會將上一個Node的waitStatus置爲-1狀態,目的也是爲了多自旋一次

爲什麼阻塞狀態ws=-1要讓下一個隊列線程來修改?(入隊的時候會把上一個線程狀態ws改爲-1)

1、因爲線程阻塞park了,自己不能修改ws狀態,不能之前修改,怕出異常了

2、睡眠了是讓隊列下一個線程看到的,自己看不看無關緊要

14、進行第二次自旋,目的還是爲了儘量不讓線程park進入阻塞,儘量在java級別實現,不調用os的api,提高效率

15、第二次進入shouldParkAfterFailedAcquire(p, node)-----在上一次head.waitStatus已經被設置爲-1,直接返回true

16、進入parkAndCheckInterrupt()-----阻塞park隊列中的t2線程,在這個位置阻塞,下次喚醒也是這個位置,記住了

方法不能正常返回結束,代碼被阻塞在這裏,無法繼續往下執行,等待喚醒

 

三、t3線程拿鎖失敗進入隊列阻塞源碼分析過程

17、t2被阻塞後,因爲t1休眠(休眠時間夠長),此時cpu分配給了t3,線程t3此時進來

18、繼續1、2、3步驟往下執行

此時state=1,拿鎖失敗,並且t3不是鎖持有線程t1,所以返回false

19、進入addWaiter 方法-----創建線程t3的節點,獲取隊尾節點,隊尾節點爲t2線程的Node

線程t3入隊操作,維護隊列關係

此過程對應的圖解(但是ws還沒設置)

20、將t2線程的Node節點返回,進入acquireQueued()方法-----

拿到t3線程Node的前驅t2線程的Node,不會自旋拿鎖,直接進入shouldParkAfterFailedAcquire(參數1:t2線程的Node,參數2:t3線程的Node節點)

下次還會自旋一次,這裏設置自旋,爲了儘量不讓線程park進入阻塞,儘量在java級別實現,不調用os的api,提高效率

21、進入shouldParkAfterFailedAcquire()方法------

pred.waitStatus就是t2線程Node的狀態,就是0,第一次進入時,將隊列上一個元素的waitStatus置爲-1,返回false

22、第二次進入shouldParkAfterFailedAcquire()-----在上一次t2.waitStatus已經被設置爲-1,直接返回true

 

23、進入parkAndCheckInterrupt()-----阻塞park隊列中的t2線程,在這個位置阻塞,下次喚醒也是這個位置,記住了

方法不能正常返回結束,代碼被阻塞在這裏,無法繼續往下執行,等待喚醒

 

四、t1釋放鎖源碼分析過程

24、線程t2、t3都已經被阻塞,等待t1釋放鎖被喚醒,此時t1釋放鎖

25、進入unlock()方法-----

26、進入release()方法-----嘗試釋放鎖

27、進入tryRelease(arg)方法-----

t1線程只上了一次lock(),所以state爲1,c=state-1=0,就是釋放鎖,並將持有鎖線程置爲空,將state設置爲0,返回true

(如果多次加鎖,需要多次釋放鎖纔可以完全釋放

28、返回true,進入if 語句體,拿到head節點,調用unparkSuccessor() 喚醒頭節點Node的下一個元素

29、進入unparkSuccessor()-----

將head的waitStatus置爲0,喚醒head下一個Node的線程,這裏就是t2線程,方法結束

30、一直返回,直到finnally結束,進入喚醒線程位置,就是當時被阻塞的位置

 

五、t2線程被喚醒後拿鎖源碼分析過程

31、t2線程被喚醒,進入t2當時被阻塞的位置,繼續執行代碼,代碼返回false

32、繼續for循環一次,拿鎖成功,返回false,方法層層返回

對應的圖解過程

33、方法正常返回,輸出t2線程名字,繼續執行,釋放t2線程鎖

 

六、t2線程釋放鎖源碼分析過程

34、進入unlock()方法,release()方法-----

35、進入unparkSuccessor()方法-----喚醒t3線程,結束方法,層層返回

36、結束t2線程,進入t3被阻塞的位置繼續執行

 

七、t3線程被喚醒後拿到鎖源碼分析過程

37、t3線程被喚醒,進入t3當時被阻塞的位置,繼續執行代碼,代碼返回false

38、繼續for循環一次,拿鎖成功,返回false,方法層層返回

對應的圖解過程

39、方法正常返回,輸出t3線程名字,程序繼續執行,釋放t3鎖

 

八、t3線程釋放鎖源碼分析過程

40、進入unlock()方法,release()方法----- 因爲此時隊列沒有線程的Node,head的waitStatus=0,返回false,結束方法,層層返回

 

 

此時的head和tail的狀態

41、t3釋放鎖成功,三個線程執行完畢,程序結束

42、輸出結果(t1線程立即輸出,t2、t3隔兩秒輸出。也有可能t3在t2前面執行,上面源碼分析假設t2在t3前面執行)

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章