漫画:如何写出更优雅的单例模式?

举报
feichaiyu 发表于 2019/10/26 23:20:21 2019/10/26
【摘要】 上一次为大家介绍了单例模式的基本概念和几种实现方式,没看过的小伙伴们可以点击下面链接:漫画:什么是单例设计模式?如果懒得去看也不要紧,让我们来简单回顾一下。线程安全的懒汉型单例模式:Singleton { Singleton() {} Singleton instance = ; Singleton getInstance() { (instance == ) {...

上一次为大家介绍了单例模式的基本概念和几种实现方式,没看过的小伙伴们可以点击下面链接:


漫画:什么是单例设计模式?


如果懒得去看也不要紧,让我们来简单回顾一下。


线程安全的懒汉型单例模式:


Singleton {
    Singleton() {}  Singleton instance = ;  Singleton getInstance() {
          (instance == ) {      (Singleton.){  (instance == ) {     instance = Singleton();
                }
             }
          }
          instance;
      }
}


该实现方式有几个值得注意的技术点:


  1. 使用双重锁检测机制,确保并发情况下instance对象不会被重复初始化。

  2. 使用volatile修饰符,防止指令重排引发的初始化问题。



这个实现方式虽然保证了线程安全,但仍然存在一些缺陷,如何写出更优雅的单例模式呢?让我们进入今天的主题。

2.jpg

3.jpg

4.jpg

用静态内部类实现单例模式:


Singleton {
    LazyHolder {
        Singleton = Singleton();
    }
    Singleton (){}
    Singleton getInstance() {
        LazyHolder.;
    }
}


这里有几个需要注意的点:


1.从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。


2.INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

5.jpg

6.jpg

7.jpg

8.jpg

如何利用反射打破单例模式的约束?其实很简单,我们来看下代码。



利用反射打破单例:

Constructor con = Singleton..getDeclaredConstructor();
con.();
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
System..println(singleton1.equals(singleton2));



代码可以简单归纳为三个步骤:


第一步,获得单例类的构造器。


第二步,把构造器设置为可访问。


第三步,使用newInstance方法构造对象。


最后为了确认这两个对象是否真的是不同的对象,我们使用equals方法进行比较。毫无疑问,比较结果是false。

9.jpg

10.jpg

用枚举实现单例模式:


SingletonEnum {
    ;
}

11.jpg

12.jpg

让我们来做一个实验,仍然执行刚才的反射代码:


Constructor con = SingletonEnum..getDeclaredConstructor();
con.setAccessible();
SingletonEnum singleton1 = (SingletonEnum)con.newInstance();
SingletonEnum singleton2 = (SingletonEnum)con.newInstance();
System..println(singleton1.equals(singleton2));


执行结果如下:


Exception in thread "main" java.lang.NoSuchMethodException: com.heitian.ssm.utils.SingletonEnum.<init>()

at java.lang.Class.getConstructor0(Class.java:2892)

at java.lang.Class.getDeclaredConstructor(Class.java:2058)

at com.heitian.ssm.utils.SingletonTest.main(SingletonTest.java:22)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

13.jpg

14.jpg

15.jpg

16.jpg

17.png

几点补充:


使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。


对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。


喜欢本文的朋友们,欢迎长按下图关注订阅号程序员小灰,收看更多精彩内容

1.jpg

转载声明:本文转载自公众号【程序员小灰】

原文链接:https://mp.weixin.qq.com/s/AdJI5a4w515SPPI_4gVImA


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。