最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JDK源碼之線程并發(fā)協(xié)調(diào)神器CountDownLatch和CyclicBarrier詳解

 更新時間:2022年02月23日 15:15:44   作者:慕楓技術(shù)筆記  
我一直認為程序是對于現(xiàn)實世界的邏輯描述,而在現(xiàn)實世界中很多事情都需要各方協(xié)調(diào)合作才能完成,就好比完成一個平臺的交付不可能只靠一個人,而需要研發(fā)、測試、產(chǎn)品以及項目經(jīng)理等不同角色人員進行通力合作才能完成最終的交付

引言

那么在程序的世界中是如何對這種協(xié)調(diào)關(guān)系進行描述的呢?今天就和大家聊聊Java大神Doug Lea在并發(fā)包中如何通過CountDownLatch和CyclicBarrier實現(xiàn)任務(wù)協(xié)調(diào)的代碼描述。

CountDownLatch

我相信大家都知道好代碼的一個重要特性就是代碼中類、變量等的命名可以做到顧名思義,也就是說看到命名就可以大概知道這個類或者變量表達了怎樣的業(yè)務(wù)語義。就拿 CountDownLatch 來說,它的命名形象的表示了其能力屬性,Count代表著計數(shù),Down代表著計數(shù)器的遞減操作,而Latch表示計數(shù)器遞減后的結(jié)果動作。CountDownLatch結(jié)合起來的字面意思就是計數(shù)器遞減后打開門栓,通過后面內(nèi)容的描述,回過頭來看大家肯定會覺得這個命名十分之形象。

好了通過它的類的名稱,我們猜測了它的功能是通過計數(shù)器的遞減操作來控制線程,那我們再看看官方描述是不是這個意思。

/**
* A synchronization aid that allows one or more threads to wait until
* a set of operations being performed in other threads completes.
*
* <p>A {@code CountDownLatch} is initialized with a given <em>count</em>.
* The {@link #await await} methods block until the current count reaches
* zero due to invocations of the {@link #countDown} method, after which
* all waiting threads are released and any subsequent invocations of
* {@link #await await} return immediately. This is a one-shot phenomenon
* -- the count cannot be reset. If you need a version that resets the
* count, consider using a {@link CyclicBarrier}.
*...
*/

上面注釋的大致意思就是CountDownLatch是一個線程同步器,它允許一個或者多個線程阻塞等待直到其他線程中業(yè)務(wù)執(zhí)行完成。CountDownLatch可以通過一個計數(shù)器進行初始化,他可以讓那個等待的線程被阻塞,直到對應(yīng)的計數(shù)器被置為0。當計數(shù)器置為0后,阻塞的線程被釋放。另外它是一個一次性使用的同步器,計數(shù)器無法被重置。

通過JDK的官方描述我們可以明確CountDownLatch三個核心特征:

1、它是一種線程同步器,用以協(xié)調(diào)線程的執(zhí)行觸發(fā)時機;

2、它本質(zhì)是一個計數(shù)器,是控制線程的號令槍;

3、它是一次性使用的,用完即失效。

知道了CountDownLatch是一個什么東東之后,我們再一起來看下它的使用場景是什么,我們在什么樣的情況下可以使用它幫我們解決一些代碼中的問題。

使用場景

就像上文描述的,CountDownLatch就像是田徑賽場上裁判員發(fā)射的發(fā)令槍,所有參賽的選手準備就緒后,發(fā)令槍一響,所有運動員聞聲而動。那么在Java多線程場景中,CountDownLatch就是線程協(xié)調(diào)者,它的計數(shù)器在沒有減為0之前。假設(shè)有這樣一個業(yè)務(wù)場景,在一個監(jiān)控告警平臺中,需要從告警服務(wù)中查詢告警信息以及從工單服務(wù)中查詢工單信息,然后再分析哪些告警沒有轉(zhuǎn)工單。按照老系統(tǒng)的做法,參見如下簡化后的偽代碼:

List<Alarm> alarmList = alarmService.getAlarm();
List<WorkOrder> workOrderList = workOrderService.getWorkOrder();
List<Alarm> notTransferToWorkOrder = analysis(alarmList, workOrderList);

大家能看出來這段偽代碼有什么需要進行優(yōu)化的地方嗎?我們來一起分析一下。這段代碼在數(shù)據(jù)量不大的時候可能沒什么影響,但是一旦告警以及工單的數(shù)據(jù)量大的時候,獲取告警信息或者獲取工單信息都可能出現(xiàn)數(shù)據(jù)查詢慢的問題,那就會導致這個分析任務(wù)就會出現(xiàn)性能瓶頸的問題。那么我們應(yīng)該怎么進行優(yōu)化呢?從業(yè)務(wù)以及代碼我們可以看的出來,獲取告警信息以及獲取工單信息,實際上并沒有業(yè)務(wù)上面的耦合性,在上述代碼中他們是順序執(zhí)行的,因此要進行性能優(yōu)化,可以考慮將它們進行并行執(zhí)行。

那么修改優(yōu)化后的偽代碼如下所示:

Executor executor = Executors.newFixedThreadPool(2);
executor.execute(()-> { alarmList = alarmService.getAlarm(); });
executor.execute(()-> { workOrderList = workOrderService.getWorkOrder(); });
 
List<Alarm> notTransferToWorkOrder = analysis(alarmList, workOrderList);

我們通過使用線程池的方式,在獲取告警信息以及工單信息的時候并發(fā)執(zhí)行,不再像之前的執(zhí)行完獲取告警信息再執(zhí)行獲取工單信息,這樣效率更高。但是這樣的實現(xiàn)方式還是存在問題,由于在線的線程中執(zhí)行操作,并不知道其實際的執(zhí)行結(jié)果,這就不好判斷執(zhí)行數(shù)據(jù)分析的具體時機。這個時候CountDownLatch就派上用場了,利用它可以實現(xiàn)線程揀的等待,條件滿足后再放開執(zhí)行后續(xù)的邏輯。這就好比公司組織團建,約定好了早上8點半在公司大門集合,那么司機師傅肯定要等到所有參加團建的同時都到齊后才會發(fā)車。

使用CountDownLatch之后的偽代碼如下所示:

Executor executor = Executors.newFixedThreadPool(2);
CountDownLatch latch = new CountDownLatch(2);
executor.execute(()-> { alarmList = alarmService.getAlarm();
                      latch.countDown();
                      });
executor.execute(()-> { workOrderList = workOrderService.getWorkOrder(); 
                      latch.countDown();
                      });
latch.await();
List<Alarm> notTransferToWorkOrder = analysis(alarmList, workOrderList);

底層實現(xiàn)原理

初始化

在使用CountDownLatch之前我們得先進行初始化,在初始化的過程中實際做了兩件事情,一個是創(chuàng)建了一個AQS的同步隊列,另外一個是將AQS中的state設(shè)置成了count,這個state是AQS的核心變量(AQS是并發(fā)包的底層實現(xiàn)基礎(chǔ),關(guān)于它的分析我們放到下一篇文章中進行)。

從代碼中我們可以看的出來實際創(chuàng)建了Sync內(nèi)部類實例,而Sync繼承了AQS,同時重寫了AQS加鎖解鎖的方法,并通過Sync的對象,調(diào)用AQS的方法,阻塞線程的運行。Sync內(nèi)部類的代碼如下所示,其中tryAcquireShared方法重寫了AQS的模板方法,主要用來獲取共享鎖,在CountDownLatch內(nèi)部主要通過判斷獲取到的state的值是否為0來決定到底有沒有獲取到鎖。如果獲取到的state為0,則表示獲取鎖成功,此時線程不會阻塞,反之則獲取鎖失敗,線程會阻塞。

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
 
        Sync(int count) {
            setState(count);
        }
 
        int getCount() {
            return getState();
        }
		//嘗試加共享鎖(通過state判斷)
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
		//嘗試釋放共享鎖(通過state判斷)
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

計數(shù)器遞減

如上文場景中介紹的代碼,每個線程在執(zhí)行完成自身業(yè)務(wù)后執(zhí)行countDown操作,表示該線程已經(jīng)準備完成。同時檢查count值是否為0。如果為0則需要喚醒所有等待的線程。如下代碼所示,實際上它調(diào)用的是父類AQS的releaseShared方法。

public void countDown() {
        sync.releaseShared(1);
    }

tryReleaseShared這個方法實際是進行嘗試釋放鎖的操作,如果此次count遞減為0,然后釋放所有的線程。

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

大致的代碼執(zhí)行邏輯可參見下圖:

阻塞線程

await的作用就是將當前線程阻塞住,直到count值減為0才會放開執(zhí)行。它實際調(diào)用了內(nèi)部類的tryAcquireSharedNanos方法,這個方法實際是Sync類的父類AQS中的方法。

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

AQS提供了可以響應(yīng)中斷的獲取公平鎖的實現(xiàn)的方式。tryAcquireShared在上文已經(jīng)進行了介紹,該方法的作用是嘗試獲取共享鎖,如果獲取失敗,則線程將會被加入到AQS的同步隊列中進行等待,也就是所謂的線程阻塞。

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

CyclicBarrier

我們還是從CyclicBarrier的字面意思來先進行理解,Cyclic是循環(huán)的意思而Barrier則表示柵欄、障礙的意思,字面的意思就是可循環(huán)的柵欄。還是老套路,在進行CyclicBarrier之前,我們先來看下JDK是怎么描述的。

/**
* A synchronization aid that allows a set of threads to all wait for
* each other to reach a common barrier point. CyclicBarriers are
* useful in programs involving a fixed sized party of threads that
* must occasionally wait for each other. The barrier is called
* <em>cyclic</em> because it can be re-used after the waiting threads
* are released.
*
* <p>A {@code CyclicBarrier} supports an optional {@link Runnable} command
* that is run once per barrier point, after the last thread in the party
* arrives, but before any threads are released.
* This <em>barrier action</em> is useful
* for updating shared-state before any of the parties continue.
*...
**/

通過JDK的描述,我們可以看得出來,CyclicBarrier也是一個線程同步協(xié)調(diào)器,用以協(xié)調(diào)一組進程的執(zhí)行。當指定個數(shù)的線程到達柵欄后,可以放開柵欄,結(jié)束線程阻塞狀態(tài)。這么看上去它和CountDownLatch作用差不多了,實際上還是有區(qū)別的,CyclicBarrier是可循環(huán)使用的,而CountDownLatch卻是一次性的。我們來看下CyclicBarrier的核心屬性。

//柵欄入口的鎖
private final ReentrantLock lock = new ReentrantLock();
//線程等待條件
private final Condition trip = lock.newCondition();
//攔截的線程數(shù)量
private final int parties;
//在下一個柵欄代數(shù)到來前執(zhí)行的任務(wù)
private final Runnable barrierCommand;
//當前的柵欄代數(shù)
private Generation generation = new Generation();

CyclicBarrier 的源碼實現(xiàn)和 CountDownLatch 大同小異,CountDownLatch 基于 AQS 的共享模式的使用,而 CyclicBarrier 基于 Condition 來實現(xiàn)的。

CyclicBarrier內(nèi)部維護了parties和count變量,parties表示每次參與到一個Generation中需要被攔截的線程數(shù)量,而count是內(nèi)部計數(shù)器,在初始化的時候count與parties相等,當每次調(diào)用await方法的時候計數(shù)器count就會減1,這和上文中的countDown類似。

使用場景

還是以上文中的業(yè)務(wù)場景為例我們再分析一下,上文中我們通過CountDownLatch實現(xiàn)了查詢告警信息與查詢工單信息的線程協(xié)調(diào)問題,但是新的問題又出現(xiàn)了。因為告警信息和工單信息都是實時在產(chǎn)生的,而使用CountDownLatch的實現(xiàn)方式只能完成一次的線程協(xié)調(diào),后續(xù)產(chǎn)生的告警信息以及工單信息如果還有需要查詢到之后再進行數(shù)據(jù)分析的話,它就愛莫能助了。也就是說,如果需要進行持續(xù)的線程之間的互相等待完成之后再執(zhí)行后續(xù)的業(yè)務(wù)操作的話,這個時候就需要使用CyclicBarrier 來實現(xiàn)我們的需求了。

底層實現(xiàn)原理

初始化

CyclicBarrier 存在兩種的構(gòu)造函數(shù),一種是構(gòu)建CyclicBarrier 的時候指定每次需要進行協(xié)調(diào)的線程個數(shù)以及解除阻塞之后需要進行后續(xù)任務(wù)的執(zhí)行,另一種只是設(shè)置需要協(xié)調(diào)的線程個數(shù)不設(shè)置后續(xù)執(zhí)行的任務(wù)。

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
 
 public CyclicBarrier(int parties) {
        this(parties, null);
    }

阻塞等待

對于CyclicBarrier 來說,其最核心的等待方法實現(xiàn)就是dowait方法,具體代碼如下所示:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;
 
            if (g.broken)
                throw new BrokenBarrierException();
 
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
 
            int index = --count;
            //如果count計算為0,則需要喚醒所有線程并進入到下一階段的線程協(xié)調(diào)期
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
 
            //計數(shù)器不為0,繼續(xù)進行循環(huán)
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }
 
                if (g.broken)
                    throw new BrokenBarrierException();
 
                if (g != generation)
                    return index;
 
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

我們可以看到在dowait方法中進行了count的遞減操作,檢查count的值是否為0,如果在初始化的時候定義好了要執(zhí)行的任務(wù),那么在count為0的時候就進行任務(wù)執(zhí)行,任務(wù)執(zhí)行完成之后調(diào)用nextGeneration進行下一次的線程協(xié)調(diào)周期,同時喚醒所有線程并重置計數(shù)器。

總結(jié)

本文分別從使用場景以及底層實現(xiàn)的角度分別介紹了線程同步協(xié)調(diào)神器CountDownLatch和CyclicBarrier,雖然它們都可以起到協(xié)調(diào)線程的作用但是實際上它們還是有區(qū)別的。CountDownLatch比較適合一個線程與其他多個線程之間的同步協(xié)調(diào)場景,而CyclicBarrier則適合一組線程之間的互相等待。另外CountDownLatch是一次性產(chǎn)品,而CyclicBarrier的計數(shù)器是可以重復使用的,可以進行自動重置計數(shù)器。

到此這篇關(guān)于JDK源碼之線程并發(fā)協(xié)調(diào)神器CountDownLatch和CyclicBarrier詳解的文章就介紹到這了,更多相關(guān)Java 線程并發(fā)協(xié)調(diào)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實現(xiàn)拓撲排序的示例代碼

    Java實現(xiàn)拓撲排序的示例代碼

    這篇文章我們要講的是拓撲排序,這是一個針對有向無環(huán)圖的算法,主要是為了解決前驅(qū)后繼的關(guān)系,感興趣的小伙伴可以跟隨小編一起學習一下
    2022-05-05
  • java高并發(fā)之線程的基本操作詳解

    java高并發(fā)之線程的基本操作詳解

    本文主要介紹java高并發(fā)線程的基本操作,這里整理詳細的資料來解釋線程的知識,有需要的學習高并發(fā)的朋友可以參考下
    2021-10-10
  • 使用spring通過aop獲取方法參數(shù)和參數(shù)值

    使用spring通過aop獲取方法參數(shù)和參數(shù)值

    這篇文章主要介紹了使用spring通過aop獲取方法參數(shù)和參數(shù)值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • SpringMvc返回modelandview返回的頁面無法跳轉(zhuǎn)問題及解決

    SpringMvc返回modelandview返回的頁面無法跳轉(zhuǎn)問題及解決

    這篇文章主要介紹了SpringMvc返回modelandview返回的頁面無法跳轉(zhuǎn)問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Java連接Linux服務(wù)器過程分析(附代碼)

    Java連接Linux服務(wù)器過程分析(附代碼)

    這篇文章主要介紹了Java連接Linux服務(wù)器過程分析(附代碼),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • 淺談JAVA版本號的問題 Java版本號與JDk版本

    淺談JAVA版本號的問題 Java版本號與JDk版本

    這篇文章主要介紹了淺談JAVA版本號的問題 Java版本號與JDk版本,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • MyBatis之insert主鍵自增和自定義主鍵詳解

    MyBatis之insert主鍵自增和自定義主鍵詳解

    本文介紹了如何使用MyBatis解決插入數(shù)據(jù)時因主鍵唯一性約束導致的錯誤問題,以及如何自定義主鍵生成規(guī)則,文中詳細解釋了如何在MyBatis中配置自增主鍵,并提供了測試示例
    2024-12-12
  • SpringFox實現(xiàn)自動生成RESTful?API文檔

    SpringFox實現(xiàn)自動生成RESTful?API文檔

    在開發(fā)?RESTful?API?時,編寫?API?文檔是一個重要的任務(wù),這篇文章為大家介紹了如何使用?SpringFox?自動生成?RESTful?API?文檔,并提供示例代碼,需要的可以參考一下
    2023-06-06
  • 一次詭異的full gc查找問題全過程

    一次詭異的full gc查找問題全過程

    這篇文章主要給大家分享介紹了一次詭異的full gc查找問題全部過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧啊
    2018-11-11
  • IDEA代碼熱部署和熱加載的三種實現(xiàn)方案

    IDEA代碼熱部署和熱加載的三種實現(xiàn)方案

    在日常開發(fā)中,我們需要經(jīng)常修改 Java 代碼,手動重啟項目,查看修改后的效果,如果在項目小時,重啟速度比較快,等待的時間是較短的,我們可以使用代碼熱加載和熱部署解決該問題,本文給大家介紹了三種實現(xiàn)方案,需要的朋友可以參考下
    2023-11-11

最新評論

尚志市| 新野县| 贵州省| 法库县| 原平市| 山丹县| 新干县| 瑞丽市| 宝兴县| 呼玛县| 双流县| 葫芦岛市| 富裕县| 永顺县| 新源县| 固安县| 綦江县| 咸阳市| 右玉县| 巴彦县| 内黄县| 临湘市| 揭西县| 荆门市| 竹溪县| 应城市| 德化县| 湛江市| 林州市| 福清市| 青冈县| 清河县| 子洲县| 沐川县| 莱芜市| 昆山市| 襄汾县| 瓮安县| 南召县| 定州市| 册亨县|