23种常用设计模式之单例模式,以及它的十种实现方式

说明

单例模式(Singleton)是一种常用的创建型软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

应用场景

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。

模式特征

  • 私有化默认构造器
  • 含获取唯一实例的公共静态函数(如:getInstance)

实现方式

实现方式 例子
饿汉式 SingletonHungry、SingletonHungryVariant
懒汉式 SingletonLazy、SingletonLazySynchronizedMethod、SingletonLazySynchronizedBlock
双重检查 SingletonDoubleCheck、SingletonDoubleCheckWithVolatile
枚举 SingletonEnumHungry、SingletonEnumLazy
静态内部类 SingletonStaticInnerClass

代码实现

饿汉式(线程安全)
public class SingletonHungry {
    private static SingletonHungry singleton = new SingletonHungry();

    private SingletonHungry() {}

    public static SingletonHungry getInstance() {
        return singleton;
    }

}
饿汉式变体(线程安全)
public class SingletonHungryVariant {

    private static SingletonHungryVariant singleton;

    static {
        singleton = new SingletonHungryVariant();
    }

    private SingletonHungryVariant(){}

    public static SingletonHungryVariant getInstance(){
        return singleton;
    }
}
懒汉式(线程不安全)
public class SingletonLazy {

    private static SingletonLazy singleton;

    private SingletonLazy() {}

    public static SingletonLazy getInstance() {
        if (null == singleton) {
            singleton = new SingletonLazy();
        }
        return singleton;
    }
}
懒汉式 同步方法实现(线程安全 不推荐使用)
public class SingletonLazySynchronizedMethod {

    private static SingletonLazySynchronizedMethod singleton;

    private SingletonLazySynchronizedMethod() {}

    public static synchronized SingletonLazySynchronizedMethod getInstance() {
        if (singleton == null) {
            singleton = new SingletonLazySynchronizedMethod();
        }
        return singleton;
    }
}
懒汉式 同步代码块实现(线程不安全 不可用)
public class SingletonLazySynchronizedBlock {

    private static SingletonLazySynchronizedBlock singleton;

    private SingletonLazySynchronizedBlock() {}

    public static SingletonLazySynchronizedBlock getInstance() {
        if (singleton == null) {
            synchronized (SingletonLazySynchronizedBlock.class) {
                singleton = new SingletonLazySynchronizedBlock();
            }
        }
        return singleton;
    }
}
未使用volatile的双重检查(线程不安全 不可用 可能会发生空指针异常)
public class SingletonDoubleCheck {
    private static SingletonDoubleCheck singleton;

    private SingletonDoubleCheck(){}

    private static SingletonDoubleCheck getInstance(){
        if (singleton == null){//一
            synchronized (SingletonDoubleCheck.class){
                if (singleton == null){
                    singleton = new SingletonDoubleCheck();//二
                }
            }
        }
        return singleton;
    }
}

说明:
Java创建一个对象,在Java虚拟机上会进行以下三步操作
1、给singleton分配内存
2、调用构造器方法,执行初始化
3、将对象引用赋值给变量
Java实例化一个对象的操作(new)不是原子性的,存在指令重排序的问题,上面三个操作的顺序可能是123,也可能是132(步骤1不会重排序,因为23依赖1的结果),当指令顺序为132时,就可能出现对象空指针异常,表格示意如下:

时间 线程a 线程b
t1 分配内存
t2 变量赋值
t3 判断对象是否为null
t4 由于对象变量已赋值,故不为null,访问并修改对象
t5 初始化对象
使用volatile的双重检查(线程安全 可用)
public class SingletonDoubleCheckWithVolatile {
    private volatile static SingletonDoubleCheckWithVolatile singleton;

    private SingletonDoubleCheckWithVolatile(){}

    private static SingletonDoubleCheckWithVolatile getInstance(){
        if (singleton == null){
            synchronized (SingletonDoubleCheckWithVolatile.class){
                if (singleton == null){
                    singleton = new SingletonDoubleCheckWithVolatile();
                }
            }
        }
        return singleton;
    }
}

区别仅仅是比上一种实现方式多了一个volatile关键字

枚举实现 饿汉式
public enum SingletonEnumHungry {
    INSTANCE;

    public static SingletonEnumHungry getInstance(){
        return INSTANCE;
    }
}
枚举实现 懒汉式
public class SingletonEnumLazy {

    private SingletonEnumLazy(){}

    private enum SingletonHolder{
        INSTANCE;

        private SingletonEnumLazy singleton;

        SingletonHolder(){
            this.singleton = new SingletonEnumLazy();
        }

        public SingletonEnumLazy getSingleton(){
            return singleton;
        }
    }

    public static SingletonEnumLazy getInstance(){
        return SingletonHolder.INSTANCE.getSingleton();
    }
}
静态内部类实现 (推荐使用)
public class SingletonStaticInnerClass {
    private SingletonStaticInnerClass(){}

    private static class SingletonHolder{
        private static SingletonStaticInnerClass singleton = new SingletonStaticInnerClass();
    }

    public static SingletonStaticInnerClass getInstance(){
        return SingletonHolder.singleton;
    }
}

优缺点

优点:
  • 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
  • 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
  • 提供了对唯一实例的受控访问。
  • 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例。
  • 避免对共享资源的多重占用。
缺点:
  • 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  • 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

重要说明

  • 网上有很多教程实现单例时,使用final关键字修饰类,来阻止类被继承,其实这个有点多余了,因为在构造器私有化的情况下,类是无法被继承的
  • 虽然构造器私有化了,但是也不是说百分百就能保证单例,因为Java的反射机制就可能会破坏单例,因为在使用的过程中,可以根据情况,在私有化构造器中增强判断,以饿汉式举栗如下:
public class SingletonHungry {
    private static SingletonHungry singleton = new SingletonHungry();

    private SingletonHungry() {
        //防止反射实例化对象
        if (singleton != null)
            throw new RuntimeException("the class object cannot be instantiated");
    }

    public static SingletonHungry getInstance() {
        return singleton;
    }

}
  • 对于验证线程不安全,参考如下(懒汉式举栗):
public class SingletonLazy {

    private static SingletonLazy singleton;
    private static int count = 0;

    private SingletonLazy() {
        System.out.println("实例化了:" + ++count + "次");
    }

    public static SingletonLazy getInstance() {
        if (null == singleton) {
            singleton = new SingletonLazy();
        }
        return singleton;
    }

    public static void main(String[] args) {
        Runnable task = SingletonLazy::getInstance;
        // 模拟多线程环境下使用 Singleton 类获得对象
        for (int i = 0; i < 10; i++) {
            new Thread(task, "" + i).start();
        }
    }
}
原创文章 67 获赞 31 访问量 5万+

猜你喜欢

转载自blog.csdn.net/u012534326/article/details/102512388
今日推荐