本文首發於一世流雲的專欄: https://segmentfault.com/blog...
本系列文章中所說的juc-locks鎖框架就是指java.util.concurrent.locks
包,該包提供了一系列基礎的鎖工具,用以對synchronizd、wait、notify等進行補充、加強。
juc-locks鎖框架中一共就三個接口:Lock、Condition、ReadWriteLock,接下來對這些接口做介紹,更詳細的信息能夠參考Oracle官方的文檔。java
Lock接口能夠視爲synchronized的加強版,提供了更靈活的功能。該接口提供了限時鎖等待、鎖中斷、鎖嘗試等功能。segmentfault
該接口的方法聲明以下:
api
須要注意lock()
和lockInterruptibly()
這兩個方法的區別:oracle
lock()方法
相似於使用synchronized關鍵字加鎖,若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在得到鎖以前,該線程將一直處於休眠狀態。
lockInterruptibly()
方法顧名思義,就是若是鎖不可用,那麼當前正在等待的線程是能夠被中斷的,這比synchronized關鍵字更加靈活。
能夠看到,Lock做爲一種同步器,通常會用一個finally語句塊確保鎖最終會釋放。框架
Lock lock = ...; if (lock.tryLock()) { try { // manipulate protected state } finally { lock.unlock(); } } else { // perform alternative actions }
Condition能夠看作是Obejct類的wait()
、notify()
、notifyAll()
方法的替代品,與Lock配合使用。
當線程執行condition對象的await
方法時,當前線程會當即釋放鎖,並進入對象的等待區,等待其它線程喚醒或中斷。工具
JUC在實現Conditon對象時,實際上是經過實現AQS框架,來實現了一個Condition等待隊列,這個在後面講AQS框架時會詳細介紹,目前只要瞭解Condition如何使用便可。
Oracle官方文檔中給出了一個緩衝隊列的示例:性能
假定有一個緩衝隊列,支持 put 和 take 方法。若是試圖在空隊列中執行 take 操做,則線程將一直阻塞,直到隊列中有可用元素;若是試圖在滿隊列上執行 put 操做,則線程也將一直阻塞,直到隊列不滿。測試
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) //防止虛假喚醒,Condition的await調用通常會放在一個循環判斷中 notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
等待 Condition 時,爲了防止發生「虛假喚醒」, Condition 通常都是在一個循環中被等待,並測試正被等待的狀態聲明,如上述代碼註釋部分。
雖然上面這個示例程序即便不用while,改用if判斷也不會出現問題,可是最佳實踐仍是作while循環判斷—— Guarded Suspension模式,以防遺漏狀況。
ReadWriteLock接口是一個單獨的接口(未繼承Lock接口),該接口提供了獲取讀鎖和寫鎖的方法。spa
所謂讀寫鎖,是一對相關的鎖——讀鎖和寫鎖,讀鎖用於只讀操做,寫鎖用於寫入操做。讀鎖能夠由多個線程同時保持,而寫鎖是獨佔的,只能由一個線程獲取。
讀寫鎖的阻塞狀況以下圖:
線程
舉個例子,假設我有一份共享數據——訂單金額,大多數狀況下,線程只會進行高頻的數據訪問(讀取訂單金額),數據修改(修改訂單金額)的頻率較低。
那麼通常狀況下,若是採用互斥鎖,讀/寫和讀/讀都是互斥的,性能顯然不如採用讀寫鎖。
另外,因爲讀寫鎖自己的實現就遠比獨佔鎖複雜,所以,讀寫鎖比較適用於如下情形: