ReentrantLock与公平锁、非公平锁实现
前言?
最近開始讀JDK源碼,所有心得準備總結成一個專欄,JDK Analysis系列的第一篇,就從萬眾矚目的ReentrantLock開始吧,而談到ReentrantLock,就不得不說AQS,它是AbstractQueuedSynchronizer類的簡稱,Doug Lea上神在JDK1.5將其引入,這才有了現在的并發包java.util.concurrent,所以要理解ReentrantLock的原理,AQS也是必須要搞懂的。這篇就先闡述ReentrantLock最基本的公平鎖和非公平鎖的實現,以及部分涉及的AQS原理,AQS源碼解讀將在后續跟進。整個系列基于JDK1.8.0_92。
公平鎖與非公平鎖?
大家都知道,在JDK1.5之前,我們在多線程的環境下要想保證線程安全,就必須要使用synchronized關鍵字來實現對象鎖或者類鎖,以此滿足這樣的需求,JDK1.5之后則使用Lock來實現更加細粒度的鎖。在剛接觸Java的時候,學到這兩種方式的時候,粗略地知道后者更加貼近面向對象的思想,但是在工作中遇到一些奇奇怪怪的需求的時候,只是知道這個是遠遠不夠的。所謂公平鎖,就是線程按照執行順序排成一排,依次獲取鎖,但是這種方式在高并發的場景下極其損耗性能;這時候,非公平鎖應運而生了,所謂非公平鎖,就是不管執行順序,每個線程獲取鎖的幾率都是相同的,獲取失敗了,才會采用像公平鎖那樣的方式。這樣做的好處是,JVM可以花比較少的時間在線程調度上,更多的時間則是用在執行邏輯代碼里面。
公平鎖、非公平鎖的創建方式:
//創建一個非公平鎖,默認是非公平鎖
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
//創建一個公平鎖,構造傳參true
Lock lock = new ReentrantLock(true);
1
2
3
4
5
6
相關源碼:
? ? public ReentrantLock() {
? ? ? ? sync = new NonfairSync();
? ? }
? ? public ReentrantLock(boolean fair) {
? ? ? ? sync = fair ? new FairSync() : new NonfairSync();
? ? }
1
2
3
4
5
6
7
這部分源碼比較簡單,這里對于源碼就不贅述。
NonfairSync 非公平鎖?
在談NonfairSync之前,首先要談談ReentrantLock類里面定義的一個類屬性Sync,它才是ReentrantLock實現的精髓。它首先在屬性里聲明,然后以抽象靜態內部類的形式實現了AQS,源碼如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
? ? ? ? private static final long serialVersionUID = -5179523762034025860L;
? ? ? ? //聲明的lock()方法,供子類實現
? ? ? ? abstract void lock();
? ? ? ? //非公平鎖的獲取方式,相較于公平鎖的tryAcquire()
? ? ? ? final boolean nonfairTryAcquire(int acquires) {
? ? ? ? ? ? final Thread current = Thread.currentThread();
? ? ? ? ? ? int c = getState();
? ? ? ? ? ? if (c == 0) {
? ? ? ? ? ? ? ? if (compareAndSetState(0, acquires)) {
? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? else if (current == getExclusiveOwnerThread()) {
? ? ? ? ? ? ? ? int nextc = c + acquires;
? ? ? ? ? ? ? ? if (nextc < 0) // overflow
? ? ? ? ? ? ? ? ? ? throw new Error("Maximum lock count exceeded");
? ? ? ? ? ? ? ? setState(nextc);
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? //釋放鎖
? ? ? ? protected final boolean tryRelease(int releases) {
? ? ? ? ? ? int c = getState() - releases;
? ? ? ? ? ? if (Thread.currentThread() != getExclusiveOwnerThread())
? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();
? ? ? ? ? ? boolean free = false;
? ? ? ? ? ? if (c == 0) {
? ? ? ? ? ? ? ? free = true;
? ? ? ? ? ? ? ? setExclusiveOwnerThread(null);
? ? ? ? ? ? }
? ? ? ? ? ? setState(c);
? ? ? ? ? ? return free;
? ? ? ? }
? ? ? ? //判斷當前線程是否是鎖的持有者
? ? ? ? protected final boolean isHeldExclusively() {
? ? ? ? ? ? return getExclusiveOwnerThread() == Thread.currentThread();
? ? ? ? }
? ? ? ? final ConditionObject newCondition() {
? ? ? ? ? ? return new ConditionObject();
? ? ? ? }
? ? ? ? //獲取當前鎖持有線程,如果在隊列中等待獲取鎖,則返回null
? ? ? ? final Thread getOwner() {
? ? ? ? ? ? return getState() == 0 ? null : getExclusiveOwnerThread();
? ? ? ? }
? ? ? ? //返回當前線程status的狀態,如果持有鎖就讀取status,沒有就0
? ? ? ? final int getHoldCount() {
? ? ? ? ? ? return isHeldExclusively() ? getState() : 0;
? ? ? ? }
? ? ? ? //是否上鎖
? ? ? ? final boolean isLocked() {
? ? ? ? ? ? return getState() != 0;
? ? ? ? }
? ? ? ? /**
? ? ? ? ?* Reconstitutes the instance from a stream (that is, deserializes it).
? ? ? ? ?*/
? ? ? ? private void readObject(java.io.ObjectInputStream s)
? ? ? ? ? ? throws java.io.IOException, ClassNotFoundException {
? ? ? ? ? ? s.defaultReadObject();
? ? ? ? ? ? setState(0); // reset to unlocked state
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
此后,NonfairSync繼承它來實現非公平鎖,FairSync繼承它來實現公平鎖,AQS提供一個tryAcquire()的模板方法來使得公平鎖和非公平鎖的實現方式顯得靈活。我們來看看NonfairSync的源碼:
? ? static final class NonfairSync extends Sync {
? ? ? ? private static final long serialVersionUID = 7316153563782823691L;
? ? ? ? final void lock() {
? ? ? ? ? ? if (compareAndSetState(0, 1))
? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());
? ? ? ? ? ? else
? ? ? ? ? ? ? ? acquire(1);
? ? ? ? }
? ? ? ? protected final boolean tryAcquire(int acquires) {
? ? ? ? ? ? return nonfairTryAcquire(acquires);
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
如代碼所示,在lock的時候,先是嘗試將AQS的status從0設為1,成功的話就把當前線程設置為鎖的持有者,如果嘗試失敗了,基于模板方法,實際調用的是Sync的nonfairTryAcquire(int acquires)方法,該方法源碼如下:
? ? ? ?final boolean nonfairTryAcquire(int acquires) {
? ? ? ? ? ? final Thread current = Thread.currentThread();
? ? ? ? ? ? int c = getState();
? ? ? ? ? ? if (c == 0) {
? ? ? ? ? ? ? ? if (compareAndSetState(0, acquires)) {
? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? //ReentrantLock是可重入鎖是這里實現的
? ? ? ? ? ? else if (current == getExclusiveOwnerThread()) {
? ? ? ? ? ? ? ? int nextc = c + acquires;
? ? ? ? ? ? ? ? if (nextc < 0) // overflow
? ? ? ? ? ? ? ? ? ? throw new Error("Maximum lock count exceeded");
? ? ? ? ? ? ? ? setState(nextc);
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? ? ? return false;
? ? ? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
首先獲取當前線程,和當前AQS維護的鎖的狀態,如果狀態為0,則嘗試將AQS的status從0設為acquires(實際是1),如果設置成功,則獲取鎖成功,把當前鎖設置為鎖的持有者,返回true;如果當前線程已經是鎖的持有者,則把status+acquires,如果結果越界,拋出異常,如果成功,返回true。細心的同學可以發現,一共有兩次原子設status從0到1,為什么呢?因為這樣可以提高獲取鎖的概率,因為是非公平的,所以有必要進行這樣的操作,而且這樣的操作與鎖相對來講損耗微乎其微。
FairSync 公平鎖?
公平鎖就是每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以后會按照FIFO的規則從隊列中獲取,下面是FairSync 的源碼:
static final class FairSync extends Sync {
? ? ? ? private static final long serialVersionUID = -3000897897090466540L;
? ? ? ? final void lock() {
? ? ? ? ? ? acquire(1);
? ? ? ? }
? ? ? ? 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;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? else if (current == getExclusiveOwnerThread()) {
? ? ? ? ? ? ? ? int nextc = c + acquires;
? ? ? ? ? ? ? ? if (nextc < 0)
? ? ? ? ? ? ? ? ? ? throw new Error("Maximum lock count exceeded");
? ? ? ? ? ? ? ? setState(nextc);
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? ? ? return false;
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
來看子類的 tryAcquire方法,與非公平鎖比較,獲取鎖的操作只有一點不同,就是加入了hasQueuedPredecessors() 方法,該方法又大有來頭,ctrl進去是這的:
? ? public final boolean hasQueuedPredecessors() {
? ? ? ? Node t = tail;?
? ? ? ? Node h = head;
? ? ? ? Node s;
? ? ? ? //head沒有next ----> false
? ? ? ? //head有next,next持有的線程不是當前線程 ----> true
? ? ? ? //head有next,next持有的線程是當前線程 ----> false
? ? ? ? return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
? ? }
1
2
3
4
5
6
7
8
9
該方法的簽名是:查詢是否有其他線程比當前線程等待獲取鎖花費了更多的時間。在AQS中對線程是做了一個FIFO隊列,這里的tail是尾,head是頭,具體的實現會在后續跟進,這里就不多做贅述,有意思的是return那一行,其中的意思在上面做了解答,查詢是否有其他線程比當前線程等待獲取鎖花費了更多的時間,有就返回true,沒有就返回false,也就是說該方法返回false,才進行addWaiter狀態的更改嘗試,其余和部分和非公平鎖的部分一樣。
ctrl點進acquire(1)是這樣的:
? ? public final void acquire(int arg) {
? ? ? ? if (!tryAcquire(arg) &&
? ? ? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
? ? ? ? ? ? selfInterrupt();
? ? }
1
2
3
4
5
首先通過tryAcquire方法嘗試獲取鎖,如果成功直接返回,否則通過acquireQueued()再次嘗試獲取。在acquireQueued()中會先通過addWaiter將當前線程加入到CLH隊列的隊尾,在CLH隊列中等待。在等待過程中線程處于休眠狀態,直到成功獲取鎖才會返回。
---------------------?
作者:Lovnx?
來源:CSDN?
原文:https://blog.csdn.net/rickiyeat/article/details/78307739?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的ReentrantLock与公平锁、非公平锁实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ReentrantLock实现原理深入探
- 下一篇: Java并发编程--ReentrantR