欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?synchronized與死鎖深入探究

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

1.synchronized的特性

1). 互斥性

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

  • 進(jìn)入 synchronized 修飾的代碼塊 , 相當(dāng)于加鎖.
  • 退出 synchronized 修飾著代碼塊 , 相當(dāng)于解鎖.

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

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

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

如果理解阻塞等待?

針對(duì)每一把鎖 , 操作系統(tǒng)都會(huì)維護(hù)一個(gè)等待隊(duì)列 , 當(dāng)一個(gè)線程獲取到這個(gè)鎖之后 , 其他線性再嘗試獲取這個(gè)鎖 , 就會(huì)獲取不到鎖 , 陷入阻塞等待. 一直等到之前這個(gè)線程釋放鎖后 , 操作系統(tǒng)才會(huì)喚醒其他線程來(lái)再次競(jìng)爭(zhēng)這個(gè)鎖.

2)可重入

synchronized 對(duì)同一個(gè)線程來(lái)說(shuō)是可重入的 , 不會(huì)出現(xiàn)把自己鎖死的情況.

如何理解把自己鎖死?

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

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

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

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

2.synchronized使用示例:

1). 修飾普通方法

鎖的是 Counter 對(duì)象.

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).修飾代碼塊.明確指定鎖哪個(gè)對(duì)象

鎖當(dāng)前對(duì)象:

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

鎖類對(duì)象:

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

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

顧名思義 , 對(duì)象鎖用來(lái)鎖住當(dāng)前對(duì)象 , 類鎖用來(lái)鎖住當(dāng)前類.如果一個(gè)類有多個(gè)實(shí)例對(duì)象 , 那么如果對(duì)其中一個(gè)對(duì)象加鎖 , 別的線程只會(huì)在訪問(wèn)這個(gè)對(duì)象時(shí)阻塞等待 , 訪問(wèn)其他對(duì)象時(shí)沒(méi)有影響.但如果是類鎖 , 那么當(dāng)一個(gè)線程對(duì)這個(gè)類加鎖后 , 其他線程訪問(wèn)該類的所有對(duì)象都要阻塞等待.

3.Java標(biāo)準(zhǔn)庫(kù)中的線程安全類

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

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

但還有一些是線程安全的 , 使用一些鎖機(jī)制來(lái)控制.

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

這些線程之所以不加鎖是因?yàn)?, 加鎖會(huì)損失部分性能.

4.死鎖是什么

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

死鎖的三個(gè)典型情況

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

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

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

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)沒(méi)有打印任何日志 , 說(shuō)明沒(méi)有線程拿到兩把鎖.

通過(guò)jconsole查看線程的情況:

3)多個(gè)線程多把鎖

例如常見(jiàn)經(jīng)典案例--"哲學(xué)家就餐問(wèn)題"

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

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

死鎖是一個(gè)嚴(yán)重的"BUG" , 導(dǎo)致一個(gè)程序的線程"卡死"無(wú)法正常工作.

5.如果避免死鎖

死鎖的四個(gè)必要條件:

1.互斥使用: 當(dāng)資源被一個(gè)線程占有時(shí) , 別的線程不能使用

2.不可搶占: 資源請(qǐng)求者不能從資源獲取者手中奪取資源 , 只能等資源占有者主動(dòng)釋放.

3.請(qǐng)求和保持: 當(dāng)資源請(qǐng)求者請(qǐng)求獲取別的資源時(shí) , 保存對(duì)原有資源的占有.

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

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

如何破除死鎖?

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

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

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

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();
    }

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

相關(guān)文章

最新評(píng)論