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

          多線程編程之可重入鎖

          共 3615字,需瀏覽 8分鐘

           ·

          2021-11-08 21:50

          在目前web的開(kāi)發(fā)大環(huán)境下,高并發(fā),高可用的應(yīng)用場(chǎng)景越來(lái)越普遍,對(duì)我們的要求也越來(lái)越要求越高了,為了應(yīng)對(duì)這樣超高的要求(比如多線程環(huán)境下的數(shù)據(jù)共享問(wèn)題),我們必須掌握很多常用的技術(shù)方案,比如鎖(Lock)(就是在某個(gè)方法或資源上加鎖,確保同一時(shí)間段內(nèi)只有我們可以訪問(wèn)該資源),這樣才能寫(xiě)出更可靠的應(yīng)用程序,今天我們就一起來(lái)看下一個(gè)很常用的鎖——可重入鎖(ReentrantLock)。

          在開(kāi)始今天的內(nèi)容之前,我們先考慮這樣一個(gè)場(chǎng)景:我們有一個(gè)審核業(yè)務(wù),同一級(jí)的審核人員有兩個(gè),但是業(yè)務(wù)只能審核一次,不能重復(fù)審核。

          如上圖,如果整個(gè)審核方法不加鎖的情況下,很可能發(fā)生同一筆數(shù)據(jù)審核兩次的情況。因?yàn)閷徍诉^(guò)程會(huì)涉及多個(gè)步驟,假如第一個(gè)人員在查詢未審核數(shù)據(jù)后,進(jìn)行業(yè)務(wù)審核(處在第三步),但是尚未提交審核結(jié)果,這時(shí)候第二個(gè)人進(jìn)來(lái),也是查了未審核數(shù)據(jù)(第二步),由于第一個(gè)人員未提交審核結(jié)果,這時(shí)候數(shù)據(jù)依然是未審核,然后第二個(gè)人開(kāi)始審核,這時(shí)候第一個(gè)人提交了審核結(jié)果,然后緊接著第二個(gè)人提交審核結(jié)果。最后,審核結(jié)果就會(huì)變成兩條。

          接下來(lái),我們講的內(nèi)容,就是為了解決這樣的額應(yīng)用場(chǎng)景。

          一個(gè)不加鎖的案例

          在開(kāi)始可重入鎖的介紹之前,我們先看一個(gè)和上面類(lèi)似的例子,算是簡(jiǎn)化版:

          public?class?Example?{
          ????private?static?int?i;
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????ThreadPoolExecutor?executor?=?new?ThreadPoolExecutor(5,?10,?1,?TimeUnit.MICROSECONDS,?new?ArrayBlockingQueue(100));
          ????????for?(int?j?=?0;?j?1000;?j++)?{
          ????????????Thread.sleep(10L);
          ????????????final?int?finalJ?=?j;
          ????????????executor.submit(()?->?test(finalJ));
          ????????}
          ????????executor.shutdown();
          ????}

          ????public?static?void?test(int?j)?{
          ????????System.out.println("==第"?+?j?+?"次調(diào)用==start");
          ????????i?++;
          ????????Thread.sleep(20L);
          ????????i?++;
          ????????System.out.println(i);
          ????????System.out.println("==第"?+?j?+?"次調(diào)用==end");
          ????}

          }

          上面這段代碼其實(shí)就是模擬多線程共享數(shù)據(jù)(就是這里的i),并對(duì)數(shù)據(jù)進(jìn)行操作的一個(gè)示例,運(yùn)行結(jié)果可以很直觀的說(shuō)明,不加鎖的情況下,在一個(gè)線程未執(zhí)行完方法之前,另一個(gè)方法也會(huì)進(jìn)入方法執(zhí)行。按照我們代碼的邏輯,應(yīng)該是先打印start,然后打印i的值,然后再打印end,但是實(shí)際情況卻并發(fā)如此,往往可能是這樣的:

          上面的運(yùn)行結(jié)果很直觀的說(shuō)明,在第1995次未正常運(yùn)行結(jié)束時(shí),第1996次已經(jīng)開(kāi)始了,同樣在第1996次未運(yùn)行完的時(shí)候,第1998次都開(kāi)始了。而且不論你運(yùn)行多少次,上面的結(jié)果都大同小異。

          這時(shí)候,如果我們將代碼調(diào)整一下,加上鎖,看下會(huì)發(fā)生什么:

          public?class?Example?{
          ????//?可重入鎖
          ????private?static?final?ReentrantLock?mainLock?=?new?ReentrantLock();
          ????private?static?int?i;
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????ThreadPoolExecutor?executor?=?new?ThreadPoolExecutor(5,?10,?1,?TimeUnit.MICROSECONDS,?new?ArrayBlockingQueue(100));
          ????????for?(int?j?=?0;?j?1000;?j++)?{
          ????????????Thread.sleep(10L);
          ????????????final?int?finalJ?=?j;
          ????????????executor.submit(()?->?testLock(finalJ));
          ????????}
          ????????executor.shutdown();
          ????}

          ????public?static?void?testLock(int?j)?{
          ????????final?ReentrantLock?reentrantLock?=?mainLock;
          ????????//?如果被其它線程占用鎖,會(huì)阻塞在此等待鎖釋放
          ????????reentrantLock.lock();
          ????????try?{
          ????????????System.out.println("==第"?+?j?+?"次調(diào)用==start");
          ????????????i?++;
          ????????????Thread.sleep(20L);
          ????????????i?++;
          ????????????System.out.println(i);
          ????????????System.out.println("==第"?+?j?+?"次調(diào)用==end");
          ????????}?catch?(Exception?e)?{
          ????????????e.printStackTrace();
          ????????}?finally?{
          ????????????//?執(zhí)行完之后必須釋放鎖
          ????????????reentrantLock.unlock();
          ????????}
          ????}

          }

          然后我們運(yùn)行一下:

          這時(shí)候,你會(huì)發(fā)現(xiàn),無(wú)論你運(yùn)行多少次,都是像上面這樣規(guī)整,也和我們的代碼邏輯是一致的,這其實(shí)就是加鎖的作用,目的就是為了控制資源的訪問(wèn)秩序。

          當(dāng)然,上面的代碼其實(shí)還是存在問(wèn)題的,因?yàn)樵谘h(huán)中使用線程池本身就是不合理的,當(dāng)單個(gè)線程執(zhí)行時(shí)間較長(zhǎng),for中啟動(dòng)前程前的業(yè)務(wù)響應(yīng)比較快的時(shí)候(就是這里的Thread.sleep(10L);),所有的壓力都會(huì)到線程池上,會(huì)把線程池的資源耗盡,然后報(bào)如下錯(cuò)誤:

          這時(shí)候解決方法有兩個(gè),一個(gè)就是人為增加線程啟動(dòng)前的業(yè)務(wù)處理時(shí)間,這里就是增加睡眠時(shí)間,比如調(diào)整到Thread.sleep(20L);;另一個(gè)是提高線程中的業(yè)務(wù)處理效率,只要比前面的業(yè)務(wù)處理快就行,但是在實(shí)際業(yè)務(wù)中,這個(gè)是不可能的;最好的解決方法是重構(gòu)業(yè)務(wù)邏輯,想辦法把for循環(huán)放進(jìn)線程里面,我之前修復(fù)的異步線程問(wèn)題就用的是這個(gè)方法。好了,下面開(kāi)始理論方面的學(xué)習(xí)。

          什么是可重入鎖

          可重入鎖,顧名思義就是可以重復(fù)加鎖的一種鎖,它是指,線程可對(duì)同一把鎖進(jìn)行重復(fù)加鎖,而不會(huì)被阻塞住,這樣可避免死鎖的產(chǎn)生。

          加鎖的方式

          它的加鎖方式有三種,分別是locktrylocktrylock(long,TimeUnit)。上面我們加鎖的方法只是其中一種,也是最簡(jiǎn)單的。

          可以看到ReentrantLock的使用方式比較簡(jiǎn)單,創(chuàng)建出一個(gè)ReentrantLock對(duì)象,通過(guò)lock()方法進(jìn)行加鎖,使用unlock()方法進(jìn)行釋放鎖操作。

          使用lock來(lái)獲取鎖的話,如果鎖被其他線程持有,那么就會(huì)處于等待狀態(tài)。同時(shí),需要我們?nèi)ブ鲃?dòng)的調(diào)用``unlock`方法去釋放鎖,即使發(fā)生異常,它也不會(huì)主動(dòng)釋放鎖,需要我們顯式的釋放。

          使用trylock方法獲取鎖,是有返回值的,獲取成功返回true,獲取失敗返回false,不會(huì)一直處于等待狀態(tài)。

          使用trylock(long,TimeUnit)指定時(shí)間參數(shù)來(lái)獲取鎖,在等待時(shí)間內(nèi)獲取到鎖返回true,超時(shí)返回false。還可以調(diào)用lockInterruptibly方法去中斷鎖,如果線程正在等待獲取鎖,可以中斷線程的等待狀態(tài)。

          總結(jié)

          關(guān)于鎖這一塊,其實(shí)內(nèi)容比較多,涉及的知識(shí)也比較雜,不僅包括javasynchronized、原子類(lèi)、鎖等這些線程安全的知識(shí),還包括數(shù)據(jù)的行級(jí)鎖、表級(jí)鎖等內(nèi)容,如果是分布式應(yīng)用,還需要考慮分布式鎖的實(shí)現(xiàn),這里面還涉及了redis的知識(shí),想要完全掌握還是難度很大的,但是隨著我們一點(diǎn)點(diǎn)的學(xué)習(xí)和應(yīng)用,你慢慢會(huì)掌握很多常用的技術(shù)和解決方案,你會(huì)更清楚各種鎖和技術(shù)的應(yīng)用場(chǎng)景,你會(huì)涉及出更優(yōu)秀的高并發(fā)高可用的系統(tǒng),為了實(shí)現(xiàn)這個(gè)目標(biāo),讓我們一起學(xué)習(xí),一起遇見(jiàn)更好的自己,加油吧!

          項(xiàng)目路徑:

          https://github.com/Syske/example-everyday

          本項(xiàng)目會(huì)每日更新,讓我們一起學(xué)習(xí),一起進(jìn)步,遇見(jiàn)更好的自己,加油呀

          - END -


          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  久久人妖TS三区系列电影 | 蜜桃成人中文字幕 | 国产精品嫩草影院久久久 | 天天操天天摸天天日不卡 | 亚洲视频在线播出 |