多線程編程中,當代碼須要同步時咱們會用到鎖。Java爲咱們提供了內置鎖(synchronized
)和顯式鎖(ReentrantLock
)兩種同步方式。顯式鎖是JDK1.5引入的,這兩種鎖有什麼異同呢?是僅僅增長了一種選擇仍是另有其因?本文爲您一探究竟。html
Java內置鎖經過synchronized關鍵字使用,使用其修飾方法或者代碼塊,就能保證方法或者代碼塊以同步方式執行。使用起來很是近簡單,就像下面這樣:java
// synchronized關鍵字用法示例 public synchronized void add(int t){// 同步方法 this.v += t; } public static synchronized void sub(int t){// 同步靜態方法 value -= t; } public int decrementAndGet(){ synchronized(obj){// 同步代碼塊 return --v; } }
這就是內置鎖的所有用法,你已經學會了。git
內置鎖使用起來很是方便,不須要顯式的獲取和釋放,任何一個對象都能做爲一把內置鎖。使用內置鎖可以解決大部分的同步場景。「任何一個對象都能做爲一把內置鎖」也意味着出現synchronized關鍵字的地方,都有一個對象與之關聯,具體說來:github
內置鎖這麼好用,爲何還需多出一個顯式鎖呢?由於有些事情內置鎖是作不了的,好比:編程
顯式鎖(ReentrantLock)正式爲了解決這些靈活需求而生。ReentrantLock的字面意思是可重入鎖,可重入的意思是線程能夠同時屢次請求同一把鎖,而不會本身致使本身死鎖。下面是內置鎖和顯式鎖的區別:api
可定時:RenentrantLock.tryLock(long timeout, TimeUnit unit)
提供了一種以定時結束等待的方式,若是線程在指定的時間內沒有得到鎖,該方法就會返回false並結束線程等待。緩存
可中斷:你必定見過InterruptedException,不少跟多線程相關的方法會拋出該異常,這個異常並非一個缺陷致使的負擔,而是一種必須,或者說是一件好事。可中斷性給咱們提供了一種讓線程提早結束的方式(而不是非得等到線程執行結束),這對於要取消耗時的任務很是有用。對於內置鎖,線程拿不到內置鎖就會一直等待,除了獲取鎖沒有其餘辦法可以讓其結束等待。RenentrantLock.lockInterruptibly()
給咱們提供了一種以中斷結束等待的方式。安全
條件隊列(condition queue):線程在獲取鎖以後,可能會因爲等待某個條件發生而進入等待狀態(內置鎖經過Object.wait()
方法,顯式鎖經過Condition.await()
方法),進入等待狀態的線程會掛起並自動釋放鎖,這些線程會被放入到條件隊列當中。synchronized對應的只有一個條件隊列,而ReentrantLock能夠有多個條件隊列,多個隊列有什麼好處呢?請往下看。markdown
條件謂詞:線程在獲取鎖以後,有時候還須要等待某個條件知足才能作事情,好比生產者須要等到「緩存不滿」才能往隊列裏放入消息,而消費者須要等到「緩存非空」才能從隊列裏取出消息。這些條件被稱做條件謂詞,線程須要先獲取鎖,而後判斷條件謂詞是否知足,若是不知足就不往下執行,相應的線程就會放棄執行權並自動釋放鎖。使用同一把鎖的不一樣的線程可能有不一樣的條件謂詞,若是隻有一個條件隊列,當某個條件謂詞知足時就沒法判斷該喚醒條件隊列裏的哪個線程;可是若是每一個條件謂詞都有一個單獨的條件隊列,當某個條件知足時咱們就知道應該喚醒對應隊列上的線程(內置鎖經過Object.notify()
或者Object.notifyAll()
方法喚醒,顯式鎖經過Condition.signal()
或者Condition.signalAll()
方法喚醒)。這就是多個條件隊列的好處。多線程
使用內置鎖時,對象自己既是一把鎖又是一個條件隊列;使用顯式鎖時,RenentrantLock的對象是鎖,條件隊列經過RenentrantLock.newCondition()
方法獲取,屢次調用該方法能夠獲得多個條件隊列。
一個使用顯式鎖的典型示例以下:
// 顯式鎖的使用示例 ReentrantLock lock = new ReentrantLock(); // 獲取鎖,這是跟synchronized關鍵字對應的用法。 lock.lock(); try{ // your code }finally{ lock.unlock(); } // 可定時,超過指定時間爲獲得鎖就放棄 try { lock.tryLock(10, TimeUnit.SECONDS); try { // your code }finally { lock.unlock(); } } catch (InterruptedException e1) { // exception handling } // 可中斷,等待獲取鎖的過程當中線程線程可被中斷 try { lock.lockInterruptibly(); try { // your code }finally { lock.unlock(); } } catch (InterruptedException e) { // exception handling } // 多個等待隊列,具體參考[ArrayBlockingQueue](https://github.com/CarpenterLee/JCRecipes/blob/master/markdown/ArrayBlockingQueue.md) /** Condition for waiting takes */ private final Condition notEmpty = lock.newCondition(); /** Condition for waiting puts */ private final Condition notFull = lock.newCondition();
注意,上述代碼將unlock()
放在finally塊裏,這麼作是必需的。顯式鎖不像內置鎖那樣會自動釋放,使用顯式鎖必定要在finally塊中手動釋放,若是獲取鎖後因爲異常的緣由沒有釋放鎖,那麼這把鎖將永遠得不到釋放!將unlock()放在finally塊中,保證不管發生什麼都可以正常釋放。
內置鎖可以解決大部分須要同步的場景,只有在須要額外靈活性是才須要考慮顯式鎖,好比可定時、可中斷、多等待隊列等特性。
顯式鎖雖然靈活,可是須要顯式的申請和釋放,而且釋放必定要放到finally塊中,不然可能會由於異常致使鎖永遠沒法釋放!這是顯式鎖最明顯的缺點。
綜上,當須要同步時請優先考慮更安全的更易用的隱式鎖。
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html