一、相似之處:Lock鎖 vs Synchronized 代碼塊
Lock鎖是一種類似于synchronized 同步代碼塊的執行緒同步機制,從Java 5開始java.util.concurrent.locks引入了若干個Lock鎖的實作類,所以通常情況下我們不需要實作自己的鎖,重要的是需要知道如何使用它們,了解它們實作背后的原理,
Lock鎖API的基本使用方法和Synchronized 關鍵字大同小異,代碼如下
Lock lock = new ReentrantLock(); //實體化鎖
//lock.lock(); //上鎖
boolean locked = lock.tryLock(); //嘗試上鎖
if(locked){
try {
//被鎖定的同步代碼塊,同時只能被一個執行緒執行
}finally {
lock.unlock(); //放在finally代碼塊中,保證鎖一定會被釋放
}
}
synchronized(obj){
//被鎖定的同步代碼塊,同時只能被一個執行緒執行
}
Lock鎖使用看上去麻煩一點,但是java默認提供了很多Lock鎖,能滿足更多的應用場景,比如:基于信號量加鎖、讀寫鎖等等,關注我的專欄《java并發編程》,后續都會介紹,
二、Lock介面中的方法
Lock介面實作方法通常會維護一個計數器,當計數器=0的時候資源被釋放,當計數器大于1的時候資源被鎖定,
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
- lock() - 呼叫該方法會使鎖定計數器增加1,如果此時共享資源是空閑的,則將鎖交給呼叫該方法的執行緒,
- unlock() - 呼叫該方法使鎖定計數器減少1,當鎖定計數器為0時,資源被釋放,
- tryLock() - 如果該資源是空閑的,那么呼叫該方法將回傳true,鎖定計數器將增加1,如果資源處于被占用狀態,那么該方法回傳false,但是執行緒將不被阻塞,
- tryLock(long timeout, TimeUnit unit) - 按照該方法嘗試獲得鎖,如果資源此時被占用,執行緒在退出前等待一定的時間段,該時間段由該方法的引數定義,以期望在此時間內獲得資源鎖,
- lockInterruptibly() - 如果資源是空閑的,該方法會獲取鎖,同時允許執行緒在獲取資源時被其他執行緒打斷,這意味著,如果當前執行緒正在等待一個鎖,但其他執行緒要求獲得該鎖,那么當前執行緒將被中斷,并立即回傳不會獲得鎖,
三、不同點:Lock鎖 vs Synchronized 代碼塊
使用synchronized同步塊和使用Lock API 之間還是有一些區別的
- 一個synchronized同步塊必須完全包含在一個方法中 - 但Lock API的lock()和unlock()操作,可以在不同的方法中進行
- synchronized同步塊不支持公平性原則,任何執行緒都可以在釋放后重新獲得鎖,不能指定優先級,但我們可以通過指定fairness 屬性在Lock API中實作公平的優先級,可以實作等待時間最長的執行緒被賦予對鎖的占有權,
- 如果一個執行緒無法訪問synchronized同步塊,它就會被阻塞等待,Lock API提供了tryLock()方法,嘗試獲取鎖物件,獲取到鎖回傳true,否則回傳false,回傳false并不阻塞執行緒,所以使用該方法可以減少等待鎖的執行緒的阻塞時間,
四、鎖的可重入性
”可重入“意味著某個執行緒可以安全地多次獲得同一個鎖物件,而不會造成死鎖,
4.1. synchronized鎖的可重入性
下面的代碼synchronized代碼塊嵌套synchronized代碼塊,鎖定同一個this物件,不會產生死鎖,證明synchronized代碼塊針對同一個物件加鎖,是可重入的,
public void testLock(){
synchronized (this) {
System.out.println("第1次獲取鎖,鎖物件是:" + this);
int index = 1;
do {
synchronized (this) {
System.out.println("第" + (++index) + "次獲取鎖,鎖物件是:" + this);
}
} while (index != 10);
}
}
上面的這段代碼輸出結果是
第1次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第2次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第3次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第4次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第5次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第6次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第7次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第8次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第9次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
第10次獲取鎖,鎖物件是:com.example.demo.thread.TestLockReentrant@769c9116
4.2.ReentrantLock可重入鎖
Lock介面的實作類ReentrantLock,也是可重入鎖,一般來說類名包含Reentrant的Lock介面實作類實作的鎖都是可重入的,
public void testLock1(){
Lock lock = new ReentrantLock(); //實體化鎖
lock.lock(); //上鎖
System.out.println("第1次獲取鎖,鎖物件是:" + lock);
try {
int index = 1;
do {
lock.lock(); //上鎖
try {
System.out.println("第" + (++index) + "次獲取鎖,鎖物件是:" + lock);
}finally {
lock.unlock();
}
} while (index != 10);
}finally {
lock.unlock(); //放在finally代碼塊中,保證鎖一定會被釋放
}
}
當執行緒第一次獲得鎖的時候,計數器被設定為1,在解鎖之前,該執行緒可以再次獲得鎖,每次計數器都會增加1,對于每一個解鎖操作,計數器被遞減1,當計數器為0時鎖定資源被釋放,所以最重要的是:lock(tryLock)要與unlock方法成對出現,即:在代碼中加鎖一次就必須解鎖一次,否則就死鎖
五、Lock鎖的公平性
Java的synchronized 同步塊對試圖進入它們的執行緒,被授予訪問權(占有權)的優先級順序沒有任何保證,因此如果許多執行緒不斷爭奪對同一個synchronized 同步塊的訪問權,就有可能有一個或多個執行緒從未被授予訪問權,這就造成了所謂的 "執行緒饑餓",為了避免這種情況,鎖應該是公平的,
Lock lock = new ReentrantLock(true);
可重入鎖提供了一個公平性引數fairness ,通過該引數Lock鎖將遵守鎖請求的順序,即在一個執行緒解鎖資源后,鎖將被交給等待時間最長的執行緒,這種公平模式是通過在鎖的建構式中傳遞 "true "來設定的,
歡迎關注我的博客,更多精品知識合集
本文轉載注明出處(必須帶連接,不能只轉文字):字母哥博客 - zimug.com
覺得對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創作動力!,另外,筆者最近一段時間輸出了如下的精品內容,期待您的關注,
- 《kafka修煉之道》
- 《手摸手教你學Spring Boot2.0》
- 《Spring Security-JWT-OAuth2一本通》
- 《實戰前后端分離RBAC權限管理系統》
- 《實戰SpringCloud微服務從青銅到王者》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/473396.html
標籤:Java
