線程之活躍度失敗(死鎖、活鎖、饑餓)

線程活躍度

活躍度問(wèn)題是指線程或進(jìn)程長(zhǎng)時(shí)間得不到cpu占用。《Java并發(fā)編程實(shí)戰(zhàn)》中提到,無(wú)論執(zhí)行計(jì)算密集操作還是執(zhí)行某個(gè)可能阻塞的操作,如果持有鎖的時(shí)間過(guò)長(zhǎng),都會(huì)帶來(lái)活躍性或性能問(wèn)題。

活躍度失敗有那幾種

  • 死鎖也就是互相等著對(duì)方釋放資源,結(jié)果誰(shuí)也得不到。

  • 活鎖可能發(fā)生讓某一個(gè)線程一直處于等待狀態(tài),其他線程都可以調(diào)用到。

  • 饑餓我就感覺(jué)用搶占式說(shuō)好說(shuō),每次來(lái)就執(zhí)行優(yōu)先級(jí)高的,那么優(yōu)先級(jí)低的可能永遠(yuǎn)執(zhí)行不到。

死鎖

多線程以及多進(jìn)程改善了系統(tǒng)資源的利用率并提高了系統(tǒng) 的處理能力。然而,并發(fā)執(zhí)行也帶來(lái)了新的問(wèn)題——死鎖。死鎖是指多個(gè)線程因競(jìng)爭(zhēng)資源而造成的一種僵局(互相等待),若無(wú)外力作用,這些進(jìn)程都將無(wú)法向前推進(jìn)。

死鎖產(chǎn)生的原因

  • 系統(tǒng)資源的競(jìng)爭(zhēng)

    通常系統(tǒng)中擁有的不可剝奪資源,其數(shù)量不足以滿足多個(gè)進(jìn)程運(yùn)行的需要,使得進(jìn)程在 運(yùn)行過(guò)程中,會(huì)因爭(zhēng)奪資源而陷入僵局,如磁帶機(jī)、打印機(jī)等。只有對(duì)不可剝奪資源的競(jìng)爭(zhēng) 才可能產(chǎn)生死鎖,對(duì)可剝奪資源的競(jìng)爭(zhēng)是不會(huì)引起死鎖的。

  • 進(jìn)程推進(jìn)順序非法

    進(jìn)程在運(yùn)行過(guò)程中,請(qǐng)求和釋放資源的順序不當(dāng),也同樣會(huì)導(dǎo)致死鎖。例如,并發(fā)進(jìn)程 P1、P2分別保持了資源R1、R2,而進(jìn)程P1申請(qǐng)資源R2,進(jìn)程P2申請(qǐng)資源R1時(shí),兩者都 會(huì)因?yàn)樗栀Y源被占用而阻塞。

    信號(hào)量使用不當(dāng)也會(huì)造成死鎖。進(jìn)程間彼此相互等待對(duì)方發(fā)來(lái)的消息,結(jié)果也會(huì)使得這 些進(jìn)程間無(wú)法繼續(xù)向前推進(jìn)。例如,進(jìn)程A等待進(jìn)程B發(fā)的消息,進(jìn)程B又在等待進(jìn)程A 發(fā)的消息,可以看出進(jìn)程A和B不是因?yàn)楦?jìng)爭(zhēng)同一資源,而是在等待對(duì)方的資源導(dǎo)致死鎖。

死鎖產(chǎn)生的必要四個(gè)條件

產(chǎn)生死鎖必須同時(shí)滿足以下四個(gè)條件,只要其中任一條件不成立,死鎖就不會(huì)發(fā)生。

  • 互斥條件:進(jìn)程要求對(duì)所分配的資源(如打印機(jī))進(jìn)行排他性控制,即在一段時(shí)間內(nèi)某 資源僅為一個(gè)進(jìn)程所占有。此時(shí)若有其他進(jìn)程請(qǐng)求該資源,則請(qǐng)求進(jìn)程只能等待。

  • 不剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能 由獲得該資源的進(jìn)程自己來(lái)釋放(只能是主動(dòng)釋放)。

  • 請(qǐng)求和保持條件:進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源 已被其他進(jìn)程占有,此時(shí)請(qǐng)求進(jìn)程被阻塞,但對(duì)自己已獲得的資源保持不放。

  • 循環(huán)等待條件:存在一種進(jìn)程資源的循環(huán)等待鏈,鏈中每一個(gè)進(jìn)程已獲得的資源同時(shí)被 鏈中下一個(gè)進(jìn)程所請(qǐng)求。即存在一個(gè)處于等待狀態(tài)的進(jìn)程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的資源被P0占有。

寫(xiě)一個(gè)死鎖的例子

/**
 * @Author 安仔夏天勤奮
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread1 implements Runnable {
    @Override
    public void run() {
        ////模擬線程1占用資源1并申請(qǐng)獲得資源2的鎖
        try {
            System.out.println("LockThread1 is running");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待線程2鎖定資源2
                synchronized (ThreadResource.resource2){
                    System.out.println("LockThread1 lock resource2");
                }
                System.out.println("LockThread1 release resource2");
            }
            System.out.println("LockThread1 release resource1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread1 is stop");
    }
}

/**
 * @Author 安仔夏天勤奮
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread2 implements Runnable {
    @Override
    public void run() {
        //模擬線程2占用資源2并申請(qǐng)獲得資源1的鎖:
        try {
            System.out.println("LockThread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待線程1鎖定資源1
                synchronized (ThreadResource.resource1) {
                    System.out.println("LockThread2 lock resource1");
                }
                System.out.println("LockThread2 release resource1");
            }
            System.out.println("LockThread2 release resource2");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread2 is stop");
    }
}

/**
 * @Author 安仔夏天勤奮
 * Create Date is  2019/4/29
 * Des
 */
public class ThreadResource {
    public static Object resource1 = new Object();
    public static Object resource2 = new Object();
}

/**
 * @Author 安仔夏天勤奮
 * Create Date is  2019/4/29
 * Des
 */
public class DeadLock{
    public static void main(String[] args) {
        new Thread(new LockThread1()).start();
        new Thread(new LockThread2()).start();
    }
}

運(yùn)行結(jié)果

Thread1 is running
Thread2 is running
Thread1 lock resource1
Thread2 lock resource2
?
并且程序一直無(wú)法結(jié)束。這就是由于線程1占用了資源1,此時(shí)線程2已經(jīng)占用資源2,這個(gè)時(shí)候線程1想要使用資源2,線程2想要使用資源1。兩個(gè)線程都無(wú)法讓步,導(dǎo)致程序死鎖。

如何避免死鎖

死鎖是可以避免的。用于避免死鎖的技術(shù)三種方式:

  • 加鎖順序(線程按照一定的順序加鎖)

  • 加鎖時(shí)限(線程嘗試獲取鎖的時(shí)候加上一定的時(shí)限,超過(guò)時(shí)限則放棄對(duì)該鎖的請(qǐng)求,并釋放自己占有的鎖)

  • 死鎖檢測(cè)

由上面的例子可以看出當(dāng)線程在同步某個(gè)對(duì)象里,再去鎖定另外一個(gè)對(duì)象的話,就和容易發(fā)生死鎖的情況。最好是線程每次只鎖定一個(gè)對(duì)象并且在鎖定該對(duì)象的過(guò)程中不再去鎖定其他的對(duì)象,這樣就不會(huì)導(dǎo)致死鎖了。比如將以上的線程改成下面這種寫(xiě)法就可以避免死鎖

/**
 * @Author 安仔夏天勤奮
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread1 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread1 is running");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待線程2鎖定資源2
            }
            System.out.println("LockThread1 release resource1");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread1 lock resource2");
            }
            System.out.println("LockThread1 release resource2");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread1 is stop");
}

/**
 * @Author 安仔夏天勤奮
 * Create Date is  2019/4/29
 * Des
 */
public class LockThread2 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("LockThread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待線程1鎖定資源1
            }
            System.out.println("LockThread2 release resource2");
            synchronized (ThreadResource.resource1) {
                System.out.println("LockThread2 lock resource1");
            }
            System.out.println("LockThread2 release resource1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread2 is stop");
    }
}

運(yùn)行結(jié)果

LockThread1 is running
LockThread1 lock resource1
LockThread2 is running
LockThread2 lock resource2
LockThread1 release resource1
LockThread1 lock resource2
LockThread1 release resource2
LockThread1 is stop
LockThread2 release resource2
LockThread2 lock resource1
LockThread2 release resource1
LockThread2 is stop

如果需要同時(shí)去鎖定兩個(gè)對(duì)象,可以根據(jù)加鎖順序定義一個(gè)先后的規(guī)則。按照上面的例子,需要同時(shí)鎖定兩個(gè)資源,可以根據(jù)資源的hashcode值大小來(lái)判斷先后鎖定順序。代碼如下:

public class LockThread3 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("LockThread3 is running");
            if ( ThreadResource.resource1.hashCode() > ThreadResource.resource2.hashCode() ) {
                //先鎖定resource1
                synchronized (ThreadResource.resource1) {
                    System.out.println("LockThread3 lock resource1");
                    Thread.sleep(2000);
                    synchronized (ThreadResource.resource2) {
                        System.out.println("LockThread3 lock resource2");
                    }
                    System.out.println("LockThread3 release resource2");
                }
                System.out.println("LockThread3 release resource1");
            }else {
                //先鎖定resource2
                synchronized (ThreadResource.resource2) {
                    System.out.println("LockThread3 lock resource2");
                    Thread.sleep(2000);
                    synchronized (ThreadResource.resource1) {
                        System.out.println("LockThread3 lock resource1");
                    }
                    System.out.println("LockThread3 release resource1");
                }
                System.out.println("LockThread3 release resource2");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("LockThread3 is stop");
    }
}

死鎖更詳細(xì)可參照

活鎖

活鎖是指線程1可以使用資源,但它很禮貌,讓其他線程先使用資源,線程2也可以使用資源,但它很紳士,也讓其他線程先使用資源。這樣你讓我,我讓你,最后兩個(gè)線程都無(wú)法使用資源。

活鎖不會(huì)被阻塞,而是不停檢測(cè)一個(gè)永遠(yuǎn)不可能為真的條件。除去進(jìn)程本身持有的資源外,活鎖狀態(tài)的進(jìn)程會(huì)持續(xù)耗費(fèi)寶貴的CPU時(shí)間。

舉個(gè)例子,兩個(gè)人在走廊上碰見(jiàn),大家都互相很有禮貌,互相禮讓,A從左到右,B也從從左轉(zhuǎn)向右,發(fā)現(xiàn)又擋住了地方,繼續(xù)轉(zhuǎn)換方向,但又碰到了,反反復(fù)復(fù),一直沒(méi)有機(jī)會(huì)運(yùn)行下去。

活鎖例子

活鎖的解決方法

  • 調(diào)整重試機(jī)制。

  • 引入一些隨機(jī)性。

活鎖和死鎖的區(qū)別

  • 活鎖的實(shí)體是在不斷的改變狀態(tài),所謂的“活”, 而處于死鎖的實(shí)體表現(xiàn)為等待。

  • 活鎖有可能自行解開(kāi),死鎖則不能。

饑餓

饑餓是指如果線程T1占用了資源R,線程T2又請(qǐng)求封鎖R,于是T2等待。T3也請(qǐng)求資源R,當(dāng)T1釋放了R上的封鎖后,系統(tǒng)首先批準(zhǔn)了T3的請(qǐng)求,T2仍然等待。然后T4又請(qǐng)求封鎖R,當(dāng)T3釋放了R上的封鎖之后,系統(tǒng)又批準(zhǔn)了T4的請(qǐng)求......,T2可能永遠(yuǎn)等待。

線程長(zhǎng)時(shí)間無(wú)法獲得共享資源從而繼續(xù)相繼的處理。這種情況經(jīng)常發(fā)生在當(dāng)共享資源被“貪婪”線程長(zhǎng)時(shí)間占據(jù)時(shí)。假設(shè)一個(gè)對(duì)象提供的互斥方法需要很長(zhǎng)時(shí)間處理才能返回,然而如果某線程老是頻繁激活這個(gè)方法,那么其他需要訪問(wèn)該對(duì)象的線程就會(huì)被長(zhǎng)時(shí)間阻塞,而處于饑餓狀態(tài)。

饑餓.png

Java中的讀寫(xiě)鎖的實(shí)現(xiàn)類ReentranctReadWriteLock,在默認(rèn)使用非公平模式(不是先來(lái)先處理的模式)的情況下,如果某個(gè)線程想要讀取資源,只要沒(méi)有線程正在對(duì)該資源進(jìn)行寫(xiě)操作且沒(méi)有線程請(qǐng)求對(duì)該資源的寫(xiě)操作即可。如果讀操作發(fā)生的比較頻繁,我們又沒(méi)有提升寫(xiě)操作的優(yōu)先級(jí),那么就會(huì)產(chǎn)生“饑餓”現(xiàn)象。請(qǐng)求寫(xiě)操作的線程會(huì)一直阻塞,直到所有的讀線程都從ReentranctReadWriteLock上解鎖了。如果一直保證新線程的讀操作權(quán)限,那么等待寫(xiě)操作的線程就會(huì)一直阻塞下去,結(jié)果就發(fā)生了“饑餓”。

java代碼會(huì)引起這種類型的饑餓

synchronized(obj) {
 while (true) {
 // .... infinite loop
 }
}

優(yōu)先級(jí)引起也會(huì)引起線程饑餓

高優(yōu)先級(jí)線程吞噬所有的低優(yōu)先級(jí)線程的CPU時(shí)間。例如在java中調(diào)用了Thread.setPriority方法設(shè)置了線程優(yōu)先級(jí),優(yōu)先級(jí)低的線程始終得不到執(zhí)行的機(jī)會(huì),雖然線程優(yōu)先級(jí)對(duì)于不同操作系統(tǒng)的實(shí)現(xiàn)方式不一樣,即便設(shè)置了優(yōu)先級(jí)也不一定會(huì)有效果,但還是有可能會(huì)出現(xiàn)這種情況。

饑餓的解決辦法有

  • 提升寫(xiě)請(qǐng)求的優(yōu)先級(jí)或者采用公平策略。

  • 在synchronized方法或者塊中避免無(wú)限循環(huán)。

  • 采用線程默認(rèn)的優(yōu)先級(jí)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來(lái)是分開(kāi)三篇的,后來(lái)想想還是整...
    coder_pig閱讀 1,772評(píng)論 2 17
  • 1、競(jìng)態(tài)條件: 定義:競(jìng)態(tài)條件指的是一種特殊的情況,在這種情況下各個(gè)執(zhí)行單元以一種沒(méi)有邏輯的順序執(zhí)行動(dòng)作,從而導(dǎo)致...
    Hughman閱讀 1,439評(píng)論 0 7
  • 1.解決信號(hào)量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 1,015評(píng)論 0 1
  • 又來(lái)到了一個(gè)老生常談的問(wèn)題,應(yīng)用層軟件開(kāi)發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問(wèn)題開(kāi)始,來(lái)談?wù)劜?..
    tangsl閱讀 4,322評(píng)論 0 23
  • ## 研究是一種能力 在我讀研究生的時(shí)候,才有了第一次真正接觸到“研究能力”這個(gè)概念。和同學(xué)們相比,連最初到問(wèn)題如...
    都市牛閱讀 287評(píng)論 0 0

友情鏈接更多精彩內(nèi)容