Java中primitive type的執行緒安全性

lzprgmr發表於2014-07-11

Java中primite type,如char,integer,bool之類的,它們的讀寫操作都是atomic的,但是有幾個例外:

  1. long和double型別不是atomic的,因為long和double都是8位元組的,而在32位的CPU上,其機器字長為32位,操作8個位元組需要多個指令操作。
  2. ++i或者i++,因為要先讀後寫,也是多步操作。

這些情況下,需要使用AutomicInteger,AutomicLong。

 

同時,java中的reference的讀寫也是automic的,雖然reference到底佔幾個位元組沒有明確定義,但至少以下可以保證:

  1. 在32位的CPU下,使用的是4位元組的reference
  2. 在64位的CPU下,可以使用4位元組或者8位元組的reference

所以無論如何,對reference的操作肯定是單步操作,是automic的。

事實上,一個蠻有用的例子是cache switch - 你有兩個cache pool,分別用兩個reference指向:cache_in_build和cache_in_use,等cache_in_build完成了,你需要把cache_in_use指向它,沒有問題,這是執行緒安全的。

但是問題是,jvm可以會reorder你的statement:

  1. cache_in_build.build()
  2. cache_in_use = cache_in_build

期望是cache在build完了之後,切換過去。

但是這兩行程式碼並沒有前後的dependency關係,所以JVM可以為了優化,進行亂序執行,變成:

  1. cache_in_use = cache_in_build
  2. cache_in_build.build()

於是,如果另一個執行緒在使用cache_in_use,在程式碼#1執行完之後,就訪問到了正在build的那個cache,這是個問題。

解決方案是cache_in_use必須定義為volatile,保證happen-before的關係,拒絕亂序執行,從而保證其他執行緒看到的資料是有效的。

相關文章