1. 程式人生 > 實用技巧 >ReentrantLock與synchronized

ReentrantLock與synchronized

ReentrantLock和synchronized同樣都是用於多執行緒同步,它們在功能上有相近之處,但通常而言,ReentrantLock可以用於替代synchronized。

1, ReentrantLock具備synchronized功能

 1 static Object monitor = new Object();
 2 synchronized(monitor) {
 3     //執行程式碼
 4 }
 5 
 6 //建立一個重入鎖,並且產生一個條件監視器物件
 7 static ReentrantLock lock = new ReentrantLock();
 8 static
Condition monitor = lock.newCondition(); 9 lock.lock(); 10 //執行程式碼 11 lock.unlock();

  可以注意到,ReentrantLock有顯示鎖物件,鎖物件可以由使用者決定請求鎖和釋放鎖的時機,它們甚至可以不在同一個程式碼塊中,而synchronized並沒有這麼靈活。

synchronized使用的是Object物件內建的監視器,通過Object.wait/Object.notify()等方法對當前執行緒做等待和喚醒操作。synchronized只能有一個監視器,如果呼叫監視器的notifyAll,那麼會喚醒所有執行緒,較為不靈活。

ReentrantLock使用的是條件監視器Condition,通過ReentrantLock.newCondition()方法來獲取。同一個ReentrantLock可以建立多個condition例項。每個Condition維護有自己的等待執行緒waiter佇列,呼叫signalAll只會喚醒自己佇列內的執行緒。與Object.wait()/Object.notify()的使用方式一樣,Condition呼叫await()/signal()系列方法來達到同樣的目的。

監視器的使用需要注意兩點:

1) 監視器的wait和notify操作會改變執行緒在等待佇列裡的狀態,這個狀態是所有執行緒可見的,必須保證執行緒安全,所以一定要有鎖支撐。也就是說,呼叫wait/notify型別的方法時,必須在該監視器觀察的鎖內部執行。

2) 監視器的notify方法並不會直接喚醒執行緒,它只會改變執行緒在等待佇列裡的狀態,真正的喚醒操作是抽象佇列同步器(AQS)完成的,

2, ReentrantLock更靈活

ReentrantLock的靈活性體現在以下幾個方面

  ReentrantLock可以指定公平鎖或非公平鎖,而synchronized限制為公平鎖。ReentrantLock預設為非公平鎖。

  ReentrantLock的條件監視器較之synchronized更加方便靈活。ObjectMonitor的等待佇列個數僅有一個,而Condition支援多個佇列。ObjectMonitor釋放鎖進入wait或wait timeout狀態,必須響應中斷,Condition可以不響應中斷。

3, 概括比較synchronized和ReentrantLock優劣

  ReentrantLock獲取鎖和釋放鎖的操作更加靈活,且具備獨立的條件監視器,等待和喚醒執行緒的操作也更加方便和多樣化,在多執行緒環境下,ReentrantLock的執行效率比synchronized高。

  但是,synchronized的存在還是有意義的,程式不僅僅是執行執行更快的操作和更靈活的就會更優秀,還要考慮到維護成本,synchronized具有完備的語義,一個獲得鎖操作就一定會對應一個釋放鎖操作,否則就會有編譯期異常出現,對於語法友好來講,synchronized可維護性更高。

4, ReentrantLock的條件監視器

  Condition,即條件,這個類在AQS裡起到的是監視器monitor的作用,監視器是用於監控一段同步的程式碼塊,可以用於執行緒的阻塞和解除阻塞。

  每當條件監視器增加一個等待執行緒的時候,該執行緒也會進入一個條件等待佇列,下次signal方法呼叫的時候,會從佇列裡獲取節點,挨個喚醒。

Condition核心方法:

  a) await():當前執行緒進入等待狀態,直到響應通知SIGNAL或者中斷Interrupt。

  b) awaitUninterruptily():當前執行緒進入等待狀態,知道響應通知SIGNAL。

  c) awaitNanos(long):指定一個納秒為單位的超時時長,當前執行緒進入等待狀態,直到響應通知、中斷或者超時,其返回值為剩餘時間,小於0則超時。

  d) awaitUntil(Date):制定一個超時時刻,當前執行緒進入等待狀態,知道響應通知、中斷或者超時

  e) signal/signalAll:對condition佇列中的執行緒進行喚醒/喚醒全部

從這些方法可以看出condition方法共分為兩類:

  1) await:等效於Object.wait。

  2) signal:等效於Object.notify。

  wait和notify是Object提供的native方法,Condition為了與Object的方法區分而另行命名的。

  以AQS的Condition實現類ConditionObject為例,ConditionObject維護了一個雙向waiter佇列,下面兩個屬性記錄了它的首尾節點。

1 //條件佇列頭結點
2 private transient Node firstWaiter;
3 //條件佇列尾結點
4 private transient Node lastWaiter;

  Node節點物件為一個雙向連結串列節點,其資料域為執行緒的引用。

await方法的實現

 1 public final void await() throws InterruptedException {
 2     //如果當前執行緒是中斷狀態,那麼丟擲中斷異常
 3     if(Thread.interrupted()) {
 4         throw new InterruptedException();
 5     }
 6     //把當前執行緒新增到waiter佇列尾
 7     Node node = addConditionWaiter();
 8     //釋放當前節點擁有的鎖,因為後面還要新增鎖,不釋放會造成死鎖
 9     long savedState = fullyRelease(node);
10     int interruptMode = 0;
11     while(!isOnSyncQuequ(node)) {
12         LockSupport.park(this);
13         if((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
14             break;
15         }
16     }
17     if(acquireQueued(node, savedState) && interruptMode != THROW_IE) {
18         interruptMode = REINTERRUPT;
19     }
20     if(node.nextWaiter != null) {
21         //clean up if cancelled
22         unlinkCancelledWaiters();
23     }
24     if(interruptMode != 0) {
25         reportInteruptAfterWait(interruptMode);
26     }
27 }

  需要注意的是,阻塞當前執行緒使用的方法為LockSupport.park(),如果需要喚醒,那麼需要有signal()方法來呼叫LockSupport.unpark(Thread);

signal方法的實現

signal方法用於喚醒Condition等待佇列中的下一個等待節點

 1 public final void signal() {
 2     //只有獨佔模式才能使用signal,否則丟擲異常
 3     if(!isHeldExclusively()) {
 4         throw new IllegalMoinitorStateException();
 5     }
 6     Node first = firstWaiter;
 7     if(first != null) {
 8         doSignal(first);
 9     }
10 }
11 private void doSignal(Node first) {
12     //從等待佇列中移除節點,並嘗試喚醒節點
13     do {
14         if(firstWaiter = first.nextWaiter == null) {
15             lastWaiter = null;
16         }
17         first.nextWaiter = null;
18     }
19     while(!transferForSignal(first) && (first = firstWaiter) != null);
20 }
21 final boolean transferForSignal(Node node) {
22     //如果設定waitStatus失敗,那麼說明節點在signal之前被取消了,此時返回false
23     if(!compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
24         return false;
25     }
26     //這個佇列放到sync佇列的尾部
27     Node p = enq(node);
28     //獲取入隊節點的前驅節點狀態
29     int ws = p.waitStatus;
30     //如果前驅節點取消了,那麼可以直接喚醒當前節點的執行緒
31     //如果前驅結點沒有取消,那麼設定當前節點為SIGNAL,而不是喚醒這個執行緒
32     if(ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) {
33         LockSupport.unpark(node.thread);
34     }
35     return true;
36 }