单例模式的定义
一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。
单例模式的优缺点
优点:
1.由于系统中内存只存在一个对象,因此可以节约系统的的资源,对于一些频繁的创建和销毁的对象单例模式无意可以提供系统的性能;
2.避免对资源的多重占用;
3.提供了对唯一实例的受控访问;
缺点:
1.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难;
2.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态;
3.单例类职责过重,在一定程度上违背了单一职责。
单例模式的实现方式
单例的实现方式主要分为以下几种:
1.饿汉式
/**
* 单例模式饿汉式
* @author mikechen
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
//构造方法设置成私有
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
①特点:在类加载的同时就已经创建好对象,加载速度块
②优点:实现简单,执行效率高,线程安全
③缺点:类加载时就初始化,可能占用不必要内存浪费
2.懒汉式
/**
* 懒加载实现单例模式
*
* @author mikechen
*/
public class SingletonLazy {
private static SingletonLazy instance;
//构造方法设置成私有
private SingletonLazy() {}
public static synchronized SingletonLazy getInstance() {
if (instance == null)
instance = new SingletonLazy();
return instance;
}
}
①特点:在使用时才进行初始化,即懒加载
②优点:只在第一次使用的时候调用,一定程度上节省了资源
③缺点:第一次加载需要进行及时实例化,反应稍慢,最大的问题是每次调用getInstance 都进行同步,造成不必要的同步开销
3.双重检测
/**
* Double Check Lool (DCL)实现单例
*
* @author mikechen
*/
public class SingletonDCL {
private volatile static SingletonDCL instance = null;
//构造方法私有
private SingletonDCL() {
}
public static SingletonDCL getInstance() {
//进行两次非空判断 ,第一层是为了避免不必要的同步
if (instance == null) {
//获取Singleton3.class的锁,避免实例化多次
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
①特点:使用volatile关键字保证底层指令执行顺序
②优点:入口处判断null,可以省去每次加锁的耗费,提升性能
③缺点:第一次加载反应稍慢,也由于Java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷
4.静态内部类
/**
* 静态内部类实现单例模式
*
* @author mikechen
*/
public class SingletonStatic {
private SingletonStatic() {
}
public static SingletonStatic getInstance() {
return SingLineHolder.instance;
}
private static class SingLineHolder {
private static final SingletonStatic instance = new SingletonStatic();
}
}
①在类内部有一个静态内部类
②优点:确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化
③缺点:无法传参
5.枚举
/**
* 枚举实现单例模式
*
* @author mikechen
*/
public enum SingletonEnum {
INSTANCE;
SingletonEnum() {}
public void getName() {}
}
最佳的单例实现模式就是枚举模式,利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。
除此之外,写法还特别简单,线程安全的同时,还有防止反序列化生成多个对象。
单例模式的应用场景
1.美国总统
生活中的例子,美国总统的职位是Singleton,在任何时刻只能由一个现任的总统。
2.连接池
比如数据库连接池的设计一般也是采用单例模式,数据库连接池属于重量级资源,一个应用中只需要保留一份即可,既节省了资源又方便管理,所以数据库连接池采用单例模式进行设计会是一个非常好的选择。
3.线程池
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
4.资源文件
Windows的Task Manager(任务管理器)就是很典型的单例模式,想想看,是不是,你能打开两个任务管理器吗?
windows的Recycle Bin(回收站)也是典型的单例应用,在整个系统运行过程中,回收站一直维护着仅有的一个实例。
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5.配置文件
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
6.计数器
网站的计数器,一般也是采用单例模式实现,否则难以同步。
单例模式总结
总之,单例模式的应用场景大致归类如下:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或耗资源过多,但又经常用到的对象。
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。