首页 > 技术文章 > Java设计模式-单例模式

liyhbk 2021-01-07 16:03 原文

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点!

单例模式应该是23种设计模式中最简单的一种模式了。它有以下几个要素:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

单例模式5种写法-- 饿汉式单例懒汉式单例双层校验锁静态内部类枚举

  饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;

  懒汉式在调用取得实例方法的时候才会实例化对象。

饿汉式单例

饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建,好处是编写简单,但是无法做到延迟创建对象。

/**
 * 饿汉式单例
 */
public class Singleton {
    // 将自身实例化对象设置为一个属性,并用 static、final 修饰
    private static final Singleton singleton = new Singleton();

    // 构造方法私有化
    private Singleton (){}

    // 静态方法返回该实例
    public static Singleton getInstance(){
        return singleton;
    }
}

懒汉式单例

懒汉式单例模式,线程不安全,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象
这种方法可以实现延时加载(lazy landing),但是有一个致命弱点:线程不安全。如果有两条线程同时调用getSingleton()方法,就有很大可能导致重复创建对象。

/**
 * 懒汉式单例
 */
public class Singleton {
    // 将自身实例化对象设置为一个属性,并用 static 修饰
    private static Singleton singleton;

    // 构造方法私有化
    private Singleton() {}

    // 静态方法返回该实例
    public static synchronized Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

双层校验锁

双重非空判断,new对象前加一次锁。

volatile关键字,由于volatile关键字屏蔽了虚拟机中一些必要的代码优化,所以运行效率并不是很高,因此建议没有特别的需要不要使用,而volatile关键字可以防止指令重排。

/**
 * 双层校验锁
 */
public class Singleton {
    // 将自身实例化对象设置为一个属性,并用 volatile 修饰
    private volatile static Singleton singleton;

    // 构造方法私有化
    private Singleton() {}

    // 静态方法返回该实例
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

静态内部类

由于静态内部类跟外部类是平级的,所以外部类加载的时候不会影响内部类,因此实现了lazy loading,同时也是利用静态变量的方式,使得INSTANCE只会在SingletonHolder加载的时候初始化一次,从而保证不会有多线程初始化的情况,因此也是线程安全的。

/**
 * 静态内部类
 */
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    private Singleton (){}

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
} 

枚举

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,是线程安全的,但是很少见使用。

/**
 * 枚举
 */
public enum Singleton {
    INSTANCE;

    public void whateverMethod() {}
}

饿汉式和懒汉式区别

  1.从名字上来说,饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉模式,只有当调用getInstance的时候,才会去初始化这个单例。

  2.饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的。

  3.饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

  4.在Java中,饿汉式单例要优于懒汉式单例。

单例模式的优点:

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景:

由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。以下是一些适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

单例模式注意事项:

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

  

推荐阅读