[Java併發系列] 3.Java中的鎖

xiangdong發表於2017-11-14

Java併發系列目錄

討論J.U.C包中locks下面的類(包括介面)

鎖主要是用來控制多個執行緒訪問共享資源的一種方式,通常情況下,一個鎖可以防止在同一時間內多個執行緒同時訪問共享資源(讀寫鎖除外,讀寫鎖在同一時間內,可以允許有多個讀鎖同時讀共享資源)。

1. Lock介面

Lock介面同synchronized關鍵字的作用類似,都是提供了同步的功能。但是Lock在使用的時候,需要顯式的去獲取鎖。與synchronized相比,Lock失去了隱式獲取鎖的便捷性,但是可以控制鎖的獲取和釋放,可中斷鎖和超時鎖。

2. Lock介面主要API

  • void lock(); 獲取鎖
  • void lockInterruptibly(); 可中斷的獲取鎖,此方法和lock()方法的區別在於: 當使用lockInterruptibly獲取鎖時可以中斷當前執行緒;
  • boolean tryLock(); 嘗試非阻塞的獲取鎖,當呼叫此方法之後,如果能獲取則返回true,如果不能獲取則直接返回false;
  • boolean tryLock(long time, TimeUnit unit); 超時獲取鎖
  • void unlock(); 釋放鎖
  • Condition newCondition(); 獲取等待通知元件,該元件和當前的鎖繫結在一起,只有當前執行緒獲的了鎖,才能呼叫該元件的wait()方法,呼叫wait()方法之後,當前執行緒將釋放鎖;

3. Lock介面的實現類

1. ReentrantLock(重入鎖)

重入鎖,就是支援重進入的鎖,表示該鎖支援一個執行緒對資源的重複加鎖。

重進入: 指的是任意執行緒在獲取鎖能夠再次獲取該所而不會被阻塞。
公平與非公平獲取鎖:公平指的是在絕對時間上,先對鎖進行請求的執行緒(等待時間最長的執行緒優先獲取鎖)首先獲取鎖,那麼這個鎖是公平的,反之,則是非公平的。

①. 鎖的重進入

如果要實現鎖的重進入,那麼就就緒解決兩個問題:

  • 鎖的獲取:要獲取鎖,那麼鎖就需要去檢查獲取該鎖的執行緒是否是已獲取此鎖的執行緒(也就是是否是當前執行緒佔有此鎖),如果是,那麼獲取成功;如下程式碼是非公平獲取鎖的方式
    ```java
    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的方式設定同步狀態;如果鎖已被佔有,則判斷當前執行緒是否是佔有此鎖的執行緒,然後再來決定獲取操作是否成功,如果獲取鎖的執行緒再次請求獲取鎖,則將同步狀態值進行增加並且返回true。
所以重入鎖的獲取就是當執行緒重入成功,增加鎖的同步狀態值即可。

- **鎖的釋放:**執行緒重複N此獲取鎖,那麼就需要釋放N次,其他的執行緒才可以獲取該鎖。如下程式碼是釋放鎖的程式碼:

```java
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;
        }複製程式碼

如果一個鎖被某個執行緒獲取了N次,那麼前(N-1)次都會返回false,而當同步狀態完成被釋放時(c=0),將佔有執行緒設定為null,才會返回true。

②. 公平與非公平的獲取鎖

如下是公平獲取鎖的程式碼:

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;
        }
    }複製程式碼

與上面非公平獲取鎖的程式碼相比,在這段程式碼中,僅僅在if條件中多了一個hasQueuedPredecessors()方法,此方法就是判斷在同步佇列中,當前節點是否有前驅節點(即有比當前執行緒更早的獲取鎖的執行緒),因此當hasQueuedPredecessors()返回true時,就需要等待前驅執行緒獲取並釋放鎖之後才能繼續獲取鎖。

2. ReadWriteLock(讀寫鎖)

排他鎖:指的是在同一時刻只允許一個執行緒進行訪問
讀寫鎖:在同一時間,允許有多個讀執行緒進行訪問,而在寫執行緒進行訪問時,讀執行緒和其他寫執行緒均會被阻塞。讀寫鎖維護了一個讀鎖和一個寫鎖,通過讀寫分離,來提升併發效能(至少比排他鎖效能好多了)。

//todo 讀寫鎖內容較多,留待以後來寫

4. LockSupport類

LockSupport類位於在J.U.C.locks包中,它主要是定義了一些公共靜態方法,這些方法提供了最基本的執行緒阻塞和喚醒功能。如下表是LockSupport中提供的一些方法及描述:

方法 描述
public static void park() 阻塞當前執行緒,當其他執行緒呼叫unpark()或者中斷當前執行緒時,才能從park()方法返回
fipublic static void parkNanos(long nanos) 阻塞當前執行緒,超過nanos納秒之後,自動返回
public static void parkUntil(long deadline) 阻塞當前執行緒,直到deadline時間
public static void unpark(Thread thread) 喚醒處於阻塞的執行緒

相關文章