Java 并發(fā)開發(fā):Lock 框架詳解

摘要:

我們已經(jīng)知道,synchronized 是Java的關鍵字,是Java的內(nèi)置特性钠糊,在JVM層面實現(xiàn)了對臨界資源的同步互斥訪問壹哺,但 synchronized 粒度有些大,在處理實際問題時存在諸多局限性艘刚,比如響應中斷等管宵。Lock 提供了比 synchronized更廣泛的鎖操作,它能以更優(yōu)雅的方式處理線程同步問題攀甚。本文以synchronized與Lock的對比為切入點箩朴,對Java中的Lock框架的枝干部分進行了詳細介紹,最后給出了鎖的一些相關概念秋度。

一. synchronized 的局限性 與 Lock 的優(yōu)點

如果一個代碼塊被synchronized關鍵字修飾炸庞,當一個線程獲取了對應的鎖,并執(zhí)行該代碼塊時荚斯,其他線程便只能一直等待直至占有鎖的線程釋放鎖埠居。事實上,占有鎖的線程釋放鎖一般會是以下三種情況之一:

占有鎖的線程執(zhí)行完了該代碼塊事期,然后釋放對鎖的占有滥壕;

占有鎖線程執(zhí)行發(fā)生異常,此時JVM會讓線程自動釋放鎖兽泣;

占有鎖線程進入 WAITING 狀態(tài)從而釋放鎖绎橘,例如在該線程中調(diào)用wait()方法等。

synchronized 是Java語言的內(nèi)置特性唠倦,可以輕松實現(xiàn)對臨界資源的同步互斥訪問称鳞。那么涮较,為什么還會出現(xiàn)Lock呢?試考慮以下三種情況:

Case 1 :

在使用synchronized關鍵字的情形下冈止,假如占有鎖的線程由于要等待IO或者其他原因(比如調(diào)用sleep方法)被阻塞了法希,但是又沒有釋放鎖,那么其他線程就只能一直等待靶瘸,別無他法苫亦。這會極大影響程序執(zhí)行效率。因此怨咪,就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間?(解決方案:tryLock(long time, TimeUnit unit))?或者 能夠響應中斷?(解決方案:lockInterruptibly()))屋剑,這種情況可以通過 Lock 解決。

Case 2 :

我們知道诗眨,當多個線程讀寫文件時唉匾,讀操作和寫操作會發(fā)生沖突現(xiàn)象,寫操作和寫操作也會發(fā)生沖突現(xiàn)象匠楚,但是讀操作和讀操作不會發(fā)生沖突現(xiàn)象揍拆。但是如果采用synchronized關鍵字實現(xiàn)同步的話,就會導致一個問題哲嘲,即當多個線程都只是進行讀操作時说墨,也只有一個線程在可以進行讀操作,其他線程只能等待鎖的釋放而無法進行讀操作与斤。因此肪康,需要一種機制來使得當多個線程都只是進行讀操作時,線程之間不會發(fā)生沖突撩穿。同樣地磷支,Lock也可以解決這種情況?(解決方案:ReentrantReadWriteLock)?。

Case 3 :

我們可以通過Lock得知線程有沒有成功獲取到鎖?(解決方案:ReentrantLock)?食寡,但這個是synchronized無法辦到的雾狈。

上面提到的三種情形,我們都可以通過Lock來解決抵皱,但 synchronized 關鍵字卻無能為力善榛。事實上,Lock 是 java.util.concurrent.locks包 下的接口叨叙,Lock 實現(xiàn)提供了比 synchronized 關鍵字 更廣泛的鎖操作锭弊,它能以更優(yōu)雅的方式處理線程同步問題。也就是說擂错,Lock提供了比synchronized更多的功能味滞。但是要注意以下幾點:

1)synchronized是Java的關鍵字,因此是Java的內(nèi)置特性,是基于JVM層面實現(xiàn)的剑鞍。而Lock是一個Java接口昨凡,是基于JDK層面實現(xiàn)的,通過這個接口可以實現(xiàn)同步訪問蚁署;

2)采用synchronized方式不需要用戶去手動釋放鎖便脊,當synchronized方法或者synchronized代碼塊執(zhí)行完之后,系統(tǒng)會自動讓線程釋放對鎖的占用光戈;而?Lock則必須要用戶去手動釋放鎖哪痰,如果沒有主動釋放鎖,就有可能導致死鎖現(xiàn)象久妆。

二. java.util.concurrent.locks包下常用的類與接口

以下是 java.util.concurrent.locks包下主要常用的類與接口的關系:

1晌杰、Lock

通過查看Lock的源碼可知,Lock 是一個接口:

publicinterfaceLock {voidlock();voidlockInterruptibly()throws InterruptedException;// 可以響應中斷booleantryLock();booleantryLock(long time, TimeUnit unit)throws InterruptedException;// 可以響應中斷voidunlock();ConditionnewCondition();

}

下面來逐個分析Lock接口中每個方法筷弦。lock()肋演、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用來獲取鎖的烂琴。unLock()方法是用來釋放鎖的爹殊。newCondition() 返回?綁定到此 Lock 的新的 Condition 實例?,用于線程間的協(xié)作奸绷,詳細內(nèi)容見文章《Java 并發(fā):線程間通信與協(xié)作》梗夸。

1). lock()

在Lock中聲明了四個方法來獲取鎖,那么這四個方法有何區(qū)別呢健盒?首先绒瘦,lock()方法是平常使用得最多的一個方法,就是用來獲取鎖扣癣。如果鎖已被其他線程獲取,則進行等待憨降。在前面已經(jīng)講到父虑,如果采用Lock,必須主動去釋放鎖授药,并且在發(fā)生異常時士嚎,不會自動釋放鎖。因此悔叽,一般來說莱衩,使用Lock必須在try…catch…塊中進行,并且將釋放鎖的操作放在finally塊中進行娇澎,以保證鎖一定被被釋放笨蚁,防止死鎖的發(fā)生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Locklock = ...;lock.lock();try{//處理任務}catch(Exception ex){}finally{lock.unlock();//釋放鎖

}

2). tryLock() & tryLock(long time, TimeUnit unit)

tryLock()方法是有返回值的括细,它表示用來嘗試獲取鎖伪很,如果獲取成功,則返回true奋单;如果獲取失旓笔浴(即鎖已被其他線程獲取)览濒,則返回false呆盖,也就是說,這個方法無論如何都會立即返回(在拿不到鎖時不會一直在那等待)贷笛。

tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的絮短,只不過區(qū)別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內(nèi)如果還拿不到鎖昨忆,就返回false丁频,同時可以響應中斷。如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖邑贴,則返回true席里。

一般情況下,通過tryLock來獲取鎖時是這樣使用的:

Locklock = ...;if(lock.tryLock()) {try{//處理任務? ? }catch(Exception ex){? ? }finally{lock.unlock();//釋放鎖? ? } }else {//如果不能獲取鎖拢驾,則直接做其他事情

}

3). lockInterruptibly()

lockInterruptibly()方法比較特殊奖磁,當通過這個方法去獲取鎖時,如果線程?正在等待獲取鎖繁疤,則這個線程能夠?響應中斷咖为,即中斷線程的等待狀態(tài)。例如稠腊,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時躁染,假若此時線程A獲取到了鎖,而線程B只有在等待架忌,那么對線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過程吞彤。

由于lockInterruptibly()的聲明中拋出了異常,所以lock.lockInterruptibly()必須放在try塊中或者在調(diào)用lockInterruptibly()的方法外聲明拋出 InterruptedException叹放,但推薦使用后者饰恕,原因稍后闡述。因此井仰,lockInterruptibly()一般的使用形式如下:

publicvoidmethod() throws InterruptedException {lock.lockInterruptibly();try {//.....? ? }finally {lock.unlock();

}

}

注意埋嵌,當一個線程獲取了鎖之后,是不會被interrupt()方法中斷的俱恶。因為interrupt()方法只能中斷阻塞過程中的線程而不能中斷正在運行過程中的線程雹嗦。因此范舀,當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到俐银,那么只有進行等待的情況下尿背,才可以響應中斷的。與 synchronized 相比捶惜,當一個線程處于等待某個鎖的狀態(tài)田藐,是無法被中斷的,只有一直等待下去吱七。

2汽久、ReentrantLock

ReentrantLock,即?可重入鎖踊餐。ReentrantLock是唯一實現(xiàn)了Lock接口的類景醇,并且ReentrantLock提供了更多的方法。下面通過一些實例學習如何使用 ReentrantLock吝岭。

例 1 : Lock 的正確使用

publicclassTest {private ArrayList arrayList =new ArrayList();publicstaticvoidmain(String[] args) {? ? ? ? final Test test =new Test();new Thread("A") {publicvoidrun() {? ? ? ? ? ? ? ? test.insert(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();new Thread("B") {publicvoidrun() {? ? ? ? ? ? ? ? test.insert(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();? ? }publicvoidinsert(Thread thread) {? ? ? ? Locklock =new ReentrantLock();// 注意這個地方:lock被聲明為局部變量lock.lock();try {? ? ? ? ? ? System.out.println("線程" + thread.getName() +"得到了鎖...");for (int i =0; i <5; i++) {? ? ? ? ? ? ? ? arrayList.add(i);? ? ? ? ? ? }? ? ? ? }catch (Exception e) {? ? ? ? }finally {? ? ? ? ? ? System.out.println("線程" + thread.getName() +"釋放了鎖...");lock.unlock();? ? ? ? }? ? }}/* Output:? ? ? ? 線程A得到了鎖...? ? ? ? 線程B得到了鎖...? ? ? ? 線程A釋放了鎖...? ? ? ? 線程B釋放了鎖... *///:~

結果或許讓人覺得詫異三痰。第二個線程怎么會在第一個線程釋放鎖之前得到了鎖?原因在于窜管,在insert方法中的lock變量是局部變量散劫,每個線程執(zhí)行該方法時都會保存一個副本,那么每個線程執(zhí)行到lock.lock()處獲取的是不同的鎖幕帆,所以就不會對臨界資源形成同步互斥訪問获搏。因此,我們只需要將lock聲明為成員變量即可失乾,如下所示常熙。

publicclassTest {private ArrayList arrayList =new ArrayList();private Locklock =new ReentrantLock();// 注意這個地方:lock被聲明為成員變量? ? ...}/* Output:? ? ? ? 線程A得到了鎖...? ? ? ? 線程A釋放了鎖...? ? ? ? 線程B得到了鎖...? ? ? ? 線程B釋放了鎖... *///:~

例 2 : tryLock() & tryLock(long time, TimeUnit unit)

publicclassTest {private ArrayList arrayList =new ArrayList();private Locklock =new ReentrantLock();// 注意這個地方:lock 被聲明為成員變量publicstaticvoidmain(String[] args) {? ? ? ? final Test test =new Test();new Thread("A") {publicvoidrun() {? ? ? ? ? ? ? ? test.insert(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();new Thread("B") {publicvoidrun() {? ? ? ? ? ? ? ? test.insert(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();? ? }publicvoidinsert(Thread thread) {if (lock.tryLock()) {// 使用 tryLock()try {? ? ? ? ? ? ? ? System.out.println("線程" + thread.getName() +"得到了鎖...");for (int i =0; i <5; i++) {? ? ? ? ? ? ? ? ? ? arrayList.add(i);? ? ? ? ? ? ? ? }? ? ? ? ? ? }catch (Exception e) {? ? ? ? ? ? }finally {? ? ? ? ? ? ? ? System.out.println("線程" + thread.getName() +"釋放了鎖...");lock.unlock();? ? ? ? ? ? }? ? ? ? }else {? ? ? ? ? ? System.out.println("線程" + thread.getName() +"獲取鎖失敗...");? ? ? ? }? ? }}/* Output:? ? ? ? 線程A得到了鎖...? ? ? ? 線程B獲取鎖失敗...? ? ? ? 線程A釋放了鎖... *///:~

與 tryLock() 不同的是,tryLock(long time, TimeUnit unit) 能夠響應中斷碱茁,即支持對獲取鎖的中斷裸卫,但嘗試獲取一個內(nèi)部鎖的操作(進入一個 synchronized 塊)是不能被中斷的。如下所示:

publicclassTest {private Locklock =new ReentrantLock();publicstaticvoidmain(String[] args)? {? ? ? ? Test test =new Test();? ? ? ? MyThread thread1 =new MyThread(test,"A");? ? ? ? MyThread thread2 =new MyThread(test,"B");? ? ? ? thread1.start();? ? ? ? thread2.start();try {? ? ? ? ? ? Thread.sleep(2000);? ? ? ? }catch (InterruptedException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? ? ? thread2.interrupt();? ? }publicvoidinsert(Thread thread) throws InterruptedException{if(lock.tryLock(4, TimeUnit.SECONDS)){try {? ? ? ? ? ? ? ? System.out.println("time=" + System.currentTimeMillis() +" ,線程 " + thread.getName()+"得到了鎖...");long now = System.currentTimeMillis();while (System.currentTimeMillis() - now <5000) {// 為了避免Thread.sleep()而需要捕獲InterruptedException而帶來的理解上的困惑,// 此處用這種方法空轉(zhuǎn)3秒? ? ? ? ? ? ? ? }? ? ? ? ? ? }finally{lock.unlock();? ? ? ? ? ? }? ? ? ? }else {? ? ? ? ? ? System.out.println("線程 " + thread.getName()+"放棄了對鎖的獲取...");? ? ? ? }? ? }}classMyThreadextendsThread {private Test test =null;publicMyThread(Test test,String name) {? ? ? ? super(name);this.test = test;? ? }? ? @Overridepublicvoidrun() {try {? ? ? ? ? ? test.insert(Thread.currentThread());? ? ? ? }catch (InterruptedException e) {? ? ? ? ? ? System.out.println("time=" + System.currentTimeMillis() +" ,線程 " + Thread.currentThread().getName() +"被中斷...");? ? ? ? }? ? }}/* Output:? ? ? ? time=1486693682559, 線程A 得到了鎖...? ? ? ? time=1486693684560, 線程B 被中斷...(響應中斷早芭,時間恰好間隔2s) *///:~

例 3 : 使用 lockInterruptibly() 響應中斷

publicclassTest {private Locklock =new ReentrantLock();publicstaticvoidmain(String[] args)? {? ? ? ? Test test =new Test();? ? ? ? MyThread thread1 =new MyThread(test,"A");? ? ? ? MyThread thread2 =new MyThread(test,"B");? ? ? ? thread1.start();? ? ? ? thread2.start();try {? ? ? ? ? ? Thread.sleep(2000);? ? ? ? }catch (InterruptedException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? ? ? thread2.interrupt();? ? }publicvoidinsert(Thread thread) throws InterruptedException{//注意彼城,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面退个,然后將 InterruptedException 拋出lock.lockInterruptibly();try {? ? ? ? ? ? ? System.out.println("線程 " + thread.getName()+"得到了鎖...");long startTime = System.currentTimeMillis();for(? ? ;? ? ; ) {// 耗時操作if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)break;//插入數(shù)據(jù)? ? ? ? ? ? }? ? ? ? }finally {? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"執(zhí)行finally...");lock.unlock();? ? ? ? ? ? System.out.println("線程 " + thread.getName()+"釋放了鎖");? ? ? ? }? ? ? ? System.out.println("over");? ? }}classMyThreadextendsThread {private Test test =null;publicMyThread(Test test,String name) {? ? ? ? super(name);this.test = test;? ? }? ? @Overridepublicvoidrun() {try {? ? ? ? ? ? test.insert(Thread.currentThread());? ? ? ? }catch (InterruptedException e) {? ? ? ? ? ? System.out.println("線程 " + Thread.currentThread().getName() +"被中斷...");? ? ? ? }? ? }}/* Output:? ? ? ? 線程 A得到了鎖...? ? ? ? 線程 B被中斷... *///:~

運行上述代碼之后,發(fā)現(xiàn)?thread2 能夠被正確中斷调炬,放棄對任務的執(zhí)行语盈。特別需要注意的是,如果需要正確中斷等待鎖的線程缰泡,必須將獲取鎖放在外面(try 語句塊外)刀荒,然后將 InterruptedException 拋出代嗤。如果不這樣做,像如下代碼所示:

import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;publicclassTest {private Locklock =new ReentrantLock();publicstaticvoidmain(String[] args) {? ? ? ? Test test =new Test();? ? ? ? MyThread thread1 =new MyThread(test,"A");? ? ? ? MyThread thread2 =new MyThread(test,"B");? ? ? ? thread1.start();? ? ? ? thread2.start();try {? ? ? ? ? ? Thread.sleep(5000);? ? ? ? ? ? System.out.println("線程" + Thread.currentThread().getName()? ? ? ? ? ? ? ? ? ? +" 睡醒了...");? ? ? ? }catch (InterruptedException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? ? ? thread2.interrupt();? ? }publicvoidinsert(Thread thread) {try {// 注意缠借,如果將獲取鎖放在try語句塊里干毅,則必定會執(zhí)行finally語句塊中的解鎖操作。若線程在獲取鎖時被中斷泼返,則再執(zhí)行解鎖操作就會導致異常硝逢,因為該線程并未獲得到鎖。lock.lockInterruptibly();? ? ? ? ? ? System.out.println("線程 " + thread.getName() +"得到了鎖...");long startTime = System.currentTimeMillis();for (;;) {if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)// 耗時操作break;// 插入數(shù)據(jù)? ? ? ? ? ? }? ? ? ? }catch (Exception e) {? ? ? ? }finally {? ? ? ? ? ? System.out.println(Thread.currentThread().getName()? ? ? ? ? ? ? ? ? ? +"執(zhí)行finally...");lock.unlock();? ? ? ? ? ? System.out.println("線程 " + thread.getName() +"釋放了鎖...");? ? ? ? }? ? }}classMyThreadextendsThread {private Test test =null;publicMyThread(Test test, String name) {? ? ? ? super(name);this.test = test;? ? }? ? @Overridepublicvoidrun() {? ? ? ? test.insert(Thread.currentThread());? ? ? ? System.out.println("線程 " + Thread.currentThread().getName() +"被中斷...");? ? }}/* Output:? ? ? ? 線程A 得到了鎖...? ? ? ? 線程main 睡醒了...? ? ? ? B執(zhí)行finally...? ? ? ? Exception in thread "B"? ? ? ? ? ? java.lang.IllegalMonitorStateException? ? ? ? ? ? at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)? ? ? ? ? ? at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)? ? ? ? ? ? at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)? ? ? ? ? ? at Test.insert(Test.java:39)? ? ? ? ? ? at MyThread.run(Test.java:56) *///:~

注意绅喉,上述代碼就將鎖的獲取操作放在try語句塊里渠鸽,則必定會執(zhí)行finally語句塊中的解鎖操作。在 準備獲取鎖的 線程B 被中斷后柴罐,再執(zhí)行解鎖操作就會拋出 IllegalMonitorStateException徽缚,因為該線程并未獲得到鎖卻執(zhí)行了解鎖操作。

3革屠、ReadWriteLock

ReadWriteLock也是一個接口凿试,在它里面只定義了兩個方法:

publicinterfaceReadWriteLock {/**? ? * Returns the lock used for reading.? ? *? ? *@return the lock used for reading.? ? */LockreadLock();/**? ? * Returns the lock used for writing.? ? *? ? *@return the lock used for writing.? ? */LockwriteLock();

}

一個用來獲取讀鎖,一個用來獲取寫鎖似芝。也就是說那婉,將對臨界資源的讀寫操作分成兩個鎖來分配給線程,從而使得多個線程可以同時進行讀操作国觉。下面的 ReentrantReadWriteLock 實現(xiàn)了 ReadWriteLock 接口吧恃。

4、ReentrantReadWriteLock

ReentrantReadWriteLock 里面提供了很多豐富的方法麻诀,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖痕寓。下面通過幾個例子來看一下ReentrantReadWriteLock具體用法。假如有多個線程要同時進行讀操作的話蝇闭,先看一下synchronized達到的效果:

publicclassTest {publicstaticvoidmain(String[] args)? {? ? ? ? final Test test =new Test();new Thread("A"){publicvoidrun() {? ? ? ? ? ? ? ? test.get(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();new Thread("B"){publicvoidrun() {? ? ? ? ? ? ? ? test.get(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();? ? }public synchronizedvoidget(Thread thread) {long start = System.currentTimeMillis();? ? ? ? System.out.println("線程"+ thread.getName()+"開始讀操作...");while(System.currentTimeMillis() - start <=1) {? ? ? ? ? ? System.out.println("線程"+ thread.getName()+"正在進行讀操作...");? ? ? ? }? ? ? ? System.out.println("線程"+ thread.getName()+"讀操作完畢...");? ? }}/* Output:? ? ? ? 線程A開始讀操作...? ? ? ? 線程A正在進行讀操作...? ? ? ? ...? ? ? ? 線程A正在進行讀操作...? ? ? ? 線程A讀操作完畢...? ? ? ? 線程B開始讀操作...? ? ? ? 線程B正在進行讀操作...? ? ? ? ...? ? ? ? 線程B正在進行讀操作...? ? ? ? 線程B讀操作完畢... *///:~

這段程序的輸出結果會是呻率,直到線程A執(zhí)行完讀操作之后,才會打印線程B執(zhí)行讀操作的信息呻引。而改成使用讀寫鎖的話:

publicclassTest {private ReentrantReadWriteLock rwl =new ReentrantReadWriteLock();publicstaticvoidmain(String[] args) {? ? ? ? final Test test =new Test();new Thread("A") {publicvoidrun() {? ? ? ? ? ? ? ? test.get(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();new Thread("B") {publicvoidrun() {? ? ? ? ? ? ? ? test.get(Thread.currentThread());? ? ? ? ? ? };? ? ? ? }.start();? ? }publicvoidget(Thread thread) {? ? ? ? rwl.readLock().lock();// 在外面獲取鎖try {long start = System.currentTimeMillis();? ? ? ? ? ? System.out.println("線程" + thread.getName() +"開始讀操作...");while (System.currentTimeMillis() - start <=1) {? ? ? ? ? ? ? ? System.out.println("線程" + thread.getName() +"正在進行讀操作...");? ? ? ? ? ? }? ? ? ? ? ? System.out.println("線程" + thread.getName() +"讀操作完畢...");? ? ? ? }finally {? ? ? ? ? ? rwl.readLock().unlock();? ? ? ? }? ? }}/* Output:? ? ? ? 線程A開始讀操作...? ? ? ? 線程B開始讀操作...? ? ? ? 線程A正在進行讀操作...? ? ? ? 線程A正在進行讀操作...? ? ? ? 線程B正在進行讀操作...? ? ? ? ...? ? ? ? 線程A讀操作完畢...? ? ? ? 線程B讀操作完畢... *///:~

我們可以看到礼仗,線程A和線程B在同時進行讀操作,這樣就大大提升了讀操作的效率逻悠。不過要注意的是元践,如果有一個線程已經(jīng)占用了讀鎖,則此時其他線程如果要申請寫鎖童谒,則申請寫鎖的線程會一直等待釋放讀鎖单旁。如果有一個線程已經(jīng)占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖饥伊,則申請的線程也會一直等待釋放寫鎖象浑。

5蔫饰、Lock和synchronized的選擇

總的來說,Lock和synchronized有以下幾點不同:

(1)?Lock是一個接口愉豺,是JDK層面的實現(xiàn)篓吁;而synchronized是Java中的關鍵字,是Java的內(nèi)置特性蚪拦,是JVM層面的實現(xiàn)杖剪;

(2)?synchronized 在發(fā)生異常時,會自動釋放線程占有的鎖外盯,因此不會導致死鎖現(xiàn)象發(fā)生摘盆;而Lock在發(fā)生異常時,如果沒有主動通過unLock()去釋放鎖饱苟,則很可能造成死鎖現(xiàn)象孩擂,因此使用Lock時需要在finally塊中釋放鎖;

(3)?Lock 可以讓等待鎖的線程響應中斷箱熬,而使用synchronized時类垦,等待的線程會一直等待下去,不能夠響應中斷城须;

(4)?通過Lock可以知道有沒有成功獲取鎖蚤认,而synchronized卻無法辦到;

(5)?Lock可以提高多個線程進行讀操作的效率糕伐。

在性能上來說砰琢,如果競爭資源不激烈,兩者的性能是差不多的良瞧。而當競爭資源非常激烈時(即有大量線程同時競爭)陪汽,此時Lock的性能要遠遠優(yōu)于synchronized。所以說褥蚯,在具體使用時要根據(jù)適當情況選擇挚冤。

三. 鎖的相關概念介紹

1、可重入鎖

如果鎖具備可重入性赞庶,則稱作為?可重入鎖?训挡。像?synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了?鎖的分配機制:基于線程的分配歧强,而不是基于方法調(diào)用的分配澜薄。舉個簡單的例子,當一個線程執(zhí)行到某個synchronized方法時摊册,比如說method1表悬,而在method1中會調(diào)用另外一個synchronized方法method2,此時線程不必重新去申請鎖丧靡,而是可以直接執(zhí)行方法method2蟆沫。

classMyClass {publicsynchronizedvoidmethod1() {? ? ? ? method2();? ? }publicsynchronizedvoidmethod2() {

}

}

上述代碼中的兩個方法method1和method2都用synchronized修飾了。假如某一時刻温治,線程A執(zhí)行到了method1饭庞,此時線程A獲取了這個對象的鎖,而由于method2也是synchronized方法熬荆,假如synchronized不具備可重入性舟山,此時線程A需要重新申請鎖。但是卤恳,這就會造成死鎖累盗,因為線程A已經(jīng)持有了該對象的鎖,而又在申請獲取該對象的鎖突琳,這樣就會線程A一直等待永遠不會獲取到的鎖若债。而由于synchronized和Lock都具備可重入性,所以不會發(fā)生上述現(xiàn)象拆融。

2蠢琳、可中斷鎖

顧名思義,可中斷鎖就是可以響應中斷的鎖镜豹。在Java中傲须,synchronized就不是可中斷鎖,而Lock是可中斷鎖趟脂。

如果某一線程A正在執(zhí)行鎖中的代碼泰讽,另一線程B正在等待獲取該鎖,可能由于等待時間過長昔期,線程B不想等待了已卸,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它镇眷,這種就是可中斷鎖咬最。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法時已經(jīng)體現(xiàn)了Lock的可中斷性。

3欠动、公平鎖

公平鎖即?盡量?以請求鎖的順序來獲取鎖永乌。比如,同是有多個線程在等待一個鎖具伍,當這個鎖被釋放時翅雏,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖人芽。而非公平鎖則無法保證鎖的獲取是按照請求鎖的順序進行的望几,這樣就可能導致某個或者一些線程永遠獲取不到鎖。

在Java中萤厅,synchronized就是非公平鎖橄抹,它無法保證等待的線程獲取鎖的順序靴迫。而對于ReentrantLock 和 ReentrantReadWriteLock,它默認情況下是非公平鎖楼誓,但是可以設置為公平鎖玉锌。

看下面兩個例子:

Case : 公平鎖

publicclassRunFair {publicstaticvoidmain(String[] args) throws InterruptedException {? ? ? ? final Service service =new Service(true);// 公平鎖,設為 true? ? ? ? Runnable runnable =new Runnable() {? ? ? ? ? ? @Overridepublicvoidrun() {? ? ? ? ? ? ? ? System.out.println("★線程" + Thread.currentThread().getName()? ? ? ? ? ? ? ? ? ? ? ? +"運行了");? ? ? ? ? ? ? ? service.serviceMethod();? ? ? ? ? ? }? ? ? ? };? ? ? ? Thread[] threadArray =new Thread[10];for (int i =0; i <10; i++)? ? ? ? ? ? threadArray[i] =new Thread(runnable);for (int i =0; i <10; i++)? ? ? ? ? ? threadArray[i].start();? ? }}classService {private ReentrantLocklock;publicService(boolean isFair) {? ? ? ? super();lock =new ReentrantLock(isFair);? ? }publicvoidserviceMethod() {try {lock.lock();? ? ? ? ? ? System.out.println("ThreadName=" + Thread.currentThread().getName()? ? ? ? ? ? ? ? ? ? +"獲得鎖定");? ? ? ? }finally {lock.unlock();? ? ? ? }? ? }}/* Output:? ? ? ? ★線程Thread-0運行了? ? ? ? ★線程Thread-1運行了? ? ? ? ThreadName=Thread-1獲得鎖定? ? ? ? ThreadName=Thread-0獲得鎖定? ? ? ? ★線程Thread-2運行了? ? ? ? ThreadName=Thread-2獲得鎖定? ? ? ? ★線程Thread-3運行了? ? ? ? ★線程Thread-4運行了? ? ? ? ThreadName=Thread-4獲得鎖定? ? ? ? ★線程Thread-5運行了? ? ? ? ThreadName=Thread-5獲得鎖定? ? ? ? ThreadName=Thread-3獲得鎖定? ? ? ? ★線程Thread-6運行了? ? ? ? ★線程Thread-7運行了? ? ? ? ThreadName=Thread-6獲得鎖定? ? ? ? ★線程Thread-8運行了? ? ? ? ★線程Thread-9運行了? ? ? ? ThreadName=Thread-7獲得鎖定? ? ? ? ThreadName=Thread-8獲得鎖定? ? ? ? ThreadName=Thread-9獲得鎖定*///:~

Case: 非公平鎖

publicclassRunFair {publicstaticvoidmain(String[] args)throws InterruptedException {final Service service =new Service(false);// 非公平鎖疟羹,設為 false? ? ? ? ...}/* Output:? ? ? ? ★線程Thread-0運行了? ? ? ? ThreadName=Thread-0獲得鎖定? ? ? ? ★線程Thread-2運行了? ? ? ? ThreadName=Thread-2獲得鎖定? ? ? ? ★線程Thread-6運行了? ? ? ? ★線程Thread-1運行了? ? ? ? ThreadName=Thread-6獲得鎖定? ? ? ? ★線程Thread-3運行了? ? ? ? ThreadName=Thread-3獲得鎖定? ? ? ? ★線程Thread-7運行了? ? ? ? ThreadName=Thread-7獲得鎖定? ? ? ? ★線程Thread-4運行了? ? ? ? ThreadName=Thread-4獲得鎖定? ? ? ? ★線程Thread-5運行了? ? ? ? ThreadName=Thread-5獲得鎖定? ? ? ? ★線程Thread-8運行了? ? ? ? ThreadName=Thread-8獲得鎖定? ? ? ? ★線程Thread-9運行了? ? ? ? ThreadName=Thread-9獲得鎖定? ? ? ? ThreadName=Thread-1獲得鎖定*///:~

根據(jù)上面代碼演示結果我們可以看出(線程數(shù)越多越明顯)主守,在公平鎖案例下,多個線程在等待一個鎖時榄融,一般而言参淫,等待時間最久的線程(最先請求的線程)會獲得該鎖。而在非公平鎖例下愧杯,則無法保證鎖的獲取是按照請求鎖的順序進行的涎才。

另外, 在ReentrantLock類中定義了很多方法民效,舉幾個例子:

isFair() //判斷鎖是否是公平鎖

isLocked() //判斷鎖是否被任何線程獲取了

isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了

hasQueuedThreads() //判斷是否有線程在等待該鎖

getHoldCount() //查詢當前線程占有l(wèi)ock鎖的次數(shù)

getQueueLength() // 獲取正在等待此鎖的線程數(shù)

getWaitQueueLength(Condition condition) // 獲取正在等待此鎖相關條件condition的線程數(shù)在ReentrantReadWriteLock中也有類似的方法憔维,同樣也可以設置為公平鎖和非公平鎖。不過要記住畏邢,ReentrantReadWriteLock并未實現(xiàn)Lock接口业扒,它實現(xiàn)的是ReadWriteLock接口。

4.讀寫鎖

讀寫鎖將對臨界資源的訪問分成了兩個鎖舒萎,一個讀鎖和一個寫鎖程储。正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發(fā)生沖突臂寝。ReadWriteLock就是讀寫鎖章鲤,它是一個接口,ReentrantReadWriteLock實現(xiàn)了這個接口咆贬“芑玻可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖掏缎。上一節(jié)已經(jīng)演示過了讀寫鎖的使用方法皱蹦,在此不再贅述。

歡迎加入技術QQ群:364595326

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眷蜈,一起剝皮案震驚了整個濱河市沪哺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酌儒,老刑警劉巖辜妓,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡籍滴,警方通過查閱死者的電腦和手機酪夷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來异逐,“玉大人捶索,你說我怎么就攤上這事』艺埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵辅甥,是天一觀的道長酝润。 經(jīng)常有香客問我,道長璃弄,這世上最難降的妖魔是什么要销? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮夏块,結果婚禮上疏咐,老公的妹妹穿的比我還像新娘。我一直安慰自己脐供,他們只是感情好浑塞,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著政己,像睡著了一般酌壕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上歇由,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天卵牍,我揣著相機與錄音,去河邊找鬼沦泌。 笑死糊昙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谢谦。 我是一名探鬼主播释牺,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼他宛!你這毒婦竟也來了船侧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厅各,失蹤者是張志新(化名)和其女友劉穎镜撩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡袁梗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年宜鸯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遮怜。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡淋袖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锯梁,到底是詐尸還是另有隱情即碗,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布陌凳,位于F島的核電站剥懒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏合敦。R本人自食惡果不足惜初橘,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望充岛。 院中可真熱鬧保檐,春花似錦、人聲如沸崔梗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炒俱。三九已至盐肃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間权悟,已是汗流浹背砸王。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留峦阁,地道東北人谦铃。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像榔昔,于是被迫代替她去往敵國和親驹闰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容