volatile 关键字
Java 内存模型(JMM)
在Java内存模型中,线程有自己的工作内存,工作内存存储在寄存器或高速缓存中,线程读取或写入的是工作内存中的变量副本,而不是直接读写主内存,这样用寄存器或高速缓存解决了处理器与内存读写速度不一致的问题。
这样做可能会造成一个线程在主内存修改了一个变量,而另外一个线程还继续使用它在工作内存中的变量拷贝,造成数据不一致的情况。
volatile 关键字的作用
将变量声明为volatile可以解决这个问题,保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。
另外volatile关键字的主要作用是在生成指令时在适当的位置添加内存屏障的方式来禁止特定类型的处理器进行指令重排序
可见性的原理
volatile是基于Inter的MESI缓存一致性协议的,该协议是用来解决多个CPU间在涉及到同一块内存区域的数据时,各自的缓存数据不一致的问题。
MESI的内容大概为在CPU写数据时,如果发现操作的变量为共享变量也就是说在其他CPU中存在该变量的副本,就会通知其他CPU将该变量的缓存设置为无效,其他CPU读取这个变量发现是无效的就会从内存中重新读取。
CPU是通过总线嗅探来得知自己缓存中的数据是否过期,当发现自己缓存行对应的内存地址被修改,会将缓存行设置为无效,当下次处理这个无效缓存行时,会从内存中读取。
所以过多的使用volatile会导致总线风暴,大量无效的交互会使总线带宽打满
重排序了解吗?
- 重排序:在Java中允许编译器对指令进行重排序来获取比较好的性能,对于单线程重排序过程不会影响程序的执行,但会影响到多线程并发执行的正确性。
并发编程的三个重要特性
- 原子性:一个操作或多次操作,要么所有操作全部都得到执行,要么都不执行,不会出现受到其他因素而中断执行的情况。synchronized关键字可以保证代码片段的原子性。
- 可见性:当一个线程对共享变量进行了修改,那么其他线程是立即可以看到修改后的最新值,volatile可以保证共享变量的可见性。
- 有序性:java在编译以及运行时会对代码进行优化,代码实际的运行顺序未必就是编写代码时的顺序,volatile可以禁止指令进行重排序。
说说 synchronized 关键字和 volatile 关键字的区别
- volatile不能保证数据的原子性可能造成线程的安全问题,而synchronized能保证线程安全
- volatile主要作用是解决变量在多个线程间的可见性以及有序性,而synchronized关键字是解决多线程间访问资源的同步性。
- Volatile关键字只能作用于变量,synchronized能修饰方法和代码块
- 多线程访问volatile变量不会阻塞,而多线程访问synchronized代码块会造成阻塞。