1. 程式人生 > >獨享還是共享,你選擇哪一種鎖?

獨享還是共享,你選擇哪一種鎖?


 

之前在的文章中已經寫了公平鎖和非公平鎖了,接下來就該介紹第二種鎖了,他就是共享鎖和獨享鎖,顧名思義,獨享,只能被一個執行緒 所持有,而共享,就是說可以被多個執行緒所共有。

鎖的分類

1.公平鎖/非公平鎖

2.可重入鎖3.獨享鎖/共享鎖4.互斥鎖/讀寫鎖5.樂觀鎖/悲觀鎖6.分段鎖7.偏向鎖/輕量級鎖/重量級鎖8.自旋鎖

之前的第一次分享中我們已經說過了公平鎖和非公平鎖了,這次我們組要來解析一下這個獨享鎖和共享鎖。

獨享鎖

獨享鎖其實有很多名稱的,有人稱它為獨享鎖,有人也稱它為獨佔鎖,其實大致上都是一個意思,

獨享鎖,只能夠被一個執行緒所持有,

而他的例項我們之前的公平鎖和非公平鎖也都說過一次,我們可以再看一下這個例項,

 

 

ReentrantLock(獨享)

ReentrantLock是基於AQS來實現的,那什麼是AQS呢?

AQS全稱AbstractQueuedSynchronizer,如果說使用翻譯軟體來看“摘要排隊同步器”,但是很多人喜歡稱它為抽象佇列同步器。 其實叫什麼倒是沒有那麼重要,只要記住英文,這才是最重要的。

 

AQS它定義了一套多執行緒訪問共享資源的同步器框架,很多類都是依賴於AQS來比如說我們一會將要介紹的ReentrantLock。

你看原始碼

/*    查詢是否有任何執行緒正在等待與此鎖相關聯的給定條件。    請注意,由於超時和*中斷可能隨時發生,
此方法主要用於監視系統狀態*/ public boolean hasWaiters(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

這裡就指明瞭我們說的ReentrantLock是依賴AQS的,而AQS它是JUC併發包中的一個核心的一個元件。 也是不可或缺的元件。

 

AQS解決了子啊實現同步器時涉及當的大量細節問題,例如獲取同步狀態、FIFO同步佇列。基於AQS來構建同步器可以帶來很多好處。它不僅能夠極大地減少實現工作,而且也不必處理在多個位置上發生的競爭問題。

 

在基於AQS構建的同步器中,只能在一個時刻發生阻塞,從而降低上下文切換的開銷,提高了吞吐量。

 

AQS的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態。

咱們可以看一下

public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer    implements java.io.Serializable {    private static final long serialVersionUID = 7373984972572414691L;

而它典型的例子ReentrantLock中:

使用一個int型別的成員變數state來表示同步狀態,當state>0時表示已經獲取了鎖

這就是我們之前看的int c = getState();

而當c等於0的時候說明當前沒有執行緒佔有鎖,它提供了三個方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))來對同步狀態state進行操作,所以AQS可以確保對state的操作是安全的。

關於AQS我就解釋這麼多把,如果想深入瞭解的可以仔細的研究一下,而在這個ReentrantLock中的原始碼是這樣的

/**它預設是非公平鎖*/public ReentrantLock() {    sync = new NonfairSync();}   /**   建立ReentrantLock,公平鎖or非公平鎖   */public ReentrantLock(boolean fair) {     sync = fair ? new FairSync() : new NonfairSync(); } /** 而他會分別呼叫lock方法和unlock方法來釋放鎖 */ public void lock() {         sync.lock();     } public void unlock() {         sync.release(1);     }

但是其實他不僅僅是會呼叫lock和unlock方法,因為我們的執行緒不可能一點問題沒有,如果說進入到了waiting狀態,在這個時候如果沒有unpark()方法,就沒有辦法來喚醒他, 所以,也就接踵而至出現了tryLock(),tryLock(long,TimeUnit)來做一些嘗試加鎖或者說是超市來滿足某些特定的場景的需求了。

 

ReentrantLock會保證method-body在同一時間只有一個執行緒在執行這段程式碼,或者說,同一時刻只有一個執行緒的lock方法會返回。其餘執行緒會被掛起,直到獲取鎖。

 

從這裡我們就能看出,其實ReentrantLock實現的就是一個獨佔鎖的功能:有且只有一個執行緒獲取到鎖,其餘執行緒全部掛起,直到該擁有鎖的執行緒釋放鎖,被掛起的執行緒被喚醒重新開始競爭鎖。

 

而在原始碼中通過AQS來獲取獨享鎖是通過呼叫acquire方法,其實這個方法是阻塞的,

/***以獨佔模式獲取,忽略中斷。通過至少呼叫tryAcquire實現成功返回。否則執行緒排隊,可能重複阻塞和解除阻塞,呼叫tryAcquire直到成功。此方法可用於實現方法lock。*/ public final void acquire(int arg) {        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();    }

 

它通過tryAcquire(由子類Sync實現)嘗試獲取鎖,這也是上面原始碼中的lock方法的實現步驟

 

而沒有獲取到鎖則呼叫AQS的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);        }    }

這段的意思大致就是說 當前驅節點是頭節點,並且獨佔時才返回

而在下面的if判斷中,他會去進行阻塞,而且還要去判斷是否打斷,如果我們的節點狀態是Node.SIGNAL時, 完蛋了,執行緒將會執行parkAndCheckInterrupt方法,知道有執行緒release的時候,這時候就會進行一個unpark來迴圈的去獲取鎖。 而這個方法通過LockSupport.park(this)將當前的執行緒掛起到WATING的狀態,就需要我們去執行unpark方法了來喚醒他,也就是我說的那個release, 通過這樣的一種FIFO機制的等待就實現了LOCK的操作。

 

這上面的程式碼只是進行加鎖,但是沒有釋放鎖,如果說我們獲得了鎖不進行釋放,那麼很自然的出現一種情況,死鎖!

所以必須要進行一個釋放,

我們來看看內部是怎麼釋放鎖的

 public void unlock()              { sync.release(1); } public final boolean release(int arg) {         if (tryRelease(arg)) {             Node h = head;             if (h != null && h.waitStatus != 0)                 unparkSuccessor(h);             return true;         }         return false;     }

unlock方法間接呼叫AQS的release(1)來完成釋放

tryRelease(int)方法進行了特殊的判定,如果成立則會將head傳入unparkSuccessor(Node) 方法中並且返回true,否則返回的就是false。

public final boolean release(int arg) {        if (tryRelease(arg)) {            Node h = head;            if (h != null && h.waitStatus != 0)                unparkSuccessor(h);            return true;        }        return false;    }

 

而他在執行了unparkSuccessor方法中的時候,就已經意味著要真正的釋放鎖了。 這其實就是獨享鎖進行獲取鎖和釋放鎖的一個過程!有興趣的可以去原始碼中把註釋翻譯一下看看。

 

共享鎖

從我們之前的獨享所就能看得出來,獨享鎖是使用的一個狀態來進行鎖標記的,共享鎖其實也差不多,但是JAVA中有不想定力兩個狀態,所以區別出現了, 他們的鎖狀態時不一樣的。

 

基本的流程是一樣的,主要區別在於判斷鎖獲取的條件上,由於是共享鎖,也就允許多個執行緒同時獲取,所以同步狀態的數量同時的大於1的,如果同步狀態為非0,則執行緒就可以獲取鎖,只有當同步狀態為0時,才說明共享數量的鎖已經被全部獲取,其餘執行緒只能等待。

 

最典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

 

我們來看一下他的獲取鎖和釋放鎖的程式碼體現。

    //獲取鎖指定離不開這個lock方法,    public void lock() {                sync.acquireShared(1);    }    //acquireShared()首先會通過tryAcquireShared()來嘗試獲取鎖。    //如果說獲取不到那麼他就回去執行  doAcquireShared(arg);直到獲取到鎖才會返回    //你看方法名do是不是想到了do-while呢?    public final void acquireShared(int arg) {            if (tryAcquireShared(arg) < 0)                doAcquireShared(arg);    }    // tryAcquireShared()來嘗試獲取鎖。    protected int tryAcquireShared(int arg) {            throw new UnsupportedOperationException();    }    //只有這個方法獲取到鎖了才會進行返回    private void doAcquireShared(int arg) {            final Node node = addWaiter(Node.SHARED);            boolean failed = true;            try {                boolean interrupted = false;                for (;;) {                    final Node p = node.predecessor();                    if (p == head) {                        int r = tryAcquireShared(arg);                        if (r >= 0) {                            setHeadAndPropagate(node, r);                            p.next = null; // help GC                            if (interrupted)                                selfInterrupt();                            failed = false;                            return;                        }                    }                    if (shouldParkAfterFailedAcquire(p, node) &&                        parkAndCheckInterrupt())                        interrupted = true;                }            } finally {                if (failed)                    cancelAcquire(node);            }        }     //上面的這些方法全部都是在AbstractQueuedSynchronizer中     //而他通過Sync來呼叫的acquireShared     //而Sync則是繼承的AbstractQueuedSynchronizer     abstract static class Sync extends AbstractQueuedSynchronizer      而他呼叫的tryAcquireShared則是在ReentrantReadWriteLock中     protected final int tryAcquireShared(int unused) {                 Thread current = Thread.currentThread();                 //獲取狀態                 int c = getState();                 //如果說鎖狀態不是0 並且獲取鎖的執行緒不是current執行緒 返回-1                 if (exclusiveCount(c) != 0 &&                     getExclusiveOwnerThread() != current)                     return -1;                 //統計讀鎖的次數                 int r = sharedCount(c);                 //若無需等待,並且共享讀鎖共享次數小於MAX_COUNT,則會把鎖的共享次數加一,                 //否則他會去執行fullTryAcquireShared                 if (!readerShouldBlock() &&                     r < MAX_COUNT &&                     compareAndSetState(c, c + SHARED_UNIT)) {                     if (r == 0) {                         firstReader = current;                         firstReaderHoldCount = 1;                     } else if (firstReader == current) {                         firstReaderHoldCount++;                     } else {                         HoldCounter rh = cachedHoldCounter;                         if (rh == null || rh.tid != getThreadId(current))                             cachedHoldCounter = rh = readHolds.get();                         else if (rh.count == 0)                             readHolds.set(rh);                         rh.count++;                     }                     return 1;                 }                 return fullTryAcquireShared(current);             }        /** fullTryAcquireShared()會根據是否需要阻塞等待        讀取鎖的共享計數是否超過限制”等等進行處理。        如果不需要阻塞等待,並且鎖的共享計數沒有超過限制,        則通過CAS嘗試獲取鎖,並返回1。*/      final int fullTryAcquireShared(Thread current) {          /*           * This code is in part redundant with that in           * tryAcquireShared but is simpler overall by not           * complicating tryAcquireShared with interactions between           * retries and lazily reading hold counts.           */          HoldCounter rh = null;          for (;;) {              int c = getState();              if (exclusiveCount(c) != 0) {                  if (getExclusiveOwnerThread() != current)                      return -1;                  // else we hold the exclusive lock; blocking here                  // would cause deadlock.              } else if (readerShouldBlock()) {                  // Make sure we're not acquiring read lock reentrantly                  if (firstReader == current) {                      // assert firstReaderHoldCount > 0;                  } else {                      if (rh == null) {                          rh = cachedHoldCounter;                          if (rh == null || rh.tid != getThreadId(current)) {                              rh = readHolds.get();                              if (rh.count == 0)                                  readHolds.remove();                          }                      }                      if (rh.count == 0)                          return -1;                  }              }              if (sharedCount(c) == MAX_COUNT)                  throw new Error("Maximum lock count exceeded");              if (compareAndSetState(c, c + SHARED_UNIT)) {                  if (sharedCount(c) == 0) {                      firstReader = current;                      firstReaderHoldCount = 1;                  } else if (firstReader == current) {                      firstReaderHoldCount++;                  } else {                      if (rh == null)                          rh = cachedHoldCounter;                      if (rh == null || rh.tid != getThreadId(current))                          rh = readHolds.get();                      else if (rh.count == 0)                          readHolds.set(rh);                      rh.count++;                      cachedHoldCounter = rh; // cache for release                  }                  return 1;              }          }      }

 

以上的原始碼就是共享鎖的一個獲取鎖的過程

接下來肯定是要進行鎖的釋放了

unlock()

    public void unlock() {            sync.releaseShared(1);    }    //和獲取鎖的過程類似,他首先會通過tryReleaseShared()去嘗試釋放共享鎖。嘗試成功,則直接返回;嘗試失敗,    //則通過doReleaseShared()去釋放共享鎖。     public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {            doReleaseShared();            return true;        }        return false;    }    //是嘗試釋放共享鎖第一步。    protected final boolean tryReleaseShared(int unused) {        Thread current = Thread.currentThread();        if (firstReader == current) {            // assert firstReaderHoldCount > 0;            if (firstReaderHoldCount == 1)                firstReader = null;            else                firstReaderHoldCount--;        } else {            HoldCounter rh = cachedHoldCounter;            if (rh == null || rh.tid != getThreadId(current))                rh = readHolds.get();            int count = rh.count;            if (count <= 1) {                readHolds.remove();                if (count <= 0)                    throw unmatchedUnlockException();            }            --rh.count;        }        for (;;) {            int c = getState();            int nextc = c - SHARED_UNIT;            if (compareAndSetState(c, nextc))                // Releasing the read lock has no effect on readers,                // but it may allow waiting writers to proceed if                // both read and write locks are now free.                return nextc == 0;        }    }    //持續執行釋放共享鎖    private void doReleaseShared() {            /*             * Ensure that a release propagates, even if there are other             * in-progress acquires/releases.  This proceeds in the usual             * way of trying to unparkSuccessor of head if it needs             * signal. But if it does not, status is set to PROPAGATE to             * ensure that upon release, propagation continues.             * Additionally, we must loop in case a new node is added             * while we are doing this. Also, unlike other uses of             * unparkSuccessor, we need to know if CAS to reset status             * fails, if so rechecking.             */            for (;;) {                Node h = head;                if (h != null && h != tail) {                    int ws = h.waitStatus;                    if (ws == Node.SIGNAL) {                        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))                            continue;            // loop to recheck cases                        unparkSuccessor(h);                    }                    else if (ws == 0 &&                             !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))                        continue;                // loop on failed CAS                }                if (h == head)                   // loop if head changed                    break;            }        }

以上的程式碼就是共享鎖和非共享鎖的原始碼。需要注意的時候,在這裡其實很亂,有些方法是定義在ReentrantReadWriteLock中的, 而有一些方法是定義在AbstractQueuedSynchorizer中的,所以在來回切換看程式碼的時候尤其要注意,不要出現失誤。

 

總結

獨享鎖:同時只能有一個執行緒獲得鎖。

共享鎖:可以有多個執行緒同時獲得鎖。

關於獨享鎖和共享鎖,你明白了嗎?

 


 

Java 極客技術公眾號,是由一群熱愛 Java 開發的技術人組建成立,專注分享原創、高質量的 Java 文章。如果您覺得我們的文章還不錯,請幫忙讚賞、在看、轉發支援,鼓勵我們分享出更好的文章。 關注公眾號,大家可以在公眾號後臺回覆“部落格園”,免費獲得作者 Java 知識體系/面試必看資料。

 

&n