概述
单例模式是一种创建者模式。当我们需要确保系统中某个类仅能存在一个对象时,比如:全局信息类例如当项目启动时我们将一个配置文件读取为一个Config类的实例从而在业务逻辑中通过操作对象读取配置、无状态的工具类仅需一个实例进行复用即可,也就是当该对象仅需一个实例即可或处于安全考虑而做出的限制并且反复创建该实例会消耗系统资源,此时可以使用单例模式。
实现方法
在实现一个单例类时,由于涉及到类加载、对象属性的创建、对象属性的访问等问题,需要考虑到对象创建时间、创建与赋值的方式、线程安全、阻止反射与对象序列化造成的被多例等情况。
Eager 饿汉式
饿汉式是最简单的单例模式,类中的对象属性在类加载时就被初始化,由于JVM加载类时保证单线程,所以避免了线程问题,但eager加载方式造成即使运行过程中全程未使用类该类也会被加载,造成不必要的资源浪费。
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton {}
public static Singleton getInstance() {
return singleton;
}
}
等同代码
public class Singleton {
private static Singleton singleton;
static {
singleton = new Singleton();
}
private Singleton {}
public static Singleton getInstance() {
return singleton;
}
}
Lazy 懒加载
懒加载即在使用时对单例类进行实例化,但简单的懒加载未考虑线程安全问题,在getInstance()
方法中,若两个线程同时进入实例对象等于null
的if
语句中,对象将会被实例化两次从而违背单例模式的初衷。
// 线程不安全的懒加载单例类代码
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
// 重量级锁线程安全的懒加载单例类实现
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
// 方法内部锁线程安全的懒加载单例类实现(双重检查)
public class Singleton {
// 由于实例化对象非原子操作,所以加volatile
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
// 第一次判断让实例构造完成后能并发执行
if(singleton == null) {
synchronized (Singleton.class) {
// 第二次判断防止第一次判断同时进入多个线程
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
// 静态内部类懒汉式写法
public class Singleton {
private Singleton() {}
// 使用了静态内部类,让JVM在类加载也就是内部类被Singleton.getInstance()方法内调用时时为我们初始化
private static class SingletonInstance {
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.singleton;
}
}
Enum 枚举式
枚举式就是用java
中枚举类的形式构造的单例类,枚举式在保证能懒加载线程安全(枚举对象是以static
形式初始化,JVM保证线程安全)的同时,由于java
中规定了在枚举对象序列化时仅输出name
,而反序列化时使用name
查找对象,从而实现了单例而非被破坏。在反射中,由于newInstance()
在用户试图创建enum
类型的对象时会检查从而报错,所以十分安全。
// 枚举式实现Resource的单例,通过Something.INSTANCE.getInstance()即可访问
class Resource{
}
public enum Something {
INSTANCE;
private Resource instance;
Something() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
总结
结合多方因素,能同时实现懒加载、线程安全、阻止序列化反序列化与反射破坏单例就是最佳的枚举式单例模式实现。