单例模式的总体概述
单例模式,属于创建型模式,《设计模式》一书对它做了定义:保证一个类仅有一个实例,并提供一个全局访问点。
单例模式适用于无状态的工具类、全局信息类等场景。例如日志工具类,在系统中记录日志;假设我们需要统计网站的访问次数,可以设置一个全局计数器。
单例模式的优势有
- 在内存里只有一个实例,减少了内存开销;
- 可以避免对资源的多重占用;
- 设置全局访问点,严格控制访问。
单例模式的研究重点大概有以下几个:
- 构造私有,提供静态输出接口
- 线程安全,确保全局唯一
- 延迟初始化
- 防止反射攻击
- 防止序列化破坏单例模式
多种实现方式与比较
线程安全的饿汉模式
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
也可以通过静态代码块的形式实现。实现与静态常量基本相同,只是把实例化过程放到了静态代码块中。
private final static HungrySingleton2 instance;
static {
instance = new HungrySingleton2();
}
饿汉单例模式的特点有
- 实现简单
- 线程安全
- 类加载时初始化实例
线程安全的懒汉单例模式
懒汉式用于解决延迟初始化问题,用到了才实例化。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
对于多线程来说,上面的实现存在竞态条件:先检查后执行,无法保证全局唯一。
通过给getInstance方法添加synchronized修饰,或者同步代码块形式很容易实现线程安全,保证全局唯一。
public synchronized static LazySingleton getInstance() {...}
或者
public static LazySingleton getInstance() {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
return instance;
}
然而代码会对性能造成影响,在第一个实例创建成功后,我们便不再需要锁。因此外层再对instance做一次空判断,即双重检查锁定。
双重检查锁定和volatile优化
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
但是JVM即时编译器中存在指令重排序优化。instance赋值语句包含了下面3个操作:
- 分配内存给对象
- 初始化对象
- 设置instance引用,指向刚分配的内存地址
程序执行时,步骤2和3可能出现重排序,导致instance先指向了内存地址,再初始化对象。其他线程外层校验是instance不为空,调用未完成初始化对象的方法会报空指针异常。
禁止指令重排序是volatile的两大特性之一。使用volatile修饰instance,在赋值操作后加入内存栅栏,赋值之前的所有操作均可见。
静态内部类实现延迟加载
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
private static class InnerClass {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.INSTANCE;
}
}
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当外部类第一次被加载时,并不需要去加载InnerClassr,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载InnerClass类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
在静态内部类的初始化阶段(class文件被加载后,被线程使用之前),执行类的初始化,JVM会获取一个类Class对象的初始化锁,锁可以同步多个线程对一个类的初始化。因此,类初始化允许重排序,非构造线程是无法看到重排序的。
单元素枚举实现单例模式
《Effective Java》推荐使用单元素枚举类型实现Singleton,书中这样描述“功能上与公有域方法类似,但更加简洁无偿地提供了序列化机制,绝对防止多次实例化”。
public enum EnumSingleton {
INSTANCE {
@Override
protected void print() {
System.out.println("使用枚举构建单例模式");
}
};
protected abstract void print();
public static EnumSingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.getInstance();
instance.print();
}
}
反编译代码
public abstract class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
protected abstract void print();
public static EnumSingleton getInstance()
{
return INSTANCE;
}
public static void main(String args[])
{
EnumSingleton instance = getInstance();
instance.print();
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0) {
protected void print()
{
System.out.println("enum singleton");
}
};
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
从反编译出的代码中可以看出,EnumInstance类在加载时,就把INSTANCE属性初始化好了,和饿汉模式类似。
- 类final--不能被继承
- 构造器私有--不能外部实例化
- 类变量是静态的--类加载初始化
【注】在EnumInstance类中,不定义print()方法的话,class反编译后是final类型的。
各种实现方式的选取
最好的实现方式是枚举,可以避免反射和序列化对单例模式的破坏;不能使用线程不安全的实现方式;如果程序一开始要加载的资源太多,就应该选取懒加载;饿汉单例模式在对象创建需要配置文件时不适用。
下一小节:《如何避免反射和序列化破坏单例模式》
来源:CSDN
作者:大唐雨夜
链接:https://blog.csdn.net/LIZHONGPING00/article/details/104595874