设计模式——单例模式详解

2023-10-10 15:31:08 浏览数 (2)

什么是单例模式

单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。在程序中多次使用同一个对象且作用相同的时候,为了防止频繁的创建对象,单例模式可以让程序在内存中创建一个对象,让所有的调用者都共享这一单例对象

单例的实现主要是通过以下两个步骤:

1.将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例; 2.在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

单例模式类型

饿汉式:在类加载的时候已经创建好该单例对象。

懒汉式:在需要使用对象的时候才会去创建对象

单例模式的实现

饿汉式

步骤如下:

1)构造器私有化(防止new )

2)类的内部创建对象

3)向外暴露一个静态的公共方法。getInstance

4)代码实现:

代码语言:javascript复制
class Singleton {
    //1.构造器私有化,只能在内部new对象
    private Singleton(){
    }
    //2.内部实例化
    private final static Singleton instance =new Singleton();
    //3.提供get方法供外部使用,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }

}

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

懒汉式

代码语言:javascript复制
class Singleton{
    private Singleton(){

    }
    private static Singleton instance;
    public static synchronized Singleton getInstance(){
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}

效率低 比如当单例创建好之后,此时不会存在任何线程安全问题,但是每次访问仍然要加锁。导致效率低下。

双重检查

代码语言:javascript复制
class Singleton{
    private Singleton(){}
    //volatile确保INSTANCE每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。(JDK1.5之后)
    private static volatile Singleton instance;
    public static Singleton getInstance(){
        if(instance==null){
            synchronized (Singleton.class){
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
}

使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率。 Double-Check概念是多线程开发中常使用到的,我们进行了两次if (singleton == null)检查, 这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton ==- null),直接return实例化对象,也避免的反复进行方法同步. 线程安全;延迟加载;效率较高 结论:在实际开发中,推荐使用这种单例设计模式

静态内部类

代码语言:javascript复制
class Singleton{
    private Singleton(){

    }
    private static class SingletonInstance{
        private static final Singleton INSTANCE =new Singleton();
    }
    public static synchronized Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。 推荐使用 关于静态内部类又是如何实现线程安全的呢?见:静态内部类利用了类的加载机制实现多线程安全

枚举

代码语言:javascript复制
enum Singleton{
    INSTANCE;
}

枚举是饿汉式的

它也是静态成员变量,在类加载的时候就初始化完毕了。 问:反射能破坏其单例吗? 答:不能。反射在通过Newinstance创建对象会检查该类是否是枚举类型,是的话就反射失败 推荐使用

单例模式在JDK中的体现

Java.lang.Runtime就是经典的单例模式(饿汉式)

单例模式存在的问题

1.单例对OOP特性的支持不友好OOP的四大特性是封装、抽象、继承、多态。 2.单例会隐藏类之间的依赖关系

由于单例类不需要创建,只要调用函数就能产生,所以如果代码特别复杂,那么调用关系就会比较隐蔽,在阅读代码时,就需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类

3.单例对代码的扩展性不友好

比如一开始对于数据库连接池只需要一个。

而后期某些sql查询比较慢,执行的时候长期占用数据库连接池的资源,导致其他查询无法响应。

为了解决这个问题,就希望将慢sql和其他sql隔离开执行,这样就需要要创建两个数据库连接池,慢sq|独享一个连接池。但是已经将数据库连接设置成单例了。显然无法应对这样的需求变更

所以,数据库连接池、线程池这类的资源池,最好还是不要设计成单例类。实际上,一些开源的数据库连接池、线程池也确实没有设计成单例类。

4.单例不支持有参数的构造函数

比如我们创建一个连接池的单例对象,我们没法通过参数来指定连接池的大小。

解决思路是:将参数放到另外一个全局变量中。具体的代码实现如下。Config 是一个存储了paramA和 paramB值的全局变量。里面的值既可以像下面的代码那样通过静态常量来定义,也可以从配置文件中加载得到。实际上,这种方式是最值得推荐的.

0 人点赞