Java併發系列目錄
- [Java併發系列] 1.Java併發機制的底層實現
- [Java併發系列] 2.Java中的原子操作類
- [Java併發系列] 3.Java中的鎖
討論J.U.C包中locks下面的類(包括介面)
鎖主要是用來控制多個執行緒訪問共享資源的一種方式,通常情況下,一個鎖可以防止在同一時間內多個執行緒同時訪問共享資源(讀寫鎖除外,讀寫鎖在同一時間內,可以允許有多個讀鎖同時讀共享資源)。
1. Lock介面
Lock介面同synchronized關鍵字的作用類似,都是提供了同步的功能。但是Lock在使用的時候,需要顯式的去獲取鎖。與synchronized相比,Lock失去了隱式獲取鎖的便捷性,但是可以控制鎖的獲取和釋放,可中斷鎖和超時鎖。
2. Lock介面主要API
- void lock(); 獲取鎖
- void lockInterruptibly(); 可中斷的獲取鎖,此方法和lock()方法的區別在於: 當使用lockInterruptibly獲取鎖時可以中斷當前執行緒;
- boolean tryLock(); 嘗試非阻塞的獲取鎖,當呼叫此方法之後,如果能獲取則返回true,如果不能獲取則直接返回false;
- boolean tryLock(long time, TimeUnit unit); 超時獲取鎖
- void unlock(); 釋放鎖
- Condition newCondition(); 獲取等待通知元件,該元件和當前的鎖繫結在一起,只有當前執行緒獲的了鎖,才能呼叫該元件的wait()方法,呼叫wait()方法之後,當前執行緒將釋放鎖;
3. Lock介面的實現類
1. ReentrantLock(重入鎖)
重入鎖,就是支援重進入的鎖,表示該鎖支援一個執行緒對資源的重複加鎖。
重進入: 指的是任意執行緒在獲取鎖能夠再次獲取該所而不會被阻塞。
公平與非公平獲取鎖:公平指的是在絕對時間上,先對鎖進行請求的執行緒(等待時間最長的執行緒優先獲取鎖)首先獲取鎖,那麼這個鎖是公平的,反之,則是非公平的。
①. 鎖的重進入
如果要實現鎖的重進入,那麼就就緒解決兩個問題:
- 鎖的獲取:要獲取鎖,那麼鎖就需要去檢查獲取該鎖的執行緒是否是已獲取此鎖的執行緒(也就是是否是當前執行緒佔有此鎖),如果是,那麼獲取成功;如下程式碼是非公平獲取鎖的方式
```java
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; }複製程式碼
在此方法中,首先判斷此鎖是否已被佔有,如果沒有則使用CAS的方式設定同步狀態;如果鎖已被佔有,則判斷當前執行緒是否是佔有此鎖的執行緒,然後再來決定獲取操作是否成功,如果獲取鎖的執行緒再次請求獲取鎖,則將同步狀態值進行增加並且返回true。
所以重入鎖的獲取就是當執行緒重入成功,增加鎖的同步狀態值即可。
- **鎖的釋放:**執行緒重複N此獲取鎖,那麼就需要釋放N次,其他的執行緒才可以獲取該鎖。如下程式碼是釋放鎖的程式碼:
```java
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;
}複製程式碼
如果一個鎖被某個執行緒獲取了N次,那麼前(N-1)次都會返回false,而當同步狀態完成被釋放時(c=0),將佔有執行緒設定為null,才會返回true。
②. 公平與非公平的獲取鎖
如下是公平獲取鎖的程式碼:
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;
}
}複製程式碼
與上面非公平獲取鎖的程式碼相比,在這段程式碼中,僅僅在if條件中多了一個hasQueuedPredecessors()方法,此方法就是判斷在同步佇列中,當前節點是否有前驅節點(即有比當前執行緒更早的獲取鎖的執行緒),因此當hasQueuedPredecessors()返回true時,就需要等待前驅執行緒獲取並釋放鎖之後才能繼續獲取鎖。
2. ReadWriteLock(讀寫鎖)
排他鎖:指的是在同一時刻只允許一個執行緒進行訪問
讀寫鎖:在同一時間,允許有多個讀執行緒進行訪問,而在寫執行緒進行訪問時,讀執行緒和其他寫執行緒均會被阻塞。讀寫鎖維護了一個讀鎖和一個寫鎖,通過讀寫分離,來提升併發效能(至少比排他鎖效能好多了)。
//todo 讀寫鎖內容較多,留待以後來寫
4. LockSupport類
LockSupport類位於在J.U.C.locks包中,它主要是定義了一些公共靜態方法,這些方法提供了最基本的執行緒阻塞和喚醒功能。如下表是LockSupport中提供的一些方法及描述:
方法 | 描述 |
---|---|
public static void park() | 阻塞當前執行緒,當其他執行緒呼叫unpark()或者中斷當前執行緒時,才能從park()方法返回 |
fipublic static void parkNanos(long nanos) | 阻塞當前執行緒,超過nanos納秒之後,自動返回 |
public static void parkUntil(long deadline) | 阻塞當前執行緒,直到deadline時間 |
public static void unpark(Thread thread) | 喚醒處於阻塞的執行緒 |