创建型:Singleton(单例模式)
单例模式,或者称为元件模式。一般来说,在所有模式中,属于最小代码实现的翘楚。刚找工作那会,经常在笔试题遇到写出你知道的设计模式,基本上单例是必写的,不为啥,至少占地面积小。
一般我们有这两种实现:
package top.gabin.oa.web.design.singleton; /** * 简单单例模式示例 * @author linjiabin on 16/5/4 */ public class SimpleSingleton { private static Object singleton = new Object(); private static Object singleton2; public static Object getSingleton() { return singleton; } public static Object getSingleton2() { if (singleton2 == null) { singleton2 = new Object(); } return singleton2; } }
静态变量在整个应用中只会持有一份对象,而全局访问点也只有一个,这就是我们一般定义的单例了:保证只有一个对象,一般也只有一个全局访问点。至于初始化的时间,要看创建对象的资源损耗和使用频率。一般使用频率高,损耗低的会直接初始化。
当然看起来简单的东西并不见得容易维护,其实单例模式往往还要解决并发访问的问题,这不在讨论范围,并且我也对并发没有那么深的见解。
*************************************************************************************
*************************************************************************************
2020.07.27重读设计模式后:
单例模式的单例类有两个职责:
1、提供全局访问点
2、负责系统中的某个组件功能
一般有两种初始化单例对象的方式:
1、立即初始化,jvm会保证单例对象只有一个,但不包括同时存在两个或以上ClassLoader的情况下(多个ClassLoader会导致类被多次加载);且在jdk1.2之前的版本,会出现单例被吃掉的情况(GCC认为单例没有被引用,必须实现注册表接口)
2、延迟初始化,需要的时候才初始化;会导致并发问题,不在乎性能的情况下,可以直接在方法上加synchronized;或者也可以采用双重检查锁的方式,如以下的例子
package top.gabin.patterns; // 双重检查锁示例 public class SimpleSingleton { // 2、静态变量,一个类只有一个,多个实例也是共用一个; // volatile告诉虚拟机和编译器这里不要做指令重排 private static volatile SimpleSingleton simpleSingleton; // 1、私有化构造器,使得外部不能新建实例 private SimpleSingleton() { } // 3、全局访问点 public static SimpleSingleton getInstance() { if (simpleSingleton == null) { synchronized (SimpleSingleton.class) { if (simpleSingleton == null) { // 同步之后,需要再判断一次,避免其他线程修改 simpleSingleton = new SimpleSingleton(); } } } return simpleSingleton; } }
******************************************************************************************************
有些面试中会问这里需不需要加volatile,一般大家也都知道,要加,但是为什么要加,可能就说不是很清楚
或者呢,会说个指令重排序。
但再往内讲呢,如果想逼格高点地回答,可以扯到
1、Class二进制文件中,对初始化执行的指令不是原子性,有多条指令
2、其中有两条指令有可能被重排序,这两条指令执行的大概一个是初始化(非实例化)对象,另一个是给变量赋值(把new出来的对象关联到引用变量)。一般来说,按照这个顺序执行是没有问题的,但是呢CPU是会对指令重排序的
3、重排序之后,有可能先把实例化好(但未初始化的对象)和变量关联起来,那么这个时候单例的对象就不为空了,可是呢,实际上这个对象还不完整,可能里面的属性值都还没设好
ps:对象的初始化过程:
- 申请内存
- 实例化,为属性|域设置默认值
- 初始化,为属性|域设置初始值
所以加volatile是为了上面加红的地方不被乱序执行
******************************************************************************************************
另外再补充一点,静态变量初始化为什么能保证单例呢,以及在什么情况下,会失效呢
这里就要扯到JVM中ClassLoader的机制中的双亲委派机制
因为这种机制可以保证同一个全限定名称的类只会被加载一次,静态变量自然也就只有一份。
可是呢,这个机制其实可以被打破的。说起来要理解的概念就比较多了。
我们简单来理解下
1、假设ClassLoader将Class加载到内存,大家都知道我们经常会这样用Object.class。也就是说这其实也是个对象
2、既然是对象,为什么不会new多次呢,其实它也是可以被new多次的(new只是一种概念,作者不太清楚是否ClassLoader也是new的概念)。只不过Java设计的时候ClassLoader的机制用了模板方法区实现,除非我们故意重写其中的一个LoadClass的方法,否则这些个类对象都是单例模式的实现
那基于上面的简单理解,在我们打破这种单例模式的前提之下,我们多次LoadClass,不就使得类被加载多次了吗。那么这种情况下,其实静态变量的单例模式实现,可能就有问题了