文章目录
应用场景
由于单例模式只生成一个实例, 减少了系统性能开销(如: 当一个对象的产生需要比较多的资源时, 如读取配置, 产生其他依赖对象, 则可以通过在应用启动时直接产生一个单例对象, 然后永久驻留内存的方式来解决)
- Windows中的任务管理器;
- 文件系统, 一个操作系统只能有一个文件系统;
- 数据库连接池的设计与实现;
- Spring中, 一个Component就只有一个实例Java-Web中, 一个Servlet类只有一个实例;
实现要点
- 声明为private来隐藏构造器
- private static Singleton实例
- 声明为public来暴露实例获取方法
单例模式主要追求三个方面性能
- 线程安全
- 调用效率高
- 延迟加载
实现方式
主要有五种实现方式,懒汉式(延迟加载,使用时初始化),饿汉式(声明时初始化),双重检查,静态内部类,枚举。
饿汉式
这种方式基于类ClassLoder机制,使用static来定义静态成员变量或静态代码,让instance在类加载时就进行初始化;避免了同步问题,实现线程安全。饿汉式的优势在于实现简单,劣势在于不是懒加载模式(lazy initialization)
存在问题
- 在需要实例之前就完成了初始化,在系统中单例场景较多的情况下,会造成内存占用,加载速度慢问题
- 由于在调用getInstance()之前就完成了初始化,如果需要给getInstance()函数传入参数,将会无法实现
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
};
public static Singleton getInstance() {
return instance;
}
}
//static 静态代码块
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){
}
public static Singleton getInstance() {
return this.instance;
}
}
静态内部类
由于内部类不会在类的外部被使用,所以只有在调用getInstance()方法时JVM才会被加载静态内部类。同时依赖JVM的 ClassLoader 类加载机制保证了不会出现同步问题。
public class Singleton {
private Singleton() {
};
public static Singleton getInstance() {
return Holder.instance;
}
//静态内部类实现创建单例对象
private static class Holder{
private static Singleton instance = new Singleton();
}
}
枚举方法 实现单例
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒。
- 线程安全
由于枚举类的会在编译期编译为继承自java.lang.Enum的类,其构造函数为私有,不能再创建枚举对象,枚举对象的声明和初始化都是在static块中,所以由JVM的ClassLoader机制保证了线程的安全性。但是不能实现延迟加载
- 序列化
由于枚举类型采用了特殊的序列化方法,从而保证了在一个JVM中只能有一个实例。
枚举类的实例都是static的,且存在于一个数组中,可以用values()方法获取该数组
在序列化时,只输出代表枚举类型的名字属性 name
反序列化时,根据名字在静态的数组中查找对应的枚举对象,由于没有创建新的对象,因而保证了一个JVM中只有一个对象
public enum Singleton {
INSTANCE;
public String error(){
return "error";
}
}
饿汉式,静态内部类,枚举类型方式实现单例模式 原理都是利用借助了类加载的时候初始化单例。即借助了ClassLoader的线程安全机制。这几种实现方式,虽然并没有显示的使用synchronized,但是还是其底层实现原理还是用到了synchronized。
所谓ClassLoader的线程安全机制,就是ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字,同步了多线程下的创建实例形式。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的,也就是保证了线程安全。
懒汉式 实现单例
懒汉式,线程不安全的实现
由于没有同步,多个线程可能同时检测到实例没有初始化而分别初始化,从而破坏单例约束。
public class Singleton {
private static Singleton instance;
private Singleton() {
};
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式,线程安全但效率低下的实现 ,synchronized 静态同步方法
public class Singleton {
private static Singleton instance;
private Singleton() {
};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
由于对象只需要在初次初始化时需要同步,多数情况下不需要互斥的获得对象,加锁会造成巨大无意义的资源消耗
加双重检查机制
这种方法对比于上面的方法确保了只有在初始化的时候需要同步,当初始化完成后,再次调用getInstance不会再进入synchronized块。 内部检查是必要的,由于在同步块外的if语句中可能有多个线程同时检测到instance为null,同时想要获取锁,所以在进入同步块后还需要再判