lock的兩個子類——ReentrantLock和ReentrantReadWriteLock
ReentrantLock
相比synchronized功能更加強大
用法
調用ReentrantLock的lock方法加鎖,unlock方法解鎖(一般在finally中寫unlock)
Object中的wait()、wait(long)等價于Condition中的await()、await(long time, TimeUnit unit)
Object中的notify()、notifyAll()等價于Condition中的singal()、signalAll()
使用Condition實現等待/通知
ReentrantLock可以實現如果關鍵字synchronized與wait、notify等的功能,但需要借助Condition對象,使用Condition可以有更好的靈活性——比如多路通知,即可以在一個Lock對象里面創建多個Condition(即對象監視器)實例,,編程對象可以注冊在指定的Condition中,從而可以有選擇性地進行線程通知,在調度線程上更有靈活性。
使用notify/notifyAll,被通知的線程是JVM選擇的,而ReentrantLock結合Condition類可以實現“選擇性通知”,事實上,synchronized相當于整個Lock對象中只有一個單一的Condition對象,所有的線程都注冊在它一個對象身上。線程開始notifyAll的時候,需要通知所有的waiting線程,產生效率問題。
實現通知部分線程:
private Lock lock = new ReentrantLock();
private Condition cA = lock.newCondition();
private Condition cB = lock.newCondition();
...
在某wait函數中:
lock.lock();
conditionA.await();
在某notify函數中:
lock.lock();
conditionA.signalAll();
公平鎖與非公平鎖
公平鎖:線程獲取鎖的順序是按照線程加鎖的順序來分配的,類似FIFO,但只是大部分線程按照FIFO
非公平鎖:未必按照FIFO,可能導致部分線程永遠獲取不到鎖
Lock lock = new ReentrantLock(isFair)
通過設置如上的isFair判斷是否為公平鎖(默認情況下,ReentrantLock為非公平鎖);當持有鎖的時間或者請求鎖的時間較長的時候,使用公平鎖好一些,因為非公平鎖的插隊帶來的效率提升此時可能不會有
非公平鎖的性能優于公平鎖,原因如下:
- 從線程進入了RUNNABLE狀態到run狀態是要比較久的時間的。而且,在一個鎖釋放之后,其他的線程會需要重新來獲取鎖。其中經歷了持有鎖的線程釋放鎖,其他線程從掛起恢復到RUNNABLE狀態,其他線程請求鎖,獲得鎖,線程執行,這一系列步驟。如果這個時候,存在一個線程直接請求鎖,可能就避開掛起到恢復RUNNABLE狀態的這段消耗,所以性能更優化。 相當于等待一個阻塞的進程和一個就緒的進程進入運行使用CPU的狀態
- 公平鎖需要多加入一個條件判斷
getHoldCount()、getQueueLength()、getWaitQueueLength()的測試
-
getHoldCount()
查詢當前線程保持此鎖定的次數,也就是調用lock方法的次數
-
getQueueLength()
返回正等待獲取此鎖定的線程估計數,比如有5個線程,1個線程首先執行await()方法,調用此函數的返回值是4,說明有4個線程同時在等待lock的釋放
-
getWaitQueueLength(Condition)
返回等待與此鎖相關的給定條件Condition的線程估計數,比如5個線程,每個賢臣都執行了同一個Condition的await方法,則調用此函數的返回值為5
hasQueuedThread、hasQueuedThreads、hasWaiters的測試
-
hasQueuedThread(Thread)
查詢指定的線程是否在等待獲取此鎖定
-
hasQueuedThreads()
查詢是否有線程在等待獲取此鎖定
-
hasWaiters(Condition)
查詢是否有線程在等待與此鎖相關的condition條件
isFair()、isHeldByCurrentThread()、isLocked的測試
-
isFair()
判斷是否為公平鎖
-
isHeldByCurrentThread()
查詢當前線程是否保持此鎖定,一般用于unlock前的條件判斷
-
isLocked()
判斷此鎖是否由任意線程持有
lockInterruptibly()、tryLock()、tryLock(long timeout, TimeUnit unit)的測試
-
lockInterruptibly()
如果當前線程未被中斷,則獲取鎖定;否則拋出異常(可以利用此終止異常)
-
tryLock()
調用時鎖定未被另一個線程持有的情況下,才獲取此鎖定
-
tryLock(long timeout, TimeUnit unit)
如果鎖定在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖定。
ReentrantReadWriteLock類
類ReentrantLock具有完全互斥排他的效果,即同一時間只有一個線程在執行ReentrantLock.lock方法后面的任務,雖然安全,但效率低。
而ReentrantReadWriteLock類可以在某些不需要操作實例變量的方法中,使用讀寫鎖來提高方法的代碼運行速度。
讀寫鎖有兩個鎖:讀——共享鎖,寫——排它鎖。
使用
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock.lock()
則可以有多個線程共享讀
如果把readLock改成writeLock,則將會導致讀寫、寫寫互斥