Java 併發開發:Lock 框架詳解

摘要:

咱們已經知道,synchronized 是Java的關鍵字,是Java的內置特性,在JVM層面實現了對臨界資源的同步互斥訪問,但 synchronized 粒度有些大,在處理實際問題時存在諸多侷限性,好比響應中斷等。Lock 提供了比 synchronized更普遍的鎖操做,它能以更優雅的方式處理線程同步問題。本文以synchronized與Lock的對比爲切入點,對Java中的Lock框架的枝幹部分進行了詳細介紹,最後給出了鎖的一些相關概念。html

一. synchronized 的侷限性 與 Lock 的優勢

若是一個代碼塊被synchronized關鍵字修飾,當一個線程獲取了對應的鎖,並執行該代碼塊時,其餘線程便只能一直等待直至佔有鎖的線程釋放鎖。事實上,佔有鎖的線程釋放鎖通常會是如下三種狀況之一:java

  • 佔有鎖的線程執行完了該代碼塊,而後釋放對鎖的佔有;
  • 佔有鎖線程執行發生異常,此時JVM會讓線程自動釋放鎖;
  • 佔有鎖線程進入 WAITING 狀態從而釋放鎖,例如在該線程中調用wait()方法等。

synchronized 是Java語言的內置特性,能夠輕鬆實現對臨界資源的同步互斥訪問。那麼,爲何還會出現Lock呢?試考慮如下三種狀況:併發

Case 1 :框架

在使用synchronized關鍵字的情形下,假如佔有鎖的線程因爲要等待IO或者其餘緣由(好比調用sleep方法)被阻塞了,可是又沒有釋放鎖,那麼其餘線程就只能一直等待,別無他法。這會極大影響程序執行效率。所以,就須要有一種機制能夠不讓等待的線程一直無期限地等待下去(好比只等待必定的時間 (解決方案:tryLock(long time, TimeUnit unit)) 或者 可以響應中斷 (解決方案:lockInterruptibly())),這種狀況能夠經過 Lock 解決。ide

Case 2 :性能

咱們知道,當多個線程讀寫文件時,讀操做和寫操做會發生衝突現象,寫操做和寫操做也會發生衝突現象,可是讀操做和讀操做不會發生衝突現象。可是若是採用synchronized關鍵字實現同步的話,就會致使一個問題,即當多個線程都只是進行讀操做時,也只有一個線程在能夠進行讀操做,其餘線程只能等待鎖的釋放而沒法進行讀操做。所以,須要一種機制來使得當多個線程都只是進行讀操做時,線程之間不會發生衝突。一樣地,Lock也能夠解決這種狀況 (解決方案:ReentrantReadWriteLock) 。學習

Case 3 :this

咱們能夠經過Lock得知線程有沒有成功獲取到鎖 (解決方案:ReentrantLock) ,但這個是synchronized沒法辦到的。spa

上面提到的三種情形,咱們均可以經過Lock來解決,但 synchronized 關鍵字卻無能爲力。事實上,Lock 是 java.util.concurrent.locks包 下的接口,Lock 實現提供了比 synchronized 關鍵字 更普遍的鎖操做,它能以更優雅的方式處理線程同步問題。也就是說,Lock提供了比synchronized更多的功能。可是要注意如下幾點:線程

1)synchronized是Java的關鍵字,所以是Java的內置特性,是基於JVM層面實現的。而Lock是一個Java接口,是基於JDK層面實現的,經過這個接口能夠實現同步訪問;

2)採用synchronized方式不須要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完以後,系統會自動讓線程釋放對鎖的佔用;而 Lock則必需要用戶去手動釋放鎖,若是沒有主動釋放鎖,就有可能致使死鎖現象。

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

如下是 java.util.concurrent.locks包下主要經常使用的類與接口的關係:

一、Lock

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

public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; // 能夠響應中斷 boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 能夠響應中斷 void unlock(); Condition newCondition(); }

下面來逐個分析Lock接口中每一個方法。lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用來獲取鎖的。unLock()方法是用來釋放鎖的。newCondition() 返回 綁定到此 Lock 的新的 Condition 實例 ,用於線程間的協做,詳細內容見文章《Java 併發:線程間通訊與協做》

1). lock()

在Lock中聲明瞭四個方法來獲取鎖,那麼這四個方法有何區別呢?首先,lock()方法是日常使用得最多的一個方法,就是用來獲取鎖。若是鎖已被其餘線程獲取,則進行等待。在前面已經講到,若是採用Lock,必須主動去釋放鎖,而且在發生異常時,不會自動釋放鎖。所以,通常來講,使用Lock必須在try…catch…塊中進行,而且將釋放鎖的操做放在finally塊中進行,以保證鎖必定被被釋放,防止死鎖的發生。一般使用Lock來進行同步的話,是如下面這種形式去使用的:

Lock lock = ...;
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()方法是相似的,只不過區別在於這個方法在拿不到鎖時會等待必定的時間,在時間期限以內若是還拿不到鎖,就返回false,同時能夠響應中斷。若是一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

通常狀況下,經過tryLock來獲取鎖時是這樣使用的:

Lock lock = ...;
if(lock.tryLock()) { try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock(); //釋放鎖 } }else { //若是不能獲取鎖,則直接作其餘事情 }

3). lockInterruptibly()

lockInterruptibly()方法比較特殊,當經過這個方法去獲取鎖時,若是線程 正在等待獲取鎖,則這個線程可以 響應中斷,即中斷線程的等待狀態。例如,當兩個線程同時經過lock.lockInterruptibly()想獲取某個鎖時,倘若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用threadB.interrupt()方法可以中斷線程B的等待過程。

因爲lockInterruptibly()的聲明中拋出了異常,因此lock.lockInterruptibly()必須放在try塊中或者在調用lockInterruptibly()的方法外聲明拋出 InterruptedException,但推薦使用後者,緣由稍後闡述。所以,lockInterruptibly()通常的使用形式以下:

public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }

注意,當一個線程獲取了鎖以後,是不會被interrupt()方法中斷的。由於interrupt()方法只能中斷阻塞過程當中的線程而不能中斷正在運行過程當中的線程。所以,當經過lockInterruptibly()方法獲取某個鎖時,若是不能獲取到,那麼只有進行等待的狀況下,才能夠響應中斷的。與 synchronized 相比,當一個線程處於等待某個鎖的狀態,是沒法被中斷的,只有一直等待下去。

二、ReentrantLock

ReentrantLock,即 可重入鎖。ReentrantLock是惟一實現了Lock接口的類,而且ReentrantLock提供了更多的方法。下面經過一些實例學習如何使用 ReentrantLock。

例 1 : Lock 的正確使用

public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public static void main(String[] args) { final Test test = new Test(); new Thread("A") { public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread("B") { public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(Thread thread) { Lock lock = 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變量是局部變量,每一個線程執行該方法時都會保存一個副本,那麼每一個線程執行到lock.lock()處獲取的是不一樣的鎖,因此就不會對臨界資源造成同步互斥訪問。所以,咱們只須要將lock聲明爲成員變量便可,以下所示。

public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Lock lock = new ReentrantLock(); // 注意這個地方:lock被聲明爲成員變量 ... }/* Output: 線程A獲得了鎖... 線程A釋放了鎖... 線程B獲得了鎖... 線程B釋放了鎖... *///:~

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

public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Lock lock = new ReentrantLock(); // 注意這個地方:lock 被聲明爲成員變量 public static void main(String[] args) { final Test test = new Test(); new Thread("A") { public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread("B") { public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(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) 可以響應中斷,即支持對獲取鎖的中斷,但嘗試獲取一個內部鎖的操做(進入一個 synchronized 塊)是不能被中斷的。以下所示:

public class Test { private Lock lock = new ReentrantLock(); public static void main(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(); } public void insert(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而帶來的理解上的困惑, // 此處用這種方法空轉3秒 } }finally{ lock.unlock(); } }else { System.out.println("線程 " + thread.getName()+"放棄了對鎖的獲取..."); } } } class MyThread extends Thread { private Test test = null; public MyThread(Test test,String name) { super(name); this.test = test; } @Override public void run() { 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() 響應中斷

public class Test { private Lock lock = new ReentrantLock(); public static void main(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(); } public void insert(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; //插入數據 } }finally { System.out.println(Thread.currentThread().getName()+"執行finally..."); lock.unlock(); System.out.println("線程 " + thread.getName()+"釋放了鎖"); } System.out.println("over"); } } class MyThread extends Thread { private Test test = null; public MyThread(Test test,String name) { super(name); this.test = test; } @Override public void run() { try { test.insert(Thread.currentThread()); } catch (InterruptedException e) { System.out.println("線程 " + Thread.currentThread().getName() + "被中斷..."); } } }/* Output: 線程 A獲得了鎖... 線程 B被中斷... *///:~

運行上述代碼以後,發現 thread2 可以被正確中斷,放棄對任務的執行。特別須要注意的是,若是須要正確中斷等待鎖的線程,必須將獲取鎖放在外面(try 語句塊外),而後將 InterruptedException 拋出。若是不這樣作,像以下代碼所示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test { private Lock lock = new ReentrantLock(); public static void main(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(); } public void insert(Thread thread) { try { // 注意,若是將獲取鎖放在try語句塊裏,則一定會執行finally語句塊中的解鎖操做。若線程在獲取鎖時被中斷,則再執行解鎖操做就會致使異常,由於該線程並未得到到鎖。 lock.lockInterruptibly(); System.out.println("線程 " + thread.getName() + "獲得了鎖..."); long startTime = System.currentTimeMillis(); for (;;) { if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) // 耗時操做 break; // 插入數據 } } catch (Exception e) { } finally { System.out.println(Thread.currentThread().getName() + "執行finally..."); lock.unlock(); System.out.println("線程 " + thread.getName() + "釋放了鎖..."); } } } class MyThread extends Thread { private Test test = null; public MyThread(Test test, String name) { super(name); this.test = test; } @Override public void run() { test.insert(Thread.currentThread()); System.out.println("線程 " + Thread.currentThread().getName() + "被中斷..."); } }/* Output: 線程A 獲得了鎖... 線程main 睡醒了... B執行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語句塊裏,則一定會執行finally語句塊中的解鎖操做。在 準備獲取鎖的 線程B 被中斷後,再執行解鎖操做就會拋出 IllegalMonitorStateException,由於該線程並未得到到鎖卻執行了解鎖操做。

三、ReadWriteLock

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

public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading. */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing. */ Lock writeLock(); }

一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說,將對臨界資源的讀寫操做分紅兩個鎖來分配給線程,從而使得多個線程能夠同時進行讀操做。下面的 ReentrantReadWriteLock 實現了 ReadWriteLock 接口。

四、ReentrantReadWriteLock

ReentrantReadWriteLock 裏面提供了不少豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。下面經過幾個例子來看一下ReentrantReadWriteLock具體用法。假若有多個線程要同時進行讀操做的話,先看一下synchronized達到的效果:

public class Test { public static void main(String[] args) { final Test test = new Test(); new Thread("A"){ public void run() { test.get(Thread.currentThread()); }; }.start(); new Thread("B"){ public void run() { test.get(Thread.currentThread()); }; }.start(); } public synchronized void get(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執行完讀操做以後,纔會打印線程B執行讀操做的信息。而改爲使用讀寫鎖的話:

public class Test { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final Test test = new Test(); new Thread("A") { public void run() { test.get(Thread.currentThread()); }; }.start(); new Thread("B") { public void run() { test.get(Thread.currentThread()); }; }.start(); } public void get(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在同時進行讀操做,這樣就大大提高了讀操做的效率。不過要注意的是,若是有一個線程已經佔用了讀鎖,則此時其餘線程若是要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。若是有一個線程已經佔用了寫鎖,則此時其餘線程若是申請寫鎖或者讀鎖,則申請的線程也會一直等待釋放寫鎖。

五、Lock和synchronized的選擇

總的來講,Lock和synchronized有如下幾點不一樣:

  • (1) Lock是一個接口,是JDK層面的實現;而synchronized是Java中的關鍵字,是Java的內置特性,是JVM層面的實現;
  • (2) synchronized 在發生異常時,會自動釋放線程佔有的鎖,所以不會致使死鎖現象發生;而Lock在發生異常時,若是沒有主動經過unLock()去釋放鎖,則極可能形成死鎖現象,所以使用Lock時須要在finally塊中釋放鎖;
  • (3) Lock 可讓等待鎖的線程響應中斷,而使用synchronized時,等待的線程會一直等待下去,不可以響應中斷;
  • (4) 經過Lock能夠知道有沒有成功獲取鎖,而synchronized卻沒法辦到;
  • (5) Lock能夠提升多個線程進行讀操做的效率。

在性能上來講,若是競爭資源不激烈,二者的性能是差很少的。而當競爭資源很是激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。因此說,在具體使用時要根據適當狀況選擇。

三. 鎖的相關概念介紹

一、可重入鎖

若是鎖具有可重入性,則稱做爲 可重入鎖 。像 synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上代表了 鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,好比說method1,而在method1中會調用另一個synchronized方法method2,此時線程沒必要從新去申請鎖,而是能夠直接執行方法method2。

class MyClass { public synchronized void method1() { method2(); } public synchronized void method2() { } }

上述代碼中的兩個方法method1和method2都用synchronized修飾了。假如某一時刻,線程A執行到了method1,此時線程A獲取了這個對象的鎖,而因爲method2也是synchronized方法,假如synchronized不具有可重入性,此時線程A須要從新申請鎖。可是,這就會形成死鎖,由於線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖,這樣就會線程A一直等待永遠不會獲取到的鎖。而因爲synchronized和Lock都具有可重入性,因此不會發生上述現象。

二、可中斷鎖

顧名思義,可中斷鎖就是能夠響應中斷的鎖。在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
若是某一線程A正在執行鎖中的代碼,另外一線程B正在等待獲取該鎖,可能因爲等待時間過長,線程B不想等待了,想先處理其餘事情,咱們可讓它中斷本身或者在別的線程中中斷它,這種就是可中斷鎖。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法時已經體現了Lock的可中斷性。

三、公平鎖

公平鎖即 儘可能 以請求鎖的順序來獲取鎖。好比,同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最早請求的線程)會得到該所,這種就是公平鎖。而非公平鎖則沒法保證鎖的獲取是按照請求鎖的順序進行的,這樣就可能致使某個或者一些線程永遠獲取不到鎖。

在Java中,synchronized就是非公平鎖,它沒法保證等待的線程獲取鎖的順序。而對於ReentrantLock 和 ReentrantReadWriteLock,它默認狀況下是非公平鎖,可是能夠設置爲公平鎖。

看下面兩個例子:

Case : 公平鎖

public class RunFair { public static void main(String[] args) throws InterruptedException { final Service service = new Service(true); // 公平鎖,設爲 true Runnable runnable = new Runnable() { @Override public void run() { 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(); } } class Service { private ReentrantLock lock; public Service(boolean isFair) { super(); lock = new ReentrantLock(isFair); } public void serviceMethod() { 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: 非公平鎖

public class RunFair { public static void main(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得到鎖定 *///:~

根據上面代碼演示結果咱們能夠看出(線程數越多越明顯),在公平鎖案例下,多個線程在等待一個鎖時,通常而言,等待時間最久的線程(最早請求的線程)會得到該鎖。而在非公平鎖例下,則沒法保證鎖的獲取是按照請求鎖的順序進行的。

另外, 在ReentrantLock類中定義了不少方法,舉幾個例子:

  • isFair() //判斷鎖是不是公平鎖
  • isLocked() //判斷鎖是否被任何線程獲取了
  • isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了
  • hasQueuedThreads() //判斷是否有線程在等待該鎖
  • getHoldCount() //查詢當前線程佔有lock鎖的次數
  • getQueueLength() // 獲取正在等待此鎖的線程數
  • getWaitQueueLength(Condition condition) // 獲取正在等待此鎖相關條件condition的線程數在ReentrantReadWriteLock中也有相似的方法,一樣也能夠設置爲公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock並未實現Lock接口,它實現的是ReadWriteLock接口。

4.讀寫鎖

讀寫鎖將對臨界資源的訪問分紅了兩個鎖,一個讀鎖和一個寫鎖。正由於有了讀寫鎖,才使得多個線程之間的讀操做不會發生衝突。ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。能夠經過readLock()獲取讀鎖,經過writeLock()獲取寫鎖。上一節已經演示過了讀寫鎖的使用方法,在此再也不贅述。

轉自:https://www.cnblogs.com/aishangJava/p/6555291.html

相關文章
相關標籤/搜索