专栏首页码农UP2U设计模式学习笔记|单例模式 Singleton

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

单例模式:饿汉、懒汉、枚举类!!!!

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

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

单例模式是要使类的实例在内存中只有一份。听起来挺容易的,但是这个还真是没有想象的那么简单。我的代码使用 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(gh_3c91b47a82e0),作者:码农UP2U

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • LeetCode | 94.二叉树的中序遍历

    这次来写一下 LeetCode 的第 94 题,二叉树的中序遍历。

    码农UP2U
  • Redis | 源码阅读 —— 字符串

    使用过 Redis 的都知道 Redis 用的最多的可能是它的 Key/Value 的缓存,在 Redis 用作 Key/Value 的缓存时,...

    码农UP2U
  • Socket 编程

    最近在录一套关于 Web 页面获取 MAC 地址的视频,Web 页面要获取 MAC 地址是以前项目中的真实案例,其中的解决方法也换了几波。最终的...

    码农UP2U
  • 如何性能测试中进行业务验证

    在性能测试过程中,验证HTTP code和响应业务code码是比较基础的,但是在一些业务中,这些参数并不能保证接口正常响应了,很可能返回了错误信息,所以这个时候...

    FunTester
  • 【SAP ABAP系列】删除SAP数据库表数据的几种方法

    输入要删除数据的自定义表名,回车。进入后,在menu中输tcode:&sap_edit,回车,点击运行,即可进入修改界面。

    matinal
  • Spring Boot Actuator详解与深入应用(三):Prometheus+Grafana应用监控

    本文系《Spring Boot Actuator详解与深入应用》中的第三篇。在前两篇文章,我们主要讲了Spring Boot Actuator 1.x与 2.x...

    aoho求索
  • 一支烟的时间导致他错失女神,Python查看撤回消息,力挽狂澜!

    微信(WeChat) 是腾讯公司于2011年1月21日推出的一个为智能终端提供即时通讯服务的免费应用程序,由张小龙所带领的腾讯广州研发中心产品团队打造 。在互联...

    IT派
  • 《挑战30天C++入门极限》C/C++中结构体(struct)知识点强化

    landv
  • 一道小小面试题的细节分析

    跟着阿笨一起玩NET
  • PHP魔术方法

    单列模式,又称职责模式,可以用来在程序中创建一个单一功能的访问点,就是实例化出来的对象是唯一的。

    白胡杨同学

扫码关注云+社区

领取腾讯云代金券