赞
踩
目录
场景二解释:声明单例为什么需要使用volatile、以及双重检查
六、synchronized/lock如何实现原子性、有序性、可见性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
强制将对缓存的修改操作(即写操作)立即写入主存;如果是写操作,导致其他线程中对应的缓存无效,让其他线程只能从主存中拿刚刚更新的。
2)禁止指令重排序。
3)volatile只能保证【可见性】、【有序性】,并不能保证【原子性】。
4)synchronized和Lock能保证【可见性】、【有序性】、【原子性】
- int a = 10; //A
- int r = 2; //B
- a = a + 3; //C
- r = a*a; //D
在执行的时候,可能存在B,A,C,D的顺序。
现有如下代码:
- //线程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及以后的代码执行是可见的
综上,执行的流程会与期望一致
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;
- }
- }
只有读取和赋值【而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作】才是原子操作
synchronized和Lock通过保证任一时刻只有一个线程执行含有共享变量的代码块(对于没有 synchronized和Lock修饰的非同步方法、非同步代码块,不会阻塞的,它们与 synchronized和Lock无关),那么自然就不存在原子性问题了,从而保证了原子性。
synchronized和Lock通过保证同一时刻只有一个线程获取锁然后执行同步代码(保证原子性),并且在释放锁之前会将对变量的修改刷新到主存当中(保证可见性),因此可以保证可见性。
synchronized和Lock保证每个时刻是有一个线程执行同步代码(保证原子性),其原子内部顺序执行,保证有序性,原子外部没有互斥资源,不需要保证有序性,所有保证了有序性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。