一、锁
两种特性:互斥性(mutual exclusion)、可见性(visibility)、原子性(atomic)
互斥性就是一次只有一个线程可以访问该共享数据,可见性就是释放锁之前,对共享数据的修改,随后获取锁的另一个线程是可见的,也就是说一个线程修改了共享变量的值,另一个线程访问该共享变量的时候能立即得到最新修改的值。原子性就是多个变量或者某个变量的当前值和修改值之间存在某种一定约束。
二、volatile
volatile变量具有可见性,但不具有原子性。这就是说线程能够自动发现votatile变量的最新值。如果想让volatile变量提供线程安全,这我们必须满足该变量满足下面2个特性。另一方面,volatile变量可以防止该变量操作的指令重排和优化。
第一、对于volatile变量的写操作不能依赖于该变量的当前值,比如volatile修饰变量x,而变量x操作:x++。如果多个线程操作x++,并不能达到预期的结果,原因在于x++这个操作是:首先cpu从内存中读取变量x的值到cpu的一级cache中,接着cpu对cache中的值做修改,最后把cache中修改的值回写到内存中。这一组操作需要以原子性的方式执行,才能保证线程安全,但是volatile本身不满足原子性。
第二、该volatile变量没有在具有其他变量的不变式中,比如2个volatile变量start和end,而 start<=end这个语句在多线程中可能出现线程安全问题。
三、性能
一般情况下使用volatile变量的同步机子的性能要优于锁,就是说,在目前大多数的处理器架构上,volatile读操作开销非常低 —— 几乎和非volatile读操作一样。而 volatile写操作的开销要比非volatile写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。
四、运用
在使用volatile的时候,需要记住该变量的状态独立于程序其他内容时,才能使用volatile变量。如下是几个运用:
1、状态标志。比如volatile变量isInitialized是一个boolean类型,用于表示某种操作是否已经初始化完成。
2、一次性安全发布
3、独立观察
4、开销较低的读-写锁策略
五、总结
如果一个变量被volatile修饰,那么cpu在读取该变量的时候都不会从cpu本身的cache缓存中读取,而是每次从内存中读取,这样保证了每个volatile变量的可见性。但是volatile变量不满足原子性。更加说明一下x++的不是线程安全的。比如多个线程操作volatile变量x进行x++.其中x初始值是0,线程A和线程B都从内存中读取x到各自的cache中,如cacheA(x=0),和cacheB(x=0),此时2个线程各自在自己的cache中操作变量x,这时线程A的cache值为cacheA(x=1),线程B的cache值为cacheB(x=1)。由于变量x是volatile类型,所以线程会把修改变量x的值立即回写到内存中。比如线程A立即回写,则变量x的值在内存中为1,而后线程B也回写,则变量x的值为1。这跟预期的结果有差距的。