面试总结一:对volatile的理解
对volatile的理解
本节内容综述如下:
volatile:JVM提供的轻量级同步机制(可见性、不保原子性、禁指令重排)
JMM:抽象、规范、不真实存在(可见性、原子性、有序性要禁重排)
单例模式:单线程版、DCL版、DCL优化volatile版
volatile
保证可见性
验证volatile的可见性
1.如果int num= ,number变量没有添加volatile关键字修饰
2.如果添加了volatile修饰,可以解决可见性
如果不加 volatile 关键字,则主线程会进入死循环,加 volatile 则主线程能够退出,说明加了 volatile 关键字变量,当有一个线程修改了值,会马上被另一个线程感知到,当前值作废,从新从主内存中获取值。对其他线程可见,这就叫可见性。
package thread.Volatile1;
import java.util.concurrent.TimeUnit;
/**
* 验证volatile的可见性
* 1.如果int num= ,number变量没有添加volatile关键字修饰
* 2.如果添加了volatile修饰,可以解决可见性
*/
class MyData {
int num = 0;
// volatile int num = 0;
public void addToSixty() {
this.num = 60;
}
}
public class VolatileDemo1 {
public static void main(String[] args) {
visibilityByVolatile();
}
//volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
public static void visibilityByVolatile() {
MyData myData = new MyData();
//第一个线程
new Thread(( )->{
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
//线程暂停3s
TimeUnit.SECONDS.sleep(3);
myData.addToSixty();
System.out.println(Thread.currentThread().getName()+"\t update value:"+myData.num);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"thread1").start();
//第二个线程是main线程
while (myData.num == 0) {
//如果myData的num一直为零,main线程就一直在这里循环
}
System.out.println(Thread.currentThread().getName()+"\t mission is over , num value is"+myData.num);
}
}
不保证原子性
Volatile不保证原子性
1.原子性是什么?
不可分割,完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么、同时失败
2.如何解决原子性?
①方法加synchronized
②Atomic
package thread.Volatile2;
/**
* Created with IntelliJ IDEA.2018.2.8.x64.
* User: JIAN
* Date: 2019/12/20 Time: 11:04
* Description: 验证Volatile不保证原子性
*/
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Volatile不保证原子性
* 1.原子性是什么?
* 不可分割,完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么、同时失败
* 2.如何解决原子性?
* 1.方法加synchronized
* 2.Atomic
*/
class MyData {
volatile int number = 0;
public void addTo60() {
this.number = 60;
}
//注意:此时number前面是加了volatile关键字修饰的,volatile是不保证原子性的
public /*1.方法加 synchronized*/ void addPlusPlus() {
number++;
}
AtomicInteger atomicInteger=new AtomicInteger();
public void addAtomic() {
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo2 {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
myData.addPlusPlus();
myData.addAtomic();
}
}, String.valueOf(i)).start();
}
//需要等待上面20个线程全部都计算完成后,再用main线程取得最终的结果值看是多少?
//暂停一会儿线程
/*try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
while (Thread.activeCount() > 2) {//这个while循环约等于上面注释的代码
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.number);
System.out.println(Thread.currentThread().getName()+"\t finally atomicnum value is "+myData.atomicInteger);
}
}
禁止指令排序
计算机在执行程序时,为了提高性能,编译器个处理器常常会对指令做重排,一般分为以下 3 种
①编译器优化的重排
②指令并行的重排
③内存系统的重排
单线程环境里面确保程序最终执行的结果和代码执行的结果一致
处理器在进行重排序时必须考虑指令之间的【数据依赖性】
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证用的变量能否一致性是无法确定的,结果无法预测
JMM(Java内存模型)
对volatile的实操(如单例模式)
单线程版
package thread.Sigleton;
/**
* Created with IntelliJ IDEA.2018.2.8.x64.
* User: JIAN
* Date: 2019/12/21 Time: 8:40
* Description: NoDescription
*/
//单机版下的单例模式
public class singletonDemo1 {
private static singletonDemo1 instance = null;
private singletonDemo1() {
System.out.println(Thread.currentThread().getName()+" \t 我是构造方法singletonDemo().....");
}
public static /*可以解决,但是锁了整个代码,并发性严重下降synchronized*/ singletonDemo1 getInstance() {
if (instance == null) {
instance = new singletonDemo1();//真正控制的这一行
}
return instance;
}
public static void main(String[] args) {
//单线程(main线程的操作动作。。。)
/*System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());
System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());
System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());
System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());*/
//并发之后,情况发生了很大的变化
for(int i=1;i<=10;i++){
new Thread(()->{
singletonDemo1.getInstance();
},String.valueOf(i)).start();
}
}
}
DCL版+ volatile优化版
package thread.Sigleton;
/**
* Created with IntelliJ IDEA.2018.2.8.x64.
* User: JIAN
* Date: 2019/12/21 Time: 8:40
* Description: NoDescription
*/
/**
* DCL版下的单例模式(Double Check Lock 双端检锁机制)
*
* 双端检锁机制不一定线程安全,原因是有指令重排序的存在
* 加入volatile可以禁止指令重排序!
*
*/
public class singletonDemo2 {
private static volatile singletonDemo2 instance = null;//禁止指令重排
private singletonDemo2() {
System.out.println(Thread.currentThread().getName()+" \t 我是构造方法singletonDemo().....");
}
public static /*可以解决,但是锁了整个代码,并发性严重下降synchronized*/ singletonDemo2 getInstance() {
if (instance == null) {
synchronized (singletonDemo2.class) {
if (instance == null) {
instance = new singletonDemo2();//真正控制的这一行
}
}
}
return instance;
}
public static void main(String[] args) {
//单线程(main线程的操作动作。。。)
/*System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());
System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());
System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());
System.out.println(singletonDemo.getInstance()==singletonDemo.getInstance());*/
//并发之后,情况发生了很大的变化
for(int i=1;i<=10;i++){
new Thread(()->{
singletonDemo2.getInstance();
},String.valueOf(i)).start();
}
}
}
如果没有加 volatile 就不一定是线程安全的,原因是指令重排序的存在,加入 volatile 可以禁止指令重排。
原因是在于某一个线程执行到第一次检测,读取到的 instance 不为 null 时,instance 的引用对象可能还没有完成初始化。
instance = new Singleton() 可以分为以下三步完成
memory = allocate(); // 1.分配对象空间
instance(memory); // 2.初始化对象
instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance != null
步骤 2 和步骤 3 不存在依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种优化是允许的。
会可能发生以下重排序
memory = allocate(); // 1.分配对象空间
instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance != null,但对象还没有初始化完成
instance(memory); // 2.初始化对象
所以不加 volatile 返回的实例不为空,但可能是未初始化的实例
上一篇: 初始化之线程模型