小言_互联网的博客

关于单例模式你需要知道的都在这了

319人阅读  评论(0)

关于单例模式你需要知道的都在这了

定义

单例模式确保一个类只有一个实例,并提供一个全局访问点

做法

把类的构造函数设置为private,用一个静态变量存储本身的唯一一个实例,然后通过静态方法获取唯一的实例。

应用场景

下面列举的应用场景引用于https://blog.csdn.net/tanyujing/article/details/14160941

  1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
  2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  9. HttpApplication 也是单例模式的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

单例模式与全局变量

全局变量也能提供全局访问点,但全局变量在程序一开始就要初始化,而单例模式可以选择在加载类的时候创建,也可以选择在需要实例的时候再创建。最重要的是,全局变量不能保证只有一个实例被创建。

单例模式的不同实现

下面介绍三种常见的单例模式的实现–饥汉式、懒汉式和双重检查加锁式,更多的实现可以参考
https://www.cnblogs.com/zhaoyan001/p/6365064.html

饥汉式单例模式

EagerSingleton.java

package priv.mxz.design_pattern.singleton_pattern;

class EagerSingleton {
    private static EagerSingleton instance=new EagerSingleton();

    private EagerSingleton(){}

    static public EagerSingleton getInstance(){
        return instance;
    }
}


在类加载的时候已经把实例初始化,在调用静态方法getInstance前已经有了具体的实例,如果程序一直没有使用这么实例,则会造成内存上的浪费。由于在类加载的时候已经初始化了实例,所以是线程安全的。

懒汉式单例模式

LazySingleton.java

package priv.mxz.design_pattern.singleton_pattern;

class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton(){ }

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

在调用getInstance后才判断是否有具体的实例,如果没有则初始化一个,这样可以避免在实例还不被使用时占用内存。
代码中getInstance用了关键字synchronized,后者用于线程同步,保证同一时刻只有一个线程能运行getInstance方法且中途不能切换线程。如果没有了synchronized,那么上述代码就是线程不安全的,因为可能存在这样的情况:线程A和线程B都是第一次运行getInstance方法,首先线程A执行完if语句,判断出需要创建实例,然后准备创建实例,但此时CPU控制权切换到了线程B,此时线程B也执行if判断,也判断出需要创建实例,然后线程B创建了自己的实例并返回,切换回线程A,线程A也创建了自己的实例并覆盖线程B创建的instance并返回,这样线程AB得到了两个不同的instance。
加了synchronized后代码是线程安全的,但每次调用getInstance都需要执行同步操作,实际上只有第一次instance是null时才可能有同步异常,所以每次都执行同步操作其实降低了性能。

双重检查加锁式(Double Checked Locking)

LazySingletonDCL.java

package priv.mxz.design_pattern.singleton_pattern;

class LazySingletonDCL {
    private volatile static LazySingletonDCL instance;

    private LazySingletonDCL(){}

    public static LazySingletonDCL getInstance(){
        if (instance==null){
            synchronized (LazySingletonDCL.class){
                if (instance==null){
                    instance=new LazySingletonDCL();
                }
            }
        }
        return instance;
    }
}

volatile关键字保证了当instance发生变化时(被创建为实例)多个线程能马上知道变化发生。避免实例已经被创建,另一个线程依然判断instance依然为null的情况。
getInstance方法中,跟懒汉式单例模式对比,DCL式把同步放到了判断instance为null后,如果instance不为null则不会有同步操作,提高了系统性能。
instance为null的情况下,线程先获得同步锁,拿到以后还要再次判断instance是否为null,是的话才创建实例,不是的话什么都不干。这么做的原因是两个线程A和B依然可能先后执行完第一个if判断,然后同时进入到同步部分,然后线程A获取到锁,然后创建了一个实例,随后锁交给线程B,此时在同步部分的代码里,instance已经不是null(线程B通过volatile知道线程A已经创建了一个实例),如果不再次判断,那么线程B还会创建一个实例。这就是需要双重检查的原因。

测试代码

SingletonPattern.java

package priv.mxz.design_pattern.singleton_pattern;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class SingletonPattern {

    public static void beforeGetInstance(Class cls)throws IllegalAccessException{
        Field[] fields=cls.getDeclaredFields();
        if (fields!=null && fields.length>0){
            for (Field field:fields){
                field.setAccessible(true);
                if (field.getType()==cls && Modifier.isStatic(field.getModifiers())){
                    System.out.println("private instance of "+cls.getName()+ " before getInstance() is "+
                            field.get(cls));
                }
            }
        }
    }

    public static void main(String[] args){

        try {
            beforeGetInstance(EagerSingleton.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        try {
            beforeGetInstance(LazySingleton.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        try {
            beforeGetInstance(LazySingletonDCL.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }


        EagerSingleton es1=EagerSingleton.getInstance();
        EagerSingleton es2=EagerSingleton.getInstance();
        System.out.println("two EagerSingleton equal: "+ (es1==es2));

//        EagerSingleton es3=new EagerSingleton();  失败,无法编译通过

        LazySingleton ls1=LazySingleton.getInstance();
        LazySingleton ls2=LazySingleton.getInstance();
        System.out.println("two LazySingleton equal:"+(ls1==ls2));

        LazySingletonDCL lsd1=LazySingletonDCL.getInstance();
        LazySingletonDCL lsd2=LazySingletonDCL.getInstance();
        System.out.println("two LazySingleton DCL equal:"+(lsd1==lsd2));

    }
}

下面是结果

private instance of priv.mxz.design_pattern.singleton_pattern.EagerSingleton before getInstance() is priv.mxz.design_pattern.singleton_pattern.EagerSingleton@14ae5a5
private instance of priv.mxz.design_pattern.singleton_pattern.LazySingleton before getInstance() is null
private instance of priv.mxz.design_pattern.singleton_pattern.LazySingletonDCL before getInstance() is null
two EagerSingleton equal: true
two LazySingleton equal:true
two LazySingleton DCL equal:true

main函数中我们分别把三种单例模式的类的Class实例传进函数beforeGetInstance,beforeGetInstance通过反射的方式判断类中静态变量instance的值是否为null,以此来判断单例模式是饥汉式还是懒汉式(即判断在getInstance被调用之前instance是否已经初始化),从输出可以看到EagerSingleton在调用getInstance前已经初始化了实例,其他两个则没有,值为null

接着我们分别用三个类的getInstance各自创建了两个实例,判断着两个实例是否相同,从结果可知,三个类都实现了单例模式的基本功能–返回的两个实例是完全相同的唯一一个实例。

优缺点

优点:
单例模式可以替代全局变量为程序提供唯一的全局访问点,避免了对象的多次的创建与销毁,提高了系统性能。

缺点:
程序中可能有许多地方依赖于单例类,单例类的职责过于繁重,修改单例类可能牵一发而动全身。
单例类有创建产品的工厂角色,也有产品本身的角色,违背了“单一职责”的设计原则。
单例类不方便继承,不利于扩展。

总结

单例模式为外部提供了单例类唯一的类实例的访问入口,可以保证在程序运行期间只有一个单例类实例,常见的应用场景有日志记录器和垃圾回收器等,单例模式的实现有多种方式,比如饥汉式,懒汉式和DLC式。单例模式的实现有利于提高系统性能,保证了实例的唯一性,但修改单例类时可能会对系统造成较大的影响。


转载:https://blog.csdn.net/qq_36267931/article/details/105655082
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场