成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

[Java并發(fā)-4]解決Java死鎖的問(wèn)題

stonezhu / 3061人閱讀

摘要:例如,張三同時(shí)申請(qǐng)賬本和,賬本管理員如果發(fā)現(xiàn)文件架上只有賬本,這個(gè)時(shí)候賬本管理員是不會(huì)把賬本拿下來(lái)給張三的,只有賬本和都在的時(shí)候才會(huì)給張三。但仍需注意的是,有時(shí)候預(yù)防死鎖成本也是很高的。

在上一篇中,我們嘗試使用了 Account.class作為互斥鎖,來(lái)解決轉(zhuǎn)賬問(wèn)題。但是很容易發(fā)現(xiàn)這樣,所有的轉(zhuǎn)賬操作都是串行的,性能太差了。

讓我們嘗試提升下性能。

向現(xiàn)實(shí)世界要答案

現(xiàn)實(shí)世界中,轉(zhuǎn)賬操作是支持并發(fā)的。

我設(shè)想下,在古代沒(méi)有信息化的時(shí)候。賬戶(hù)的存在就是一個(gè)個(gè)賬本,而且每個(gè)用戶(hù)都有一個(gè)賬本。這些賬本都放在架子上。銀行柜員在轉(zhuǎn)賬時(shí)候,是去架子上同時(shí)拿到轉(zhuǎn)入賬本和轉(zhuǎn)出賬本,然后做轉(zhuǎn)賬都。這時(shí)候這個(gè)柜員會(huì)遇到3種情況
1,架子上剛好有 轉(zhuǎn)入和轉(zhuǎn)出賬本,同時(shí)拿走即可。
2,如果架子上只有轉(zhuǎn)入和轉(zhuǎn)出賬本之一,柜員先拿走一本,在等著另一本被送回來(lái)。
3,轉(zhuǎn)入和轉(zhuǎn)出賬本都沒(méi)有,柜員只好等著2個(gè)賬本被送回來(lái)。

上面的步驟轉(zhuǎn)換成編碼,其實(shí)就是2把鎖實(shí)現(xiàn)。轉(zhuǎn)入賬本一把鎖,轉(zhuǎn)出賬本一把鎖。在 transfer() 方法內(nèi)部,我們首先嘗試鎖定轉(zhuǎn)出賬戶(hù) this(先把轉(zhuǎn)出賬本拿到手),然后嘗試鎖定轉(zhuǎn)入賬戶(hù) target(再把轉(zhuǎn)入賬本拿到手),只有當(dāng)兩者都成功時(shí),才執(zhí)行轉(zhuǎn)賬操作。這個(gè)邏輯可以圖形化為下圖這個(gè)樣子。

如下所示。經(jīng)過(guò)這樣的優(yōu)化后,賬戶(hù) A 轉(zhuǎn)賬戶(hù) B 和賬戶(hù) C 轉(zhuǎn)賬戶(hù) D 這兩個(gè)轉(zhuǎn)賬操作就可以并行了。

class Account {
  private int balance;
  // 轉(zhuǎn)賬
  void transfer(Account target, int amt){
    // 鎖定轉(zhuǎn)出賬戶(hù)
    synchronized(this) {              
      // 鎖定轉(zhuǎn)入賬戶(hù)
      synchronized(target) {           
        if (this.balance > amt) {
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}

相對(duì)于用 Account.class 作為互斥鎖,鎖定的范圍太大,而我們鎖定兩個(gè)賬戶(hù)范圍就小多了,這樣的鎖,上一章我們介紹過(guò),叫細(xì)粒度鎖使用細(xì)粒度鎖可以提高并行度,是性能優(yōu)化的一個(gè)重要手段。

使用細(xì)粒度鎖這么簡(jiǎn)單嘛?編寫(xiě)并發(fā)程序就需要這樣時(shí)時(shí)刻刻保持謹(jǐn)慎。

使用細(xì)粒度鎖是有代價(jià)的,這個(gè)代價(jià)就是可能會(huì)導(dǎo)致死鎖。

我們還是通過(guò)現(xiàn)實(shí)世界看一下死鎖產(chǎn)生的原因。如果有客戶(hù)找柜員張三做個(gè)轉(zhuǎn)賬業(yè)務(wù):賬戶(hù) A 轉(zhuǎn)賬戶(hù) B 100 元,此時(shí)另一個(gè)客戶(hù)找柜員李四也做個(gè)轉(zhuǎn)賬業(yè)務(wù):賬戶(hù) B 轉(zhuǎn)賬戶(hù) A 100 元,于是張三和李四同時(shí)都去文件架上拿賬本,這時(shí)候有可能湊巧張三拿到了賬本 A,李四拿到了賬本 B。張三拿到賬本 A 后就等著賬本 B(賬本 B 已經(jīng)被李四拿走),而李四拿到賬本 B 后就等著賬本 A(賬本 A 已經(jīng)被張三拿走),他們要等多久呢?他們會(huì)永遠(yuǎn)等待下去…因?yàn)閺埲粫?huì)把賬本 A 送回去,李四也不會(huì)把賬本 B 送回去。我們姑且稱(chēng)為死等吧。

現(xiàn)實(shí)世界里的死等,就是編程領(lǐng)域的死鎖了。

死鎖 一組互相競(jìng)爭(zhēng)資源的線(xiàn)程因互相等待,導(dǎo)致“永久”阻塞的現(xiàn)象
class Account {
  private int balance;
  // 轉(zhuǎn)賬
  void transfer(Account target, int amt){
    // 鎖定轉(zhuǎn)出賬戶(hù)
    synchronized(this){     ①
      // 鎖定轉(zhuǎn)入賬戶(hù)
      synchronized(target){ ②
        if (this.balance > amt) {
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}

關(guān)于這種現(xiàn)象,我們還可以借助資源分配圖來(lái)可視化鎖的占用情況(資源分配圖是個(gè)有向圖,它可以描述資源和線(xiàn)程的狀態(tài))。其中,資源用方形節(jié)點(diǎn)表示,線(xiàn)程用圓形節(jié)點(diǎn)表示;資源中的點(diǎn)指向線(xiàn)程的邊表示線(xiàn)程已經(jīng)獲得該資源,線(xiàn)程指向資源的邊則表示線(xiàn)程請(qǐng)求資源,但尚未得到。轉(zhuǎn)賬發(fā)生死鎖時(shí)的資源分配圖就如下圖所示。


轉(zhuǎn)賬發(fā)生死鎖時(shí)的資源分配圖

如何預(yù)防死鎖

并發(fā)程序一旦死鎖,一般沒(méi)有特別好的方法,很多時(shí)候我們只能重啟應(yīng)用。因此,解決死鎖問(wèn)題最好的辦法還是規(guī)避死鎖。

那如何避免死鎖呢?要避免死鎖就需要分析死鎖發(fā)生的條件,有個(gè)叫 Coffman 的牛人早就總結(jié)過(guò)了,只有以下這四個(gè)條件都發(fā)生時(shí)才會(huì)出現(xiàn)死鎖:

1,互斥,共享資源 X 和 Y 只能被一個(gè)線(xiàn)程占用;
2,占有且等待,線(xiàn)程 T1 已經(jīng)取得共享資源 X,在等待共享資源 Y 的時(shí)候,不釋放共享資源 X;
3,不可搶占,其他線(xiàn)程不能強(qiáng)行搶占線(xiàn)程 T1 占有的資源;
4,循環(huán)等待,線(xiàn)程 T1 等待線(xiàn)程 T2 占有的資源,線(xiàn)程 T2 等待線(xiàn)程 T1 占有的資源,就是循環(huán)等待。

反過(guò)來(lái)分析,也就是說(shuō)只要我們破壞其中一個(gè),就可以成功避免死鎖的發(fā)生

其中,互斥這個(gè)條件我們沒(méi)有辦法破壞,因?yàn)槲覀冇面i為的就是互斥。不過(guò)其他三個(gè)條件都是有辦法破壞掉的,到底如何做呢?

1,對(duì)于“占用且等待”這個(gè)條件,我們可以一次性申請(qǐng)所有的資源,這樣就不存在等待了。
2,對(duì)于“不可搶占”這個(gè)條件,占用部分資源的線(xiàn)程進(jìn)一步申請(qǐng)其他資源時(shí),如果申請(qǐng)不到,可以主動(dòng)釋放它占有的資源,這樣不可搶占這個(gè)條件就破壞掉了。
3,對(duì)于“循環(huán)等待”這個(gè)條件,可以靠按序申請(qǐng)資源來(lái)預(yù)防。所謂按序申請(qǐng),是指資源是有線(xiàn)性順序的,申請(qǐng)的時(shí)候可以先申請(qǐng)資源序號(hào)小的,再申請(qǐng)資源序號(hào)大的,這樣線(xiàn)性化后自然就不存在循環(huán)了。

我們已經(jīng)從理論上解決了如何預(yù)防死鎖,下面我們就來(lái)嘗試用代碼實(shí)踐一下這些理論。

1. 破壞占用且等待條件

從理論上講,要破壞這個(gè)條件,可以一次性申請(qǐng)所有資源。在現(xiàn)實(shí)世界里,就拿前面我們提到的轉(zhuǎn)賬操作來(lái)講??梢栽黾右粋€(gè)賬本管理員,然后只允許賬本管理員從文件架上拿賬本,也就是說(shuō)柜員不能直接在文件架上拿賬本,必須通過(guò)賬本管理員才能拿到想要的賬本。例如,張三同時(shí)申請(qǐng)賬本 A 和 B,賬本管理員如果發(fā)現(xiàn)文件架上只有賬本 A,這個(gè)時(shí)候賬本管理員是不會(huì)把賬本 A 拿下來(lái)給張三的,只有賬本 A 和 B 都在的時(shí)候才會(huì)給張三。這樣就保證了“一次性申請(qǐng)所有資源”。


通過(guò)賬本管理員拿賬本圖

對(duì)應(yīng)到編程領(lǐng)域,“同時(shí)申請(qǐng)”這個(gè)操作是一個(gè)臨界區(qū),我們也需要一個(gè)角色(Java 里面的類(lèi))來(lái)管理這個(gè)臨界區(qū),我們就把這個(gè)角色定為 Allocator。它有兩個(gè)重要功能,分別是:同時(shí)申請(qǐng)資源 apply() 和同時(shí)釋放資源 free()。賬戶(hù) Account 類(lèi)里面持有一個(gè) Allocator 的單例(必須是單例,只能由一個(gè)人來(lái)分配資源)。當(dāng)賬戶(hù) Account 在執(zhí)行轉(zhuǎn)賬操作的時(shí)候,首先向 Allocator 同時(shí)申請(qǐng)轉(zhuǎn)出賬戶(hù)和轉(zhuǎn)入賬戶(hù)這兩個(gè)資源,成功后再鎖定這兩個(gè)資源;當(dāng)轉(zhuǎn)賬操作執(zhí)行完,釋放鎖之后,我們需通知 Allocator 同時(shí)釋放轉(zhuǎn)出賬戶(hù)和轉(zhuǎn)入賬戶(hù)這兩個(gè)資源。具體的代碼實(shí)現(xiàn)如下。

class Allocator {
  private List als =
    new ArrayList<>();
  // 一次性申請(qǐng)所有資源
  synchronized boolean apply(
    Object from, Object to){
    if(als.contains(from) ||
         als.contains(to)){
      return false;  
    } else {
      als.add(from);
      als.add(to);  
    }
    return true;
  }
  // 歸還資源
  synchronized void free(
    Object from, Object to){
    als.remove(from);
    als.remove(to);
  }
}

class Account {
  // actr 應(yīng)該為單例
  private Allocator actr;
  private int balance;
  // 轉(zhuǎn)賬
  void transfer(Account target, int amt){
    // 一次性申請(qǐng)轉(zhuǎn)出賬戶(hù)和轉(zhuǎn)入賬戶(hù),直到成功
    while(!actr.apply(this, target))
      ;
    try{
      // 鎖定轉(zhuǎn)出賬戶(hù)
      synchronized(this){              
        // 鎖定轉(zhuǎn)入賬戶(hù)
        synchronized(target){           
          if (this.balance > amt){
            this.balance -= amt;
            target.balance += amt;
          }
        }
      }
    } finally {
      actr.free(this, target)
    }
  } 
}

2. 破壞不可搶占條件

破壞不可搶占條件看上去很簡(jiǎn)單,核心是要能夠主動(dòng)釋放它占有的資源,這一點(diǎn) synchronized 是做不到的。原因是 synchronized 申請(qǐng)資源的時(shí)候,如果申請(qǐng)不到,線(xiàn)程直接進(jìn)入阻塞狀態(tài)了,而線(xiàn)程進(jìn)入阻塞狀態(tài),也釋放不了線(xiàn)程已經(jīng)占有的資源。java.util.concurrent 這個(gè)包下面提供的 Lock 是可以輕松解決這個(gè)問(wèn)題的。關(guān)于這個(gè)話(huà)題,咱們后面會(huì)詳細(xì)講。

3. 破壞循環(huán)等待條件

破壞這個(gè)條件,需要對(duì)資源進(jìn)行排序,然后按序申請(qǐng)資源。這個(gè)實(shí)現(xiàn)非常簡(jiǎn)單,我們假設(shè)每個(gè)賬戶(hù)都有不同的屬性 id,這個(gè) id 可以作為排序字段,申請(qǐng)的時(shí)候,我們可以按照從小到大的順序來(lái)申請(qǐng)。比如下面代碼中,①~⑥處的代碼對(duì)轉(zhuǎn)出賬戶(hù)(this)和轉(zhuǎn)入賬戶(hù)(target)排序,然后按照序號(hào)從小到大的順序鎖定賬戶(hù)。這樣就不存在“循環(huán)”等待了。

class Account {
  private int id;
  private int balance;
  // 轉(zhuǎn)賬
  void transfer(Account target, int amt){
    Account left = this        ①
    Account right = target;    ②
    if (this.id > target.id) { ③
      left = target;           ④
      right = this;            ⑤
    }                          ⑥
    // 鎖定序號(hào)小的賬戶(hù)
    synchronized(left){
      // 鎖定序號(hào)大的賬戶(hù)
      synchronized(right){ 
        if (this.balance > amt){
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}
總結(jié)

當(dāng)我們?cè)诰幊淌澜缋镉龅絾?wèn)題時(shí),應(yīng)不局限于當(dāng)下,可以換個(gè)思路,向現(xiàn)實(shí)世界要答案,利用現(xiàn)實(shí)世界的模型來(lái)構(gòu)思解決方案,這樣往往能夠讓我們的方案更容易理解,也更能夠看清楚問(wèn)題的本質(zhì)。

用細(xì)粒度鎖來(lái)鎖定多個(gè)資源時(shí),要注意死鎖的問(wèn)題.

預(yù)防死鎖主要是破壞三個(gè)條件中的一個(gè),有了這個(gè)思路后,實(shí)現(xiàn)就簡(jiǎn)單了。但仍需注意的是,有時(shí)候預(yù)防死鎖成本也是很高的。例如上面轉(zhuǎn)賬那個(gè)例子,我們破壞占用且等待條件上我們也是鎖了所有的賬戶(hù),而且還是用了死循環(huán) while(!actr.apply(this, target));方法,不過(guò)好在 apply() 這個(gè)方法基本不耗時(shí)。 在轉(zhuǎn)賬這個(gè)例子中,破壞循環(huán)等待條件就是成本最低的一個(gè)方案。

所以我們?cè)谶x擇具體方案的時(shí)候,還需要評(píng)估一下操作成本,從中選擇一個(gè)成本最低的方案

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/74155.html

相關(guān)文章

  • 并發(fā)編程之死鎖解析

    摘要:如何檢測(cè)死鎖由于死鎖極難通過(guò)人工的方式查出來(lái),因此提供了命令來(lái)檢測(cè)某個(gè)進(jìn)程中心線(xiàn)程的情況,并排查有沒(méi)有死鎖。線(xiàn)程持有的鎖,等待的鎖。避免出現(xiàn)死鎖,如果出現(xiàn)了死鎖,則可以使用命令查看線(xiàn)程是否有死鎖。 showImg(https://segmentfault.com/img/remote/1460000014936757); 前言 在 Java 的并發(fā)編程中,有一個(gè)問(wèn)題需要特別注意,那就是...

    yy736044583 評(píng)論0 收藏0
  • Java多線(xiàn)程學(xué)習(xí)(七)并發(fā)編程中一些問(wèn)題

    摘要:因?yàn)槎嗑€(xiàn)程競(jìng)爭(zhēng)鎖時(shí)會(huì)引起上下文切換。減少線(xiàn)程的使用。舉個(gè)例子如果說(shuō)服務(wù)器的帶寬只有,某個(gè)資源的下載速度是,系統(tǒng)啟動(dòng)個(gè)線(xiàn)程下載該資源并不會(huì)導(dǎo)致下載速度編程,所以在并發(fā)編程時(shí),需要考慮這些資源的限制。 最近私下做一項(xiàng)目,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是項(xiàng)目不存在bug,與其懼怕,何不與其剛正面。 系列文章傳送門(mén): Java多線(xiàn)程學(xué)習(xí)(一)Java多線(xiàn)程入門(mén) Jav...

    yimo 評(píng)論0 收藏0
  • Java多線(xiàn)程學(xué)習(xí)(七)并發(fā)編程中一些問(wèn)題

    摘要:相比與其他操作系統(tǒng)包括其他類(lèi)系統(tǒng)有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文切換和模式切換的時(shí)間消耗非常少。因?yàn)槎嗑€(xiàn)程競(jìng)爭(zhēng)鎖時(shí)會(huì)引起上下文切換。減少線(xiàn)程的使用。很多編程語(yǔ)言中都有協(xié)程。所以如何避免死鎖的產(chǎn)生,在我們使用并發(fā)編程時(shí)至關(guān)重要。 系列文章傳送門(mén): Java多線(xiàn)程學(xué)習(xí)(一)Java多線(xiàn)程入門(mén) Java多線(xiàn)程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線(xiàn)程學(xué)習(xí)(二)syn...

    dingding199389 評(píng)論0 收藏0
  • 多線(xiàn)程編程完全指南

    摘要:在這個(gè)范圍廣大的并發(fā)技術(shù)領(lǐng)域當(dāng)中多線(xiàn)程編程可以說(shuō)是基礎(chǔ)和核心,大多數(shù)抽象并發(fā)問(wèn)題的構(gòu)思與解決都是基于多線(xiàn)程模型來(lái)進(jìn)行的。一般來(lái)說(shuō),多線(xiàn)程程序會(huì)面臨三類(lèi)問(wèn)題正確性問(wèn)題效率問(wèn)題死鎖問(wèn)題。 多線(xiàn)程編程或者說(shuō)范圍更大的并發(fā)編程是一種非常復(fù)雜且容易出錯(cuò)的編程方式,但是我們?yōu)槭裁催€要冒著風(fēng)險(xiǎn)艱辛地學(xué)習(xí)各種多線(xiàn)程編程技術(shù)、解決各種并發(fā)問(wèn)題呢? 因?yàn)椴l(fā)是整個(gè)分布式集群的基礎(chǔ),通過(guò)分布式集群不僅可以大...

    mengera88 評(píng)論0 收藏0
  • 一起學(xué)并發(fā)編程 - 死鎖跟蹤分析

    摘要:上一章介紹過(guò)關(guān)鍵字,使用它可以給程序互斥部分加上一把鎖從而達(dá)到同步的效果,但錯(cuò)誤的用法會(huì)導(dǎo)致多個(gè)線(xiàn)程同時(shí)被阻塞死鎖死鎖多個(gè)線(xiàn)程同時(shí)被阻塞,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。由于線(xiàn)程被無(wú)限期地阻塞,因此程序不可能正常終止。 上一章介紹過(guò)synchronized關(guān)鍵字,使用它可以給程序互斥部分加上一把鎖從而達(dá)到同步的效果,但錯(cuò)誤的用法會(huì)導(dǎo)致多個(gè)線(xiàn)程同時(shí)被阻塞.... 死鎖 死鎖...

    ACb0y 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<