volatile关键字作用是为成员变量的同步访问提供了一种免锁机制,如果声明一个成员变量是volatile的,那么会通知编译器和虚拟机这个成员变量是可能其他线程并发更新的
会保证从线程的共享内存加载到线程工作内存是最新的,不会从线程工作缓存中读值
主要是为了安全地读取这个值
volatile变量不具有原子性
jvm运行时刻内存的分配:其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,(从线程内存中读值)
在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。
read and load 从主存复制变量到当前工作内存
use and assign 执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容
其中use and assign 可以多次出现
但是这些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
同步格言:如果向一个变量中写入值,而和这个变量接下来可能被另一个线程读取;或者,从一个变量中读值,而这个变量可能之前被另一个线程写入的,那么必须使用同步
final修饰的变量是线程安全的
多线程环境下,会共享同一份数据(线程公共的内存空间)。为了提高效率,JVM会为每一个线程设置一个线程私有的内存空间(线程工作内存),并将共享数据拷贝过来。写操作实际上写的是线程私有的数据。当写操作完毕后,将线程私有的数据写回到线程公共的内存空间。
如果在写回之前其他线程读取该数据,那么返回的可能是修改前的数据,视读取线程的执行效率而定。
内存可见性问题是,当多个线程操作共享数据时,彼此不可见。
解决这个问题有两种方法:
1、加锁:加锁会保证读取的数据一定是写回之后的,内存刷新。但是效率较低
public class TestVolatile {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start();
while(true){
synchronized (myThread) {
if(myThread.isFlag()){
System.out.println("flag被设置为true");
break;
}
}
}
}
}
class MyThread implements Runnable{
private volatile boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+flag);
}
}
2、volatile:会保证数据在读操作之前,上一次写操作必须生效,即写回。
1)修改volatile变量时会强制将修改后的值刷新的主内存中。
2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
相较于synchronized是一种较为轻量级的同步策略,但是
== volatile不具备互斥性;不能保证变量的原子性==
public class TestVolatile {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start();
while(true){
if(myThread.isFlag()){
System.out.println("flag被设置为true");
break;
}
}
}
}