浅谈volatile

浅谈volatile

这篇文章我们主要了解一下几个问题

  • volatile的特性与指令重排序
  • DCL单例
  • volatile的实现,内存屏障

volatile的特性和指令重排序

首先volatile拥有可见性,这里就不过多解释了

然后另外一点是它能解决指令重排序。

那么问题来了什么是指令冲排序?

通俗的讲

cpu的速度至少比内存快100倍,为了提升效率,会打乱原来的执行顺序,比如先执行指令A,但是A执行的比较慢,那么这个时候可能会直接去执行指令B,B先执行完了,这样B指令可能就排在了A指令前面,但是前提是A指令和B指令没有依赖关系。

在更通俗一点就是cpu执行指令是乱序执行的,他们没有固定的顺序。

按道理说cpu这样做会让程序运行效率更高,为什么我们还要去解决指令重排序不让它乱序执行呢?

这里我们就要先谈谈new一个对象会有哪些过程

这里我借助idea的‘jclasslibs’来查看new一个对象具体干了些什么。

具体的代码如下

public class newObject {
    public static void main(String[] args) {
        Object o = new Object();
    }
}

一段没太大意义的代码

我们看下它具体调用了哪些Java指令

总共5条指令,我们来一一讲解

1.new:在Java堆内存中申请分配内存空间,并将地址压入栈中。

2.dup:复制操作数栈顶的值,然后在压入栈中,这个时候栈顶有两个一样的值。也就是两个一样的对象地址

3.invokespecial:调用初始化方法,这是一个实例方法,它会从操作数栈顶弹出一个this引用,也就是说它会弹出之前入栈的一个对象地址。

4.astore_1:这里的指令本质上是astore_n,当前帧的局部变量数组的索引,这条指令的具体含义就是,将堆栈顶部的内容存储到局部变量

5.return:返回

我们大致了解了这些指令的含义

我们梳理一下,new一个对象大致分这几步

1.在堆区分配对象需要的内存

2.对所有实例对象赋默认值

3.初始化

4.栈区地址引用

我们知道object的默认值是null,假如说现在第三步和第二步顺序调换了,那么原本我是要返回一个对象的给别人的,结果就会是返回一个空给别人。

这里我们模糊的描述了它大概会发生什么问题,接下来会有实际例子。

DCL单例

DCL单例模式也就是双重检查锁单例,普通的单例有什么问题在这里我们不进行过多讨论,我们这里只谈DCL。

先看下DCL单例的代码

public class DCLSingleton {
    private /** volatile  */ static DCLSingleton instance = null;
    public  static DCLSingleton getInstance() {
        if(null == instance) {    // 线程二检测到instance不为空
            synchronized (DCLSingleton.class) {
                if(null == instance) {
                    instance = new DCLSingleton();    // 线程一被指令重排,先执行了赋值,在执行初始化
                }
            }
        }
        return instance;
    }
}

这里我们具体解释一下

1.此时有两个线程都在调用getInstance(),这个时候instance还为null,此时线程A进来了,第四行代码的if判断会通过,在A线程还没跑完这个时候B线程也进来了,因为A还没到new对象那一步,所以B也会通过第四行代码的判断。

2.这个时候为了保证原子性,所以我们加一把锁,这个时候A在执行B就得等着。

3.A到了第6行代码,判断通过接着成功创建对象,然后释放锁,接着将单例对象返回了。

4.B获得到锁开始执行,但是因为A已经创建了对象此时instance已经不为null,所以判断不通过直接返回单例对象。

这是理想情况,但是假如说发生指令重排序,初始化变量和变量赋默认值的顺序对调了会怎么样

我们直接跳到第三步

A通过第6行代码的判断,在创建对象的时候发生了指令重排序,在还没成功初始化对象的时候就已经把对象赋值给了instance,也就是将默认值赋值给了instance,紧接着它会释放锁,此时还没初始化B线程进来了,发现还为null,那么它也去创建对象,这个时候它创建完以后直接返回,A初始化完了也直接返回,那么这里对象就被创建了两次。

此时它已经不是单例了。

这就是比较实际的指令重排序了,解决方法就是直接把代码里volatile取消注释就好了,其实对于DCL指令重排序有很多解决方法,这里就不展开讨论了。

volatile的实现,内存屏障

volatile是如何解决指令重排序的。

它是通过内存屏障去解决了指令重排序,具体有以下这些内存屏障,这些内存屏障是jvm级别的与操作系统不同。

jvm定义了内存屏障的规范

StoreStoreBarrier

volatile 写操作

StoreLoadBarrier

LoadLoadBarrier

volatile 读操作

LoadStoreBarrier

读写操作被这些内存屏障隔离他们之间不能指令重排

而jvm调用操作系统的实现则是各种各样的缓存一致性协议。

常见有mesi协议,是基于英特尔cpu。

操作系统级别解决指令重排序是基于锁总线和缓存一致性协议。

在深就不往下谈了,讲到这里已经比较深了。

原文地址:https://www.cnblogs.com/ccsert/p/12665989.html

时间: 2024-10-01 01:28:36

浅谈volatile的相关文章

浅谈Volatile与多线程

标题:浅谈Volatile与多线程 2011-04-19 22:49:17 最近看的比较杂,摘了一些人的笔记!随着多核的日益普及,越来越多的程序将通过多线程并行化的方式来提升性能.然而,编写正确的多线程程序一直是一件非常困的事情,volatile关键字的使用就是其中一个典型的例子. C/C++中的volatile一般不能用于多线程同步 在C/C++中,如果想把一个变量声明为volatile,就相当于告诉编译器这个变量是“易变的”,他随时可能在其他地方被修改,所以编译器不能对其做任何变化:即每次读

浅谈linux内核栈(基于3.16-rc4)

在3.16-rc4内核源码中,内核给每个进程分配的内核栈大小为8KB.这个内核栈被称为异常栈,在进程的内核空间运行时或者执行异常处理程序时,使用的都是异常栈,看下异常栈的代码(include/linux/sched.h): 1 union thread_union { 2 struct thread_info thread_info; 3 unsigned long stack[THREAD_SIZE/sizeof(long)]; 4 }; THREAD_SIZE值为8KB,因此内核为进程的异常

浅谈getaddrinfo函数的超时处理机制

在sockproxy上发现,getaddrinfo 解析域名相比ping对域名的解析,慢很多.我觉得ping用了gethostbyname解析域名.问题变为getaddrinfo解析域名,是否比 gethostbyname慢.写测试程序,分别用getaddrinfo和gethostbyname解析,发现getaddrinfo确实慢. strace跟踪发现,getaddrinfo和DNS服务器通信10次,gethostbyname和DNS服务器通信2次. gethostbyname是古老的域名解析

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

浅谈——页面静态化

现在互联网发展越来越迅速,对网站的性能要求越来越高,也就是如何应对高并发量.像12306需要应付上亿人同时来抢票,淘宝双十一--所以,如何提高网站的性能,是做网站都需要考虑的. 首先网站性能优化的方面有很多:1,使用缓存,最传统的一级二级缓存:2,将服务和数据库分开,使用不同的服务器,分工更加明确,效率更加高:3,分布式,提供多台服务器,利用反向代理服务器nginx进行反向代理,将请求分散开来:4,数据库的读写分离,不同的数据库,将读操作和写操作分开,并实时同步即可:5,分布式缓存,使用memc

单页应用SEO浅谈

单页应用SEO浅谈 前言 单页应用(Single Page Application)越来越受web开发者欢迎,单页应用的体验可以模拟原生应用,一次开发,多端兼容.单页应用并不是一个全新发明的技术,而是随着互联网的发展,满足用户体验的一种综合技术. SEO 一直以来,搜索引擎优化(SEO)是开发者容易忽略的部分.SEO是针对搜索(Google.百度.雅虎搜索等)在技术细节上的优化,例如语义.搜索关键词与内容相关性.收录量.搜索排名等.SEO也是同行.市场竞争常用的的营销手段.Google.百度的搜

浅谈html标签

浅谈html各常用标签用法 标题标签:<h1>-<h6>来表示,使标题字体变粗. <br />换行标记 <hr />水平分隔符 &nbsp空格符 &copy版权符 <a href>a标签超链接 href可接链接地址 <p>段落标签<blockquote>引用标签及可用做缩进 <table>表格中的<ul>无序列表<ol>有序列表<dl>自定义列表<row

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

[nRF51822] 14、浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构(科普类干货)

蓝牙在短距离无线通信领域占据举足轻重的地位—— 从手机.平板.PC到车载设备, 到耳机.游戏手柄.音响.电视, 再到手环.电子秤.智能医疗器械(血糖仪.数字血压计.血气计.数字脉搏/心率监视器.数字体温计.耳温枪.皮肤水分计等), 再到智能家居等领域均占有一席之地. 而蓝牙低功耗(BLE)是在蓝牙4.0协议上修改以适用低功耗应用场景的一种蓝牙协议. 随着上一股智能消费类电子大潮的到来,BLE的各种应用也像雨后春笋般在市场上铺开. 如果想 紧跟蓝牙协议的最新动态 ,可以在https://www.b