Lock 鎖底層實現

2022-09-25 06:02:44

★ 1、講講 Lock 鎖

是一個介面,有三個實現類,分別是常用的 可重入鎖,讀鎖、寫鎖。常用的是可重入鎖

加鎖使用lock() 方法,解鎖使用 unlock() 方法。Lock的底層是 AQS+CAS機制 實現。

Lock 常用子類 可重入鎖ReentrantLock 有兩種模式, 公平鎖模式、非公平鎖模式

公平鎖模式非公平鎖模式 的應用

  • 預設一般建立的是非公平鎖,就是允許執行緒插隊,而不是按先來後到順序

  • 並行量高的,非公平可能會導致執行緒餓死 === 做中介軟體,比如rocketmq 就需要關注鎖公平和不公平

    • mq 訊息佇列的應用,比如網易雲多個使用者的評論->mq->如果是非公平鎖,那麼導致執行緒飢餓,導致等待時間過長-不穩定

    • 解決:mq原始碼的queue包下有:

      RoundQueue(執行緒不安全),ConcurrentTreeMap(執行緒安全-put 方法使用了lock 加鎖,且 lock = new ReentrantLock(true);)

可重入鎖的意思是 對於同一執行緒可以重複去獲取鎖。應用場景--遞迴,例如資料夾遍歷目錄下的所有檔名。



★ 2、和synchronized 的使用區別/ 說說lock 和 synchronized 鎖的區別

  • synchronized 是一個 關鍵字,使用C++實現的,沒辦法控制鎖的開始、鎖結束,也沒辦法中斷執行緒的執行

  • 而 lock 是 java層面的實現可以獲取鎖的狀態,開啟鎖,釋放鎖,通過設定可以中斷執行緒的執行,更加靈活

  • 是否自動是否鎖:synchronized 會自動是否鎖,而 lock 需要手動呼叫unlock 方法釋放,否則會死迴圈

lock.lock();//其他沒有拿到鎖的執行緒?阻塞 卡著不動
boolean res = lock.tryLock(1000, TimeUnit.MILLISECONDS);//一秒之後如果沒有拿到鎖,就返回false

lock.lockInterruptibly();//中斷方法



★ 3、講講 trylock、lock方法

lock 鎖設計上的核心成員:鎖狀態、鎖擁有者、等待佇列

原始碼方面:在 ReentrantLock 中 使用了關鍵成員是同步器AQS(原始碼中的Sync)


trylock方法:獲取鎖/是否鎖成功

  • 鎖的狀態,0 代表未佔用鎖,大於0 則代表佔用鎖的次數。

  • 首先當前執行緒以CAS的方式,嘗試將鎖的狀態從0修改成1,就是嘗試獲取鎖。

  • 獲取到了就把當前執行緒設定給AQS的屬性exclusiveOwnerThread,也就是指明當前鎖的擁有者是當前執行緒

        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;
        }

lock方法:加鎖

  • 非公平鎖模式,首先當前執行緒以CAS的方式,嘗試將鎖的狀態從0修改成1,就是嘗試獲取鎖。

  • 獲取到了就把當前執行緒設定給AQS的屬性exclusiveOwnerThread,也就是指明當前鎖的擁有者是當前執行緒

  • 當前鎖已經被佔用,執行緒會進入等待佇列,不斷地搶鎖,搶到鎖直接從等待佇列彈出,否則判斷執行緒的狀態是否需要掛起(阻塞),這裡迴圈搶鎖,不斷呼叫了嘗試獲取鎖的方法,也利用了CAS思想。

// 非公平鎖模式 lock = new ReentrantLock();
final void lock() {
    // 首先以 CAS 的方式,嘗試將state 從0修改成1 
   if (compareAndSetState(0, 1))
         setExclusiveOwnerThread(Thread.currentThread());
   else
         acquire(1);
}

// compareAndSetState(0, 1)--> CAS 機制
protected final boolean compareAndSetState(int expect, int update) {
 // See below for intrinsics setup to support this
  return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}


// acquire(1);--> CAS 機制
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//進入等待佇列,繼續不斷嘗試獲取鎖,直到搶到鎖則彈出佇列,否則判斷執行緒的狀態是否需要掛起
            selfInterrupt();
    }

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);
        }
    }




如果本文對你有幫助的話記得給一樂點個贊哦,感謝!