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

Java 多線程死鎖的產(chǎn)生以及如何避免死鎖

 更新時(shí)間:2019年09月27日 10:32:27   作者:jayxu無捷之徑  
這篇文章主要介紹了Java 多線程死鎖的產(chǎn)生以及如何避免死鎖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、死鎖的定義

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

下面我們通過一些實(shí)例來說明死鎖現(xiàn)象。

先看生活中的一個實(shí)例,2個人一起吃飯但是只有一雙筷子,2人輪流吃(同時(shí)擁有2只筷子才能吃)。某一個時(shí)候,一個拿了左筷子,一人拿了右筷子,2個人都同時(shí)占用一個資源,等待另一個資源,這個時(shí)候甲在等待乙吃完并釋放它占有的筷子,同理,乙也在等待甲吃完并釋放它占有的筷子,這樣就陷入了一個死循環(huán),誰也無法繼續(xù)吃飯。。。
在計(jì)算機(jī)系統(tǒng)中也存在類似的情況。例如,某計(jì)算機(jī)系統(tǒng)中只有一臺打印機(jī)和一臺輸入 設(shè)備,進(jìn)程P1正占用輸入設(shè)備,同時(shí)又提出使用打印機(jī)的請求,但此時(shí)打印機(jī)正被進(jìn)程P2 所占用,而P2在未釋放打印機(jī)之前,又提出請求使用正被P1占用著的輸入設(shè)備。這樣兩個進(jìn)程相互無休止地等待下去,均無法繼續(xù)執(zhí)行,此時(shí)兩個進(jìn)程陷入死鎖狀態(tài)。

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

1) 系統(tǒng)資源的競爭

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

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

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

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

3) 死鎖產(chǎn)生的必要條件

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

  • 互斥條件:進(jìn)程要求對所分配的資源(如打印機(jī))進(jìn)行排他性控制,即在一段時(shí)間內(nèi)某 資源僅為一個進(jìn)程所占有。此時(shí)若有其他進(jìn)程請求該資源,則請求進(jìn)程只能等待。
  • 不剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能 由獲得該資源的進(jìn)程自己來釋放(只能是主動釋放)。
  • 請求和保持條件:進(jìn)程已經(jīng)保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進(jìn)程占有,此時(shí)請求進(jìn)程被阻塞,但對自己已獲得的資源保持不放。
  • 循環(huán)等待條件:存在一種進(jìn)程資源的循環(huán)等待鏈,鏈中每一個進(jìn)程已獲得的資源同時(shí)被 鏈中下一個進(jìn)程所請求。即存在一個處于等待狀態(tài)的進(jìn)程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的資源被P0占有,如圖2-15所示。

直觀上看,循環(huán)等待條件似乎和死鎖的定義一樣,其實(shí)不然。按死鎖定義構(gòu)成等待環(huán)所 要求的條件更嚴(yán),它要求Pi等待的資源必須由P(i+1)來滿足,而循環(huán)等待條件則無此限制。 例如,系統(tǒng)中有兩臺輸出設(shè)備,P0占有一臺,PK占有另一臺,且K不屬于集合{0, 1, ..., n}。

Pn等待一臺輸出設(shè)備,它可以從P0獲得,也可能從PK獲得。因此,雖然Pn、P0和其他 一些進(jìn)程形成了循環(huán)等待圈,但PK不在圈內(nèi),若PK釋放了輸出設(shè)備,則可打破循環(huán)等待, 如圖2-16所示。因此循環(huán)等待只是死鎖的必要條件。

 

資源分配圖含圈而系統(tǒng)又不一定有死鎖的原因是同類資源數(shù)大于1。但若系統(tǒng)中每類資 源都只有一個資源,則資源分配圖含圈就變成了系統(tǒng)出現(xiàn)死鎖的充分必要條件。

產(chǎn)生死鎖的一個例子

/** 
* 一個簡單的死鎖類 
* 當(dāng)DeadLock類的對象flag==1時(shí)(td1),先鎖定o1,睡眠500毫秒 
* 而td1在睡眠的時(shí)候另一個flag==0的對象(td2)線程啟動,先鎖定o2,睡眠500毫秒 
* td1睡眠結(jié)束后需要鎖定o2才能繼續(xù)執(zhí)行,而此時(shí)o2已被td2鎖定; 
* td2睡眠結(jié)束后需要鎖定o1才能繼續(xù)執(zhí)行,而此時(shí)o1已被td1鎖定; 
* td1、td2相互等待,都需要得到對方鎖定的資源才能繼續(xù)執(zhí)行,從而死鎖。 
*/ 
public class DeadLock implements Runnable { 
  public int flag = 1; 
  //靜態(tài)對象是類的所有對象共享的 
  private static Object o1 = new Object(), o2 = new Object(); 
  @Override 
  public void run() { 
    System.out.println("flag=" + flag); 
    if (flag == 1) { 
      synchronized (o1) { 
        try { 
          Thread.sleep(500); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
        synchronized (o2) { 
          System.out.println("1"); 
        } 
      } 
    } 
    if (flag == 0) { 
      synchronized (o2) { 
        try { 
          Thread.sleep(500); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
        synchronized (o1) { 
          System.out.println("0"); 
        } 
      } 
    } 
  } 
 
  public static void main(String[] args) { 
     
    DeadLock td1 = new DeadLock(); 
    DeadLock td2 = new DeadLock(); 
    td1.flag = 1; 
    td2.flag = 0; 
    //td1,td2都處于可執(zhí)行狀態(tài),但JVM線程調(diào)度先執(zhí)行哪個線程是不確定的。 
    //td2的run()可能在td1的run()之前運(yùn)行 
    new Thread(td1).start(); 
    new Thread(td2).start(); 
 
  } 
} 

三、如何避免死鎖

在有些情況下死鎖是可以避免的。三種用于避免死鎖的技術(shù):

  1. 加鎖順序(線程按照一定的順序加鎖)
  2. 加鎖時(shí)限(線程嘗試獲取鎖的時(shí)候加上一定的時(shí)限,超過時(shí)限則放棄對該鎖的請求,并釋放自己占有的鎖)
  3. 死鎖檢測

加鎖順序

當(dāng)多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發(fā)生。

如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會發(fā)生??聪旅孢@個例子:

Thread 1:
 lock A 
 lock B

Thread 2:
  wait for A
  lock C (when A locked)

Thread 3:
  wait for A
  wait for B
  wait for C

如果一個線程(比如線程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。

例如,線程2和線程3只有在獲取了鎖A之后才能嘗試獲取鎖C(譯者注:獲取鎖A是獲取鎖C的必要條件)。因?yàn)榫€程1已經(jīng)擁有了鎖A,所以線程2和3需要一直等到鎖A被釋放。然后在它們嘗試對B或C加鎖之前,必須成功地對A加了鎖。

按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。但是,這種方式需要你事先知道所有可能會用到的鎖(譯者注:并對這些鎖做適當(dāng)?shù)呐判?,但總有些時(shí)候是無法預(yù)知的。

加鎖時(shí)限

另外一個可以避免死鎖的方法是在嘗試獲取鎖的時(shí)候加一個超時(shí)時(shí)間,這也就意味著在嘗試獲取鎖的過程中若超過了這個時(shí)限該線程則放棄對該鎖請求。若一個線程沒有在給定的時(shí)限內(nèi)成功獲得所有需要的鎖,則會進(jìn)行回退并釋放所有已經(jīng)獲得的鎖,然后等待一段隨機(jī)的時(shí)間再重試。這段隨機(jī)的等待時(shí)間讓其它線程有機(jī)會嘗試獲取相同的這些鎖,并且讓該應(yīng)用在沒有獲得鎖的時(shí)候可以繼續(xù)運(yùn)行(譯者注:加鎖超時(shí)后可以先繼續(xù)運(yùn)行干點(diǎn)其它事情,再回頭來重復(fù)之前加鎖的邏輯)。

以下是一個例子,展示了兩個線程以不同的順序嘗試獲取相同的兩個鎖,在發(fā)生超時(shí)后回退并重試的場景:

Thread 1 locks A
Thread 2 locks B

Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked

Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.

Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.

在上面的例子中,線程2比線程1早200毫秒進(jìn)行重試加鎖,因此它可以先成功地獲取到兩個鎖。這時(shí),線程1嘗試獲取鎖A并且處于等待狀態(tài)。當(dāng)線程2結(jié)束時(shí),線程1也可以順利的獲得這兩個鎖(除非線程2或者其它線程在線程1成功獲得兩個鎖之前又獲得其中的一些鎖)。

需要注意的是,由于存在鎖的超時(shí),所以我們不能認(rèn)為這種場景就一定是出現(xiàn)了死鎖。也可能是因?yàn)楂@得了鎖的線程(導(dǎo)致其它線程超時(shí))需要很長的時(shí)間去完成它的任務(wù)。

此外,如果有非常多的線程同一時(shí)間去競爭同一批資源,就算有超時(shí)和回退機(jī)制,還是可能會導(dǎo)致這些線程重復(fù)地嘗試但卻始終得不到鎖。如果只有兩個線程,并且重試的超時(shí)時(shí)間設(shè)定為0到500毫秒之間,這種現(xiàn)象可能不會發(fā)生,但是如果是10個或20個線程情況就不同了。因?yàn)檫@些線程等待相等的重試時(shí)間的概率就高的多(或者非常接近以至于會出現(xiàn)問題)。
(譯者注:超時(shí)和重試機(jī)制是為了避免在同一時(shí)間出現(xiàn)的競爭,但是當(dāng)線程很多時(shí),其中兩個或多個線程的超時(shí)時(shí)間一樣或者接近的可能性就會很大,因此就算出現(xiàn)競爭而導(dǎo)致超時(shí)后,由于超時(shí)時(shí)間一樣,它們又會同時(shí)開始重試,導(dǎo)致新一輪的競爭,帶來了新的問題。)

這種機(jī)制存在一個問題,在Java中不能對synchronized同步塊設(shè)置超時(shí)時(shí)間。你需要創(chuàng)建一個自定義鎖,或使用Java5中java.util.concurrent包下的工具。寫一個自定義鎖類不復(fù)雜,但超出了本文的內(nèi)容。后續(xù)的Java并發(fā)系列會涵蓋自定義鎖的內(nèi)容。

死鎖檢測

死鎖檢測是一個更好的死鎖預(yù)防機(jī)制,它主要是針對那些不可能實(shí)現(xiàn)按序加鎖并且鎖超時(shí)也不可行的場景。

每當(dāng)一個線程獲得了鎖,會在線程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map、graph等等)將其記下。除此之外,每當(dāng)有線程請求鎖,也需要記錄在這個數(shù)據(jù)結(jié)構(gòu)中。

當(dāng)一個線程請求鎖失敗時(shí),這個線程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生。例如,線程A請求鎖7,但是鎖7這個時(shí)候被線程B持有,這時(shí)線程A就可以檢查一下線程B是否已經(jīng)請求了線程A當(dāng)前所持有的鎖。如果線程B確實(shí)有這樣的請求,那么就是發(fā)生了死鎖(線程A擁有鎖1,請求鎖7;線程B擁有鎖7,請求鎖1)。

當(dāng)然,死鎖一般要比兩個線程互相持有對方的鎖這種情況要復(fù)雜的多。線程A等待線程B,線程B等待線程C,線程C等待線程D,線程D又在等待線程A。線程A為了檢測死鎖,它需要遞進(jìn)地檢測所有被B請求的鎖。從線程B所請求的鎖開始,線程A找到了線程C,然后又找到了線程D,發(fā)現(xiàn)線程D請求的鎖被線程A自己持有著。這是它就知道發(fā)生了死鎖。

下面是一幅關(guān)于四個線程(A,B,C和D)之間鎖占有和請求的關(guān)系圖。像這樣的數(shù)據(jù)結(jié)構(gòu)就可以被用來檢測死鎖。

那么當(dāng)檢測出死鎖時(shí),這些線程該做些什么呢?

一個可行的做法是釋放所有鎖,回退,并且等待一段隨機(jī)的時(shí)間后重試。這個和簡單的加鎖超時(shí)類似,不一樣的是只有死鎖已經(jīng)發(fā)生了才回退,而不會是因?yàn)榧渔i的請求超時(shí)了。雖然有回退和等待,但是如果有大量的線程競爭同一批鎖,它們還是會重復(fù)地死鎖(編者注:原因同超時(shí)類似,不能從根本上減輕競爭)。

一個更好的方案是給這些線程設(shè)置優(yōu)先級,讓一個(或幾個)線程回退,剩下的線程就像沒發(fā)生死鎖一樣繼續(xù)保持著它們需要的鎖。如果賦予這些線程的優(yōu)先級是固定不變的,同一批線程總是會擁有更高的優(yōu)先級。為避免這個問題,可以在死鎖發(fā)生的時(shí)候設(shè)置隨機(jī)的優(yōu)先級。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java8 日期、時(shí)間操作代碼

    Java8 日期、時(shí)間操作代碼

    在Java8之前,日期時(shí)間API一直被開發(fā)者詬病,包括:java.util.Date是可變類型,SimpleDateFormat非線程安全等問題。故此,Java8引入了一套全新的日期時(shí)間處理API,新的API基于ISO標(biāo)準(zhǔn)日歷系統(tǒng)
    2021-09-09
  • Java 實(shí)戰(zhàn)交易平臺項(xiàng)目之寵物在線商城系統(tǒng)

    Java 實(shí)戰(zhàn)交易平臺項(xiàng)目之寵物在線商城系統(tǒng)

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實(shí)現(xiàn)一個寵物在線商城系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平
    2021-11-11
  • 詳解Spring注解@Validated 失效分析

    詳解Spring注解@Validated 失效分析

    這篇文章主要介紹了Spring注解@Validated失效分析,文中有詳細(xì)代碼示例講解了@Validated為何失效,需要的小伙伴可以參考一下
    2023-04-04
  • SpringBoot、Java 使用 Jsoup 解析 HTML 頁面的詳細(xì)步驟

    SpringBoot、Java 使用 Jsoup 解析 HTML 頁面

    這篇文章主要介紹了SpringBoot、Java 使用 Jsoup 解析 HTML 頁面的詳細(xì)步驟,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Spring Cloud EureKa Ribbon 服務(wù)注冊發(fā)現(xiàn)與調(diào)用

    Spring Cloud EureKa Ribbon 服務(wù)注冊發(fā)現(xiàn)與調(diào)用

    這篇文章主要介紹了Spring Cloud EureKa Ribbon 服務(wù)注冊發(fā)現(xiàn)與調(diào)用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • MyBatis Mapper.xml中的命名空間及命名方式

    MyBatis Mapper.xml中的命名空間及命名方式

    這篇文章主要介紹了MyBatis Mapper.xml中的命名空間及命名方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring boot配置 swagger的示例代碼

    Spring boot配置 swagger的示例代碼

    Swagger是一組開源項(xiàng)目,Spring 基于swagger規(guī)范,可以將基于SpringMVC和Spring Boot項(xiàng)目的項(xiàng)目代碼,自動生成JSON格式的描述文件,接下來通過本文給大家介紹Spring boot配置 swagger的示例代碼,一起看看吧
    2021-09-09
  • 解決SpringBoot整合Mybatis掃描不到Mapper的問題

    解決SpringBoot整合Mybatis掃描不到Mapper的問題

    這篇文章主要介紹了解決SpringBoot整合Mybatis掃描不到Mapper的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • Spring Security如何基于Authentication獲取用戶信息

    Spring Security如何基于Authentication獲取用戶信息

    這篇文章主要介紹了Spring Security如何基于Authentication獲取用戶信息,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 解決RabbitMq消息隊(duì)列Qos?Prefetch消息堵塞問題

    解決RabbitMq消息隊(duì)列Qos?Prefetch消息堵塞問題

    這篇文章主要為大家介紹了關(guān)于如何解決解決RabbitMq?Qos?Prefetch消息堵塞的問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-01-01

最新評論