目录
场景二解释:声明单例为什么需要使用volatile、以及双重检查
六、synchronized/lock如何实现原子性、有序性、可见性
一、volidate关键字定义解释
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
强制将对缓存的修改操作(即写操作)立即写入主存;如果是写操作,导致其他线程中对应的缓存无效,让其他线程只能从主存中拿刚刚更新的。
2)禁止指令重排序。
3)volatile只能保证【可见性】、【有序性】,并不能保证【原子性】。
4)synchronized和Lock能保证【可见性】、【有序性】、【原子性】
二、概念1的解释:保证【可见性】
- 主存和线程的工作内存:
- Java内存模型规定所有的变量都是存在主存当中
- 每个线程都有自己的工作内存【可以理解为每个线程都会创建一个独立的工作栈】
- 线程在工作时,会将主存中的值读取到自己的工作内存中,然后在自己的工作内存中对该值进行改变,然后再刷新到主存中。这样就存在这么一种情况:
- 线程1从主存中读取i=10,并加载到自己的工作内存
- 线程1执行i=i+10,即将i的值改变为20
- 线程2从主存中读取i=10【因为此时线程1并未将i=20刷新到主存当中】
- 线程2执行i=i-5,将i=的值改变为5
- 然后线程1,线程2分别将值刷新到主存中
- 主线程从主存中再读取i值的时候,并不能得到10+10-5=15的正确结果
- 固需要volatile关键字来使改变后的值立即刷新到主存中,即遵循多线程的【可见性】
三、概念2的理解:保证【有序性】
- 了解java执行时的顺序
- 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
int a = 10; //A int r = 2; //B a = a + 3; //C r = a*a; //D
在执行的时候,可能存在B,A,C,D的顺序。
- 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
-
现有如下代码:
//线程1: context = loadContext(); //语句1 inited = true; //语句2 //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
上述代码期望的执行逻辑是:
-
线程1通过loadContext()方法对context进行赋值
-
然后将inited赋值为true
-
因为线程2是一个while循环,当inited被改变成true时,就跳出了循环,执行doSomethiingwithconfig(context)【此时context已经在线程1中被赋值】
-
但是因为javav的指令重排特性,可能存在如下执行顺序
-
先执行线程1的inited=true;
-
此时线程2监听到inited为true,开始执行doSomethiingwithconfig(context),但是由于cocntext还未被赋值,所以会抛出异常
-
线程1再通过loadContext()方法对context进行赋值
-
-
可以使用volatile修饰 inited=true来解决这个问题,添加了volatile以后的执行逻辑
-
因为volatile修饰了inited=true,固:会强制保证inited=true之前的代码已经执行完毕,且结果对inited=true及以后的代码执行是可见的
-
综上,执行的流程会与期望一致
-
-
四、volatile使用的场景
volatile能使用的场景不多,使用原则是
1)对volatile变量的写操作不依赖于当前值
2)该volatile变量没有包含在具有其他变量的不变式中
使用场景一:状态标记
volatile boolean inited = false; // inited 是 volatile 变量,是状态标志量
//线程1:
context = loadContext();
inited = true;
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
使用场景二:声明单例
class Singleton{
private volatile static Singleton instance = null; // instance 是 volatile 变量
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
场景二解释:声明单例为什么需要使用volatile、以及双重检查
五、哪些操作是原子性的
只有读取和赋值【而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作】才是原子操作
六、synchronized/lock如何实现原子性、有序性、可见性
原子性
synchronized和Lock通过保证任一时刻只有一个线程执行含有共享变量的代码块(对于没有 synchronized和Lock修饰的非同步方法、非同步代码块,不会阻塞的,它们与 synchronized和Lock无关),那么自然就不存在原子性问题了,从而保证了原子性。
有序性
synchronized和Lock通过保证同一时刻只有一个线程获取锁然后执行同步代码(保证原子性),并且在释放锁之前会将对变量的修改刷新到主存当中(保证可见性),因此可以保证可见性。
可见性
synchronized和Lock保证每个时刻是有一个线程执行同步代码(保证原子性),其原子内部顺序执行,保证有序性,原子外部没有互斥资源,不需要保证有序性,所有保证了有序性。