天天看點

Java多線程--ReentrantLock的lock和lockInterruptibly的差別

ReentrantLock的加鎖方法Lock()提供了無條件地輪詢擷取鎖的方式,lockInterruptibly()提供了可中斷的鎖擷取方式。這兩個方法的差別在哪裡呢?通過分析源碼可以知道lock方法預設處理了中斷請求,一旦監測到中斷狀态,則中斷目前線程;而lockInterruptibly()則直接抛出中斷異常,由上層調用者區去進行中斷。

      1  lock操作

         lock擷取鎖過程中,忽略了中斷,在成功擷取鎖之後,再根據中斷辨別進行中斷,即selfInterrupt中斷自己。 acquire操作源碼如下:

[java]  view plain  copy

  1. public final void acquire(int arg) {  
  2.     if (!tryAcquire(arg) &&  
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
  4.         selfInterrupt();  
  5. }  

      acquireQueued,在for循環中無條件重試擷取鎖,直到成功擷取鎖,同時傳回線程中斷狀态。該方法通過for循正常傳回時,必定是成功擷取到了鎖。

[java]  view plain  copy

  1. final boolean acquireQueued(final Node node, int arg) {  
  2.     boolean failed = true;  
  3.     try {  
  4.         boolean interrupted = false;  
  5.         for (;;) {  
  6.             final Node p = node.predecessor();  
  7.             if (p == head && tryAcquire(arg)) {  
  8.                 setHead(node);  
  9.                 p.next = null; // help GC  
  10.                 failed = false;  
  11.                 return interrupted;  
  12.             }  
  13.             if (shouldParkAfterFailedAcquire(p, node) &&  
  14.                 parkAndCheckInterrupt())  
  15.                 interrupted = true;  
  16.         }  
  17.     } finally {  
  18.         if (failed)  
  19.             cancelAcquire(node);  
  20.     }  
  21. }  

    2 lockInterruptibly操作

     可中斷加鎖,即在鎖擷取過程中不進行中斷狀态,而是直接抛出中斷異常,由上層調用者進行中斷。源碼細微差别在于鎖擷取這部分代碼,這個方法與acquireQueue差别在于方法的傳回途徑有兩種,一種是for循環結束,正常擷取到鎖;另一種是線程被喚醒後檢測到中斷請求,則立即抛出中斷異常,該操作導緻方法結束。

[java]  view plain  copy

  1.     private void doAcquireInterruptibly(int arg)  
  2.         throws InterruptedException {  
  3.         final Node node = addWaiter(Node.EXCLUSIVE);  
  4.         boolean failed = true;  
  5.         try {  
  6.             for (;;) {  
  7.                 final Node p = node.predecessor();  
  8.                 if (p == head && tryAcquire(arg)) {  
  9.                     setHead(node);  
  10.                     p.next = null; // help GC  
  11.                     failed = false;  
  12.                     return;  
  13.                 }  
  14.                 if (shouldParkAfterFailedAcquire(p, node) &&  
  15.                     parkAndCheckInterrupt())  
  16.                     throw new InterruptedException();  
  17.             }  
  18.         } finally {  
  19.             if (failed)  
  20.                 cancelAcquire(node);  
  21.         }  
  22.     }  

     結論:ReentrantLock的中斷和非中斷加鎖模式的差別在于:線程嘗試擷取鎖操作失敗後,在等待過程中,如果該線程被其他線程中斷了,它是如何響應中斷請求的。lock方法會忽略中斷請求,繼續擷取鎖直到成功;而lockInterruptibly則直接抛出中斷異常來立即響應中斷,由上層調用者進行中斷。

     那麼,為什麼要分為這兩種模式呢?這兩種加鎖方式分别适用于什麼場合呢?根據它們的實作語義來了解,我認為lock()适用于鎖擷取操作不受中斷影響的情況,此時可以忽略中斷請求正常執行加鎖操作,因為該操作僅僅記錄了中斷狀态(通過Thread.currentThread().interrupt()操作,隻是恢複了中斷狀态為true,并沒有對中斷進行響應)。如果要求被中斷線程不能參與鎖的競争操作,則此時應該使用lockInterruptibly方法,一旦檢測到中斷請求,立即傳回不再參與鎖的競争并且取消鎖擷取操作(即finally中的cancelAcquire操作)。