[英]Atomic Operations and multithreading
最近我正在閱讀一個教程,因為我發現了一個聲明說...
“Java語言規范保證了讀取或寫入的變量是一個原子操作(除非該變量的類型的long
或double
)類型的操作變量long
或double
只有原子的,如果他們與申報volatile
關鍵字。”
AtomicInteger
或AtomicLong
提供getAndDecrement()
, getAndIncrement()
和getAndSet()
等原子方法。
我對上述陳述感到困惑。你能否澄清何時使用 AtomicInteger
或AtomicLong
類。
做a = 28
(帶有a
int
)是一個原子操作。 但是,執行a++
不是原子操作,因為它需要讀取a的值,增量和寫入結果。 因此,如果您使用a++
來實現線程安全計數器,您可以讓兩個線程同時讀取該值(例如26),然后同時增加它並同時寫入它,結果導致27,而不是28。
AtomicInteger通過提供與您列出的原子操作類似的原子操作來解決此問題。 在我的示例中,您將使用incrementAndGet()
,這將保證結束值為28而不是27。
原子意味着操作完成,沒有任何可能發生的事情。 例如。 AtomicInteger上的getAndDecrement()保證變量同時返回和遞減。
如果它不是原子操作,則可能存在值遞減(例如從3到2),然后由另一個線程修改(例如,將其從2更改為5),然后返回為5。
如果需要讀取變量並根據讀取值寫入結果,則需要AtomicInteger
。 例如, i++
讀取i
(例如3
)並寫入i+1
(例如4
)。 一個線程可以同時中斷,和其他三個線程遞增i
了。 現在我們回來了, i
實際上有值6
但我們的線程仍然根據它事先讀取的內容寫入4
。
AtomicInteger.getAndIncrement
確保您不會被中斷 ,因此總是正確遞增。 此外,結果總是刷新到內存中 ,而非易失性i
可能不會刷新到內存中。 在這種情況下,其他線程甚至可能看不到更改。
我認為這意味着長讀和雙讀操作是原子的,寫操作是原子的。 但讀取+寫入不是原子的。
volatile long num;
num = num+1
以上不是線程安全的。 讀寫是兩個獨立的操作。 每個都保證是原子的,但整個表達不是。
要使其線程安全,您需要使用AtomicLong並使用getAndIncrement函數。
您可以根據要處理的數字范圍的上限/下限使用int或long。 請不要將長原子的非原子行為與AtomicLong混合。 無論你上面寫的是什么都是正確的,但你可能會混合這兩個概念。 在您進行“比較和設置”操作的情況下,AtomicXXX更有用。 例如,即使在多線程環境中代碼在原子上被修改/讀取也是不正確的:
int i =10
..
..
..
if(i == 10) i++;
在多線程環境中,兩個線程可以原子方式訪問此代碼並更新i的值並使其處於一致狀態。 因此,處理這種情況通常會保護代碼“if(i == 10)i ++;” 與同步塊。 但是,AtomicInteger類提供了實現此類功能的API,而不使用速度較慢的同步塊。 AtmoicLong API的情況也是如此
變異變量時需要操作的原子性。 做int a = 10;
是一個原子操作,但它不會給你提供問題。 提供操作的問題通常是變異的,如a++
或a = a + 2;
等等。
Java規范保證“讀取”和“寫入”是原子操作而不是它們的組合。 因此,根據規范,“讀取,添加1然后將結果寫回”的操作不是原子操作。 這些操作稱為復合操作,它們通常需要在我們的代碼中使用它們的上下文。
原子類型有助於解決此問題。 在原子類型上使用incrementAndget()使得'讀取,添加1然后將結果寫回並讀取新結果'在上下文中的單個原子操作到線程安全性。
希望這可以幫助。 順便說一句,你應該閱讀這篇關於並發和線程基礎知識的文章( http://walivi.wordpress.com/2013/08/24/concurrency-in-java-a-beginners-introduction/ )。 它很好地解釋了這些東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.