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() 这行代码,在字节码层面并不是原子操作,而是分为三个步骤:
-
分配内存:在堆中开辟一块内存空间(此时内存中全是默认值,比如
int=0,引用=null)。 -
初始化对象:调用构造器,将内存中的值填充为真正的初始值(比如
int=5,引用指向具体对象)。 -
建立关联(赋值):将堆内存的地址赋值给栈中的 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"); } }
浙公网安备 33010602011771号