互聯網JAVA面試常問問題(六)

  • 2019 年 10 月 6 日
  • 筆記

前幾篇文章,都介紹了JAVA面試中鎖相關的知識。其實關於JAVA鎖/多執行緒,你還需要知道了解關於ReentrantLock的知識,本文將從源碼入手,介紹可重入鎖的相關知識。

ReentrantLock

先來看看ReentrantLock的源碼,部分程式碼塊用省略號代替,後面會詳細展開介紹:

public class ReentrantLock implements Lock, java.io.Serializable {        private static final long serialVersionUID = 7373984872572414699L;        /** Synchronizer providing all implementation mechanics */     private final Sync sync;        abstract static class Sync extends AbstractQueuedSynchronizer {            private static final long serialVersionUID = -5179523762034025860L;        /**          * Performs {@link Lock#lock}. The main reason for subclassing          * is to allow fast path for nonfair version.          */         abstract void lock();        /**          * Performs non-fair tryLock.  tryAcquire is implemented in          * subclasses, but both need nonfair try for trylock method.          */         final boolean nonfairTryAcquire(int acquires) {             .......         }          protected final boolean tryRelease(int releases) {             int c = getState() - releases;              if (Thread.currentThread() != getExclusiveOwnerThread())                  throw new IllegalMonitorStateException();             boolean free = false;              if (c == 0) {               free = true;               setExclusiveOwnerThread(null);             }               setState(c);              return free;         }            protected final boolean isHeldExclusively() {              // While we must in general read state before owner,             // we don't need to do so to check if current thread is owner             return getExclusiveOwnerThread() == Thread.currentThread();         }          ........     }    /**      * Sync object for non-fair locks      */     static final class NonfairSync extends Sync {        .......     }    /**      * Sync object for fair locks      */     static final class FairSync extends Sync {        ...........     }  ..........  }

可以看出可重入鎖的源碼中,其實實現了公平鎖和非公平鎖。

ReentrantLock中有一個靜態內部抽象類Sync,然後有NonfairSync和FairSync兩個靜態類繼承了Sync。

其中Sync繼承了AQS(AbstractQueuedSynchronizer),接下來的文章中會介紹詳細AQS。

我們在使用可重入鎖的時候,需要明顯的加鎖和釋放鎖的過程。一般在finally程式碼中實現鎖釋放的過程。

Lock lock = new ReentrantLock();    Condition condition = lock.newCondition();  lock.lock();    try {  while(條件判斷表達式) {       condition.wait();   }  // 處理邏輯    }  finally  {   lock.unlock();   }
非公平鎖的實現
    static final class NonfairSync extends Sync {      private static final long serialVersionUID = 7316153563782823691L;          /**          * Performs lock.  Try immediate barge, backing up to normal          * acquire on failure.          */         final void lock() {              if (compareAndSetState(0, 1))                 setExclusiveOwnerThread(Thread.currentThread());              else                 acquire(1);         }          protected final boolean tryAcquire(int acquires) {              return nonfairTryAcquire(acquires);         }     }

可以看出,非公平鎖在執行lock的時候,會用CAS來嘗試將鎖狀態改成1,如果修改成功,則直接獲取鎖,用setExclusiveOwnerThread方法講當前執行緒設置為自己。如果沒有修改成功,則會執行acquire方法來嘗試獲取鎖。其中,nonfairTryAcquire實現如下:

        final boolean nonfairTryAcquire(int acquires) {          final Thread current = Thread.currentThread();          int c = getState();          if (c == 0) {          if (compareAndSetState(0, acquires)) {                     setExclusiveOwnerThread(current);                      return true;                 }             }           else if (current == getExclusiveOwnerThread()) {              int nextc = c + acquires;              if (nextc < 0) // overflow                     throw new Error("Maximum lock count exceeded");                setState(nextc);               return true;             }               return false;         }

可以看出這個方法,其實也是在用CAS嘗試將執行緒狀態置為1。其實也是一個多次嘗試獲取的過程。

所以,對於非公平鎖,當一執行緒空閑時候,其他所有等待執行緒擁有相同的優先順序,誰先爭搶到資源即可以獲取到鎖。

公平鎖的實現
  static final class FairSync extends Sync {          private static final long serialVersionUID = -3000897897090466540L;        final void lock() {             acquire(1);         }        /**          * Fair version of tryAcquire.  Don't grant access unless          * recursive call or no waiters or is first.          */         protected final boolean tryAcquire(int acquires) {              final Thread current = Thread.currentThread();              int c = getState();              if (c == 0) {                  if (!hasQueuedPredecessors() &&                     compareAndSetState(0, acquires)) {                     setExclusiveOwnerThread(current);                      return true;                 }             }              else if (current == getExclusiveOwnerThread()) {                 int nextc = c + acquires;                 if (nextc < 0)                     throw new Error("Maximum lock count exceeded");                setState(nextc);                 return true;             }                 return false;         }     }

主要看當公平鎖執行lock方法的時候,會調用 acquire方法, acquire方法首先嘗試獲取鎖並且嘗試將當前執行緒加入到一個隊列中,所以公平鎖其實是維護了一個隊列,誰等待的時間最長,當執行緒空閑時候,就會最先獲取資源:

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

如果想了解acquireQueued的話,可以參照一下程式碼:

   final boolean acquireQueued(final Node node, int arg) {         boolean failed = true;         try {             boolean interrupted = false;             for (;;) {                 final Node p = node.predecessor();                 if (p == head && tryAcquire(arg)) {                     setHead(node);                     p.next = null; // help GC                     failed = false;                     return interrupted;                 }                 if (shouldParkAfterFailedAcquire(p, node) &&                     parkAndCheckInterrupt())                     interrupted = true;             }         } finally {             if (failed)                 cancelAcquire(node);         }     }

綜上,可重入鎖利用CAS原理實現了公平鎖和非公平鎖,為什麼叫做可重入鎖呢?其實在程式碼方法tryAcquire中可以看到,執行緒可以重複獲取已經持有的鎖。

if (current == getExclusiveOwnerThread()) {      int nextc = c + acquires;      if (nextc < 0) // overflow          throw new Error("Maximum lock count exceeded");     setState(nextc);      return true;  }

今天小強從源碼的角度分析了ReentrantLock,希望對正在學習JAVA的你有所幫助。