面试官:你平时是怎么创建单例的?
我:我一般用DCL双重检锁的方式来创建单例,然后为 instance 加上 volatile 修饰,防止 DCL 失效。
面试官:那你可以具体说说 volatile 吗?
我:行!
前言
相信很多 Andorid程序员跟我一样,最开始接触到 volatile 这个关键字是在创建单例的时候,如:
public class SingleTon {
//为了防止出现 DCL失效问题,加上 volatile 关键字
private static volatile SingleTon instance;
public static SingleTon getInstance() {
if (instance == null) {
//同步锁,保证同一时刻只有一个线程进入该代码块。
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
当我们使用双重检锁(DCL)来创建单例的时候,我们会为 instance 加上 volatile关键字修饰,来防止出现DCL失效。这里其实就是利用 volatile 可以禁止指令重排序功能,来防止出现 DCL 失效问题。
那 volatile 到底是如何防止出现DCL失效,是怎么做到的呢?让我们一起来往下学习。
另外如果你想进一步了解 Synchronized,可以看我的另一篇文章 Android程序员重头学Synchronized
线程安全
就如刚刚那个创建单例的代码来说,我们需要考虑其在多线程下的运行情况,也就是在多线程并发中,线程是否安全?那线程在什么样的情况下我们可以称之为是线程安全呢?
答: 线程在保证 可见性、有序性、原子性 的情况下,就可以称之为是线程安全的。
那可见性、有序性、原子性又是什么呢?别急~,在介绍这三种特性之前,我们需要先了解一下 Java内存模型。
Java内存模型(Java Memory Model,JMM): 是 Java虚拟机规范中定义的,用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java程序在各种平台下都能达到一致的并发效果,JMM 规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
嗯~~??你还是去一下看 深入理解Java虚拟机 12.3 Java内存模型 吧。
可见性
我们都知道在计算机中 CPU 的计算速度是非常快的,但是绝大多数的计算任务光靠 CPU 是完不成的,CPU 需要与内存进行交互,也就是从内存中获取数据及将计算结果写入到内存中,相对比,这个速度就很慢了。因此,间接导致了 CPU 的计算速度大打折扣,所以为了解决这个问题,现在 CPU 厂商会在 CPU 中内置高速缓冲存储器,CPU 不再直接与内存进行交互,而是与高速缓冲存储器进行信息交换。
之前看到的一个段子,很贴切了:
内存:你跑慢点行不行?
CPU:跑慢点你养我吗?
内存:我不管!
CPU:那我只能找高速缓冲存储器了!
那这高速缓冲存储器是什么呢?百度百科是这么介绍的:
高速缓冲存储器是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。
主要由三大部分组成:
- Cache存储体:存放由主存调入的指令与数据块。
- 地址转换部件:建立目录表以实现主存地址到缓存地址的转换。
- 替换部件:在缓存已满时按一定策略进行数据块替换,并修改地址转换部件。
从此,CPU 直接从高速缓冲存储器中读取数据,计算速度大大提升ÿ