java中原子操作是線程安全的論調經常被提到
根據定義
原子操作是不會被打斷地的操作
因此被認為是線程安全的
實際上有一些原子操作不一定是線程安全的
這個問題出現的原因是盡量減少在代碼中同步關鍵字
同步會損害性能
雖然這個損失因JVM不同而不同
另外
在現代的JVM中
同步的性能正在逐步提高
盡管如此
使用同步仍然是有性能代價的
並且程序員永遠會盡力提高他們的代碼的效率
因此這個問題就延續了下來
在java中
位或者更少位數的賦值是原子的
在一個
位的硬件平台上
除了double和long型的其它原始類型通常都是使用
位進行表示
而double和long通常使用
位表示
另外
對象引用使用本機指針實現
通常也是
位的
對這些
位的類型的操作是原子的
這些原始類型通常使用
位或者
位表示
這又引入了另一個小小的神話
原始類型的大小是由語言保證的
這是不對的
java語言保證的是原始類型的表數范圍而非JVM中的存儲大小
因此
int型總是有相同的表數范圍
在一個JVM上可能使用
位實現
而在另一個JVM上可能是
位的
在此再次強調
在所有平台上被保證的是表數范圍
位以及更小的值的操作是原子的
那麼
原子操作在什麼情況下不是線程安全的?主要的一點是他們也許確實是線程安全的
但是這沒有被保證!java線程允許線程在自己的內存區保存變量的副本
允許線程使用本地的私有拷貝進行工作而非每次都使用主存的值是為了提高性能
考慮下面的類
class RealTimeClock
{
private int clkID;
public int clockID()
{
return clkID;
}
public void setClockID(int id)
{
clkID = id;
}
//
}
現在考慮RealTimeClock的一個實例以及兩個線程同時調用setClockID和clockID
並發生以下的事件序列
T
調用setClockID(
)
T
將
放入自己的私有工作內存
T
調用setClockID(
)
T
將
放入自己的私有工作內存
T
調用clockID
它返回
是從T
的私有工作內存返回的
對clockI的調用應該返回
因為這是被T
設置的
然而返回的是
因為讀寫操作是對私有工作內存的而非主存
賦值操作當然是原子的
但是因為JVM允許這種行為
因此線程安全不是一定的
同時
JVM的這種行為也不是被保證的
兩個線程擁有自己的私有拷貝而不和主存一致
如果這種行為出現
那麼私有本機變量和主存一致必須在以下兩個條件下
變量使用volatile聲明
被訪問的變量處於同步方法或者同步塊中
如果變量被聲明為volatile
在每次訪問時都會和主存一致
這個一致性是由java語言保證的
並且是原子的
即使是
位的值
(注意很多JVM沒有正確的實現volatile關鍵字
你可以在找到更多的信息
)另外
如果變量在同步方法或者同步塊中被訪問
當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖是變量被同步
使用任何一種方法都可以保證ClockID返回
也就是正確的值
變量訪問的頻度不同則你的選擇的性能不同
如果你更新很多變量
那麼使用volatile可能比使用同步更慢
記住
如果變量被聲明為volatile
那麼在每次訪問時都會和主存一致
與此對照
使用同步時
變量只在獲得鎖和釋放鎖的時候和主存一致
但是同步使得代碼有較少的並發性
如果你更新很多變量並且不想有每次訪問都和主存進行同步的損失或者你因為其它的原因想排除並發性時可以考慮使用同步
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27601.html