!!!volatile 关键字只能保证可见性,顺序性,不能保证原子性。
原子性
Java 的原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失败。
JMM 只是保证了基本的原子性,但类似于 i++ 之类的操作,看似是原子操作,其实里面涉及到:
- 获取 i 的值。
- 自增。
- 再赋值给 i。
(想将哪几步作为原子操作就将哪几步整体用synchronized和lock处理可以作为原子操作)
这三步操作,所以想要实现 i++ 这样的原子操作就需要用到 synchronized 或者是 lock 进行加锁处理。
如果是基础类的自增操作可以使用 AtomicInteger 这样的原子类来实现
(其本质是利用了 CPU 级别的 的 CAS(Compare and Swap,即比较再交换) 指令来完成的)。
可见性
现代计算机中,由于 CPU 直接从主内存中读取数据的效率不高,所以都会对应的 CPU 高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。
volatile 关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。
使用 volatile 关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。
synchronized和加锁也能能保证可见性,实现原理就是在释放锁之前其余线程是访问不到这个共享变量的。但是和 volatile 相比开销较大。
有序性
有时 JVM 为了提高整体的效率会进行指令重排导致执行的顺序可。但是 JVM 也不能是什么都进行重排,是在保证最终结果和代码顺序执行结果一致的情况下才可能进行重排。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
参考原文:https://github.com/crossoverJie/JCSprout/blob/master/MD/Threadcore.md