美文网首页Android开发程序员
Java设计模式(三):单例模式

Java设计模式(三):单例模式

作者: 青叶小小 | 来源:发表于2021-02-19 00:39 被阅读0次

一、单例模式

在任何开发语言中,单例模式应该算是大家基乎最先接触和学习的设计模式,因为,它最为简单也最为常用。

单例模式的特点:

  • 有且仅有一个实例;
  • 构造方法为私有;
  • 其实例只能由自己来创建;

单例模式的使用场景:

  • 全局配置;
  • client端用户缓存;
  • 等等;

单例也适时使用,不要觉得简单就乱用!

二、Java中的实现方式

大家能够用多少种方式来实现单例模式?
至少我能用5种方式来实现,如果还有更多其它方式,也欢迎在评论区留言。

2.1、简单暴力

/**
 *  没有延迟加载,但却最简单
 */
public class Singleton {
        private static final Singleton instance = new Singleton();
        
        private Singleton(){}
        public static Singleton getInstance(){
            return instance;
    }
}

2.2、延迟加载

针对 2.1 在类加载时就初始化,这里采用了延迟初始化。

/**
 *     延迟加载
 */
public class Singleton {
    private static Singleton instance = null;

    private Singleton(){}
    public synchronized static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

2.3、静态内部类来延迟加载

/**
 *     静态内部类,加载时没有初始化 instance,因此达到了延迟加载
 */
public class Singleton {
    private static class InternalSingleton{
        private static final Singleton instance = new Singleton();
    }

    private Singleton(){}
    public static Singleton getInstance(){
        return InternalSingleton.instance;
    }
}

2.4、双检索(DCL)

这种方式,大家要注意,网上有些是错误的。

public class Singleton {
    private static volatile Singleton instance = null; // 这里需要 volatile

    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为何要加上 volatile ,大家可以去看我的《小白系列五:关键字volatile》。

2.5、枚举方式(JDK 1.5才支持)

public enum Singleton{
    instance;  // 默认 instance = this

    public void function(){
        // ......
    }
}

三、五种方式的对比

3.1、绝对单实例

5种方式都能正确的实现单例,但只有『枚举方式』才是最为安全的,因此,它不支持反射,而其它方式虽然私有化了默认构造函数,但是,我们能可以通过反射的方式来产生多实例。

import java.lang.reflect.Constructor;

public class Demo {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = null;

        try {
            Constructor constructor = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            singleton2 = (Singleton) constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("singleton1 = " + singleton1);
        if (singleton2 != null) {
            System.out.println("singleton2 = " + singleton2);
        }
    }
}

打印结果如下:

singleton1 = Singleton@610455d6
singleton2 = Singleton@511d50c0

我们可以看到,Singleton 变成了多实例。

而枚举则不允许反射,JDK 源码中有给出判断:

// java.lang.reflect.Constructor.java

public final class Constructor<T> extends Executable {
    .....
    
    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        
        // 这里会判断类的修饰符,如果是枚举则报错
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
    
    ......
}

3.2、内存消耗

相比较静态常量,枚举会稍微多消耗点内存,因为枚举首先还是一个类,然后实例化几个由 final 修饰这个类的对象,每个实例都带有自己的一些元信息。而常量没有这一层封装,只占用基本的内存(包括引用和它的值本身),因此,要简单轻巧;如果值是基本类型而不是包装类型,那占用的内存就更少了。

枚举的使用,和单例的使用一样,我们也要适度,不能滥用;同样,大家在使用枚举时也不要担心这多出来的一点点内存开销,该用的时候还是要用。同时,我们需要注意一点:枚举的出现,能够帮助我们提升代码可读性,以及更好的可扩展性;因此,相比较多出来的一点点内存开销,不建议以牺牲代码可读性、可维护性、开发效率等而不使用枚举。

总之,Java的单例模式,我更推荐使用枚举来实现!

相关文章

  • JAVA设计模式 - 单例模式

    JAVA设计模式 - 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一...

  • 单例模式

    JAVA设计模式之单例模式 十种常用的设计模式 概念: java中单例模式是一种常见的设计模式,单例模式的写法...

  • Java中单例模式你用的哪一种?

    一起讨论java中的单例模式。单例模式是java设计模式中算是最简单的设计模式了。 * java实现单例模式的写法...

  • 单例模式

    概念 java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式模式,饿汉式模式、登记式单例三种。单例模式有以...

  • JAVA多线程之线程安全的单例模式

    概念:java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。单例模式有一...

  • JAVA设计模式之单例模式

    JAVA设计模式之单例模式

  • 设计模式

    Java 设计模式情景分析 ——单例模式 Java 设计模式情景分析——建造者模式 Java 设计模式情景分析——...

  • Java设计模式教程

    Java设计模式教程 Java工厂设计模式 Java抽象工厂模式 Java单例模式 Java建造者(Builder...

  • 关于java单例模式,这篇已经讲得很清楚了,建议收藏!

    概念 java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。 特点 单例...

  • 设计模式——单例模式

    设计模式——单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,这种类型...

网友评论

    本文标题:Java设计模式(三):单例模式

    本文链接:https://www.haomeiwen.com/subject/hhlnxltx.html