關注微信公眾號:CodingTechWork,一起學習交流進步,
引言
??重入鎖,顧名思義在于這個重字,開發程序中,我們在用到鎖時,可能會用于遞回的方法上加鎖,此時,那同一個方法物件去重復加鎖,是怎么加的呢?本文一起學習一下重入鎖這個概念,
重入鎖介紹
重入鎖概念
??重入鎖ReentrantLock,是指支持重進入的鎖,表示鎖可以支持一個執行緒對資源的重復加鎖,也就是說任意執行緒在獲取到這個鎖之后,如果說再次獲取該鎖時,不會被鎖所阻塞(遞回無阻塞),另外,重入鎖還支持鎖時的公平和非公平性(默認)選擇,
重入鎖實作
??實作重入機制,必須解決兩個問題:1)執行緒需要再次獲取鎖;2)鎖需要得到最終的釋放,
- 執行緒再次獲取鎖:
鎖一定要能夠識別獲取鎖的執行緒是否為當前占據鎖的執行緒,如果是,則獲取成功, - 鎖的最終釋放:
鎖獲取對應就會有鎖釋放,執行緒重復n次獲取了該鎖后,需要在第n次釋放鎖后,其他執行緒才能夠獲取該鎖,實作機制是計數器:鎖每獲取一次,計數器自增1,該計數表示當前鎖被重復獲取的次數;鎖每釋放一次,計數器自減1,一直減到0,表示當前執行緒已成功釋放該鎖,其他執行緒可以來獲取該鎖,
公平鎖和非公平鎖
??對于鎖的獲取,會存在公平性的問題,
??所謂公平鎖,其實就是先對鎖進行獲取的請求肯定優先進行的,鎖獲取的順序符合請求的絕對時間順序,類似于FIFO,反之,即為非公平性鎖,
ReentrantLock原始碼分析
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
ReentrantLock的重入性和公平性
??ReentrantLock不支持隱式的重入鎖,但是可以在呼叫lock()方法時,已經獲取到鎖的執行緒,能夠再次呼叫lock()方法獲取鎖且不被阻塞,
??從ReentrantLock原始碼中,我們可以看到上述的一個構造方法,ReentrantLock的公平與否,正是通過構造方法來抉擇的,內部類Sync繼承了AQS,分為公平鎖FairSync和非公平鎖NonfairSync,如果在絕對的時間上,對鎖先發出獲取請求的執行緒一定是先被滿足的,這個鎖即為公平的,其實也就是等待時間最長的執行緒最優先獲取該鎖,所以公平鎖的獲取是順序的,
??公平的鎖機制通常沒有非公平的效率高,但是公平鎖可以減少“饑餓”發生的概率,等待越久的請求越是可以得到最先滿足,不會導致一個執行緒苦苦等了長時間后得不到滿足,
ReentrantLock非公平性鎖和公平性鎖
...
...
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
...
...
非公平nonfairTryAcquire()原始碼
...
...
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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");
//設定鎖的計數值,并回傳true
setState(nextc);
return true;
}
//其他執行緒,回傳false
return false;
}
...
...
??非公平鎖是ReentrantLock默認的鎖方式,如何獲取同步狀態?如上述的原始碼中顯示該方法增加了再次獲取同步狀態的處理邏輯是通過判斷當前執行緒是否為獲取鎖的執行緒,從而決定獲取鎖的操作是true還是false,如果說是獲取鎖的執行緒發出的請求,則同步狀態值會自增1并回傳true,表示獲取鎖成功;若不是獲取鎖的執行緒發出的請求 ,則回傳false,只要CAS設定同步狀態值成功,就表示當前執行緒獲取了鎖,
公平tryAcquire()原始碼
...
...
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
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());
}
...
...
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
...
...
??公平鎖同步計數與非公平鎖的區別在于:公平鎖的方法多了一個hasQueuedPredecessors()方法判斷,判斷同步佇列中當前節點是否有前驅節點,如果有,則表示有執行緒比當前執行緒更早地發出了獲取鎖的請求,所以需要等待更早的執行緒獲取并釋放鎖后才能去獲取該鎖,
tryRelease()原始碼
...
...
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;
//將該鎖的獨占執行緒物件置為null,其他執行緒可以來獲取鎖,
setExclusiveOwnerThread(null);
}
//設定同步狀態值
setState(c);
return free;
}
...
...
??同步狀態值在再次獲取鎖時,自增1,對應的,當釋放鎖是會遞減1,上述原始碼中,可以看到如果該鎖被獲取了n次,那么前(n-1)次的tryRelease()方法必定是回傳了false,只有當第n次完全釋放鎖,同步狀態值c==0,此時獨占鎖的執行緒物件也被設定為了null,才會回傳true,表示完全釋放鎖成功,
測驗
package com.example.andya.demo.service;
import javafx.concurrent.Task;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Andya
* @date 2020/12/6
*/
public class FairAndUnfairLockTest {
//公平鎖
private static Lock fairLock = new ReentrantLockDemo(true);
//非公平鎖
private static Lock unfairLock = new ReentrantLockDemo(false);
//公平鎖執行緒
public void lockFairTask() {
TaskThread taskThread = new TaskThread(fairLock, "FAIR");
taskThread.start();
}
//非公平鎖執行緒
public void lockUnfairTask() {
TaskThread taskThread = new TaskThread(unfairLock, "UNFAIR");
taskThread.start();
}
//執行緒啟動后列印執行緒資訊
private static class TaskThread extends Thread{
private Lock lock;
private String type;
public TaskThread(Lock lock, String type) {
this.lock = lock;
this.type = type;
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
lock.lock();
try {
System.out.println(type + " lock by [" + getId() + "], waiting by "
+ ((ReentrantLockDemo)lock).getQueuedThreads());
} finally {
lock.unlock();
}
}
}
//重寫toString方法,使得執行緒列印出執行緒id來標識執行緒
@Override
public String toString() {
return getId() + "";
}
}
private static class ReentrantLockDemo extends ReentrantLock {
public ReentrantLockDemo(boolean fair) {
super(fair);
}
//獲取正在等待獲取鎖的執行緒串列
@Override
public Collection<Thread> getQueuedThreads() {
List<Thread> threadList = new ArrayList<>(super.getQueuedThreads());
Collections.reverse(threadList);
return threadList;
}
}
public static void main(String[] args) {
FairAndUnfairLockTest fairAndUnfairLockTest = new FairAndUnfairLockTest();
for (int i = 0; i < 5; i++) {
//公平鎖測驗
fairAndUnfairLockTest.lockFairTask();
//非公平鎖測驗
// fairAndUnfairLockTest.lockUnfairTask();
}
}
}
結果
// 公平鎖的結果
FAIR lock by [9], waiting by []
FAIR lock by [10], waiting by [11, 12, 9]
FAIR lock by [11], waiting by [12, 9, 13, 10]
FAIR lock by [12], waiting by [9, 13, 10, 11]
FAIR lock by [9], waiting by [13, 10, 11, 12]
FAIR lock by [13], waiting by [10, 11, 12]
FAIR lock by [10], waiting by [11, 12, 13]
FAIR lock by [11], waiting by [12, 13]
FAIR lock by [12], waiting by [13]
FAIR lock by [13], waiting by []
// 非公平鎖的結果
UNFAIR lock by [10], waiting by [9, 11]
UNFAIR lock by [10], waiting by [9, 11, 12, 13]
UNFAIR lock by [9], waiting by [11, 12, 13]
UNFAIR lock by [9], waiting by [11, 12, 13]
UNFAIR lock by [11], waiting by [12, 13]
UNFAIR lock by [11], waiting by [12, 13]
UNFAIR lock by [12], waiting by [13]
UNFAIR lock by [12], waiting by [13]
UNFAIR lock by [13], waiting by []
UNFAIR lock by [13], waiting by []
分析
??從結果中我們可以看出來,公平鎖每次都是從同步佇列中的第一個節點獲取鎖,而非公平鎖則是會連續兩次獲取鎖,
??剛釋放鎖的執行緒再次獲取同步狀態的概率比較大,會出現連續獲取鎖的情況,
??同時,我們可以看到另一個問題就是開銷,對于公平鎖的開銷會比較大一些,因為它每次都是切換到另一個執行緒,而對于非公平鎖,會出現連續獲取鎖的現象,切換次數少一些,所以非公平性鎖的開銷會更小一些,這也反映了公平鎖雖然保證了鎖的獲取按照順序進行,保證了公平性,解決了“饑餓”問題,但是代價卻是進行了大量的執行緒切換,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/231510.html
標籤:其他
