添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

1.1 概念

单例模式,是指在任何时候,该类只能被实例化一次,在任何时候,访问该类的对象,对象都是同一个。只要是程序员都会使用到,甚至都不能算是设计模式。但是在我们使用中也需要了解一下单例特性和使用场景

1.2 模式优缺点

单例模式有以下优点: >使用单例模式可以严格的控制用户怎样以及如何访问它 节约系统资源,提高系统的性能

单例模式有以下缺点: 单例类职责过重,在一定程度上违背了“单一职责原则” 如实例化对象长时间未使用,会GC回收,导致对象状态的丢失

# 2 单例模式分类

2.1 饿汉模式

public class SingletonEHan { private SingletonEHan() {} * 1.单例模式的饿汉式 private static SingletonEHan singletonEHan = new SingletonEHan(); public static SingletonEHan getInstance() { return singletonEHan; // SingletonEHan instance= SingletonEHan.getInstance(); * 2. 单例模式的饿汉式变换写法 * 基本没区别 private static SingletonEHan singletonEHanTwo = null; static { singletonEHanTwo = new SingletonEHan(); public static SingletonEHan getSingletonEHan() { if (singletonEHanTwo == null) { singletonEHanTwo = new SingletonEHan(); return singletonEHanTwo; // SingletonEHan instance= SingletonEHan.getSingletonEHan();
  • 优点:在类加载的时候就完成了实例化,避免了线程的同步问题
  • 缺点:由于在类加载的时候就实例化了,未懒加载,会造成内存的浪费
  • 2.2 懒汉模式

    2.2.1 DCL双重校验锁

    public class Singleton { private volatile static Singleton singleton; public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); return singleton;
  • 优点:线程安全,延迟加载
  • 缺点:使用了synchronized关键字,需要同步获取,影响性能
  • 2.2.2 静态内部类

    :::java
    public class SingletonIn implements Serializable {
        private static final long serialVersionUID = -2424536714640756316L;
        private SingletonIn() {
        private static class SingletonInHolder {
            private static final SingletonIn singletonIn = new SingletonIn();
        public static SingletonIn getSingletonIn() {
            return SingletonInHolder.singletonIn;
        public static void main(String[] args)  {
            SingletonIn singleton = getSingletonIn();
    
  • 优点:静态内部类随着方法调用而被加载,只加载一次,不存在并发问题,所以是线程安全的,延迟加载,效率高,推荐使用
  • 2.3 枚举

    :::java
    public enum SingletonEnum {
        INSTANCE(1,"i am singleton");
        private int code;
        private String message;
        SingletonEnum(int code, String message) {
            this.code = code;
            this.message = message;
        public final int getCode() {
            return code;
        public final String getMessage() {
            return message;
    

    大佬喜欢的用法,简单粗暴,其实有很多学问在里面,请看下文就会知道

    3 克隆-序列化-反射对单例的影响

    对象的创建方式有哪几种? 四种:new 、克隆、序列化、反射 上面的单例模式使用的都是new创建对象,那么其他三种方式对单例是否有影响呢?

    3.1 克隆对单例的影响

    实现Cloneable 接口,尽管构造函数是私有,但还会创建一个对象。因为clone方法不会调用构造函数,会直接从内存中copy内存区域。所以单例模式的类是不可以实现Cloneable接口

    以静态内部类的单例方式做测试

    public static void main(String[] args) throws CloneNotSupportedException {
            SingletonIn singleton = getSingletonIn();
            SingletonIn singleton1 = (SingletonIn) singleton.clone();
            SingletonIn singleton2 = getSingletonIn();
            System.out.println(singleton.hashCode());
            System.out.println(singleton1.hashCode());
            System.out.println(singleton2.hashCode());
    
    1173230247
    856419764
    1173230247
    

    hash值不一样,所以克隆成功了,生成了一个新对象,所以当对象的使用要求单例的时候,切记不可实现*cloneable*接口

    3.2 序列化对单例的影响

    3.2.1 静态内部类单例反序列化 如下方式,我们通过序列化反序列化获取对象,观察对象是否还是单例对象

    public static void main(String[] args) throws IOException, ClassNotFoundException {
            SingletonIn singleton = getSingletonIn();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\single.obj"));
            oos.writeObject(singleton);
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:\\single.obj")));
            SingletonIn singleton1 = (SingletonIn) ois.readObject();
            System.out.println(singleton.hashCode());
            System.out.println(singleton1.hashCode());
            System.out.println(singleton == singleton1);
    

    控制台打印结果如下:

    312714112
    692404036
    false
    

    hash值不一样,获取的对象非同一对象

    3.2.2 枚举序列化反序列化

    public static void main(String[] args) throws IOException, ClassNotFoundException {
            SingletonEnum singleton = SingletonEnum.INSTANCE;
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\single.obj"));
            oos.writeObject(singleton);
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:\\single.obj")));
            SingletonEnum singleton1 = (SingletonEnum) ois.readObject();
            System.out.println(singleton.hashCode());
            System.out.println(singleton1.hashCode());
            System.out.println(singleton == singleton1);
    
    2125039532
    2125039532
    

    枚举方式,即使序列化反序列化也不会破坏单例

    3.2.3 防止序列化对单例的破坏 自定义实现对象的readResolve()方法可以解决问题

    :::java
     private Object readResolve() {
            return getSingletonIn();
    
    312714112
    312714112
    

    这样反序列化获取到的就是原有的单例了

    3.2.4 原理分析 debug反序列化方法readObject(),调用链如下:

    主要分析这部分1767行开始 readOrdinaryObject 下面的源码

    //获取SingletonIn的Class
     Class<?> cl = desc.forClass();
            if (cl == String.class || cl == Class.class
                   || cl == ObjectStreamClass.class) {
                throw new InvalidClassException("invalid class descriptor");
            Object obj;
            try {//如果所代表的类是可序列化或者自定义序列化并且可以序列化runtime时候被实例化,则返回true
                obj = desc.isInstantiable() ? desc.newInstance() : null;
          //debug在这里通过反射创建了对象
            } catch (Exception ex) {
            passHandle = handles.assign(unshared ? unsharedMarker : obj);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(passHandle, resolveEx);
          //是否实现了自定义序列化接口
            if (desc.isExternalizable()) {
                readExternalData((Externalizable) obj, desc);
            } else {
          //读取持久化的数据并写入对象
                readSerialData(obj, desc);
            handles.finish(passHandle);
            if (obj != null & handles.lookupException(passHandle) == null &&
                desc.hasReadResolveMethod())
           //如果所代表对象实现了可序列化或者自定义序列化接口,并且定义了readResolve方法,则返回true
                //获取readResolve方法中的对象,并替换obj
                Object rep = desc.invokeReadResolve(obj);
                if (unshared && rep.getClass().isArray()) {
                    rep = cloneArray(rep);
                if (rep != obj) {
                    handles.setObject(passHandle, obj = rep);
            //返回返序列化对象
            return obj;
    

    从上面源码可知,反序列化的时候,通过反射会创建新的对象,所以不是单例,如果类实现了serializable or externalizable 接口,定义readResolve方法,可以控制最终反序列对象,所以重写该方法,可以实现单例

    而如果是枚举类型的话主要关注checkResolve(readEnum(unshared)) 方法,readEnum(unshared)返回的对象就是INSTANCE对象

    //获取枚举类对象
     private Enum<?> readEnum(boolean unshared) throws IOException {
          //分配给给定对象的下一个可用句柄,并分配赋值处理,
            int enumHandle = handles.assign(unshared ? unsharedMarker : null);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(enumHandle, resolveEx);
            String name = readString(false);
            Enum<?> result = null;
            //获取枚举类Class
            Class<?> cl = desc.forClass();
            if (cl != null) {
                    //方法返回具有指定名称的枚举类型的枚举常量,获取到INSTANCE对象
                    Enum<?> en = Enum.valueOf((Class)cl, name);
                    result = en;
                } catch (IllegalArgumentException ex) {
                 // 省略
                if (!unshared) {
                    handles.setObject(enumHandle, result);
            handles.finish(enumHandle);
            passHandle = enumHandle;
            return result;
    private Object checkResolve(Object obj) throws IOException {
           //在这一步就返回对象了
            if (!enableResolve || handles.lookupException(passHandle) != null) {
                return obj;
            Object rep = resolveObject(obj);
            if (rep != obj) {
                handles.setObject(passHandle, rep);
            return rep;
    

    魔法在java.lang.Enum.valueOf()中,最后获取的是常量类

    3.3 反射对单例的影响

    :::java
        public static void main(String[] args)
                throws ReflectiveOperationException {
            Class cls = SingletonIn.class;
            Constructor<SingletonIn> constructor = cls.getDeclaredConstructor();
            constructor.setAccessible(true);
            SingletonIn singleton = constructor.newInstance();
            SingletonIn singleton1 = getSingletonIn();
            System.out.println(singleton.hashCode());
            System.out.println(singleton1.hashCode());
            System.out.println(singleton == singleton1);
    
    1173230247
    856419764
    false
    

    反射直接破坏单例,解决方法是在构造器使用同步块互斥

        private static volatile boolean init = false;
        private SingletonIn() {
            synchronized (Singleton.class) {
                if (init) {
                    throw new RuntimeException("只能单例获取");
                init = !init;
    

    在这种条件下,再次通过反射调用就会报错:

    Exception in thread "main" java.lang.ExceptionInInitializerError
    	at designpattern1.singleton.inclass.SingletonIn.getSingletonIn(SingletonIn.java:43)
    	at designpattern1.singleton.inclass.SingletonIn.main(SingletonIn.java:57)
    Caused by: java.lang.RuntimeException: 只能单例获取
    

    枚举类型是绝对单例的,可以无责任使用 其他需根据场景使用不同版本,在有序列化和反射的场景下,选择合适的安全版本