//懒汉单例模式双重校检
public class Example2 {
private static volatile Example2 example2;//为什么要加volatile?
private Example2() {
}
public static Example2 getInstance() {
if (example2 == null) {//提高效率,不需要每次加锁完成再去判断是不是空
synchronized (Example2.class) {
if (example2 == null) {//防止创建多个实例
example2 = new Example2();
}
}
}
return example2;
}
}
肯定需要加啊,不然问你干什么
要判断需要不需要加volatile首先要知道volatile在java程序里能够起到什么样的作用?
- 确保线程可见性
- 线程的可见性是由缓存刷新来实现
- 当一个线程从主存中读入了一个volatile修饰的变量并对其修改那么,修改之后的值会立刻回写到主存当中,并且激发总线嗅探机制,使缓存中的数据失效,并重新从主存中读取。
- 禁止指令重排序
- 现代计算机由于cpu的执行速度与内存的读取速度差异太大,而又无法从硬件上获得突破,所以cpu的乱序执行出现了,在指令没有依赖的情况下,由cpu自动的对代码进行优化,提高cpu的执行效率。加入了volatile,在volatile作用的范围没有依赖的代码之间会生成内存屏障来阻碍指令的重排序
jvm中对象的创建过程:
- 申请内存
- 初始化
- 返回地址
其中初始化与返回地址没有必然的依赖关系符合指令重排序的条件,那么cpu在执行jvm创建过程中如果不加volatile就会出现返回地址但是对象的初始化没有完成(对象不可用)这就是半初始化问题,半初始化意味着对象的创建没有完成,当我们使用此对象的时候就会报错。
为什么不能所有的java代码都用volatile来防止重排序?
- CPU的速度至少比内存快100倍,为了提升效率,会打乱原来的执行效率,会在一条指令执行过程中(比如去内存读数据,大概慢100多倍),去同时执行另一条指令,前提是两条指令没有依赖关系。 提升效率只占这个问题的很小一部分。
- cpu执行过程中的伪共享问题:缓存中的数据是以行为单位进行存储,我们称之为缓存行。而cpu与高速缓存的数据交换是以字为单位进行交换,列如64位计算机一次能够执行两个双字,在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享。伪共享问题十分耗费cpu性能