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那么在每次访问时都会和主存一致与此对照使用同步时变量只在获得锁和释放锁的时候和主存一致但是同步使得代码有较少的并发性
如果你更新很多变量并且不想有每次访问都和主存进行同步的损失或者你因为其它的原因想排除并发性时可以考虑使用同步