有一半(1/3+1/6)的关系!
因为java内存模型的三个特性,volatile满足了两个!
1、可见性(满足)
2、有序性(满足了一半)
3、原子性(不满足)
下面解释下:
可见性:多个线程使用到同一个变量时,一旦有一个线程修改了变量,其他线程再读取时一定是读取到这个修改后的值。
有序性:程序执行的顺序按照代码的先后顺序执行。不会发生指令重排序,或者指令重排序在多线程环境下显现出有序性!
原子性:一个不可以再分割的操作即为原子操作。如:a = 0(a为int类型);
关于有序性(满足了一半),我下面举两个例子说明一下!
演示下指令重排序的例子1:(a加了volatile修饰,依然会发生指令重排序的!!)
public class Test {
private volatile int a;
private int b;
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
final Test test = new Test();
new Thread(new Runnable() {
@Override
public void run() {
test.a = 1;
test.b = 1;//发生指令重排序的话,b会被先赋值,a还是默认值0
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
if (0 == test.a && test.b == 1) {
System.out.println("发生指令重排序!");
}
}
}).start();
}
}
}
运行了几次,控制台打印出了:
发生指令重排序!
下面再举例一个volatile“禁止”了指令重排序的例子2:(指令重排序会有内存屏障)
参考自:https://blog.csdn.net/weixin_37817685/article/details/80261549
public class Singleton {
private Singleton() { }
private volatile static Singleton instance;
public Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
注意:singleton = new Singleton(),这不是一个原子操作,一共有3个步骤:
1. 给 singleton 分配内存
2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)
假如没有volatile修饰,有两个线程同时调用了getInstance()方法,其中一个线程A执行到singleton = new Singleton();这一句,发生了指令重排序【1-3-2】,当2还没有执行的时候,线程B发现instance不为null,直接返回instance(此时instance还没有成员变量的信息,因为2还没有执行),线程B使用instance就可能会出错了。
volatile关键字的一个作用是“禁止”指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。
注意:volatile阻止的不是singleton = new Singleton()这句话内部【1-2-3】的指令重排,而是保证了在一个写操作【1-2-3】或者【1-3-2】完成之前,不会调用读操作(if (instance == null))。
从例子2可以看出,我必须等到你写完了才能读(volatile的写屏障),表现出来的指令重排序在多线程环境下显现出有序性。
下面说一下volatile满足了可见性却不满足原子性的问题!代码如下:
private volatile static int count = 0;
public static void main(String[] args){
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
count++;
System.out.println("count=" + count);
}
}).start();
}
}
有时打印到9999,有时打印到9998。是不是发现不对劲了,看看上面的可见性:
可见性:多个线程使用到同一个变量时,一旦有一个线程修改了变量,其他线程再读取时一定是读取到这个修改后的值。
其实是因为count++(count = count + 1)并非是一个原子操作,看下图:
图片来源:https://blog.csdn.net/strivenoend/article/details/80440884
例如当主存中count变量为99,现在线程A去Read主存中的count,并存到线程内部工作栈99,然后执行count+1了,这时,另一个线程已经将主存中的count更新为100了,这会使得线程内部工作栈count为99的副本失效(使缓存行无效)。但是线程A并不需要去读取主存中的count(100),而是将自己运算的count+1(100)写到线程的缓存行,然后再写回主存,还是100!!!两个线程都count++了,结果count才加了1。
出现上述问题的原因是,count++不是一个原子操作,volatile不满足原子性。
如果对您有用的话赞一下呗!谢谢!