亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java?synchronized與死鎖深入探究

 更新時間:2023年01月30日 09:44:47   作者:Node_Hao  
這篇文章主要介紹了Java?synchronized與死鎖,Java中提供了synchronized關鍵字,將可能引發(fā)安全問題的代碼包裹在synchronized代碼塊中,表示這些代碼需要進行線程同步

1.synchronized的特性

1). 互斥性

當某個線程執(zhí)行到 synchronized 所修飾的對象時 , 該線程對象會加鎖(lock) , 其他線程如果執(zhí)行到同一個對象的 synchronized 就會產(chǎn)生阻塞等待.

  • 進入 synchronized 修飾的代碼塊 , 相當于加鎖.
  • 退出 synchronized 修飾著代碼塊 , 相當于解鎖.

synchronized 使用的鎖存儲在Java對象里 , 可以理解為每個對象在內(nèi)存中存儲時 , 都有一塊內(nèi)存表示當前的鎖定狀態(tài).類似于公廁的"有人" , "無人".

如果是"無人"狀態(tài) , 此時就可以使用 , 使用時需設置為"有人"狀態(tài).

如果是"有人"狀態(tài) , 此時就需要排隊等待.

如果理解阻塞等待?

針對每一把鎖 , 操作系統(tǒng)都會維護一個等待隊列 , 當一個線程獲取到這個鎖之后 , 其他線性再嘗試獲取這個鎖 , 就會獲取不到鎖 , 陷入阻塞等待. 一直等到之前這個線程釋放鎖后 , 操作系統(tǒng)才會喚醒其他線程來再次競爭這個鎖.

2)可重入

synchronized 對同一個線程來說是可重入的 , 不會出現(xiàn)把自己鎖死的情況.

如何理解把自己鎖死?

觀察下面這段代碼可以發(fā)現(xiàn) , 當某個線程調(diào)用add方法時 , 就會對 this 對象先加鎖 , 接著進入代碼塊又會對 this 對象再次嘗試加鎖. 站在 this 對象的角度 , 它認為自己已經(jīng)被另外的線程占用了 , 那么第二次加鎖是否需要阻塞等待呢? 如果運行上述情況 , 那么這個鎖就是可重入的 , 否則就是不可重入的.不可重入鎖會導致出現(xiàn)死鎖 , 而Java中的 synchronized 是可重入鎖 , 因此沒有上述問題.

synchronized public void add(){
        synchronized (this) {
            count++;
        }
    }

在可重入鎖內(nèi)部 , 包含了"線程持有者"和"計數(shù)器"兩個信息.

  • 如果每個線程加鎖時 , 發(fā)現(xiàn)鎖以及被占用了 , 但加鎖的人是它自己 , 那么仍然可以獲取到鎖 , 讓計數(shù)器自增.
  • 解鎖的時候當計數(shù)器遞減到0時 , 才真正釋放鎖.

2.synchronized使用示例:

1). 修飾普通方法

鎖的是 Counter 對象.

class Counter{
    public int count;
    synchronized public void add(){
            count++;
        }
}

2). 修飾靜態(tài)方法

鎖的是 Counter 類

class Counter{
    public int count;
    synchronized public static void add(){
            count++;
        }
}

3).修飾代碼塊.明確指定鎖哪個對象

鎖當前對象:

class Counter{
    public int count;
    public void add(){
        synchronized (this) {
            count++;
        }
    }
}

鎖類對象:

class Counter{
    public int count;
    public void add(){
        synchronized (Counter.class) {
            count++;
        }
    }
}

類鎖和對象鎖有什么區(qū)別?

顧名思義 , 對象鎖用來鎖住當前對象 , 類鎖用來鎖住當前類.如果一個類有多個實例對象 , 那么如果對其中一個對象加鎖 , 別的線程只會在訪問這個對象時阻塞等待 , 訪問其他對象時沒有影響.但如果是類鎖 , 那么當一個線程對這個類加鎖后 , 其他線程訪問該類的所有對象都要阻塞等待.

3.Java標準庫中的線程安全類

Java 標準庫中有很多線程是不安全的 , 這些類可能涉及多線程修改共享數(shù)據(jù) , 卻又沒有任何加鎖措施.

  • ArrayList
  • LinkedList
  • HashMap
  • HashSet
  • TreeSet
  • StringBuilder

但還有一些是線程安全的 , 使用一些鎖機制來控制.

  • Vector
  • HashTable
  • CurrentHashMap
  • StringBuffer
@Override
    @IntrinsicCandidate
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

這些線程之所以不加鎖是因為 , 加鎖會損失部分性能.

4.死鎖是什么

死鎖是這樣一種情況 , 多個線程同時被阻塞 , 其中一個或全部都在等待某個資源被釋放.由于線程被無限期的阻塞 , 因此程序不可能正常終止.

死鎖的三個典型情況

1). 一個線程一把鎖 , 連續(xù)加兩次 , 如果鎖是不可重入鎖 , 就會死鎖. Java中的synchronized和ReentranLock 都是可重入鎖 , 因此不會出現(xiàn)上述問題.

2). 兩個線程兩把鎖 , t1 和 t2 線程各種先針對鎖A和鎖B加鎖 , 再嘗試獲取對方的鎖.

例如 , 張三和女神去吃餃子 , 需要蘸醋和醬油 , 張三拿到醋 , 女神拿到醬油 , 張三對女神說:"你先把醬油給我 , 我用完就把醋給你" , 女神對張三說:"你先把醋給我 , 我用完就把醬油給你". 這時兩人爭執(zhí)不下 , 就構(gòu)成了死鎖 , 醋和醬油就是兩把鎖 , 張三和女生就是兩個線程.

public static void main(String[] args) {
        Object jiangyou = new Object();
        Object cu  = new Object();
        Thread zhangsan = new Thread(()->{
            synchronized (jiangyou){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (cu ){
                    System.out.println("張三把醬油和醋都拿到了");
                }
            }
        });
        Thread nvsheng = new Thread(()->{
            synchronized (cu ){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou){
                    System.out.println("女神把醬油和醋都拿到了");
                }
            }
        });
        zhangsan.start();
        nvsheng.start();
    }

執(zhí)行代碼后 , 發(fā)現(xiàn)沒有打印任何日志 , 說明沒有線程拿到兩把鎖.

通過jconsole查看線程的情況:

3)多個線程多把鎖

例如常見經(jīng)典案例--"哲學家就餐問題"

假設有五個哲學家圍著桌子吃飯 , 每個人中間放一個筷子 , 哲學家有兩種狀態(tài) , 1.思考人生(相當于線程的阻塞狀態(tài)) , 2.拿起筷子吃面條(相當于線程獲取到鎖執(zhí)行計算) , 由于操作系統(tǒng)的隨機調(diào)度 , 這五個哲學家隨時都可能想吃面條 , 也隨時都可能思考人生 , 但是想要吃面條就得同時拿起左右兩個筷子.

假設同一時刻 , 所有哲學家同時拿起左手的筷子 , 所有的哲學家都拿不起右手的筷子 , 就會產(chǎn)生死鎖.

死鎖是一個嚴重的"BUG" , 導致一個程序的線程"卡死"無法正常工作.

5.如果避免死鎖

死鎖的四個必要條件:

1.互斥使用: 當資源被一個線程占有時 , 別的線程不能使用

2.不可搶占: 資源請求者不能從資源獲取者手中奪取資源 , 只能等資源占有者主動釋放.

3.請求和保持: 當資源請求者請求獲取別的資源時 , 保存對原有資源的占有.

4.循環(huán)等待: 即存在一個等待隊列 , P1占有P2的資源 , P2占有P3的資源 , P3占有P1的資 源, 這樣就形成一個等待回路.

當上述四個條件都成立就會形成死鎖 , 當然破壞其中一個條件也可以打破死鎖 , 對于synchronized 來說 , 前三個條件是鎖的基本特性 , 因此想要打破死鎖只能從"循環(huán)等待"入手.

如何破除死鎖?

如果我們給鎖編號 , 然后指定一個固定的順序來加鎖(必然從小到大) , 任意線程加多把鎖的時候都遵循上述順序, 此時循環(huán)等待自然破除.

因此解決哲學家就餐問題就可以給每個筷子編號 , 每個人都遵守"先拿小的再拿大的順序".此時1號哲學家和2號哲學家為了競爭筷子其中一個人就會阻塞等待 , 這時5號哲學家就有了可乘之機 , 5號哲學家拿起4號和5號筷子吃完面條 , 四號哲學家重復上述操作也吃完面條 , 這樣就完美的打破了循環(huán)等待的問題.

同樣 , 最初的張三和女神吃餃子問題也是同樣的解決方式 , 規(guī)定兩人都按"先拿醋再拿餃子"的順序執(zhí)行 , 就可以完美解決死鎖問題.

public static void main(String[] args) {
        Object jiangyou = new Object();
        Object cu  = new Object();
        Thread zhangsan = new Thread(()->{
            synchronized (cu){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou ){
                    System.out.println("張三把醬油和醋都拿到了");
                }
            }
        });
        Thread nvsheng = new Thread(()->{
            synchronized (cu ){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou){
                    System.out.println("女神把醬油和醋都拿到了");
                }
            }
        });
        zhangsan.start();
        nvsheng.start();
    }

到此這篇關于Java synchronized與死鎖深入探究的文章就介紹到這了,更多相關Java synchronized 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論