原子性和可見性的理解
併發程式設計中常見的兩個問題:原子性和可見性,雖然經常討論,但是隻是停留在應用層面,理解仍然還不是特別深刻。做個筆錄加深一下自己的理解。
原子性:定義為不可被分割的操作。單個指令可以是原子的,多個指令通過加鎖的方式也可以實現原子性。原子性可以是針對單核多執行緒,也可以針對多核多執行緒。
1)單核多執行緒:原子性的指令不可以被中斷,一定要執行完該條指令之後才可以切換上下文。加鎖實現的原子性因為只是以互斥的方式實現,是可以被中斷的。
17. Non-atomic Treatment of double
and long
For the purposes of the Java programming language memory model, a single write to a non-volatile long
double
value
is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.
Writes and reads of volatile long
and double
values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
2)多核多執行緒:因為每個核心都會有自己的暫存器,cache,相互互動資料比較麻煩,除了支援原子性之外,還要體現的就是可見性。在單核多執行緒下,在CPU級別的可見性是固有存在的(每個執行緒下如果各自快取了變數仍會發生不一致)。在多核多執行緒下,一個執行緒修改了資料,另一個核心的執行緒是否能看見修改的資料(因為另一個核心可能從自己的暫存器載入資料,也有可能前一個核心只把資料修改到了暫存器或快取中,沒有刷到記憶體中),這時就對可見性有了要求。
仍然是上面的例子,對volatile long和double的讀寫總是原子的,同時也能保證可見性。
總結:
原子性保證了資料的完整性,不能像獅身人面像一樣,沒有保證原子性(just a joke);可見性保證了執行緒1修改完資料A後,資料A對其他執行緒也是可見的。在java開發過程中,一些常用的工具類如AtomicLong等,雖然以原子命名,但是同時具備可見性。但是哪些操作是隻具備原子性不具備可見性呢?CPU指令是原子的,但是不一定具備多執行緒可見性。
在併發程式設計中,不管是單核或是多核,一定要同時考慮兩個以上兩個特性,不然就會出現執行緒安全的問題。
第二個例子也同時說明,在開發Java程式中,JVM本身就支援volatile long和double的原子性,當沒有強功能時,也許並不需要使用java.util.concurrent.atomic.AtomicLong。