
1 什么是单例模式?
单例模式顾名思义就是 无论实例化(new)对象多少次只能是一个对象的设计模式叫做单例模式
2 单例模式如何实现
废话不多说直接上代码:
public class Mgr02 { private static volatile Mgr02 INSTANCE; public Mgr02() { } public static Mgr02 getInstance() { //业务代码略过。。。。。。。。。。。。。。。。。。。。 if (INSTANCE == null) { //双重检查 synchronized (Mgr02.class) { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr02(); } } } 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(Mgr02.getInstance().hashCode()); } }).start(); } } }
3 加个知识点 初始化对象的几个步骤:
Mgr02 m = new Mgr02() ;
1 .第一步 memory=allocaate() //1分配对象的内存空间
2.第二步 m(memory) //2 初始化对象 给成员属性赋值
3 第三步 m = memory //3 设置m引用指向第一步分配的内存空间 并生成地址值,此时 m!= null
重点来了!!!!
步骤2和步骤3 不存在数据依赖关系 ,而且无论重排前还是重排后程序的执行结果在单线程中都是没有改变的,因此这种重排是底层编译器允许的。(坑就出在这)!!
4 单例模式实现的小细节问题
上述模式称作为双检索模式 也是多线程环境下单例模式的一种 我个人比较喜欢这个,它会对当前类中的实例变量进行双重检查,由于加上了Sync关键字保证了线程的原子性和可见性,所以说当第一次线程初始化实例变量后写回主内存,其他线程的工作内存区的变量会刷新为主内存区新的变量值,这样就会保证多线程情况只有且只能有一个实例好的对象。
大家注意我提到了sync保证了线程的访问变量的原子和可见 但是排序性通过sync代码块的形式是无法实现的 所以我们只能在实例变量上面加volitale关键字来保证排序性。
大家可能有疑问 不加volitale会出现排序性问题么??
实际操作概率估计几百万分之1了 但是理论来说却是会有的
看上面第三大点的初始化对象的步骤 ,正常的步骤是1,2,3 由于2,3没有数据依赖关系 就可能出现 132 的顺序 在单线程的情况下是没问题的
但是注意!!!!
在多线程情况下就有问题了,指令重排在第一次线程检测的时候 1,3 还没有到2 ,第一个线程的初始化还没有完全完成 第二个线程去主内存区拿到的是半初始化的对象 就是说 (对象==null),
这个时候第二个线程检测对象为空 那就会创建新对象 ,同时第一个线程初始化结束也会创建一个新对象,这样的话最终有可能导致创建n个不同的对象 违反了多线程下单例模式的设计原则
5 单例模式用在哪
后期补齐 待定============
原创不易 有问题欢迎大家指正讨论 谢谢大家!!