1.1 定义

单列(Singleton):保证一个类只有一个实例,并且提供一个全局访问点

1.2 应用场景

重量级的对象,不需要多个实列,例如线程池、数据库连接池。

1.3实现方式

1.3.1 懒汉模式

延迟加载,只有在真正使用的时候才开始实例化。

  1. public class LazySingleton {
  2. private volatile static LazySingleton instance;
  3. private LazySingleton() {
  4. }
  5. private static LazySingleton getInstance() {
  6. if (instance == null) {//已经实例化后,就不需要进行加锁操作,避免影响性能
  7. synchronized (LazySingleton.class) {
  8. if (instance == null) {//多线程情况下,可能加锁瞬间已经有多个线程运行到此
  9. instance = new LazySingleton();
  10. //new LazySingleton在字节码层步骤:
  11. //1.分配空间
  12. //2.初始化
  13. //3.引用赋值
  14. // JIT,CPU有可能对2与3指令进行重排序,排序后如下
  15. //1.分配空间
  16. //3.引用赋值===>如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例
  17. //2.初始化
  18. }
  19. }
  20. }
  21. return instance;
  22. }
  23. }

注意点:

  1. 线程安全问题:使用synchronized
  2. 加锁优化:两次判断instance是否为null
  3. volatile关键字:编译器(JIT),CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,对于volatile修饰的字段,可以防止指令重排。

1.3.2 饿汉模式

类加载的初始化阶段就完成了实例的初始化。本质就是借助JVM类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步形式完成类加载的整个过程)。

类加载过程:

  1. 加载二进制数据到内存中,生成对应的class数据结构。
  2. 连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析
  3. 初始化:给类的静态变量赋初值

只有在真正使用对应的类时,才会触发初始化如:

  1. 当前类是启动类即main函数所在类
  2. 直接进行new操作
  3. 访问静态属性、静态方法
  4. 用反射访问类
  5. 初始化一个类的子类
  1. public class HungrySingleton {
  2. private static HungrySingleton instance=new HungrySingleton();
  3. public HungrySingleton() {
  4. }
  5. public static HungrySingleton getInstance(){
  6. return instance;
  7. }
  8. }

1.3.3 静态内部类

  1. public class InnerClassSingleton {
  2. private static class InnerClassHolder {
  3. private static InnerClassSingleton instance = new InnerClassSingleton();
  4. public InnerClassHolder() {
  5. }
  6. }
  7. public static InnerClassSingleton getInstance() {
  8. return InnerClassHolder.instance;
  9. }
  10. }
  1. 本质上是利用类的加载机制来保证线程安全
  2. 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式

1.4 反射攻击与解决

反射攻击:

  1. Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
  2. declaredConstructor.setAccessible(true);
  3. InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
  4. InnerClassSingleton instance = InnerClassSingleton.getInstance();
  5. System.out.println(innerClassSingleton == instance);

静态内部类防止反射攻破:

  1. public class InnerClassSingleton {
  2. private static class InnerClassHolder {
  3. private static InnerClassSingleton instance = new InnerClassSingleton();
  4. public InnerClassHolder() {
  5. // 防反射攻击
  6. if(InnerClassHolder.instance!=null){
  7. throw new RuntimeException("单列不允许多个实例");
  8. }
  9. }
  10. }
  11. public static InnerClassSingleton getInstance() {
  12. return InnerClassHolder.instance;
  13. }
  14. }

1.5 枚举类型

枚举类型也是单例模式,实现特点:

  1. 天然不支持反射创建对应的实例,且有自己的反序列化机制
  2. 利用类加载机制保证线程安全

    1. public enum EnumSingleton {
    2. INSTANCE;
    3. public void print() {
    4. System.out.println(this.hashCode());
    5. }
    6. }

    1.6 序列化

    可以利用指定方法替换反序列化流中的数据,如

    1. ANYACCESSMODIFIER Object readResolve() throws ObjectStreamException;

    ```java import java.io.ObjectStreamException; import java.io.Serializable;

public class InnerClassSingleton implements Serializable {

  1. static final long serialVersionUID = 42L;//保证版本一致
  2. private static class InnerClassHolder {
  3. private static InnerClassSingleton instance = new InnerClassSingleton();
  4. public InnerClassHolder() {
  5. if (InnerClassHolder.instance != null) {
  6. throw new RuntimeException("单列不允许多个实例");
  7. }
  8. }
  9. }
  10. public static InnerClassSingleton getInstance() {
  11. return InnerClassHolder.instance;
  12. }
  13. Object readResolve() throws ObjectStreamException {
  14. return InnerClassHolder.instance;
  15. }

}

  1. <a name="guAl4"></a>
  2. # 1.7 源码中的应用
  3. ```java
  4. //Spring & JDK
  5. java.lang.Runtime
  6. org.springframework.aop.framework.ProxyFactoryBean
  7. org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
  8. org.springframework.core.ReactiveAdapterRegistry
  9. // Tomcat
  10. org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
  11. //反序列化指定数据源
  12. java.util.Currency