突擊併發編程JUC系列演示代碼地址: https://github.com/mtcarpenter/JavaTutorial前端
鎖是用來控制多個線程訪問共享資源的方式,經過鎖能夠防止多個線程同時訪問共享資源。在 Java1.5
以前實現鎖只能使用 synchronized
關鍵字實現,可是synchronized
隱式獲取釋放鎖,在 1.5
以後官方新增了 lock
接口也是用來實現鎖的功能,,它具有與synchronized
關鍵字相似的同步功能,顯式的獲取和釋放鎖。lock
擁有了鎖獲取與釋放的可操做性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized
關鍵字所不具有的同步特性。java
LOCK 方法說明
void lock()
:獲取鎖,調用該方法當前線程將會獲取鎖,當鎖得到後,從該方法返回void lockInterruptibly() throws InterruptedException
:可中斷地獲取鎖,和lock
方法地不一樣之處在於該方法會響應中斷,即在鎖的獲取中能夠中斷當前線程boolean tryLock()
: 嘗試非阻塞地獲取鎖,調用該方法後馬上返回,若是可以獲取則返回 true 不然 返回falseboolean tryLock(long time, TimeUnit unit)
:超時地獲取鎖,當前線程在如下 3 種狀況下會返回:- 當前線程在超時時間內得到了鎖
- 當前線程在超時時間被中斷
- 超時時間結束後,返回 false
void unlock()
: 釋放鎖Condition newCondition()
:獲取鎖等待通知組件,該組件和當前的鎖綁定,當前線程只有得到了鎖,才能調用該組件的wait()
方法,而調用後,當前線程將釋放鎖。
ReentrantLock 簡介
Lock
做爲接口類爲咱們提供一組方法,只能經過的實現類進行 Lock
方法,今天咱們就講講繼承Lock
接口一個可重入的獨佔鎖 ReentrantLock
實現類,ReentrantLock
經過自定義隊列同步器(Abstract Queued Sychronized,AQS
)來實現鎖的獲取與釋放。它使用了一個 int 成員變量表示同步狀態,經過內置的 FIFO
隊列來完成資源獲取線程的排隊工做,併發包的做者(Doug Lea)指望它可以成爲實現大部分同步需求的基礎。git
獨佔鎖指該鎖在同一時刻只能被一個線程獲取,而獲取鎖的其餘線程只能在同步隊列中等待;可重入鎖指該鎖可以支持一個線程對同一個資源執行屢次加鎖操做。ReentrantLock
支持公平鎖和非公平鎖的實現。公平指線程競爭鎖的機制是公平的,而非公平指不一樣的線程獲取鎖的機制是不公平的。ReentrantLock
不但提供了synchronized
對鎖的操做功能,還提供了諸如可響應中斷鎖、可輪詢鎖請求、定時鎖等避免多線程死鎖的方法。github
ReentrantLock 方法說明
ReentrantLock()
: 無參ReentrantLock
使用的非公平鎖。ReentrantLock(boolean fair)
:ReentrantLock
能夠初始化設置是公平鎖鎖,仍是非公平鎖。getHoldCount()
:查詢當前線程在某個Lock
上的數量,若是當前線程成功獲取了Lock
,那麼該值大於等於 1;若是沒有獲取到Lock
的線程調用該方法,則返回值爲 0 。isHeldByCurrentThread()
:判斷當前線程是否持有某個Lock
,因爲Lock
的排他性,所以在某個時刻只有一個線程調用該方法返回 true。isLocked()
:判斷Lock
是否已經被線程持有。isFair()
:建立的ReentrantLock
是否爲公平鎖。hasQueuedThreads()
:在多個線程試圖獲取Lock
的時候,只有一個線程可以正常得到,其餘線程可能(若是使用tryLock()
方法失敗則不會進入阻塞)會進入阻塞,該方法的做用就是查詢是否有線程正在等待獲取鎖。hasQueuedThread(Thread thread)
:在等待獲取鎖的線程中是否包含某個指定的線程。getQueueLength()
:返回當前有多少個線程正在等待獲取鎖。
僞代碼回顧
精彩片斷 1 :編程
class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { // 加鎖 lock.lock(); try { // 業務執行 } finally { // 釋放鎖 lock.unlock() } } }
精彩片斷 2 :後端
class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { //嘗試獲取鎖 if (lock.tryLock()) { try { //處理任務 ....... } catch (Exception ex) { } finally { //釋放鎖 lock.unlock(); } } else { //else 表示沒有獲取鎖 無需關閉 // ..... 根據實際業務處理 (返回、處理其它邏輯) } } }
lock.tryLock()
: 阻塞式獲取鎖,若是可以獲取則返回 true 不然 返回 false。沒法獲取也能夠根據實際業務進行處理。多線程
案例上手
synchronized 案例
public class LockExample1 { // 請求總數 public static int requestTotal = 10000; // 併發計數 public static int count = 0; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final CountDownLatch countDownLatch = new CountDownLatch(requestTotal); for (int i = 0; i < requestTotal; i++) { executorService.execute(() -> { try { add(); } catch (Exception e) { } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("count = " + count); } private static synchronized void add() { count++; } } // 運行結果:count = 10000
給 add()
方法加上了 synchronized
鎖,保證了該方法在併發下也是同步的。併發
lock() 方法的使用
public class LockExample2 { // 請求總數 public static int requestTotal = 10000; // 併發計數 public static int count = 0; private final static Lock lock = new ReentrantLock(); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final CountDownLatch countDownLatch = new CountDownLatch(requestTotal); for (int i = 0; i < requestTotal; i++) { executorService.execute(() -> { try { add(); } catch (Exception e) { } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("count = " + count); } private static void add() { lock.lock(); try { count++; } finally { lock.unlock(); } } } // 運行結果:count = 10000
將須要同步的代碼放在 lock
和 unlock
之間,使用 lock
必定要記得釋放鎖。函數
tryLock() 方法
private static void add() { if (lock.tryLock()) { try { count++; } finally { //當獲取鎖成功時最後必定要記住finally去關閉鎖 lock.unlock(); //釋放鎖 } } else { //else時爲未獲取鎖,則無需去關閉鎖 //若是不能獲取鎖,則直接作其餘事情 System.out.println(Thread.currentThread().getName() + "沒有獲取鎖"); } }
經過 tryLock()
方法就發如今併發的狀況下會有部分線程沒法獲取到鎖。線程
tryLock(long timeout, TimeUnit unit) 能夠設置超時時間
private static void add() throws InterruptedException { if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) { try { count++; } finally { //當獲取鎖成功時最後必定要記住finally去關閉鎖 lock.unlock(); //釋放鎖 } } else { //else時爲未獲取鎖,則無需去關閉鎖 //若是不能獲取鎖,則直接作其餘事情 System.out.println(Thread.currentThread().getName() + "沒有獲取鎖"); } }
ReentrantLock 提供了公平和非公平鎖的實現
- 公平鎖:
ReentrantLock lock = new ReentrantLock(true)
。是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次得到鎖。 - 非公平鎖:
ReentrantLock lock = new ReentrantLock(false)
。若是構造函數不傳遞參數,則默認是非公平鎖。
歡迎關注公衆號 山間木匠 , 我是小春哥,從事 Java 後端開發,會一點前端、經過持續輸出系列技術文章以文會友,若是本文能爲您提供幫助,歡迎你們關注、 點贊、分享支持,咱們下期再見!<br />