Java单例模式

确保每个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式避免了状态不一致的情况。

特点:

·单例类只有一个实例;

·单例类自己创建那个唯一的实例;

·单例类为整个系统的其他对象提供这一实例。

单例模式保证了全局对象的唯一性。例如配置类等。

单例的四大原则:

·构造私有;

·以静态方法或者枚举方法返回实例;

·确保只有一个实例,尤其是多线程环境;

·确保反序列化不会构建对象。

实现实例化的方法有:

饿汉式(立即加载)

public class Singleton {
    private Singleton() {
        System.out.println("构造函数Singleton");
    }
    private static Singleton single = new Singleton();
    
    public static Singleton getInstance() {
        System.out.println("getInstance");
        return single;
    }
}

懒汉式(延迟加载)

public class Singleton {
    private Singleton() {
        System.out.println("构造函数Singleton");
    }
    private static Singleton single=null;
    public static Singleton getInstance() {
        System.out.println("getInstance");
        if(single==null){
            single = new Singleton();
        }
        return single;
    }
}

同步锁(解决多线程问题)

public class Singleton {
    private Singleton() {
        System.out.println("构造函数Singleton");
    }
    private static Singleton single=null;
    public synchronized static Singleton getInstance() {
        System.out.println("getInstance");
        if(single==null){
            single = new Singleton();
        }
        return single;
    }
}

双重检查锁(解决多线程下使用同步锁的性能问题)

为了禁止指令重排序,保证对象在“引用被赋值”之前“已经完全初始化”,实例变量要使用volatile修饰。

在JVM中,执行 single = new Singleton() 这行代码,在字节码层面并不是原子操作,而是分为三个步骤:

  1. 分配内存:在堆中开辟一块内存空间(此时内存中全是默认值,比如 int=0,引用=null)。

  2. 初始化对象:调用构造器,将内存中的值填充为真正的初始值(比如 int=5,引用指向具体对象)。

  3. 建立关联(赋值):将堆内存的地址赋值给栈中的 single 引用变量。

不使用volatile修饰可能会导致步骤2未执行,步骤3已经执行,其他线程会判断single 不为null而直接使用,但是single并未初始化而导致空指针异常。

public class Singleton {
    private Singleton() {
        System.out.println("构造函数Singleton");
    }
    private volatile static Singleton single=null;

    public  static Singleton getInstance() {
        System.out.println("getInstance");
        if(single==null){
            synchronized (Singleton.class) {
                if(single==null) {
                    single = new Singleton();
                }
            }
        }
        return single;
    }
}

内部静态类(懒汉式,延迟加载,线程安全,但是不能解决反序列化下重新构建实例的问题)

原理:延迟加载和JVM类加载锁(线程安全)

唯一的实例化时机:Java类加载的“初始化阶段”是由JVM底层加锁进行的。当外部类Singleton 被加载时,内部静态类InnerObject 不会被加载。只有第一次调用getInstance()方法时才会加载和初始化内部静态类。

线程安全:JVM在类初始化时,会获得初始化锁,因此多个线程同时调用getInstance()方法,内部静态类也只会初始化一次。

虽然构造器是private的,但是可以通过反射强行调用构造器,因此使用反射仍可以创建第二个实例。

 

public class Singleton {
    private Singleton() {
        System.out.println("构造函数Singleton");
    }
    private static class InnerObject {
        private static final Singleton single =new Singleton();
    }
    public  static Singleton getInstance() {
        return InnerObject.single;
    }
}

 

内部枚举类实现(饿汉式,防止反射和序列化攻击)

枚举类enum会继承java.lang.Enum final 类。枚举常量会被编译成静态成员(public static final修饰),在静态代码块中实例化,同样会获得类加载锁,保证了实例的唯一性。

Java反射会有检验,通过反射创建枚举实例时会抛出异常IllegalArgumentException

枚举的序列化只写入常量的name,反序列化会从缓存哈希表中获取已经存在的单例,不会重新构建。

枚举类已经继承Enum,无法继承其他类。

public class SingletonFactory {
    private enum EnumSingleton {
        Singleton;
        private Singleton singleton;
        private EnumSingleton() {
            singleton = new Singleton();
        }
        public Singleton getInstance() {
            return singleton;
        }
    }
    public static Singleton getInstance(){
        return EnumSingleton.Singleton.getInstance();
    }
}

public class Singleton {
    private Singleton() {
        System.out.println("构造函数Singleton");
    }
}

 

posted @ 2026-06-26 00:39  KUMIN  阅读(2)  评论(0)    收藏  举报