保证一个类只有一个实例,并提供一个全局访问点。
特点:延时加载,只有在正在需要的时候才开始实例化。
1、线程安全问题
2、双重判断double check,加锁优化(锁也可以直接加锁到方法上,但是会耗费性能)
3、使用volatile关键字,防止指令重排序
特点:类加载的初始化阶段就完成了实例的初始化,本质上是借助于jvm的类加载机制,保证实例的唯一性。(急加载)
类加载机制:
1、加载:加载二进制文件到内存中,并生成对应的class数据结构
2、连接:包括验证、准备(给静态变量赋默认值,引用类型为null,int为0,boolean为false等等)、解析(将符号应用替换为直接引用)
3、初始化(执行静态代码块、给静态变量赋初值)
注意:如果当前类的父类没有被加载,则会先加载父类
特点:也是借助jvm的类加载机制,保证实例的唯一性(懒加载)
当访问instance时才会初始化实例
特点:简单,线程安全,也能够防护反射攻击(禁止反射实例化enum类型类)。
1、懒汉模式:无解
2、饿汉模式
解决方法:把构造方法增加判断,如果使用反射调用私有构造方法,则会抛异常。
//不允许被实例化 private HunglySingleton(){ if(instance!=null){ throw new RuntimeException("单例不允许多例实例"); } }3、静态内部类模式
同饿汉模式的解决办法
4、enmu模式
天然的,就不能被反射调用创建实例,当使用反射newInstance()就会抛异常。
当我们对静态内部类模式测试一下,序列化并反序列化后,还是同一个实例吗?
public class InnerSingletonTest { public static void main(String[] args) throws IOException, ClassNotFoundException { InnerSingleton instance = InnerSingleton.getInstance(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("innerSingletonObject")); objectOutputStream.writeObject(instance); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("innerSingletonObject")); InnerSingleton instance2 = (InnerSingleton)objectInputStream.readObject(); objectInputStream.close(); System.out.println(instance==instance2); } } class InnerSingleton implements Serializable { private static class InnerSingletonHolder { private static InnerSingleton instance= new InnerSingleton(); } //不允许被实例化 private InnerSingleton(){ if(InnerSingletonHolder.instance!=null){ throw new RuntimeException("单例不允许多例实例"); } } /** * 获取实例 * @return */ public static InnerSingleton getInstance() { return InnerSingletonHolder.instance; } }答案是false。因为反序列化没有调用InnerSingleton的构造方法。
查看serializable接口的说明如下
需要增加一个readResolve方法
Object readResolve() throws ObjectStreamException{ return getInstance(); }注意:enum枚举类除外,有单独的处理方法,查看readObject()的源码如下:
jdk:Runtime类(饿汉)、Currency
spring:DefaultSingletonBeanRegistry、ReactiveAdapterRegistry、ProxyFactoryBean(aop包)