2016-08-20 155 views
1

「Core Java」中有一個示例,它將資金從一個賬戶轉移到另一個賬戶。我不知道什麼是條件的有用性?在這本書中,它告訴我們:爲什麼要使用條件以及Blocked和Waiting之間的區別

如果我們只是鎖定和等待沒有條件,它得到一個僵局:

private final double[] accounts; 
private Lock bankLock; 
private Condition sufficientFunds; 

public void transfer(int from, int to, int amount) { 
    bankLock.lock(); 
    try { 
     while (accounts[from] < amount) { 
      // wait... 
     } 
     // transfer funds . . . 
    } finally { 
     bankLock.unlock(); 
    } 
} 

現在,我們該怎麼做的時候沒有足夠的錢在賬戶中?我們 等到其他線程增加資金。但是這個線程只有 獲得了獨家訪問bankLock,所以沒有其他線程有一個存款的機會。

當呼叫

sufficientFunds.await(); 

當前線程現在被停用,並給出了鎖。這可讓 在另一個線程中,我們希望可以增加帳戶餘額。

Lock鎖碼,條件放棄鎖,但我不知道什麼是條件,爲什麼不只是當錢不夠時簡單地解鎖塊? 線程狀態阻塞和等待有什麼區別? block:線程無法運行; 等待:線程無法運行。 有什麼不同?

另一個問題:

while (accounts[from] < amount) { 
    ... 
    sufficientFunds.await(); 

爲什麼不寫if

if (accounts[from] < amount) { 
    ... 
+0

只問一個問題中的一個問題。如果您有兩個問題,請提出兩個問題。如果您在一個問題中有多個問題,則無法確定什麼是好答案。 –

回答

1

對課程Bank有特殊要求:如果要求將一筆款項從一個帳戶轉移到另一個帳戶,並且源帳戶上沒有足夠的資金,則必須等到存入足夠的資金以進行轉帳。你可以運行一個循環來檢查,如果它足夠的錢在每個迭代上取得只有當這種條件滿足鎖:

while (true) { 
    if (accounts[from] >= amount) { 
     bankLock.lock(); 
     try { 
      if (account[from] >= amount) { 
       // do the transfer here... 
       break; 
      } 
     } finally { 
      bankLock.unlock(); 
     } 
    } 
} 

但這種方法:

  • 揮霍上不斷檢查CPU資源(什麼人 存款足夠的錢在幾個小時或幾天?)
  • 看起來體積龐大,不地道
  • 並不總是有效(原因是出了這個問題的範圍,我可以給的鏈接解釋通訊如果你對它感興趣的話)

所以,你需要一些機制,告訴你只是在等待帳戶的一些變化。如果沒有人向其中存入資金,則一次又一次地檢查賬戶中的資金量是浪費的。還有更多–您還需要在有人存款後立即獲得鎖定,因此您可以專門檢查新帳戶的狀態,並決定是否可以進行轉帳。

您還必須記住,該存款不是帳戶允許的唯一操作。例如,還有提款。如果有人提款,不需要檢查您的賬戶是否有轉賬的可能性,因爲我們肯定知道現在更少錢。所以,我們希望喚醒存款,但不希望被撤回。我們需要以某種方式將它們分開。這是情況發生的地方。

條件是一個實現接口的對象。這只是一種抽象,可以讓你將你的鎖定/等待邏輯分成幾部分。在我們的例子中,我們可以有兩個條件:一個是賬戶餘額的增加,另一個用於減少(例如,如果有人在等待歸零銀行賬戶的關閉它):

sufficientFunds = bankLock.newCondition(); 
decreasedFunds = bankLock.newCondition(); 

現在,你不「T需要做檢查的bazillion在循環中,你可以組織你的程序,讓您只清醒的方式,並檢查帳戶,當有人資助一筆賬:

private final double[] accounts; 
private Lock bankLock; 
private Condition sufficientFunds; 

public void transfer(int from, int to, int amount) { 
    bankLock.lock(); 
    try { 
     while (accounts[from] < amount) { 
      sufficientFunds.await(); 
     } 
     // transfer funds ... 
     sufficientFunds.signalAll(); 
    } finally { 
     bankLock.unlock(); 
    } 
} 

public void deposit(int to, int amount) { 
    bankLock.lock(); 
    try { 
     // deposit funds... 
     sufficientFunds.signalAll(); 
    } finally { 
     bankLock.unlock(); 
    } 
} 

所以,讓我們看看有什麼在這裏發生並回答您的問題:

  1. transfer()正試圖獲得bankLock。如果某人已經擁有此鎖定,則線程將變爲被另一個線程阻塞,直到鎖定被釋放。

  2. 當另一個線程釋放鎖定時,您獲取它並可以檢查帳戶的狀態。如果沒有足夠的錢,你決定坐下來等待某人把錢存入賬戶,所以你打電話給sufficienFunds.await()。它使你的線程等待發生。你決定這樣做,不只是因爲另一個線程而被封鎖,這就是被封鎖的等待之間的差異。但是你是對的,在這兩種情況下你的線程都沒有運行,所以區別更合乎邏輯,而不是技術性。

  3. bankLock上創建的條件下調用await()會釋放此鎖並使其可供其他線程獲取和執行操作。在不釋放鎖的情況下,您的線程會阻止銀行中的所有操作並導致死鎖。

  4. 現在,當有人對增加金額的賬戶進行任何更改時,它會通知sufficientFunds條件,我們的轉移線程會喚醒並且循環可以進行另一次檢查。在這裏我們有兩件事要看。首先,當我們的線程被喚醒時,它會自動重新獲得鎖定,所以我們可以確保我們可以完全安全地進行檢查和修改。其次,可能發生的是,新的金額仍然不足以完成轉移(信號在條件上僅意味着發生了可能改變您等待的狀態的事情,但不保證該狀態)。在這種情況下,我們必須等待下一筆存款。這就是爲什麼我們必須使用while循環,而不是簡單的if

  5. 當賬戶中的金額足夠多時,我們進行轉賬。轉賬後我們還撥打sufficienFunds.signalAll(),因爲我們增加了to帳戶中的金額。如果某個其他線程正在等待此帳戶獲得資助,它將獲得鎖並可以開展工作。

因此,使用鎖和條件允許您以安全和有效的方式組織您的多線程程序。

+0

非常感謝,我想看看鏈接頁面爲什麼使用鎖而不是條件不總是工作。 「並不總是有效(理由超出了這個問題的範圍,如果你對此感興趣,我可以在評論中給出解釋的鏈接)」@Andrew Lygin –

+0

我的評論是關於複查我在第一個代碼片段中使用的模式。在某些情況下,它可能會中斷:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

1

我認爲在這種情況下被採取的​​的例子,改變(對最新的書的修訂,但當然不是指正)使用Lock。對於​​這樣的代碼沒有問題:

private final double[] accounts; 
    private Condition sufficientFunds; 
    public synchronized void transfer(int from, int to, int amount) 
    { 
     try { 
      while (accounts[from] < amount) { 
      // wait . . . } 
     // transfer funds . . . 
     } finally { 
      this.notifyAll(); 
     } 
    } 

當我們調用這個方法我們獲得了一個鎖,但是當我們調用wait()我們釋放它,併成爲等待調用或指定的時間notifyAll()方法進行。醒來之後,我們再次嘗試獲取鎖並再次檢查我們的狀況。並重復這一系列操作,直到我們獲得足夠的錢。


此圖爲您久等之間的差異,並阻止: enter image description here


這裏我們使用

while (accounts[from] < amount) { 

,而不是

if (accounts[from] < amount) { 

,因爲我們希望獲得方法調用的正面結果,但我們無法保證另一個線程會將所有缺失的資金記入我們預期的帳戶。 例如,如果你是一家銀行,你叫

減少(約1000)

但客戶John有沒有足夠的錢($ 500,而不是1000 $),如果你減少行動包裹在if而不是while你會嘗試等待一次。你沒有幸運,現在銀行裏沒有客戶。所以你進入if -block有時候會再次鎖定,但是你賬戶的金額沒有變化(仍然是500美元)。你不執行重複檢查(因爲if-block而不是while)並且你去轉移操作,這幾乎不是我們真正想要的。


方法sufficientFunds.await();要做三個操作它:RELEASELOCK(),等待(),鎖()。這意味着我們釋放鎖定以允許另一個線程與銀行合作並等待它完成它的操作,之後我們獲得鎖再次執行我們的餘額檢查,並且如果我們現在獲得足夠的錢,就制定計劃transfer()

1

首先爲而塊所有的理由:

它裏面的代碼只是等待在該行的線程。該循環只寫入,以便將來通知線程時(通過任何機制,例如已添加資金並在此之後使用notifyAll的線程),它將檢查while循環中再次給出的條件。如果賬戶的金額仍然較大,它將再次進入等待狀態,並且將離開鎖定。 這將允許其他線程添加資金,然後再給上述線程關注一下條件。 一旦條件失敗,它不會等待,並將繼續轉移資金。因此,將通過解鎖條件離開鎖。

阻止和等待的區別:

如果我們專注於正確使用等待,我們能夠避免爲阻止已知的條件。當我們等待時,我們認爲代碼的編寫方式是線程會正確地通知對方而不會造成死鎖。 如果thread1正在等待依賴於thread2的條件,並且thread2正在等待取決於thread1的條件,則會發生死鎖或塊。

相關問題