如何使用AQS共享鎖,Semaphore、CountDownLatch

這篇文章主要講解了“如何使用AQS共享鎖,Semaphore、CountDownLatch”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何使用AQS共享鎖,Semaphore、CountDownLatch”吧!

徐州網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站開發(fā)等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)從2013年開始到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。

共享鎖 和 AQS

1. 基于 AQS 實現(xiàn)的鎖有哪些?

如何使用AQS共享鎖,Semaphore、CountDownLatch  
圖 18-1 基于 AQS 實現(xiàn)的鎖

AQS(AbstractQueuedSynchronizer),是 Java 并發(fā)包中非常重要的一個類,大部分鎖的實現(xiàn)也是基于 AQS 實現(xiàn)的,包括:

  • ReentrantLock,可重入鎖。這個是我們最開始介紹的鎖,也是最常用的鎖。通常會與 synchronized 做比較使用。
  • ReentrantReadWriteLock,讀寫鎖。讀鎖是共享鎖、寫鎖是獨占鎖。
  • Semaphore,信號量鎖。主要用于控制流量,比如:數(shù)據(jù)庫連接池給你分配10個鏈接,那么讓你來一個連一個,連到10個還沒有人釋放,那你就等等。
  • CountDownLatch,閉鎖。Latch 門閂的意思,比如:說四個人一個漂流艇,坐滿了就推下水。

這一章節(jié)我們主要來介紹 Semaphore ,信號量鎖的實現(xiàn),其實也就是介紹一個關(guān)于共享鎖的使用和源碼分析。

2. Semaphore 共享鎖使用

Semaphore semaphore = new Semaphore(2, false); // 構(gòu)造函數(shù)入?yún)?,permits:信號量、fair:公平鎖/非公平鎖
for (int i = 0; i < 8; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "蹲坑");
            Thread.sleep(1000L);
        } catch (InterruptedException ignore) {
        } finally {
            semaphore.release();
        }
    }, "蹲坑編號:" + i).start();
}
 

這里我們模擬了一個在高速服務(wù)區(qū),廁所排隊蹲坑的場景。由于坑位有限,為了避免造成擁擠和踩踏,保安人員在門口攔著,感覺差不多,一次釋放兩個進去,一直到都釋放。你也可以想成早上坐地鐵上班,或者旺季去公園,都是一批一批的放行

「測試結(jié)果」

蹲坑編號:0蹲坑
蹲坑編號:1蹲坑

蹲坑編號:2蹲坑
蹲坑編號:3蹲坑

蹲坑編號:4蹲坑
蹲坑編號:5蹲坑

蹲坑編號:6蹲坑
蹲坑編號:7蹲坑

Process finished with exit code 0
 
  • Semaphore 的構(gòu)造函數(shù)可以傳遞是公平鎖還是非公平鎖,最終的測試結(jié)果也不同,可以自行嘗試。
  • 測試運行時,會先輸出     0坑、1坑,     之后2坑、3坑...,每次都是這樣兩個,兩個的釋放。這就是 Semaphore 信號量鎖的作用。 

3. Semaphore 源碼分析

3.1 構(gòu)造函數(shù)
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
 

permits:n. 許可證,特許證(尤指限期的)

默認(rèn)情況下只需要傳入 permits 許可證數(shù)量即可,也就是一次允許放行幾個線程。構(gòu)造函數(shù)會創(chuàng)建非公平鎖。如果你需要使用 Semaphore 共享鎖中的公平鎖,那么可以傳入第二個構(gòu)造函數(shù)的參數(shù) fair = false/true。true:FairSync,公平鎖。在我們前面的章節(jié)已經(jīng)介紹了公平鎖相關(guān)內(nèi)容和實現(xiàn),以及CLH、MCS 《公平鎖介紹》

「初始許可證數(shù)量」

FairSync/NonfairSync(int permits) {
    super(permits);
}

Sync(int permits) {
    setState(permits);
}

protected final void setState(int newState) {
    state = newState;
}
 

在構(gòu)造函數(shù)初始化的時候,無論是公平鎖還是非公平鎖,都會設(shè)置 AQS 中 state 數(shù)量值。這個值也就是為了下文中可以獲取的信號量扣減和增加的值。 

3.2 acquire 獲取信號量
方法描述
semaphore.acquire()一次獲取一個信號量,響應(yīng)中斷
semaphore.acquire(2)一次獲取n個信號量,響應(yīng)中斷(一次占2個坑)
semaphore.acquireUninterruptibly()一次獲取一個信號量,不響應(yīng)中斷
semaphore.acquireUninterruptibly(2)一次獲取n個信號量,不響應(yīng)中斷
  • 其實獲取信號量的這四個方法,主要就是,一次獲取幾個和是否響應(yīng)中斷的組合。
  • semaphore.acquire(),源碼中實際調(diào)用的方法是,     sync.acquireSharedInterruptibly(1)。也就是相應(yīng)中斷,一次只占一個坑。
  • semaphore.acquire(2),同理這個就是一次要占兩個名額,也就是許可證。     生活中的場景就是我給我朋友排的對,她來了,進來吧。 
3.3 acquire 釋放信號量
方法描述
semaphore.release()一次釋放一個信號量
semaphore.release(2)一次獲取n個信號量

有獲取就得有釋放,獲取了幾個信號量就要釋放幾個信號量。當(dāng)然你可以嘗試一下,獲取信號量 semaphore.acquire(2) 兩個,釋放信號量 semaphore.release(1),看看運行效果 

3.4 公平鎖實現(xiàn)

「信號量獲取過程」,一直到公平鎖實現(xiàn)。semaphore.acquire -> sync.acquireSharedInterruptibly(permits) -> tryAcquireShared(arg)

semaphore.acquire(1);

public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

「FairSync.tryAcquireShared」

protected int tryAcquireShared(int acquires) {
    for (;;) {
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
 
  • hasQueuedPredecessors,公平鎖的主要實現(xiàn)邏輯都在于這個方法的使用。它的目的就是判斷有線程排在自己前面沒,以及把線程添加到隊列中的邏輯實現(xiàn)。     在前面我們介紹過CLH等實現(xiàn),可以往前一章節(jié)閱讀
  • for (;;),是一個自旋的過程,通過 CAS 來設(shè)置 state 偏移量對應(yīng)值。這樣就可以避免多線程下競爭獲取信號量沖突。
  • getState(),在構(gòu)造函數(shù)中已經(jīng)初始化 state 值,在這里獲取信號量時就是使用 CAS 不斷的扣減。
  • 另外需要注意,共享鎖和獨占鎖在這里是有區(qū)別的,獨占鎖直接返回true/false,共享鎖返回的是int值。
    • 如果該值小于0,則當(dāng)前線程獲取共享鎖失敗。
    • 如果該值大于0,則當(dāng)前線程獲取共享鎖成功,并且接下來其他線程嘗試獲取共享鎖的行為很可能成功。
    • 如果該值等于0,則當(dāng)前線程獲取共享鎖成功,但是接下來其他線程嘗試獲取共享鎖的行為會失敗。
3.5 非公平鎖實現(xiàn)

「NonfairSync.nonfairTryAcquireShared」

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
 
  • 有了公平鎖的實現(xiàn),非公平鎖的理解就比較簡單了,只是拿去了     if (hasQueuedPredecessors()) 的判斷操作。
  • 其他的邏輯實現(xiàn)都和公平鎖一致。
 
3.6 獲取信號量失敗,加入同步等待隊列

在公平鎖和非公平鎖的實現(xiàn)中,我們已經(jīng)看到正常獲取信號量的邏輯。那么如果此時不能正常獲取信號量呢?其實這部分線程就需要加入到同步隊列。

「doAcquireSharedInterruptibly」

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
 
  • 首先     doAcquireSharedInterruptibly 方法來自 AQS 的內(nèi)部方法,與我們在學(xué)習(xí)競爭鎖時有部分知識點相同,但也有一些差異。比如:     addWaiter(Node.SHARED),     tryAcquireShared,我們主要介紹下這內(nèi)容。
  • Node.SHARED,其實沒有特殊含義,它只是一個標(biāo)記作用,用于判斷是否共享。     final boolean isShared() { return nextWaiter == SHARED; }
  • tryAcquireShared,主要是來自     Semaphore 共享鎖中公平鎖和非公平鎖的實現(xiàn)。用來獲取同步狀態(tài)。
  • setHeadAndPropagate(node, r),如果r > 0,同步成功后則將當(dāng)前線程結(jié)點設(shè)置為頭結(jié)點,同時 helpGC,p.next = null,斷鏈操作。
  • shouldParkAfterFailedAcquire(p, node),調(diào)整同步隊列中 node 結(jié)點的狀態(tài),并判斷是否應(yīng)該被掛起。這在我們之前關(guān)于鎖的文章中已經(jīng)介紹。
  • parkAndCheckInterrupt(),判斷是否需要被中斷,如果中斷直接拋出異常,當(dāng)前結(jié)點請求也就結(jié)束。
  • cancelAcquire(node),取消該節(jié)點的線程請求。

4. CountDownLatch 共享鎖使用

CountDownLatch 也是共享鎖的一種類型,之所以在這里體現(xiàn)下,是因為它和 Semaphore 共享鎖,既相似有不同。

CountDownLatch 更多體現(xiàn)的組團一波的思想,同樣是控制人數(shù),但是需要夠一窩。比如:我們說過的4個人一起上皮劃艇、兩個人一起上蹺蹺板、2個人一起蹲坑我沒見過,這樣的方式就是門閂 CountDownLatch 鎖的思想。

public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(10);
    ExecutorService exec = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 10; i++) {
        exec.execute(() -> {
            try {
                int millis = new Random().nextInt(10000);
                System.out.println("等待游客上船,耗時:" + millis + "(millis)");
                Thread.sleep(millis);
            } catch (Exception ignore) {
            } finally {
                latch.countDown(); // 完事一個扣減一個名額
            }
        });
    }
    // 等待游客
    latch.await();
    System.out.println("船長急躁了,開船!");
    // 關(guān)閉線程池
    exec.shutdown();
}
 
  • 這一個公園游船的場景案例,等待10個乘客上傳,他們比較墨跡。
  • 上一個扣減一個     latch.countDown()
  • 等待游客都上船     latch.await()
  • 最后船長開船?。?     急躁了

「測試結(jié)果」

等待游客上船,耗時:6689(millis)
等待游客上船,耗時:2303(millis)
等待游客上船,耗時:8208(millis)
等待游客上船,耗時:435(millis)
等待游客上船,耗時:9489(millis)
等待游客上船,耗時:4937(millis)
等待游客上船,耗時:2771(millis)
等待游客上船,耗時:4823(millis)
等待游客上船,耗時:1989(millis)
等待游客上船,耗時:8506(millis)
船長急躁了,開船!

Process finished with exit code 0
 
  • 在你實際的測試中會發(fā)現(xiàn),     船長急躁了,開船!,會需要等待一段時間。
  • 這里體現(xiàn)的就是門閂的思想,組隊、一波帶走。
  • CountDownLatch 的實現(xiàn)與 Semaphore  基本相同、細(xì)節(jié)略有差異,就不再做源碼分析了。
 

感謝各位的閱讀,以上就是“如何使用AQS共享鎖,Semaphore、CountDownLatch”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對如何使用AQS共享鎖,Semaphore、CountDownLatch這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

本文標(biāo)題:如何使用AQS共享鎖,Semaphore、CountDownLatch
當(dāng)前URL:http://muchs.cn/article16/isjhdg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供云服務(wù)器、軟件開發(fā)外貿(mào)建站、、電子商務(wù)、品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化