简介
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
特点
- 单例模式保证整个应用中某个实例有且只有一个。
- 有些对象我们只需要一个,比如:
- 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
- 每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
- 如果创造出多个实例,就会导致许多问题,比如占用过多资源,不一致的结果等
总之,选择单例模式就是为了避免不一致状态,避免政出多头。
实现
一.饿汉模式--急着加载
单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。
- 优点
在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的 - 缺点
所以饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它
public class Singleton {
//1.构造方法私有化,不允许外部直接创建对象
private Singleton(){
}
//2.创建类的唯一实例,使用private static修饰
private static Singleton instance=new Singleton();
//3.提供一个用于获取实例的方法,使用public static修饰
public static Singleton getInstance(){
return instance;
}
}
二.懒汉式--等着加载
这段代码简单明了,但是当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全
2.1整个 getInstance() 方法设为同步
最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
虽然线程安全了,但是每次都要同步,会影响性能,毕竟大部分情况下是不需要同步的
2.2双重检查锁定
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
第一个if (instance == null),其实是为了解决Version2中的效率问题,只有instance为null的时候,才进入synchronized的代码段——大大减少了几率。
第二个if (instance == null),则是跟Version2一样,是为了防止可能出现多个实例的情况。
2.3静态内部类
这种写法使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
三.枚举
public enum Singleton{
INSTANCE;
}
我们可以通过 Singleton.INSTANCE 来访问实例,这比调用getInstance()方法简单多了。
- 创建枚举默认就是线程安全的,所以不需要担心double checked locking
- 而且还能防止反序列化导致重新创建新的对象
饿汉模式和懒汉模式的对比:
- 初始化时机
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。 - 特点
饿汉模式加载类时比较慢,但运行时获取对象的速度比较快,线程安全;
懒汉模式加载类时比较快,但运行时获取对象的速度比较慢,线程不安全。
选择
一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)可以使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。
网友评论