概念

  • 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

结构

单例模式的主要有以下角色:

  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类

单例模式的实现

饿汉式

  • 类加载的时候就会创建该单例对象

静态变量方式

  • 实现过程

    单例模式 - 图1

  • 优点:简单

  • 缺点:instance对象是随着类的加载而创建的,可能造成类加载的时候,同时对象在内存中创建,但是对象并不使用造成内存的浪费。单例模式一般是调用getInstance方法的时候进行加载的,但也不排除其他加载的情况。

静态代码块

  • 实现过程

    单例模式 - 图2

其实和静态变量的原理是一样的,都是在<clinit>()执行,完成静态变量的初始化

  • 饿汉式的缺点:类在执行<clinit>()方法的时候会对静态内容完成初始化,这个时候就在内存中创建对象了。

懒汉式

  • 类加载不会导致该单例对象被创建,只有在首次使用的时候该对象才会被创建

线程不安全

  • 实现

    单例模式 - 图3

加入synchronized保证线程安全,但是每次getInstance都需要同步。也就是每次读操作都需要synchronized,这里是没必要的

单例模式 - 图4

  • 优点:在使用的时候才完成初始化,在内存中创建对象

双重检查锁

  • 实现

    单例模式 - 图5

  • 存在的问题:指令重排序

    使用volatile修饰

单例模式 - 图6

  • 添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。【推荐使用】

静态内部类

  • 实现

    由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

单例模式 - 图7

枚举方式

  • 实现

    单例模式 - 图8

破坏单例模式

  • 使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射

  • 序列化和反序列化破坏单例模式

    单例需要实现Serialable接口

单例模式 - 图9

单例模式 - 图10

  • 反射破坏单例模式

    将私有构造方法取消访问检查,这样就可以调用构造方法创建对象

单例模式 - 图11

解决单例模式被破坏

  • 解决序列化方式创建的对象

    在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

单例模式 - 图12

可以看到通过反序列化创建的是同一个对象

单例模式 - 图13


原理

单例模式 - 图14

如果是对象类

同样可以看到枚举类也有反序列化机制

单例模式 - 图15

进入到readOrdinaryObject

单例模式 - 图16

查看如何判断是否有readResolve()方法的

单例模式 - 图17

单例模式 - 图18

  • 解决反射破坏单例模式

    思路:多次调用构造方法抛出异常

单例模式 - 图19

JDK源码-Runtime类

  • 经典饿汉式单例模式

    单例模式 - 图20

单例模式 - 图21