设计模式系列之——单例模式

单例模式定义:

Ensure a class has only on instance,and provide a global pointof access to it.(确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例子。)

单例模式通用类图:
单例模式通用类图
形成单例模式3个基本要素:
  • 构造方法私有
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态共有方法
饿汉式写法:(一来就创建对象,因为我饿啊)
public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return singleton;
    }

}
懒汉式写法(需要的时候我才去创建一个,因为我懒啊)

解决线程安全问题:

public class Singleton {
    private static Singleton singleton;

    private Singleton(){}

    public static synchronized Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}
单例模式优点:
  • 内存中只有一个实例,节省内存空间
  • 避免频繁创建和销毁对象,减少性能开销
  • 避免对资源的多重占用,如可以避免对同一资源文件的同时写操作
  • 在全局都可访问,方便资源调用
适用场景:
  • 如果一个系统中出现多个对象出现了“不良反应”,就要考虑是不是该用单例。

  • 需要频繁实例化然后销毁的对象

  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象,如IO和数据库等资源

  • 需要定义大量静态常量和静态方法的工具类对象

  • 单例怎么做双重校验

熟悉类初始化机制的同学们都应该知道类初始化分三步
1、分配对象的内存空间
2、初始化对象,调用构造方法
3、将内存地址指向instance

双重检验锁模式:

public class SingleTon {
    private static SingleTon instance = null;

    private SingleTon() {}

    public static SingleTon getInstance() {
        if (instance == null) {  // 第一次判空,保证不必要的同步
            synchronized (SingleTon.class) {  // synchronized 对 Singleton 加全局锁,保证每次只要一个线程创建实例
                if(instance == null) {  // 第二次判空是为了在 null 的情况下创建实例
                    instance = new SingleTon();
                }
            }
        }

        return instance;
    }
}

在同步代码块的内部和外部都判断了instance == null,这时因为,可能会有多个线程同时进入到同步代码块外的if判断中,如果在同步代码块内部不进行判空的话,可能会初始化多个实例。

指令重排就是为了优化性能在不改变结果的情况下CPU按照自己的顺序去执行指令。
这种写法看似完美无缺,但它却是有问题的,或者说它并不担保一定完美无缺。主要原因在于instance = new Singleton()并不是原子性的操作。
当instance指向分配地址时,instance不为空
但是,2、3部之间,可能会被重排序,造成创建对象顺序变为1-3-2.试想一个场景:
线程A第一次创建对象Singleton,对象创建顺序为1-3-2;
当给instance分配完内存后,这时来了一个线程B调用了getSingleton()方法
这时候进行instance == null的判断,发现instance并不为null。
但注意这时候instance并没有初始化对象,线程B则会将这个未初始化完成的对象返回。那B线程使用instance时就可能会出现问题,这就是双重检查锁问题所在。

我们可以通过把instance声明为volatile型来解决:

private volatile static SingleTon instance = null;

参考单例模式-双重校验锁

  • 静态内部类实现单例模式
public class SingleTon {

    private SingleTon() {}

    public static SingleTon getInstance() {
        return SingleTonHolder.instance;
    }

    private static class SingleTonHolder {
        private static SingleTon instance = new SingleTon();
    }
}

使用静态内部类的优点是:因为外部类对内部类的引用属于被动引用,不属于前面提到的三种必须进行初始化的情况,所以加载类本身并不需要同时加载内部类。在需要实例化该类是才触发内部类的加载以及本类的实例化,做到了延时加载(懒加载),节约内存。同时因为JVM会保证一个类的()方法(初始化方法)执行时的线程安全,从而保证了实例在全局的唯一性。

参考:单例模式7种写法

你可能感兴趣的:(设计模式系列之——单例模式)