写点什么

JAVA 设计模式系列 -- 单例模式

用户头像
加百利
关注
发布于: 2021 年 06 月 13 日
JAVA设计模式系列--单例模式

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。


也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。


其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。


那么我们今天先来看看在我们现学习阶段使用特别多的一种设计模式-单例模式


首先我们看看单例模式在实际生活中的使用场景:


比如:


公司 CEO、部门经理等都属于单例模型

J2EE 标准中的 ServletContext 和 ServletContextConfig

Spring 框架应用中的 ApplicationContext

数据库中的连接池等也都是单例模式

还比如 Windows 的回收站

操作系统中的文件系统

多线程中的线程池

显卡的驱动程序对象

打印机的后台处理服务

应用程序的日志对象

数据库的连接池

网站的计数器

Web 应用的配置对象

应用程序中的对话框

系统中的缓存等常常被设计成单例。


那么这些场合为什么要设计成单例模式呢?单例模式的实现方式有哪些?单例模式的好处和弊端有哪些呢?


带着这些问题我们一起来详细看看单例模式

1.什么是单例模式?

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。


例如:Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

2.单例模式的实现方式有哪些?

常见的实现方式有:懒汉模式、饥汉模式、双重校验锁、静态内部类、枚举等方式实现,那我们我们紧接着就具体的一个一个的来看看他们的实现


懒汉模式:


/** * @author hz * @version 1.0 */public class Singleton {    private static Singleton instance = null;    private Singleton(){}    public static Singleton getInstance(){        //如果还没有被实例化过,就实例化一个,然后返回        if(instance == null){            instance = new Singleton();        }        return instance;    }}
复制代码


这段代码里,我们没有考虑线程安全,所以可能还是会参数多个实例,所以我们需要对线程安全问题进行解决,从而得出懒汉模式的优化版


/** * @author hz * @version 1.0 */public class Singleton {    private static Singleton instance = null;    private Singleton(){}    public static synchronized Singleton getInstance(){        //如果还没有被实例化过,就实例化一个,然后返回        if(instance == null){            instance = new Singleton();        }        return instance;    }}
复制代码


通过关键字synchronized用于确保getInstance方法线程安全,但是这种方式弊端特别大,因为所有线程到达该方法以后需要进行排队等候,所以对性能的损耗非常大,该处理解决了线程安全安全问题,并没有解决效率问题,如果要既要解决线程安全问题又要解决效率问题则需要我们后面的双重校验锁方式。


懒汉模式将实例化的时机放到了需要使用的时候(饿汉是类加载了就有实例),也就是“延迟加载”,相比饿汉,能避免了在加载的时候实例化有可能用不到的实例,但是问题也很明显,我们要花精力去解决线程安全的问题。


饿汉模式:


/** * @author hz * @version 1.0 */public class Singleton {    //类加载的时候instance就已经指向了一个实例    private static Singleton instance = new Singleton();    private Singleton(){}    public static Singleton getInstance(){        return instance;    }}
复制代码


饿汉模式相比懒汉模式,在类加载的时候就已经存在一个实例,举个例子,比如数据库连接吧,懒汉就是第一次访问数据库的时候我才去创建一个连接,而饿汉呢,是你程序启动了,类加载好了的时候,我已经有个连接了,你用不用不一定了,所以饿汉的缺点也就出来了:可能会产生很多无用的实例。


那么加载时机的问题我们已经说过了,接下来就是线程安全了,代码里我们并没有看见synchronized关键字,那么这种方式是如何确保线程安全的呢,这个就是 JVM 类加载的特性了,JVM 在加载类的时候,是单线程的,所以可以保证只存在单一的实例


双重校验锁:


/** * @author hz * @version 1.0 */public class Singleton {    private static Singleton instance = null;    private Singleton(){}    public static Singleton getInstance(){        if(instance == null){            synchronized (Singleton.class){                if(instance == null){                    instance = new Singleton();                }            }        }        return instance;    }}
复制代码


首先要说明的是,双重检验锁也是一种延迟加载,并且较好的解决了在确保线程安全的时候效率低下的问题,对比一下最原始的那种线程安全的方法(就是懒汉模式的第二种代码),那种方法将整个getInstance方法锁住,那么每次调用那个方法都要获得锁,释放锁,等待等等...而双重校验锁锁住了部分的代码。进入方法如果检查为空才进入同步代码块,这样很明显效率高了很多。


静态内部类:


/** * @author hz * @version 1.0 */public class Singleton {    private static class SingletonHolder{        private static Singleton instance = new Singleton();    }    private Singleton(){}    public static Singleton getInstance(){        return SingletonHolder.instance;    }}
复制代码


懒汉模式需要考虑线程安全,所以我们多写了好多的代码,饿汉模式利用了类加载的特性为我们省去了线程安全的考虑,那么,既能享受类加载确保线程安全带来的便利,又能延迟加载的方式,就是静态内部类


Java 静态内部类的特性是:加载的时候不会加载内部静态类,使用的时候才会进行加载。而使用到的时候类加载又是线程安全的,这就完美的达到了我们的预期效果。


枚举:


/** * @author hz * @version 1.0 */public enum Singleton {    INSTANCE;}
复制代码


JDK1.5 提供了一个新的数据类型:枚举


枚举的出现提供了一个较为优雅的方式取代以前大量的 static final 类型的变量。而这里,我们也利用枚举的特性,实现了单例模式,外部调用由原来的Singleton.getInstance变成了Singleton.INSTANCE了。


以上几种单例模式的实现各自有各自的优势,所以我们在实际使用中,需要根据自己的需求进行选择,而通过上述实现方式我们可以可以总结一下单例模式的特点:


1).单例模式只有一个实例对象;


2).该单例对象必须由单例类创建;


3).单例类对外提供一个访问该单例的全局访问点。

3.单例模式的优缺点:

以上我们具体的对比了几种单例模式的实现方式,那么单例模式有哪些优缺点呢?


优点:


单例模式可以保证内存里只有一个实例,减少了内存的开销。

可以避免对资源的多重占用。

单例模式设置全局访问点,可以优化和共享资源的访问。


缺点:


单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。

在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。

单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

4.单例模式的 应用场景:

对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。


单例模式的应用场景主要有以下几个方面:


需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。

某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。

某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。

某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。

频繁访问数据库或文件的对象。

对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。

当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

5.总结:

在单例模式各种设计的方法中,我们使用到了内部静态类的特性,使用了枚举的特性,所以基础非常重要,单例模式是设计模式之一,而设计模式其实是对语言特性不足的一面进一步的包装。吸纳基础,工作学习多加思考,设计模式也就自然而然的能够理解。

用户头像

加百利

关注

还未添加个人签名 2021.06.08 加入

还未添加个人简介

评论

发布
暂无评论
JAVA设计模式系列--单例模式