Java併發程式設計札記-(四)JUC鎖-02Lock與ReentrantLock
今天學習Lock與ReentrantLock。
Java中的鎖有兩種,synchronized與Lock。因為使用synchronized並不需要顯示地加鎖與解鎖,所以往往稱synchronized為隱式鎖,而使用Lock時則相反,所以一般稱Lock為顯示鎖。想了解synchronized更多請移步Java併發程式設計札記-(一)基礎-06synchronized詳解。
synchronized修飾方法或語句塊,所有鎖的獲取和釋放都必須出現在一個塊結構中。當需要靈活地獲取或釋放鎖時,synchronized顯然是不符合要求的。Lock介面的實現允許鎖在不同的範圍內獲取和釋放,並支援以任何順序獲取和釋放多個鎖。
一句話,Lock實現比synchronized更靈活。但凡事有利就有弊,不使用塊結構鎖就失去了使用synchronized修飾方法或語句時會出現的鎖自動釋放功能,在大多數情況下,Lock實現需要手動釋放鎖。
除了更靈活之外,Lock還有以下優點:
- Lock 實現提供了使用 synchronized 方法和語句所沒有的其他功能,包括提供了一個非塊結構的獲取鎖嘗試 (tryLock())、一個獲取可中斷鎖的嘗試 (lockInterruptibly()) 和一個獲取超時失效鎖的嘗試 (tryLock(long, TimeUnit))。
- Lock 類還可以提供與隱式監視器鎖完全不同的行為和語義,如保證排序、非重入用法或死鎖檢測。如果某個實現提供了這樣特殊的語義,則該實現必須對這些語義加以記錄。
ReentrantLock是一個可重入的互斥鎖。顧名思義,“互斥鎖”表示在某一時間點只能被同一執行緒所擁有。“可重入”表示鎖可被某一執行緒多次獲取。
當鎖沒有被某一執行緒佔有時,呼叫lock()方法的執行緒將成功獲取鎖。可以使用isHeldByCurrentThread()和 getHoldCount()方法來判斷當前執行緒是否擁有該鎖。
ReentrantLock既可以是公平鎖又可以是非公平鎖。當此類的構造方法ReentrantLock(boolean fair) 接收true作為引數時,ReentrantLock就是公平鎖,執行緒依次排隊獲取公平鎖,即鎖將被等待最長時間的執行緒佔有。與預設情況(使用非公平鎖)相比,使用公平鎖的程式在多執行緒環境下效率比較低。而且公平鎖不能保證執行緒排程的公平性,tryLock方法可在鎖未被其他執行緒佔用的情況下獲得該鎖。
方法列表
//構造方法摘要
ReentrantLock()
//建立一個 ReentrantLock 的例項。
ReentrantLock(boolean fair)
//建立一個具有給定公平策略的 ReentrantLock。
//方法摘要
int getHoldCount()
//查詢當前執行緒保持此鎖的次數。
protected Thread getOwner()
//返回目前擁有此鎖的執行緒,如果此鎖不被任何執行緒擁有,則返回 null。
protected Collection<Thread> getQueuedThreads()
//返回一個 collection,它包含可能正等待獲取此鎖的執行緒。
int getQueueLength()
//返回正等待獲取此鎖的執行緒估計數。
protected Collection<Thread> getWaitingThreads(Condition condition)
//返回一個 collection,它包含可能正在等待與此鎖相關給定條件的那些執行緒。
int getWaitQueueLength(Condition condition)
//返回等待與此鎖相關的給定條件的執行緒估計數。
boolean hasQueuedThread(Thread thread)
//查詢給定執行緒是否正在等待獲取此鎖。
boolean hasQueuedThreads()
//查詢是否有些執行緒正在等待獲取此鎖。
boolean hasWaiters(Condition condition)
//查詢是否有些執行緒正在等待與此鎖有關的給定條件。
boolean isFair()
//如果此鎖的公平設定為 true,則返回 true。
boolean isHeldByCurrentThread()
//查詢當前執行緒是否保持此鎖。
boolean isLocked()
//查詢此鎖是否由任意執行緒保持。
void lock()
//獲取鎖。
void lockInterruptibly()
//如果當前執行緒未被中斷,則獲取鎖。
Condition newCondition()
//返回用來與此 Lock 例項一起使用的 Condition 例項。
String toString()
//返回標識此鎖及其鎖定狀態的字串。
boolean tryLock()
//僅在呼叫時鎖未被另一個執行緒保持的情況下,才獲取該鎖。
boolean tryLock(long timeout, TimeUnit unit)
//如果鎖在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖。
void unlock()
//試圖釋放此鎖。
使用最典型的程式碼如下
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
修改下Java併發程式設計札記-(一)基礎-06synchronized詳解中火車票訂票系統例項,使用ReentrantLock實現執行緒安全。
例6:火車票訂票系統-ReentrantLock實現執行緒安全版
import java.util.concurrent.locks.ReentrantLock;
public class SellTickets {
public static void main(String[] args) {
TicketsWindow tw1 = new TicketsWindow();
Thread t1 = new Thread(tw1, "一號視窗");
Thread t2 = new Thread(tw1, "二號視窗");
t1.start();
t2.start();
}
}
class TicketsWindow implements Runnable {
private int tickets = 1;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "還剩餘票:" + tickets + "張");
--tickets;
System.out.println(Thread.currentThread().getName() + "賣出一張火車票,還剩" + tickets + "張");
} else {
System.out.println(Thread.currentThread().getName() + "餘票不足,暫停出售!");
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}
在某些方法的引數中出現了Condition,在後面我們會詳細介紹Condition。
總結
- 與synchronized 相比ReentrantLock的使用更靈活。Lock介面的實現允許鎖在不同的範圍內獲取和釋放,並支援以任何順序獲取和釋放多個鎖。
- ReentrantLock具有與使用 synchronized 相同的一些基本行為和語義,但功能更強大。包括提供了一個非塊結構的獲取鎖嘗試 (tryLock())、一個獲取可中斷鎖的嘗試 (lockInterruptibly()) 和一個獲取超時失效鎖的嘗試 (tryLock(long, TimeUnit))。
- ReentrantLock具有synchronized所沒有的許多特性,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變數或者輪詢鎖。
- ReentrantLock可伸縮性強,應當在高度爭用的情況下使用它。
本文就講到這裡,想了解Java併發程式設計更多內容請參考:
參考:
END.