java论坛设计模式_Java设计模式(6)

本文介绍Java设计模式第六种

这种模式是在第五种模式的基础之上演变而来,常被成为DCL(double check lock)单例模式,同时,在静态变量上面加上了volatile关键字(一定要加),这个关键字一般来说有两个作用: 内存可见和禁止指令重排序。

首先,我们来假设不加这个关键字会出现什么问题。假设两个线程同时运行到第一个if (instance == null)这段代码,此时假设线程1抢到了锁,那么它可以进行下面的同步代码块,在同步代码块里面,它又检查了一次实例是否为null,那么此时instance肯定为null,它顺利的执行了实例化代码,并且释放了锁对象,此时线程2得以执行。它执行到代码块里面的if (instance == null)会发生什么呢?

我们假设使用的多核CPU,接下来分析一下两个线程的内存模型

8358cebf1dfc0e26bc9046f797623d9e.png

从图中可以看出,假如两个线程不再同一个cache中,他们获取的对象都是主存里面的对象的一个复制,那么判断instance肯定是为空的,但是加入了volatile关键字之后,通过MESI缓存一致性协议之后,可以保证在任何一个cache当中的同一个数据发生改变之后,会通知其他cache里面的数据的副本失效,让它们重新从主存里面去获取数据。这就是内存的可见性所起到的作用。那么禁止指令重排序,这里又有什么作用呢?

原来,编译器在进行编译的时候,有可能会将一些指令进行重新排序,以达到性能优化的目的。但是此时假如代码执行到instance = new Student06();时,线程1将instance进行实例化,此时在栈中分配了instance这个引用,同时让这个引用指向了堆中的一个对象。此时如果线程2执行到了它上面的判断instance是否为null的时候,instance肯定是不为null的,因此可以跳过这一步,拿到的就是线程1分配的那个instance引用。但是这个instance在堆中,有可能只是开辟了空间,并没有完成数据的初始化。

代码部分

package singleton;

public class Student06 {

private Student06(){

}

//一定要加上volatile关键字,不然在编译阶段进行指令优化的时候可能会进行指令重排序

//也就是执行new 对象的时候,先返回了对象的引用,此时返回的引用对象还是null;

//也就是第二次检查的时候,发现install还是null,那么就又new 了多个,但是加上了volatile关键字之后

//会告诉虚拟机,禁止指令重新排序

public static volatile Student06 instance;

public static Student06 getInstance() {

if (instance == null) {

synchronized (Student01.class) {

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

if (instance == null) {

instance = new Student06();

}

}

}

return instance;

}

public static void main(String[] args) {

for (int i = 0; i < 100; i++) {

new Thread(new Runnable() {

@Override

public void run() {

System.out.println(Student06.getInstance().hashCode());

}

}).start();

}

}

}

对象的初始化以及指令重排分析

我写了一个类用来分析java生成的字节码

package singleton;

/**

* @Author: micro cloud fly

* @Description: 测试字节码文件

* @Date: Created in 4:39 下午 2020/11/2

*/

public class ByteCode {

int a=10;

public static void main(String[] args) {

ByteCode byteCode = new ByteCode();

}

}

生成的字节码如下

0 new #3

3 dup

4 invokespecial #4 >

7 astore_1

8 return

来分析一下这段字节码

0代表调用new这个方法的时候,此时,会在堆中分配一块内存,此时成员变量a为int类型的初始值0

4代表调用构造函数init方法的时候,将对象初始化,此时a=8,堆中的对象初始化完毕

7代表将byteCode这个引用指向堆中的这个对象

什么时候会重新排序呢?这个要看编译器,比如JIT编译器在发现上一行的代码和下一行的代码没有任何关系,在单线程中不会影响程序的执行的时候,就会执行,比如上面的分析中,执行的顺序可能是074,那么执行到7的时候,byteCode已经不是null了,此时a=0,返回的对象的成员变量a=0,那么它和我们需要的对象中的a=8的就不是同一个对象,因为它只是一个半初始化化的对象,所以hashcode值也不相同了。

加上violate关键字之后,jvm使用了内存屏障,对于临界区的资源禁止指令重排序,所以不会发生类似的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值