设计模式学习笔记|单例模式 Singleton

作者: 码农UP2U | 来源:发表于2020-03-24 22:31 被阅读0次

    单例模式是设计模式中比较经常听说的设计模式,也是比较容易掌握的设计模式。基本上接触过设计模式的人别的模式不一定能说出来,但是一般“单例模式”和“工厂模式”是都能说出来的。

    很多时候,我们都会以为单例模式是比较好掌握的,但是后来在我的学习当中,我发现还是有很多问题是没有考虑到的,甚至是想象不到的。

    单例模式是要使类的实例在内存中只有一份。听起来挺容易的,但是这个还真是没有想象的那么简单。我的代码使用 Java 来进行描述。

    通常情况下,在使用 Java 来完成 单例模式 的时候,都知道存在两种写法,一种是饿汉模式,另一种是懒汉模式。所谓饿汉模式,就是在类加载入内存之后,直接实例化一个对象出来;懒汉模式是在需要的时候再去实例化一个对象出来。

    为什么有饿汉模式和懒汉模式呢?这得从它们的加载时机来考虑。很多人认为,饿汉模式在类进入内存就实例化一个对象有些不妥,因为没有使用,为什么要着急实例化呢,所以就出现了懒汉模式。懒汉模式是在需要的时候才去实例化类的对象,但是懒汉模式会因为多线程的问题,会导致实例化多个对象出来,而此时就需要解决多线程同步的问题。解决多线程同步的问题,就需要用到锁,那么就又带来了效率上的问题。

    说了这么多,那么来看看,到底如何来使用 Java 语言完成一个 单例模式。

    饿汉模式

    先来看看饿汉模式的代码:

    public class Singleton01 {
    ​
        private static final Singleton01 INSTANCE = new Singleton01();
    ​
        // 构造函数为 private
        private Singleton01() {}
    ​
        public static Singleton01 getInstance() { return INSTANCE; }
    ​
        // 此处模拟类中处理业务的方法
        public void m() {
            System.out.println("m");
        }
    ​
        public static void main(String[] args) {
            Singleton01 s1 = Singleton01.getInstance();
            Singleton01 s2 = Singleton01.getInstance();
    ​
            // 两个引用指向的是一个对象
            System.out.println(s1 == s2);
        }
    }
    ​```
    单例模式的第一步就是将 构造方法 的访问修饰符设置为 private,使得外部无法直接实例化。然后在类中定义一个静态的 getInstance 方法用来获取实例。
    
    使用饿汉模式的单例,在类加载到内存后,静态变量只实例化一次,JVM 保证其线程的安全。
    
    其缺点是,不管该类是否要使用,都会马上得到一个实例。因此,这就有了懒汉模式。
    
    
    #### 懒汉模式
    懒汉模式的单例的代码:
    

    public class Singleton06 {
    private static volatile Singleton06 INSTANCE;

    private Singleton06() {}

    public static Singleton06 getInstance() {
    if (INSTANCE == null) {
    // 双重检查
    synchronized (Singleton06.class) {
    if (INSTANCE == null) {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    INSTANCE = new Singleton06();
    }
    }
    }

    return INSTANCE;
    }

    public void m() {
    System.out.println("m");
    }

    public static void main(String[] args) {
    for (int i = 0; i < 100; i ++) {
    new Thread(()->{
    System.out.println(Singleton06.getInstance().hashCode());
    }).start();
    }
    }
    }
    ​```
    以上就是懒汉模式的代码。

    在懒汉模式中,使用了 synchronized 来解决方法“不可重入”的问题,其中使用 Thread.sleep 来让线程休息一下,从而让出线程所占用的 CPU 而产生线程的切换。可以把第一个 if 判断和 synchronized 两行删掉,只留下最里面的 if 语句块的内容,就会发现会实例化多个对象了。

    实例化多个对象

    在 Java 中提供了反射的机制,即使使用单例模式,仍然可以实例化出多个对象。无论是上面的饿汉模式,还是懒汉模式,都可以实例化多个实例。

    这里使用第一个饿汉模式的代码进行测试,测试代码如下:

    Class<?> aClass = Class.();
    Singleton01 s3 = (Singleton01) aClass.newInstance();
    

    代码很简单,只有上面两句,但是这样就已经实例化出了一个对象,且通过 s3 可以调用该类中的方法。

    因此这样,就可以实例化对象出来了,内存中就有了一个类的多个实例了。

    枚举类的单例

    枚举在很多语言中都有,一般情况就是定义一些有限的常量。其实,枚举类中可以定义方法。看一下枚举类的单例代码,代码如下:

    public enum Singleton08 {
        INSTANCE;
    ​
        public void m() {
            System.out.println("m");
        }
    ​
        public static void main(String[] args) {
            for (int i = 0; i < 100; i ++) {
                new Thread(()->{
                    System.out.println(Singleton08.INSTANCE.hashCode());
                }).start();
            }
        }
    }
    

    以上代码,仍然通过 new Thread 多线程来得到其实例。
    但是,通过输出可以看出,其 hashCode 始终是一样的。
    接着使用上面的反射来获取枚举类的实例,代码如下:

    Class<?> aClass = ;
    {
        aClass = Class.();
        Singleton01 s3 = (Singleton01) aClass.newInstance();
    

    然后代码执行到 newInstance 方法时会报错,提示访问异常。
    因为枚举类没有定义构造函数,因此无法实例化。
    也就是说使用枚举类,即可以保证线程的安全,也可以防止反射来实例化。算是一种完美的方法。

    最后

    看似简单的单例模式,其中竟然也蕴含着这么多的知识点,学完真是受益非浅。虽然只是一个单例模式,掌握了一种设计模式,但是从各种实现中,又学到了很多其他的知识。比如,类实例化的时机,多线程方法的不可重入,枚举类的另类用法等。

    所以,知识如果能够串联起来,那么才能把学到的知识融会贯通,真正掌握和吸收。

    这就是我关于设计模式中单例模式的一篇笔记。



    微信中搜索 “码农UP2U” 关注我的公众号吧!!!

    相关文章

      网友评论

        本文标题:设计模式学习笔记|单例模式 Singleton

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