java堆存储对象和数组,是一块线程共享数据区,但是实际线程运行的时候,对于用到的对象都会在线程私有空间即虚拟机栈保存一个副本,为了效率。这两快内存叫主内存和工作内存。java规定所有线程对变量的操作先从主内存取得副本,然后所有的操作必须对工作内存进行(说的是堆变量)
定义了8个内存指令:
lock与unlock,与此处无关
read:把内存里的变量值拷贝到私有栈的缓存区域
load:把缓存区域里的变量值赋给线程私有变量
use:把私有变量值推入虚拟机执行引擎
assign:把虚拟机执行引擎的新值赋给私有变量
store:把私有变量的值拷贝到缓存区域
write:把缓存区域的值写回内存
如果拷贝到工作内存,必须read load,如果写回主内存,必须store write。一个线程写回前,可以多次修改本地副本变量。
以上是普通变量的情况。那么一个线程对变量的修改,可能无法让其他线程立刻看到,是一个问题。
volatile关键字就是解决线程可见性的。
通常会说,volatile是一种轻量级同步机制,个人感觉不太好,所谓同步还是和synchronized关键字比较吻合,就是线程必须一个一个来运行,而volatile关键字更多的是可见性,因此说成是种线程轻量级通讯机制更好。
那么volative是如何实现可见性?
java内存模型对被volatile关键字修饰的变量进行了特殊处理:
use指令执行前必须read和load,assign指令执行后必须store和write,如此,线程的修改是可见的。
如此以外,volatile还有另一个语义,防止指令重排序。如果两个代码是不相关的,执行的时候,顺序可能不是代码写的那样,比如先改a的值,后改b,最后可能b先被改。因为是不相关的代码,所以单线程是没有影响的,感觉不到,但是多线程环境下就会有问题,所以我们不能依赖顺序,如果想避免重排序,那么就可以加上volatile关键字,该变量就不会被冲排序。
最后,使用volatile还是同步,取决于具体的场景,哪一个的语义更加契合,如果volatile可以解决就使用volatile,效率相比同步更高