【volatile】volatile引出的思考?到底是什么?

ps:疑问都是用红色的字体标识出来,最后引出知识点。。。

第一个疑问:volatile到底是什么?

volatile是Java虚拟机提供的轻量级的同步机制。

特性:能保证可见性和非原子性和禁止指令重排。 适用于:多线程环境下的单次操作(单次读或者单次写)

volatile的特性又引发出来了一个疑问,volatile不能保证原子性,那如何解决volatile的原子性呢?

两种方式:1.加synchronized修饰;2.使用我们juc(java.util.concurrent并发包)下的AtomicInteger

 

在了解volatile的使用场景之前要懂得一个知识什么是JMM,JMM就是Java虚拟模型,画个图解释一下

JMM有几大特性:可见性;原子性和有序性。

T1线程和T2线程同时去访问主物理内存,它得真实值是5,T1和T2把主物理内存得真实值5得副本拷贝到自己得工作线程中。若T1需要修改,则在自己的工作内存中进行修改,假如由5变为3,变更为3后需要同步到主物理内存中,此时主物理内存的真实值为3.此时T1变更主物理内存中的值,T2线程可见,然后T2就再次获取主物理内存的真实值,此时已经是3了,然后拷贝3的副本到T2的工作内存中,进行变更,回写。这就是简单的对JMM的口语化的描述。

原子性:线程不可分割,完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或被分割,需要整体完整。要么同时成功,要么同时失败。

有序性:为了保证有序需要禁止指令重排。

禁止指令重排小总结:多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。所以用volatile修饰变量禁止指令重排。

以上是JMM基本特性和认识,

要想线程安全获得保证:

1.工作内存和主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决,他们都可以使一个线程修改后的变量立即对其他线程可见
2.对于指令重排导致的可见性问题和有序性问题
可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

那继续之前的问题,volatile的适用场景,那些地方用到了呢?

单例模式DCL代码,也即双端检锁机制,Double Check Lock

private static volatile SingletonDemo instance = null;//用volatile修饰禁止指令重排
private SingletonDemo(){
    System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
}
//DCL模式 Double check Lock 双端检锁机制..就是加锁得前后都进行判断
public static SingletonDemo getInstance(){
    if (instance == null){
        synchronized (SingletonDemo.class){//新建实例的上下都要判断一遍
            if(instance == null){
                instance = new SingletonDemo();
            }
        }
    }
    return instance;
}

那大家看了上面的实现单例的代码,又会有另一个疑问?synchronized修饰之后,线程就安全了,这样就能实现了单例模式,为什么还要用volatile修饰呢?

但是大家忽略了一点:双端检索机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排。

单例模式volatile分析:

原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化

instance = new SingletonDemo();可以分为以下3步完成(伪代码)

 

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.初始化对象

但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。

所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题

总结以上,就是用volatile修饰,实现禁止指令重排,正确的实例化出来一个单例。Private static volatile SingletonDemo insatance=null;

 

由于上面的问题,volatile的其中一条特性是非原子性,解决非原子性其中一条是用juc下的AtomicInteger解决,那AtomicInteger又是什么呢,原理是什么呢?这就引出了CAS

Atomic的底层实现原理是CAS,见下一篇解释https://blog.csdn.net/qq_30546099/article/details/112614484

 

 

 

ps:有什么疑问可以留言讨论。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值