什么是单例模式(Singleton Pattern)
单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式的应用场景
实现一个单例模式很简单,但我没有想过单例模式有什么用处,直到一次笔试就遇到了问单利模式的应用场景有哪些。
单例模式的应用场景:线程池(Thread Pool)、缓存(Cache)、对话框、处理偏好设置、注册表(Registry)对象,日志对象,充当打印机、显卡等设备的驱动程序的对象。这类对象只能有一个对象,如果出现多个实例就会导致很多问题,例如:程序的行为异常、资源使用过量、或者是出现不一致的结果。
Java 代码实现单例
有四种方式实现单例模式,分别是:饿汉式、懒汉式、双重检查加锁、枚举。接下来我会结合代码介绍这四种方式。
- 饿汉式
在类被加载时就在类中创建一个唯一实例。
优点:线程安全。
代码实现:
/**
* 单例模式(饿汉式实现)
* @author cnbo
*/
public class Singleton1 {
//定义一个静态的Singleton1实例
private static final Singleton1 singleton =
new Singleton1();
//私有化构造函数以致外部无法创建Singleton1实例
private Singleton1() {}
//提供一个静态方法供外部使用Singleton1实例
public static Singleton1 getInstance() {
return singleton;
}
}
- 懒汉式
在类首次被使用时才创建处一个全局唯一的实例。
缺点:线程不安全。
代码实现:
/**
* 单例模式(懒汉式实现)
* @author cnbo
*/
public class Singleton2 {
private static Singleton2 singleton;
//私有化构造函数
private Singleton2() {}
public static Singleton2 getInstance() {
//判断singleton是否为 null,如果为null则创建,否则直接返回
if (singleton == null) {
singleton = new Singleton2();
}
return singleton;
}
}
- 双重检查加锁
双重检查加锁是对饿汉式在多线程环境下的升级,保证其线程安全。之所以在代码中是用双检查是为了确保只有在第一次创建实例时才会使用同步,因为同步非常的耗资源。
代码实现:
/**
* 单例模式(线程同步实现)
* @author cnbo
*/
public class Singleton3 {
private static Singleton3 singleton;
private Singleton3() {}
public static Singleton3 getInstance() {
//双重检查加锁
if (singleton == null) {
synchronized(Singleton3.class) {
if (singleton == null) {
singleton = new Singleton3();
}
}
}
return singleton;
}
}
- 枚举
枚举是最优雅的实现单例的方式。饿汉式、懒汉式、双重检查加锁都有一个共同 的问题,那就是都可以通过反射机制调用私有构造器创建实例,这样就不能保证实例是唯一的,这有背单例的使命了。同时枚举也是线程安全的。
代码实现:
/**
* 单例模式(枚举实现)
* @author cnbo
*/
public enum Singleton4 {
INSTANCE;
}
是不是觉得枚举实现单例的代码太简单了,我想说的是,枚举就是这么任性,就是要这么简单。
FAQ
- 为什么全局变量比单例模式差
全局变量基本上是对对象的静态引用。在这样的情况下使用全局变量非常耗资源。同时使用全局变量不能确保只有一个实例。 - 单例类能否被继承
无论是枚举实现还是非枚举实现,单例类都不能被继承。
非枚举方式实现的单例类由于构造器是私有的,因而不能被继承。 - 单例是否违背例 OO 设计
答案是肯定的。单例类不只负责管理自己的实例,同时还在应用程序中担任角色。虽然单例违背例OO设计,但它让整体设计变得简单,这是可以谅解的。 - 有多个类加载器的情况下如何处理单例类
自行指定类加载器,并指定同一个类加载器。