1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        多線程編程之可重入鎖

        共 3615字,需瀏覽 8分鐘

         ·

        2021-11-08 21:50

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

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

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

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

        一個不加鎖的案例

        在開始可重入鎖的介紹之前,我們先看一個和上面類似的例子,算是簡化版:

        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ù)據(jù)(就是這里的i),并對數(shù)據(jù)進行操作的一個示例,運行結(jié)果可以很直觀的說明,不加鎖的情況下,在一個線程未執(zhí)行完方法之前,另一個方法也會進入方法執(zhí)行。按照我們代碼的邏輯,應(yīng)該是先打印start,然后打印i的值,然后再打印end,但是實際情況卻并發(fā)如此,往往可能是這樣的:

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

        這時候,如果我們將代碼調(diào)整一下,加上鎖,看下會發(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;
        ????????//?如果被其它線程占用鎖,會阻塞在此等待鎖釋放
        ????????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();
        ????????}
        ????}

        }

        然后我們運行一下:

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

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

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

        什么是可重入鎖

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

        加鎖的方式

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

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

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

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

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

        總結(jié)

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

        項目路徑:

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

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

        - END -


        瀏覽 60
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91av在线无码 | 99久久久无码国产精品性黑人 | 貂蝉胸被揉内裤被扒的小说 | 学生妹一级J人片内射视频 | 东方av在 | 久久五月丁香 | 欧美成人性视频老片在线播放 | 国产精品 色欲A片在线观看 | 波多野结衣av免费 | 看中国黄色免费操逼大片 |