2011-02-06 99 views
4

我學習java.util.concurrent庫,並在源代碼中找到很多無限循環,像這樣的java.util.concurrent中的代碼審查

//java.util.concurrent.atomic.AtomicInteger by Doug Lea 
public final int getAndSet(int newValue) { 
    for (;;) { 
     int current = get(); 
     if (compareAndSet(current, newValue)) 
      return current; 
    } 
} 

我不知道,在什麼情況下,實際值可能不相等到期望值(在這種情況下compareAndSet返回false)?

+1

這是不是真的「無限」,因爲其他線程保持你的*獲取之後,對「無限」修改值的概率()*和你的* compareAndSet前右()*是如此之低,這是不是一個問題。在你被閃電擊中的那天贏得全國彩票的可能性要高於你所寫的無限循環。 – SyntaxT3rr0r 2011-02-06 12:54:17

+0

@ SyntaxT3rr0r,你可能不會參加國家彩票) – 2011-02-06 12:59:51

+0

@Stats,我強烈建議閱讀一些理論。這裏有一些初學者友好的文章:http://www.ibm.com/developerworks/java/library/j-jtp11234/ – bestsss 2011-02-06 13:33:38

回答

3

當在另一個線程中修改該值時,get()和compareAndSet()可以看到不同的值。這是併發庫需要擔心的事情。

+0

+1。好。但爲什麼它不使用監視器來達到這個目的呢? – 2011-02-06 12:45:33

10

許多現代CPU有compareAndSet()映射到原子硬件操作。這意味着,它是線程安全的,不需要同步(相比之下,這是一個相對昂貴的操作)。然而,它本身只是compareAndSet()原子,因此爲了getAndSet()(即將變量設置爲給定值並返回它當時具有的值,而不可能將它設置爲兩者之間的不同值)代碼使用一個技巧:首先獲取值,然後嘗試compareAndSet()以及剛獲得的值和新值。如果失敗,變量被另一個線程操作,並且代碼再次嘗試。

這比使用同步更快,如果compareAndSet()很少出現故障,即如果沒有太多的線程同時寫入變量。在很多線程總是向變量寫入數據的極端情況下,同步實際上可能會更快,因爲雖然存在同步開銷,但嘗試訪問該變量的其他線程在輪到它時會等待並被喚醒,而不是必須重複操作。

1

下面是compareAndSet操作的實際用法:想象一下,您設計的算法可以計算多個線程中的某些內容。

每個線程都會記住一箇舊值,並根據它執行復雜的計算。

然後,如果舊值未被另一個計算線程更改過,它只想設置新的結果。如果舊值不是預期值,則線程放棄其自己的工作,取一個新值並重新開始計算。它使用compareAndSet

其他線程保證只獲取新值以繼續計算。

「無限」循環用於實現「繁忙等待」,這可能比讓線程休眠特別是在線程爭用率低時更便宜。

乾杯!

3

這是而不是一個無限循環,這是處理TAS(測試和設置)算法時的良好實踐。 (a)從內存中讀取(應該是易失性語義)(b)計算一個新值(c)如果舊值沒有同時改變,則寫入新值

在數據庫中,這被稱爲樂觀鎖定。它利用了共享內存的大多數併發更新都是無用的事實,在這種情況下,這是實現它的最便宜的方法。

事實上,這基本上是一個無偏見的鎖將在無條件的情況下做。它將讀取鎖的值,如果它被解鎖,它將執行線程ID的CAS並且如果成功,則鎖現在被保持。如果失敗,其他人首先獲得鎖定。儘管用一種更加複雜的方式來處理失敗案例,而不是一遍又一遍地重試操作。他們會繼續閱讀它一會兒,然後鎖快速解鎖(旋轉鎖定),然後通常休息一下,讓其他線程進入,直到他們輪到(指數退避)。