前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >voliate工作实际应用场景

voliate工作实际应用场景

作者头像
公众号 IT老哥
修改2020-09-21 15:06:29
6120
修改2020-09-21 15:06:29
举报

本文源自 公-众-号 IT老哥 的分享

哈喽大家好,我是IT老哥,今天我们来讲讲面试必问的voliate

单线程的情况下呢,我们肯定用不到这个voliate

只有在多线程的情景下才能用到,文章结尾我会举一个经典的案例

voliate三特性

  • 保证可见性;
  • 不保证复合操作的原子性;
  • 禁止指令重排。

第一:可见性

先给大家介绍一下JMM的内存模型

我们定义的共享变量就是存在主内存中,每个线程内的变量是在工作内存中操作的,当一个线程A修改了主内存里的一个共享变量,这个时候线程B是不知道这个值已经修改了,因为线程之间的工作内存是互相不可见的

那么这个时候voliate的作用就是让A、B线程可以互相感知到对方对共享变量的修改,当线程A更新了共享数据,会将数据刷回到主内存中,而线程B每次去读共享数据时去主内存中读取,这样就保证了线程之间的可见性

这种保证内存可见性的机制是:内存屏障(memory barrier)

内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。 内存屏障有两个作用:

1.阻止屏障两侧的指令重排序; 2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

这里不深入说这个机制了,对于大家目前的情况可能理解起来比较困难

第二:不保证复合操作的原子性

1、什么叫原子性?

所谓原子性,就是说一个操作不可被分割或加塞,要么全部执行,要么全不执行。

代码语言:javascript
复制
i = 0;            ---1
j = i ;           ---2
i++;              ---3
i = j + 1;        ---4

上面四个操作,有哪个几个是原子操作,那几个不是?如果不是很理解,可能会认为都是原子性操作,其实只有1才是原子操作,其余均不是。

  • 1---在Java中,对基本数据类型的变量和赋值操作都是原子性操作;
  • 2---包含了两个操作:读取i,将i值赋值给j
  • 3---包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
  • 4---同三一样

Java只保证了基本数据类型的变量和赋值操作才是原子性的(注:在32位的JDK环境下,对64位数据的读取不是原子性操作*,如long、double)

第三:有序性(禁止jvm对代码进行重排序)

有序性:即程序执行的顺序按照代码的先后顺序执行。

一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:

  1. 在单线程环境下不能改变程序运行的结果;
  2. 存在数据依赖关系的不允许重排序

第四:举个非常常见的voliate用法—DCL

什么是DCL呢,其实就是double check lock的简写

DCL很多人都在单利中用过,如下这种写法:

代码语言:javascript
复制
public class Singleton {
   private static Singleton singleton;
   private Singleton(){}

   public static Singleton getInstance(){
       if(singleton == null){                              // 1
           synchronized (Singleton.class){                 // 2
               if(singleton == null){                      // 3
                   singleton = new Singleton();            // 4
               }
           }
       }
       return singleton;
   }
}

表面上这个代码看起来很完美,但是其实有问题

先说一下他完美的一面吧:

1、如果检查第一个singleton不为null,则不需要执行下面的加锁动作,极大提高了程序的性能;

2、如果第一个singleton为null,即使有多个线程同一时间判断,但是由于synchronized的存在,只会有一个线程能够创建对象;

3、当第一个获取锁的线程创建完成后singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象;

但是到底是哪里有错误呢,听老哥细细分析

首先创建一个对象分为三个步骤:

1、分配内存空间

2、初始化对象

3、讲内存空间的地址赋值给对象的引用

但是上面我讲了,jvm可能会对代码进行重排序,所以2和3可能会颠倒,

就会变成 1 —> 3 —> 2的过程,

那么当第一个线程A抢到锁执行初始化对象时,发生了代码重排序,3和2颠倒了,这个时候对象对象还没初始化,但是对象的引用已经不为空了,

所以当第二个线程B遇到第一个if判断时不为空,这个时候就会直接返回对象,但此时A线程还没执行完步骤2(初始化对象)。就会造成线程B其实是拿到一个空的对象。造成空指针问题。

解决方案:

既然上面的问题是由于jvm对代码重排序造成的,那我们禁止重排序不就好了吗?

voliate刚好可以禁止重排序,所以改造后的代码如下:

代码语言:javascript
复制

public class Singleton {
   //通过volatile关键字来确保安全
   private volatile static Singleton singleton;

   private Singleton(){}

   public static Singleton getInstance(){
       if(singleton == null){
           synchronized (Singleton.class){
               if(singleton == null){
                   singleton = new Singleton();
               }
           }
       }
       return singleton;
   }
}

这样就不会存在2和3颠倒的问题了

解决方案二:

基于类初始化

该解决方案的根本就在于:利用classloder的机制来保证初始化instance时只有一个线程。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。

代码语言:javascript
复制
public class Singleton {
   private static class SingletonHolder{
       public static Singleton singleton = new Singleton();
   }

   public static Singleton getInstance(){
       return SingletonHolder.singleton;
   }
}

这种解决方案的实质是:允许步骤2和步骤3重排序,但是不允许其他线程看见。

晚安,兄弟们!

云服务器云硬盘数据库(包括MySQL、Redis、MongoDB、SQL Server),CDN流量包,短信流量包,cos资源包,消息队列ckafka,点播资源包,实时音视频套餐,网站管家(WAF),大禹BGP高防(包含高防包及高防IP),云解析SSL证书,手游安全MTP移动应用安全云直播等等。

代码语言:javascript
复制
给个[在看],是对IT老哥最大的支持
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT老哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文源自 公-众-号 IT老哥 的分享
相关产品与服务
实时音视频
实时音视频(Tencent RTC)基于腾讯21年来在网络与音视频技术上的深度积累,以多人音视频通话和低延时互动直播两大场景化方案,通过腾讯云服务向开发者开放,致力于帮助开发者快速搭建低成本、低延时、高品质的音视频互动解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档