在了解单例模式前,我们先来看一下它的定义:
确保一个类只有一个实例,而且自行实例化并且自行向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法,
单例模式是一种对象的创建型模式。
可以看到在定义中,提到了3个要素:
OK,有了这三点,其实就把创建单例模式的步骤罗列出来了,我们先看一下单例模式的类图,有个模糊的概念。

类图还是比较简单清晰的,自关联关系,下面我们来看下为什么要用单例模式呢?
单例模式其实是很简单的一种模式,代码也很好实现,但是我在学习单例模式的时候,一直对它怎么使用比较模糊,这里涉及到两个疑问点,一是为什么要用单例模式,二是需在哪些场景下用单例模式呢?
要搞清楚这两个问题,我们先从单例模式最常用的一个场景说起,就是线程池工具类,相信很多人都用过。我们看一下线程池的使用场景,在之前不使用线程池的时候,程序每次要执行一个现成任务,就会new一个新的线程,然后执行任务,再销毁线程。这个过程中,线程的创建和销毁对系统资源的开销是巨大的,如果使用线程池呢,在这个池中,始终维护一部分存活的线程,循环执行我们的任务,达到减少资源开销的目的。
ThreadPoolExecutor INSTANCE = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
在工具类中,我们会通过 ThreadPoolExecutor这个执行器创建线程池,在一个系统中,应该存在1个或者少量的线程池,多个任务复用池中的线程就可以。假设就以1个例,要复用这1个池中的多个线程,线程池必须有1份,否则每次调用工具类,都会new ThreadPoolExecutor创建1个线程池,也会伴随多个线程的创建和销毁动作,在用完里面的线程后就再也不用了,那线程池就没存在的意义了,既占用了系统的内存资源,又不符合业务场景,所以这里使用单例模式来维护唯一的线程池就很有必要了。
看到这里,为什么要使用单例模式就比较清晰了,单个对象复用可以减少系统资源消耗,对于一些需要频繁创建和销毁的对象,使用单例模式无疑可以提高系统的整体性能。
站在应用场景来看,一个类能不能做成单例,最容易区分的地方就在于,如果存在两个或两以上的实例会造成错误或业务场景上的歧义,也就是这个类在整个应用中,某一个时刻应该只有一个状态体现。那么除了刚才线程池工具类的例子,还有那些实际的应用场景呢?比如:操作系统中的“任务管理器”,“回收站”,网站的“计时器”,或者自定义数据库表的“自增主键计数器”等等,而且spring中的bean默认也是单例的。
单例模式的代码实现有很多种,相信大家也都听过懒汉式与饿汉式,以及饿汉式下面的线程安全问题,其实真正开发中只要熟悉一到两种就可以,但是面试时经常被问到各种写法,接着就来看下这几种代码实现的写法吧。
public class Singleton {// 1.构造方法私有化private Singleton() {}// 2.自行创建这个实例private static Singleton instance = new Singleton();// 3.提供外部可以访问的此实例的方法public static Singleton getInstance() {return instance;}
}
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例private static Singleton instance = null;// 3.自行提供外部访问此实例的方法public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
在多线程场景下,如果有两个现成同时执行到了 instance == null 且都成立,会重复创建实例。
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例private static Singleton instance = null;// 3.自行提供全局访问此实例的方法public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例,注意用volatile修饰private static Singleton instance = null;// 3.自行给全局提供访问此实例的方法public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {instance = new Singleton();}}return instance;}
}
这里虽然锁住了实例创建的代码片段,但是如果存在多个线程都进入到if (instance == null) {}判断里面,且等待锁释放的状态,也会造成创建多个实例的问题,是线程不安全的。
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例private static volatile Singleton instance = null;// 3.自行给全局提供访问此实例的方法public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
由于存在双重判断,即便后来等待的线程持有锁之后,也会再次判断此实例是否已创建,但是要需要注意用volatile修饰,保证实例在线程之间的可见性。
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个唯一的实力private static class SingletonInstance {private static final Singleton INSTANCE = new Singleton();}// 3.自行给全局提供访问这个实例的方法public static Singleton getInstance() {return SingletonInstance.INSTANCE;}
}
public enum SingletonEnum {INSTANCE;public String method() {return "what you need!";}
}
枚举类的实现方式可以说是单例模式的最佳实践,在《Effective Java》这本书中,作者就提到“单元素的枚举类型已经成为实现Singleton的最佳方法”。
枚举类的实现方式不仅可以解决上面所述的所有问题,还可以防止通过反射和反序列化来重复创建新的实例,Java虚拟机天然可以保证枚举对象的唯一性,在很多优秀的框架中,经常可以看到通过枚举实现的单例模式。
单例模式作为一种目标明确、结构简单、理解容易的设计模式,在开发工作中使用的频率相当的高,写在最后,简单总结一下单例模式的优缺点。
上一篇:夯实C++基础学习笔记
下一篇:请教:“名师出高徒”的同义词