<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java必會之并發(fā)輔助類和讀寫鎖

          共 3306字,需瀏覽 7分鐘

           ·

          2021-07-14 02:24

          CountDownLatch
          這是什么?先從一段代碼開始:
          public static void main(String[] args) {
                 for (int i = 0; i < 10; i++) {
                     int finalI = i+1;
                     new Thread(()->{
                         System.out.println("線程:"+ finalI +"開始執(zhí)行");
                    }).start();
                }
            }
          你說這段代碼中,這個10個線程是按照順序執(zhí)行的嗎?看運行結(jié)果:
          OK,看到結(jié)果之后你就需要明白如下道理:
          ??在主線程中,for循環(huán)會創(chuàng)建并啟動10個線程,但是需要注意的是,并不是線程創(chuàng)建且啟動之后就會立馬執(zhí)行線程中的任務(wù)然后再去創(chuàng)建啟動下一個線程,而是所有的線程創(chuàng)建啟動之后會加入一個線程規(guī)劃器中由操作系統(tǒng)去決定哪個線程先執(zhí)行任務(wù),這個分配是隨機的,也就是線程執(zhí)行是亂序的,不是順序的!
          然后我們再看:
          這里的主線程任務(wù)可不是一定在線程執(zhí)行完之后才會執(zhí)行:
          那如果我們有這樣的一個場景,就是需要所有的線程任務(wù)執(zhí)行完畢之后才能去執(zhí)行主線程任務(wù),那這個該怎么辦呢?

          這就需要用到我們的CountDownLatch,怎么用呢?很簡單,看代碼:
          然后我們看下代碼:
          達(dá)到我們想要的結(jié)果了,是不是很簡單?

          Cyclicbarrier

          對于CountDownLatch來說,大概就是只有計數(shù)為0才會觸發(fā)某個時間,也就是一個倒計時的效果,而這個Cyclicbarrier恰好是和CountDownLatch相反的,它是需要從0開始計數(shù),達(dá)到某個值才去觸發(fā)某個時間,通常叫做回環(huán)柵欄,看下面的例子就能秒懂:
          看下運行結(jié)果:
          這個還是很好理解的!

          semaphore

          咋一看,這個看起來很陌生,是什么呢?一般叫做信號量,通過一個示例來快速了解!
          這個例子沒什么難度吧,我們開啟5個線程來模擬小孩玩手機的現(xiàn)象,我們運行看下:
          這說明5個小孩都搶到手機,都有手機玩,那這里的前提就是,最少得有5個手機,不然5個人不夠搶啊,但是現(xiàn)在加入我就只有兩個手機咋辦?

          是不是就得看他們自己了,誰搶到誰玩,因為只有2個手機,但是有5個人,那么肯定是2個人搶到了在玩,而另外3個人要等待,一旦其中的一個人玩手機時間到放下手機后,其余的三個人又會去搶,直到最后,每個人都玩手機了!

          那面對這樣的一個情景,想一下我們該怎么做?5個人去搶2個手機,一旦2個手機被搶到,其余的人只能等待,說的專業(yè)點,這樣是不是并發(fā)線程數(shù)被控制了,這里控制了只有2個線程可以同時去執(zhí)行,其他的必須等待這兩個線程執(zhí)行完畢才可以執(zhí)行,那看看該怎么做:
          這里大家可以類比小孩子玩手機的舉例說明!

          這里需要注意的是,我們是通過semaphore來控制線程并發(fā)數(shù),其中使用semaphore.acquire();來記錄進(jìn)來的線程,執(zhí)行一次相當(dāng)于加1,一旦達(dá)到設(shè)定的線程并發(fā)數(shù)就會禁止其他線程進(jìn)入,任務(wù)執(zhí)行完畢使用semaphore.release();來減1,相當(dāng)于釋放資源,其他線程可以進(jìn)入執(zhí)行!

          所以啊,任務(wù)執(zhí)行邏輯是放在他們兩者之間的:
          ReadWriteLock
          這是啥,有了Lock為什么還要有個ReadWriteLock?我們看下之前Lock加鎖的形式:
          我們在寫資源的操作上加上了ReentrantLock,這個時候可以避免多個線程對該資源進(jìn)行寫操作,同一時間只允許一個線程來進(jìn)行寫操作,但是此時有什么問題呢?

          看我們的讀資源操作,此時是可以有多個線程進(jìn)行讀操作的,這其實也不是我們想要的,為什么?

          因為如果此時有一個線程正在進(jìn)行寫操作,我是不希望其他線程進(jìn)行讀操作的,因為這個線程很可能讀到舊數(shù)據(jù),由于多線程執(zhí)行是亂序的,所以很有可能數(shù)據(jù)還沒有真正寫入就被另一個線程讀取了,雖然我們的map使用了volatile關(guān)鍵字,不過volatile也就是保證線程緩存中的資源實時同步到共享資源,但是如果我這個線程就沒有開始真正的寫,那線程本地緩存中就沒有最新的數(shù)據(jù),那你其他線程讀取的自然不是最新的數(shù)據(jù)了!

          你可能想到了給讀操作也加上鎖,像這樣:
          但是加上鎖之后還是允許一個線程來進(jìn)行讀取資源,問題并沒有解決啊,而且有的時候我還需要在沒有線程進(jìn)行寫操作的時候,可以有多個線程進(jìn)行讀操作來增加效率,顯然,上述做法是不滿足要求的!

          這個時候就需要我們的ReadWriteLock了,表面意思理解是讀寫鎖,它主要可以保證數(shù)據(jù)的一致性,有如下特性:
          1. 支持讀-讀共存

          2. 不支持讀-寫共存

          3. 更不支持寫-寫共存

          很明顯的一點,只要有寫操作存在,就不允許其他線程進(jìn)行讀或者寫!

          我們先來看下之前資源類被多線程執(zhí)行的情況,先看不加鎖的情況,同時對代碼稍作修改:
          然后分別創(chuàng)建5個線程進(jìn)行寫操作,5個線程進(jìn)行讀操作:
          然后我們運行程序看看:
          可以看到如此一來執(zhí)行的結(jié)果很亂,在線程1執(zhí)行寫操作的時候還沒有完成就沒線程3給打斷了,我們希望的是數(shù)據(jù)一致性得到保障,線程1的寫操作完全成功了才允許其他線程繼續(xù)寫,而且假如我們模擬線程寫數(shù)據(jù)的耗時操作:
          我們在運行看看:
          這就是因為,我數(shù)據(jù)還沒有真正的寫入成功就被其他線程讀取,那結(jié)果肯定有誤,怎們辦,我們看下加鎖的情況:
          先對寫操作加鎖,看下運行情況:
          這里一定要注意思考,不然很容易暈……好吧,為了大家更好的理解,我把程序改寫一下,你會理解的更加透徹,首先修改資源類:
          主要是這些改變,使得打印信息在加鎖情況下更加直觀,然后是我們的操作邏輯:
          增加線程名稱命名,然后我們再看程序運行結(jié)果:
          這樣看是不是就直觀了很多,同一個時間內(nèi)只能有一個線程進(jìn)行讀寫操作,而且在線程進(jìn)行寫操作的時候,其他線程是無法進(jìn)行讀寫操作的,但是這里也有個問題,就是如果我沒有寫操作在進(jìn)行的時候,我想進(jìn)行讀操作,但是因為加鎖,我只能一個線程進(jìn)行讀取,其實此時數(shù)據(jù)沒有被其他線程進(jìn)行寫操作,我完全可以多個線程進(jìn)行同時讀取,這樣效率更高,但是很顯然通過Lock的形式是無法滿足的!

          這個時候就到我們的ReadWriteLock閃亮登場了!

          這里需要再回顧下它的主要特性:
          1. 支持讀-讀共存

          2. 不支持讀-寫共存

          3. 更不支持寫-寫共存

          對于讀-寫和寫-寫我們發(fā)現(xiàn)使用Lock也是可以實現(xiàn)的,但是它無法完成讀-讀共存的情況,我們看看下:
          我們在讀操作中模擬耗時操作,然后我們只運行多線程執(zhí)行讀操作的演示,結(jié)果如下:
          實際運行就會發(fā)現(xiàn),會花費5秒來執(zhí)行完程序,因為每個線程執(zhí)行讀操作需要1秒,因為加鎖是單線程執(zhí)行,所以需要五秒,我們看下使用ReadWriteLock的情況:
          這里一定要注意ReadWriteLock的用法,同Lock一樣,它是個接口,需要使用其子類ReentrantReadWriteLock,同時為了區(qū)分讀寫鎖,搞了一下形式:
          //寫鎖
          lock.writeLock()
          //讀鎖
          lock.readLock()
          然后我們看使用了ReentrantReadWriteLock之后的讀操作情況:
          可以看到,即使你加鎖了,但是對于讀操作,是允許并發(fā)執(zhí)行的,相當(dāng)于之前是需要5秒,使用ReentrantReadWriteLock之后只需要1秒,效率大大提高!

          所以對于ReentrantReadWriteLock來說記住其三個特性:

          1. 支持讀-讀共存

          2. 不支持讀-寫共存

          3. 更不支持寫-寫共存


          那么,你學(xué)會了嗎?
          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人操操操操操人人人 | 国内自拍视频免费 | 国内综合在线 | 波多野结衣在线免费AV | 猛操 女神 |