Lock是java 1.5中引入的線程同步工具,它主要用于多線程下共享資源的控制。本質上Lock僅僅是一個接口(位于源碼包中的java/util/concurrent/locks中),它包含以下方法
//嘗試獲取鎖,獲取成功則返回,否則阻塞當前線程
void lock();
//嘗試獲取鎖,線程在成功獲取鎖之前被中斷,則放棄獲取鎖,拋出異常
void lockInterruptibly() throws InterruptedException;
//嘗試獲取鎖,獲取鎖成功則返回true,否則返回false
boolean tryLock();
//嘗試獲取鎖,若在規(guī)定時間內獲取到鎖,則返回true,否則返回false,未獲取鎖之前被中斷,則拋出異常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖
void unlock();
//返回當前鎖的條件變量,通過條件變量可以實現(xiàn)類似notify和wait的功能,一個鎖可以有多個條件變量
Condition newCondition();
Lock有三個實現(xiàn)類,一個是ReentrantLock,另兩個是ReentrantReadWriteLock類中的兩個靜態(tài)內部類ReadLock和WriteLock。
使用方法:多線程下訪問(互斥)共享資源時, 訪問前加鎖,訪問結束以后解鎖,解鎖的操作推薦放入finally塊中。
Lock l = ...; //根據(jù)不同的實現(xiàn)Lock接口類的構造函數(shù)得到一個鎖對象
l.lock(); //獲取鎖位于try塊的外面
try {
// access the resource PRotected by this lock
} finally {          
     l.unlock();           
}
注意,加鎖位于對資源訪問的try塊的外部,特別是使用lockInterruptibly方法加鎖時就必須要這樣做,這為了防止線程在獲取鎖時被中斷,這時就不必(也不能)釋放鎖。
try { l.lockInterruptibly();//獲取鎖失敗時不會執(zhí)行finally塊中的unlock語句 try{ // access the resource protected by this lock }finally{ l.unlock(); }} catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace();}
需要實現(xiàn)鎖的功能,兩個必備元素,一個是表示(鎖)狀態(tài)的變量(我們假設0表示沒有線程獲取鎖,1表示已有線程占有鎖),另一個是隊列,隊列中的節(jié)點表示因未能獲取鎖而阻塞的線程。為了解決多核處理器下多線程緩存不一致的問題,表示狀態(tài)的變量必須聲明為voaltile類型,并且對表示狀態(tài)的變量和隊列的某些操作要保證原子性和可見性。原子性和可見性的操作主要通過Atomic包中的方法實現(xiàn)。
1. 讀取表示狀態(tài)的變量
2. 如果表示狀態(tài)的變量的值為0,那么當前線程嘗試將變量值設置為1(通過CAS操作完成),當多個線程同時將表示狀態(tài)的變量值由0設置成1時,僅一個線程能成功,其
它線程都會失敗
2.1 若成功,表示獲取了鎖,
2.1.1 如果該線程已位于在隊列中,則將其出列(并將下一個節(jié)點則變成了隊列的第一個節(jié)點)
2.1.2 如果該線程未入列,則不用對隊列進行維護
然后當前線程從lock方法中返回,對共享資源進行訪問。
2.2 若失敗,則當前線程將自身放入等待(鎖的)隊列中并阻塞自身,此時線程一直被阻塞在lock方法中,沒有從該方法中返回(被喚醒后仍然在lock方法中,并回 到第1步重新開始)。
3. 如果表示狀態(tài)的變量的值為1,那么將當前線程放入等待隊列中,然后將自身阻塞(被喚醒后仍然在lock方法中,并回到第1步重新開始)。
1. 釋放鎖的線程將狀態(tài)變量的值從1設置為0,并喚醒等待(鎖)隊列中的隊首節(jié)點,釋放鎖的線程從就從unlock方法中返回,繼續(xù)執(zhí)行線程后面的代碼
2. 被喚醒的線程(隊列中的隊首節(jié)點)和可能和未進入隊列并且準備獲取的線程競爭獲取鎖,重復獲取鎖的過程
注意:可能有多個線程同時競爭去獲取鎖,但是一次只能有一個線程去釋放鎖,隊列中的節(jié)點都需要它的前一個節(jié)點將其喚醒,例如有隊列A<-B-<C ,即由A釋放鎖時 喚醒B,B釋放鎖時喚醒C
鎖可以分為公平鎖和不公平鎖,重入鎖和非重入鎖(關于重入鎖的介紹會在ReentrantLock源代碼分析中介紹),以上過程實際上是非公平鎖的獲取和釋放過程。
公平鎖嚴格按照先來后到的順去獲取鎖,而非公平鎖允許插隊獲取鎖。
公平鎖獲取鎖的過程上有些不同,在使用公平鎖時,某線程想要獲取鎖,不僅需要判斷當前表示狀態(tài)的變量的值是否為0,還要判斷隊列里是否還有其他線程,若隊列中還有線程則說明當前線程需要排隊,進行入列操作,并將自身阻塞;若隊列為空,才能嘗試去獲取鎖。而對于非公平鎖,當表示狀態(tài)的變量的值是為0,就可以嘗試獲取鎖,不必理會隊列是否為空,這樣就實現(xiàn)了插隊的特點。通常來說非公平鎖的吞吐率比公平鎖要高,我們一般常用非公平鎖。
這里需要解釋一點,什么情況下才會出現(xiàn),表示鎖的狀態(tài)的變量的值是為0而且隊列中仍有其它線程等待獲取鎖的情況。
假設有三個線程A、B、C。A線程為正在運行的線程并持有鎖,隊列中有一個C線程,位于隊首。現(xiàn)在A線程要釋放鎖,具體執(zhí)行的過程操作可分為兩步:
1. 將表示鎖狀態(tài)的變量值由1變?yōu)?,
2. C線程被喚醒,這里要明確兩點:(1)C線程被喚醒并不代表C線程開始執(zhí)行,C線程此時是處于就緒狀態(tài),要等待CPU的輪詢(2)C線程目前還并未出列,C線程 要進入運行狀態(tài),并且通過競爭獲取到鎖以后才會出列。
如果C線程此時還沒有進入運行態(tài),同時未在隊列中的B線程進行獲取鎖的操作,B就會發(fā)現(xiàn)雖然當前沒有線程持有鎖,但是隊列不為空(C線程仍然位于隊列中),要滿足先來后到的特點(B在C之后執(zhí)行獲取鎖的操作),B線程就不能去嘗試獲取鎖,而是進行入列操作。
Condition 本質是一個接口,它包含如下方法
// 讓線程進入等待通知狀態(tài),在未接受到通知之前,可通過中斷結束等待狀態(tài),并拋出異常
void await() throws InterruptedException;
// 讓線程進入等通知待狀態(tài),無法被中斷
void awaitUninterruptibly();
//讓線程進入等待通知狀態(tài),超時結束等待狀態(tài),并拋出異常
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//將同一等待條件下的一個線程,從等待通知狀態(tài)轉換為等待鎖狀態(tài)
void signal();
//將同一等待條件下的所有個線程,從等待通知阻塞狀態(tài)轉換為等待鎖阻塞狀態(tài)
void signalAll();
一個Condition實例的內部實際上維護了一個隊列,隊列中的節(jié)點表示由于(某些條件不滿足而)線程自身調用await方法阻塞的線程。Condition接口中有兩個重要的方法,即 await方法和 signal方法。線程調用這個方法之前該線程必須已經(jīng)獲取了Condition實例所依附的鎖。這樣的原因有兩個,1對于await方法,它內部會執(zhí)行釋放鎖的操作,所以使用前必須獲取鎖2對于signal方法,是為了避免多個線程同時調用同一個Condition實例的singal方法時引起的(隊列)出列競爭。下面是這兩個方法的執(zhí)行流程。
await方法:
1. 入列到條件隊列(這里不是等待鎖的隊列)
2. 釋放鎖
3. 阻塞自身線程
------------被喚醒后執(zhí)行-------------
4. 嘗試去獲取鎖(執(zhí)行到這里時線程已不在條件隊列中,而是位于等待(鎖的)隊列中,參見signal方法)
4.1 成功,從await方法中返回,執(zhí)行線程后面的代碼
4.2 失敗,阻塞自己(等待前一個節(jié)點釋放鎖時將它喚醒)
注意:await方法時自身線程調用的,線程在await方法中阻塞,并沒有從await方法中返回,當喚醒后繼續(xù)執(zhí)行await方法中后面的代碼。可以看出await方法釋放了鎖, 嘗試獲得鎖。
signal方法:
1. 將條件隊列的隊首節(jié)點取出,放入等待鎖隊列的隊尾
2. 喚醒該節(jié)點對應的線程
注意:signal是由其它線程調用

下面這個例子,就是利用lock和condition實現(xiàn)B線程先打印一句信息后,然后A線程打印兩句信息(不能中斷),交替十次后結束
public class ConditionDemo { volatile int key = 0; Lock l = new ReentrantLock(); Condition c = l.newCondition(); public static void main(String[] args){ ConditionDemo demo = new ConditionDemo(); new Thread(demo.new A()).start(); new Thread(demo.new B()).start(); } class A implements Runnable{ @Override public void run() { int i = 10; while(i > 0){ l.lock(); try{ if(key == 1){ System.out.println("A is Running"); System.out.println("A is Running"); i--; key = 0; c.signal(); }else{ c.awaitUninterruptibly(); } } finally{ l.unlock(); } } } } class B implements Runnable{ @Override public void run() { int i = 10; while(i > 0){ l.lock(); try{ if(key == 0){ System.out.println("B is Running"); i--; key = 1; c.signal(); }else{ c.awaitUninterruptibly(); } } finally{ l.unlock(); } } } }}
1. Lock的加鎖和解鎖都是由java代碼配合native方法(調用操作系統(tǒng)的相關方法)實現(xiàn)的,而synchronize的加鎖和解鎖的過程是由JVM管理的
2. 當一個線程使用synchronize獲取鎖時,若鎖被其他線程占用著,那么當前只能被阻塞,直到成功獲取鎖。而Lock則提供超時鎖和可中斷等更加靈活的方式,在未能獲取鎖的 條件下提供一種退出的機制。
3. 一個鎖內部可以有多個Condition實例,即有多路條件隊列,而synchronize只有一路條件隊列;同樣Condition也提供靈活的阻塞方式,在未獲得通知之前可以通過中斷線程以 及設置等待時限等方式退出條件隊列。
4. synchronize對線程的同步僅提供獨占模式,而Lock即可以提供獨占模式,也可以提供共享模式
新聞熱點
疑難解答