AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

面試中如何答好:ReentrantLock

來源: 責編: 時間:2023-10-13 14:37:50 234觀看
導讀先了解一下讀本篇前,一定要確保已經讀過本公眾號的AQS講解。我們知道實現一把鎖要有如下幾個邏輯鎖的標識線程搶鎖的邏輯線程掛起的邏輯線程存儲邏輯線程釋放鎖的邏輯線程喚醒的邏輯我們在講解AQS的時候說過AQS基本負

先了解一下

讀本篇前,一定要確保已經讀過本公眾號的AQS講解。nTt28資訊網——每日最新資訊28at.com

我們知道實現一把鎖要有如下幾個邏輯nTt28資訊網——每日最新資訊28at.com

  • 鎖的標識
  • 線程搶鎖的邏輯
  • 線程掛起的邏輯
  • 線程存儲邏輯
  • 線程釋放鎖的邏輯
  • 線程喚醒的邏輯

我們在講解AQS的時候說過AQS基本負責了實現鎖的全部邏輯,唯獨線程搶鎖和線程釋放鎖的邏輯是交給子類來實現了,而ReentrantLock作為最常用的獨占鎖,其內部就是包含了AQS的子類實現了線程搶鎖和釋放鎖的邏輯。nTt28資訊網——每日最新資訊28at.com

我們在使用ReentrantLock的時候一般只會使用如下方法:nTt28資訊網——每日最新資訊28at.com

ReentrantLock lock=new ReentrantLock(); lock.lock(); lock.unlock(); lock.tryLock(); Condition condition=lock.newCondition(); condition.await(); condition.signal(); condition.signalAll();

技術架構

如果我們自己來實現一個鎖,那么如何設計呢?nTt28資訊網——每日最新資訊28at.com

根據AQS的邏輯,我們寫一個子類sync,這個類一定會調用父類的acquire方法進行上鎖,同時重寫tryAcquire方法實現自己搶鎖邏輯,也一定會調用release方法進行解鎖,同時重寫tryRelease方法實現釋放鎖邏輯。nTt28資訊網——每日最新資訊28at.com

圖片圖片nTt28資訊網——每日最新資訊28at.com

那么ReentrantLock是怎么實現的呢?nTt28資訊網——每日最新資訊28at.com

ReentrantLock的實現的類架構如下,ReentrantLock對外提供作為一把鎖應該具備的api,比如lock加鎖,unlock解鎖等等,而它內部真正的實現是通過靜態內部類sync實現,sync是AQS的子類,是真正的鎖,因為這把鎖需要支持公平和非公平的特性,所以sync又有兩個子類FairSync和NonfairSync分別實現公平鎖和非公平鎖。nTt28資訊網——每日最新資訊28at.com

圖片圖片nTt28資訊網——每日最新資訊28at.com

因為是否公平說的是搶鎖的時候是否公平,那兩個子類就要在上鎖方法acquire的調用和搶鎖方法tryAcquire的重寫上做文章。nTt28資訊網——每日最新資訊28at.com

公平鎖做了什么文章?nTt28資訊網——每日最新資訊28at.com

static final class FairSync extends Sync {          final void lock() {            acquire(1);        }        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;        }    }

公平鎖比較簡單,直接調用了父級類AQS的acquire方法,因為AQS的鎖默認就是公平的排隊策略。nTt28資訊網——每日最新資訊28at.com

重寫tryAcquire方法的邏輯為:nTt28資訊網——每日最新資訊28at.com

  1. 判斷當前鎖是否被占用,即state是否為0
  2. 如果當前鎖沒有被占用,然后會判斷等待隊列中是否有線程在阻塞等待,如果有,那就終止搶鎖,如果沒有,就通過cas搶鎖,搶到鎖返回true,沒有搶到鎖返回false。
  3. 如果當前鎖已經被占用,然后判斷占用鎖的線程是不是自己,如果是,就會將state加1,表示重入,返回true。如果不是自己那就是代表沒有搶到鎖,返回false。

公平就公平在老老實實排隊。nTt28資訊網——每日最新資訊28at.com

非公平鎖做了什么文章?nTt28資訊網——每日最新資訊28at.com

static final class NonfairSync extends Sync {              final void lock() {            if (compareAndSetState(0, 1))                setExclusiveOwnerThread(Thread.currentThread());            else                acquire(1);        }        protected final boolean tryAcquire(int acquires) {            return nonfairTryAcquire(acquires);        }    }        //nonfairTryAcquire代碼在父類sync里面     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;        }

非公平鎖也很簡單,沒有直接調用了父級類AQS的acquire方法,而是先通過cas搶鎖,它不管等待隊列中有沒有其他線程在排隊,直接搶鎖,這就體現了不公平。nTt28資訊網——每日最新資訊28at.com

它重寫tryAcquire方法的邏輯為:nTt28資訊網——每日最新資訊28at.com

  1. 判斷當前鎖是否被占用,即state是否為0
  2. 如果當前鎖沒有被占用,就直接通過cas搶鎖(不管等待隊列中有沒有線程在排隊),搶到鎖返回true,沒有搶到鎖返回false。
  3. 如果當前鎖已經被占用,然后判斷占用鎖的線程是不是自己,如果是,就會將state加1,表示重入,返回true。如果不是自己那就是代表沒有搶到鎖,返回false。

公平鎖和非公平分別重寫了tryAcquire方法,來滿足公平和非公平的特性。那么tryAcquire方法也是需要子類重寫的,因為它和是否公平無關,因此tryAcquire方法被抽象到sync類中重寫。nTt28資訊網——每日最新資訊28at.com

sync類中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;        }

釋放鎖的邏輯如下:nTt28資訊網——每日最新資訊28at.com

  1. 獲取state的值,然后減1
  2. 如果state為0,代表鎖已經釋放,清空aqs中的持有鎖的線程字段的值
  3. 如果state不為0,說明當前線程重入了,還需要再次釋放鎖
  4. 將state寫回

釋放鎖往往和搶鎖邏輯是對應的,每個子類搶鎖邏輯不同的話,釋放鎖的邏輯也會對應不同。nTt28資訊網——每日最新資訊28at.com

具體實現

接下來我們通過ReentrantLock的使用看下它的源碼實現nTt28資訊網——每日最新資訊28at.com

class X {            private final ReentrantLock lock = new ReentrantLock();            Condition condition1=lock.newCondition();            Condition condition2=lock.newCondition();            public void m() {                lock.lock();                try {                    if(條件1){                        condition1.await();                    }                    if(條件2){                        condition2.await();                    }                } catch (InterruptedException e) {                } finally {                    condition1.signal();                    condition2.signal();                    lock.unlock();                }            }        }

先看這個方法:lock.lock()

ReentrantLock類public void lock() {        sync.lock();    }
NonfairSync 類中  final void lock() {    if (compareAndSetState(0, 1))    setExclusiveOwnerThread(Thread.currentThread());    else      acquire(1);  }
FairSync 類中  final void lock() {      acquire(1);  }

公平鎖和非公平鎖中都實現了lock方法,公平鎖直接調用AQS的acquire,而非公平鎖先搶鎖,搶不到鎖再調用AQS的acquire方法進行上鎖nTt28資訊網——每日最新資訊28at.com

進入acquire方法后的邏輯我們就都知道了。nTt28資訊網——每日最新資訊28at.com

再看這個方法lock.unlock()

public void unlock() {        sync.release(1);}

unlock方法內直接調用了AQS的Release方法進行解鎖的邏輯,進入release方法后邏輯我們都已經知道了,這里不再往下跟。nTt28資訊網——每日最新資訊28at.com

最后看這個方法lock.tryLock()

public boolean tryLock(long timeout, TimeUnit unit)            throws InterruptedException {        return sync.tryAcquireNanos(1, unit.toNanos(timeout));    }

tryLock方法直接調用sync的tryAcquireNanos方法,看過AQS的應該知道tryAcquireNanos這個方法是父類AQS的方法,這個方法和AQS中的四個核心方法中的Acquire方法一樣都是上鎖的方法,無非是上鎖的那幾個步驟,調用tryAcquire方法嘗試搶鎖,搶不到鎖就會進入doAcquireNanos方法。nTt28資訊網——每日最新資訊28at.com

public final boolean tryAcquireNanos(int arg, long nanosTimeout)            throws InterruptedException {        if (Thread.interrupted())            throw new InterruptedException();        return tryAcquire(arg) ||            doAcquireNanos(arg, nanosTimeout);    }

doAcquireNanos這個方法做的其實就是入隊,阻塞等一系列上鎖操作,邏輯和Acquire方法中差不多,但是有兩點不同:nTt28資訊網——每日最新資訊28at.com

  1. 該方法支持阻塞指定時長。
  2. 該方法支持中斷拋異常。

看下下面的代碼nTt28資訊網——每日最新資訊28at.com

private boolean doAcquireNanos(int arg, long nanosTimeout)            throws InterruptedException {        if (nanosTimeout <= 0L)            return false;        final long deadline = System.nanoTime() + nanosTimeout;        final Node node = addWaiter(Node.EXCLUSIVE);        boolean failed = true;        try {            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return true;                }                nanosTimeout = deadline - System.nanoTime();                if (nanosTimeout <= 0L)                    return false;                if (shouldParkAfterFailedAcquire(p, node) &&                    nanosTimeout > spinForTimeoutThreshold)                    LockSupport.parkNanos(this, nanosTimeout);                if (Thread.interrupted())                    throw new InterruptedException();            }        } finally {            if (failed)                cancelAcquire(node);        }    }

這里的阻塞不再是LockSupport類的park方法,而是parkNanos方法,這個方法支持指定時長的阻塞,AQS正是利用這個方法實現阻塞指定時長,當自動喚醒后,循環中會判斷是否超過設定時長,如果超過直接返回false跳出循環。nTt28資訊網——每日最新資訊28at.com

在阻塞期間,如果線程被中斷,就會拋出異常,同樣會跳出循環,外面可以通過捕獲這個異常達到中斷阻塞的目的。nTt28資訊網——每日最新資訊28at.com

可見ReentrantLock其實啥也沒做,其tryLock方法完全是依賴AQS實現。nTt28資訊網——每日最新資訊28at.com

lock.newCondition();

在AQS那篇我們說過Condition是AQS中的條件隊列,可以按條件將一批線程由不可喚醒變為可喚醒。nTt28資訊網——每日最新資訊28at.com

ReentrantLock類 public Condition newCondition() {        return sync.newCondition(); }
sync靜態內部類final ConditionObject newCondition() {            return new ConditionObject();}

sync提供了創建Condition對象的方法,意味著ReentrantLock也擁有Condition的能力。nTt28資訊網——每日最新資訊28at.com

ReentrantLock和synchronized對比

我們下面說的ReentrantLock其實就是說AQS,因為它的同步實現主要在AQS里面。nTt28資訊網——每日最新資訊28at.com

  1. 實現方面
    ReentrantLock是jdk級別實現的,其源碼在jdk源碼中可以查看,沒有脫離java。
    synchronized是jvm級別實現的,synchronized只是java端的一個關鍵字,具體邏輯實現都在jvm中。
  2. 性能方面
    優化前的synchronized性能很差,主要表現在兩個方面:
    因為大多數情況下對于資源的爭奪并沒有那么激烈,甚至于某個時刻可能只有一個線程在工作,在這種沒有競爭或者競爭壓力很小的情況下,如果每個線程都要進行用戶態到內核態的切換其實是很耗時的。
    jdk1.6對synchronized底層實現做了優化,優化后,在單線程以及并發不是很高的情況下通過無鎖偏向和自旋鎖的方式避免用戶態到內核態的切換,因此性能提高了,優化后的synchronized和ReentrantLock性能差不多了。
    ReentrantLock是在jdk實現的,它申請互斥量就是對鎖標識state的爭奪,它是通過cas方式實現。在java端實現。
    對于爭奪不到資源的線程依然要阻塞掛起,但凡阻塞掛起都要依賴于操作系統底層,這一步的用戶態到內核態的切換是避免不了的。
    因此在單線程進入代碼塊的時候,效率是很高的,因此我們說ReentrantLock性能高于原始的synchronized
  • 申請互斥量
    synchronized的鎖其實就是爭奪Monitor鎖的擁有權,這個爭奪過程是通過操作系統底層的互斥原語Mutex實現的,這個過程會有用戶態到內核態的切換。
  • 線程阻塞掛起
    沒能搶到到Monitor鎖擁有權的線程要阻塞掛起,阻塞掛起這個動作也是依靠操作系統實現的,這個過程也需要用戶態到內核態的切換。
  1. 特性方面
    兩個都是常用的典型的獨占鎖。
    ReentrantLock可重入,可中斷,支持公平和非公平鎖,可嘗試獲取鎖,可以支持分組將線程由不可喚醒變為可喚醒。
    synchronized可重入,不可中斷,非公平鎖,不可嘗試獲取鎖,只支持一個或者全部線程由不可喚醒到可喚醒。
  2. 使用方面

synchronized不需要手動釋放鎖,ReentrantLock需要手動釋放鎖,需要考慮異常對釋放鎖的影響避免異常導致線程一直持有鎖。nTt28資訊網——每日最新資訊28at.com

以下是兩個鎖的使用方式nTt28資訊網——每日最新資訊28at.com

class X {            private final ReentrantLock lock = new ReentrantLock();            Condition condition1=lock.newCondition();            Condition condition2=lock.newCondition();            public void m() {                lock.lock();                try {                    if(1==2){                        condition1.await();                    }                    if(1==3){                        condition2.await();                    }                } catch (InterruptedException e) {                } finally {                    condition1.signal();                    condition2.signal();                    lock.unlock();                }            }        }
class X {            private final testtest sync=new testtest();;            public void m() throws InterruptedException {                synchronized(sync){                    if(1==2){                        sync.wait();                    }                    sync.notify();                    sync.notifyAll();                }            }        }

對比代碼及特性說明:nTt28資訊網——每日最新資訊28at.com

  1. 兩個鎖都是依賴一個對象:lock和sync
  2. condition和wait方法具有同樣的效果,進入condition和wait的線程將陷入等待(不可喚醒狀態),只有被分別調用signal和notify方法線程才會重新變為可喚醒狀態,請注意是可喚醒,而不是被喚醒。
  3. 可喚醒是說具備了競爭資源的資格,資源空閑后,synchronized中會在可喚醒狀態的線程中隨機挑選一個線程去拿鎖,而ReentrantLock中不可喚醒的線程變為可喚醒狀態,其實就是將條件隊列中的線程搬到等待隊列中排隊,只有隊頭的才會去嘗試拿鎖。
  4. ReentrantLock分批將線程由不可喚醒變為可喚醒也在這段代碼中體現了,代碼中按照不同的條件將線程放入不同的condition,每個condition就是一個組,釋放的時候也可以按照不同的條件進行釋放。而synchronized中進入wait的線程不能分組,釋放也只能隨機釋放一個或者全部釋放。


nTt28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-13582-0.html面試中如何答好:ReentrantLock

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Java中的Volatile到底是什么?

下一篇: 你真的了解Django Model嗎?十分鐘入門指南!

標簽:
  • 熱門焦點
Top