单例模式

创建型: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,不就使得类被加载多次了吗。那么这种情况下,其实静态变量的单例模式实现,可能就有问题了

原文地址:https://www.cnblogs.com/gabin/p/5457122.html