java多線程Lock接口簡介使用與synchronized對比 多線程下篇(三)

前面的介紹中,對於顯式鎖的概念進行了簡單介紹
顯式鎖的概念,是基於JDK層面的實現,是接口,經過這個接口能夠實現同步訪問
而不一樣於synchronized關鍵字,他是Java的內置特性,是基於JVM的實現
image_5c7f20d6_7841
Lock接口的核心概念很簡單,只有以下幾個方法
image_5c7f20d6_1834
按照邏輯能夠進行以下劃分
image_5c7f20d6_415

lock()

Lock接口,因此synchronized關鍵字更爲靈活的一種同步方案,在實際使用中,天然是可以替代synchronized關鍵字的
(ps:儘管你不須要老是使用顯式鎖,顯式鎖與隱式鎖各有利弊,可是在語法上是的確能夠替代的)
synchronized關鍵字是阻塞式的獲取鎖
lock方法就是這一邏輯的體現,也就是說對於lock()方法,若是獲取不到鎖,那麼將會進入阻塞狀態,與synchronized關鍵字同樣

lockInterruptibly()

Lock()方法是一種阻塞式的,另外Lock接口還提供了可中斷的lock獲取方法,先看下測試例子
package test2;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T28 {
private static final Lock LOCK = new ReentrantLock();
public static void main(String[] args) {
//線程A獲取加鎖以後,持有五秒鐘
Thread threadA = new Thread(() -> {
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " sleep");
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupt");
} finally {
LOCK.unlock();
}
}, "thread-A");
threadA.start();
//線程B開始後,嘗試獲取鎖
Thread threadB = new Thread(() -> {
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " working");
} finally {
LOCK.unlock();
}
}, "thread-B");
threadB.start();
//爲了確保上面的任務都開始了,主線程sleep 1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
threadB.interrupt();
}
}
示例邏輯
兩個線程A和B,使用同一把鎖
A線程獲取鎖後,休眠10s,緊接着B嘗試獲取鎖
爲了保證前面的任務都開始了,主線程sleep 1s後,將線程B進行中斷
對於lock方法,如同synchronized關鍵字,是阻塞式的,經過執行來看,能夠發現,在A持有鎖期間,線程B也是一直阻塞的,是不可以獲取到鎖,也不能被中斷(上面示例中調用interrupt()沒有任何的反應)
image_5c7f20d6_4536
將代碼稍做修改,也就是將lock方法修改成lockInterruptibly()方法,其餘暫時不變
image_5c7f20d6_7bb5
再次運行,你會發現立刻就被中斷了,而不是傻傻的等待A結束
固然,由於根本都沒有獲取到鎖,因此在finally中嘗試unlock時,將會拋出異常,這個暫時無論了,經過這個例子能夠看得出來
對於lockInterruptibly方法,這是一個「可中斷的鎖獲取操做
image_5c7f20d6_1200
小結
lockInterruptibly就是一個可中斷的鎖獲取操做,在嘗試獲取鎖的過程當中,若是不可以獲取到,若是被中斷,那麼它將可以感知到這個中斷,而不是一直阻塞下去
若是鎖不可用(被其餘線程持有),除非發生如下事件,不然將會等待
  • 該線程成功得到鎖
  • 發生中斷
若是當前線程遇到下面的事件,則將拋出 InterruptedException,並清除當前線程的已中斷狀態。
  • 在進入此方法時已經設置了該線程的中斷狀態
  • 在獲取鎖時被中斷
從上面的分析能夠看得出來,若是什麼都沒發生,這個方法與lock方法並無什麼區別,就是在等待獲取鎖,獲取不到將會阻塞
他只是額外的對可中斷提供了支持  

unlock()

unlock並無什麼特殊的,他替代了synchronized關鍵字隱式的解鎖操做
一般須要在finally中確保unlock操做會被執行,以前提到過,對於synchronized關鍵字解鎖是隱式的,也是必然的,即便出現錯誤,JVM也會保障可以正確的解鎖
可是對於Lock接口提供的unlock操做,則必須本身確保可以正確的解鎖  

tryLock()

相對於synchronized,Lock接口另外一大改進就是try lock
顧名思義,嘗試獲取鎖,既然是嘗試,那顯然並不會勢在必得
tryLock方法就是一次嘗試,若是鎖可用,則獲取鎖,並當即返回值 true。若是鎖不可用,則此方法將當即返回值 false
也就是說方法會當即返回,若是獲取到鎖返回true,不然返回false,無論如何都是立馬返回
典型的用法就是以下所示,下面的代碼還可以確保若是沒有獲取鎖,不會試圖進行unlock操做
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
tryLock只是一次嘗試,若是你須要不斷地進行嘗試,那麼可使用while替代if的條件判斷
儘管tryLock只是一次的測試,可是能夠藉助於循環(有限或者無限)進行屢次測試  

tryLock(long time, TimeUnit unit)

對於TryLock還有可中斷、配置超時時間的版本
boolean tryLock(long time,
                TimeUnit unit)
                throws InterruptedException
兩個參數,第一個爲值,第二個爲第一個參數的單位,好比1,單位秒,或者2 ,單位分鐘
在指定的超時時間內,若是可以獲取到鎖,那麼將會返回true;
若是超過了指定的時間,可是卻不能獲取到鎖,那麼將會返回false;
另外很顯然,這個方法是可中斷的,也就是說若是嘗試過程當中,出現了中斷,那麼他將會拋出InterruptedException
因此,對於這個方法,他會一直嘗試獲取鎖(也能夠認爲是必定時長內的「阻塞」,固然能夠被中斷),除非:
  • 該線程成功得到鎖
  • 超過了超時時長
  • 該線程被中斷
能夠認爲是lockInterruptibly的限時版本
若是沒有發生中斷,也認爲他就是「定時版本的lock()」
無論怎麼理解,只須要記住:他會在必定時長內嘗試進行鎖的獲取,也支持中斷

鎖小結

對於lock方法和unlock方法,就是相似於synchronized關鍵字的加鎖和解鎖,並無什麼特別的
其餘幾個方法是Lock接口針對於鎖獲取的阻塞以及可中斷兩個方面進行了拓展
隱式鎖的阻塞以及不可中斷,致使一旦開始嘗試獲取,那麼則沒辦法喚醒,將會一直等待,除非得到
  • lockInterruptibly()是阻塞式的,若是獲取不到會一直等待,可是他是可中斷的,可以經過阻塞打破這種等待
  • tryLock()不會進行任何阻塞,只是嘗試獲取一下,能獲取到就獲取,獲取不到就false,拉倒
  • tryLock(long time, TimeUnit unit),便是可中斷的,又是限時阻塞的,即便不中斷,也不會一直阻塞,即便處於阻塞中(超時時長還沒到),也能夠隨時中斷
對於lockInterruptibly()方法以及tryLock(long time, TimeUnit unit),都支持中斷,可是須要注意:
在某些實現中可能沒法中斷鎖獲取,即便可能,該操做的開銷也很大  

Condition

在隱式鎖的邏輯中,藉助於Java底層機制,每一個對象都有一個相關聯的鎖與監視器
對於synchronized的隱式鎖邏輯就是藉助於鎖與監視器,從而進行線程的同步與通訊協做
在顯式鎖中,Lock接口提供了synchronized的語意,對於監視器的概念,則藉助於Condition,可是很顯然,Condition也是與鎖關聯的
Lock接口提供了方法Condition newCondition();
Condition也是一個接口,他定義了相關的監視器方法
在顯式鎖中,能夠定義多個Condition,也就是一個鎖,能夠對應多個監視器,能夠更加細粒度的進行同步協做的處理

總結

Lock接口提供了相對於synchronized關鍵字,而更爲靈活的一種同步手段
它的核心與本質仍舊是爲了線程的同步與協做通訊
因此它的核心仍舊是鎖與監視器,也就是Lock接口與Condition接口
可是靈活是有代價的,因此並不須要在全部的地方都嘗試使用顯式鎖,若是場景知足須要,synchronized仍舊是一種很好的解決方案(也是應該被優先考慮的一種方式)
與synchronized再次對比下
  • synchronized是JVM底層實現的,Lock是JDK接口層面的
  • synchronized是隱式的,Lock是顯式的,須要手動加鎖與解鎖
  • synchronized烏不管如何都會釋放,即便出現錯誤,Lock須要本身保障正確釋放
  • synchronized是阻塞式的獲取鎖,Lock能夠阻塞獲取,可中斷,還能夠嘗試獲取,還能夠設置超時等待獲取
  • synchronized沒法判斷鎖的狀態,Lock能夠進行判斷
  • synchronized可重入,不可中斷,非公平,Lock可重入,可中斷、可配置公平性(公平和非公平均可以)
  • 若是競爭不激烈,二者的性能是差很少的,但是synchronized的性能還在不斷的優化,當競爭資源很是激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized   
  • 等   
對於Lock接口,他仍舊是一個對象,因此他是否能夠用來做爲鎖以及調用監視器方法(用在synchronized(lock)中)?
這邏輯上是沒問題的,可是最好不要那麼作,由於很容易引發混淆的,不論是維護上仍是易讀性上都有很大的問題
在lock上調用他的監視器方法,與藉助於lock實現線程的同步,本質上是沒有什麼關係的
image_5c7f20d6_6b7b
儘管看起來Lock是那麼的優秀,可是仍是要再次提醒,除非synchronized真的不行,不然你應該使用synchronized而不是Lock
相關文章
相關標籤/搜索