前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例模式(singleton)之“世上安得双全法”

单例模式(singleton)之“世上安得双全法”

作者头像
菩提树下的杨过
发布2020-02-23 14:27:33
3310
发布2020-02-23 14:27:33
举报
文章被收录于专栏:菩提树下的杨过

返沪隔离在住处,远程办公闷得慌,写篇水文来凑数~_^

单例模式作为设计模式的入门模式,网上有各种写法,有点象孔乙己“茴”字的四种写法,都研究烂了,还能玩出啥新意?稍安勿躁,先来回顾一下:

一、饿汉式

代码语言:javascript
复制
/**
 * 饿汉式
 */
public class Single01 {

    private Single01() {

    }

    public void sayHello() {
        System.out.println("hello 1");
    }

    private static Single01 instance = new Single01();

    public static Single01 getInstance() {
        return instance;
    }

}

从类加载的机制可以知道,这种写法,一旦classloader加载后,instance静态变量就被实例化了,不管你用不用得到。犹如饿了三天的汉子,见到食物就狼吞虎咽,不管好不好吃,有没有毒,由此得名。

二、懒汉式

既然“饿汉式”式写法,吃相难看,于是大佬们又研究出了下面的写法:(这里我们只说线程安全的写法,非线程安全的不提也罢)

代码语言:javascript
复制
package singleton;

public class Single02 extends SuperClass {

    private Single02() {
    }

    public void sayHello() {
        System.out.println("hello 2");
    }

    private static volatile Single02 instance = null;

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

大意是:如果用不到,就不实例化,classLoader装载时,instance为null,仅在第1次调用getInstance时才new对象。好比一个懒汉,非到饿得不行了,才去弄吃的,故名:懒汉式。

缺点:太复杂了,有点秀!这个双重检测(double check)以及volatile的作用,对于初学者得琢磨半天。

三、金屋藏娇式

代码语言:javascript
复制
package singleton;

public class Single03 {

    private Single03() {
    }

    private static class InnerHolder {
        private static Single03 instance = new Single03();
    }

    public static Single03 getInstance() {
        return InnerHolder.instance;
    }

    public void sayHello() {
        System.out.println("hello 3");
    }
}

鉴于懒汉式的写法太过复杂,于是又有人想到了:借助一个内部静态类,把需要的实例先偷偷藏起来,等到要用时才请出来,是为“金屋藏娇”。这个写法,个人认为算是常规写法中最好的1个。

四、固若金汤法(enum法)

前3种写法都有一个致命缺点,无法抵挡反序列化捣乱。试想“单例”的初衷,就是保证同一个jvm中不能new出2个相同的实例,必须“天下无双”。可惜事与愿违,java创建实例的方法不仅仅只有构造函数new这一种,可以把现有实例序列化成字符串(比如:json序列化),然后再拿json串反序列化成新对象,相当于人类的生物clone技术,虽然克隆出来的兄弟,长相不分你我,但我们都知道“好看的皮囊千篇一律,有趣的灵魂独各不相同”。所以《effective java》中提出一种新方法:

代码语言:javascript
复制
package singleton;

public enum Single04 {

    INSTANCE;

    public void sayHello() {
        System.out.println("hello 4");
    }


}

这个写法可谓思路清奇,java中的enum本身也是一个类(虽然有点特殊),但是jvm规定enum没有构造函数,而且内部就是静态类,所以天然单例,关键还能防止反序列化攻击,比如下面的代码:

代码语言:javascript
复制
Gson gson = new Gson();
Single04 single04a = Single04.INSTANCE;
String s04 = gson.toJson(single04a);
System.out.println(s04);
Single04 single04b = gson.fromJson(s04, Single04.class);
single04b.sayHello();
System.out.println(single04a.hashCode() + " " + single04b.hashCode());

输出:

代码语言:javascript
复制
"INSTANCE"
hello 4
2051450519 2051450519

看第3行,2个实例的hashcode完全相同,说明就是同1个对象。而上述测试代码,换成前3种写法的任何1种:

代码语言:javascript
复制
Gson gson = new Gson();
Single03 single03a = Single03.getInstance();
String s03 = gson.toJson(single03a);
System.out.println(s03);
Single03 single03b = gson.fromJson(s03, Single03.class);
single03b.sayHello();
System.out.println(single03a.hashCode() + " " + single03b.hashCode());

输出:

代码语言:javascript
复制
{}
hello 3
1450821318 668849042

第3行看出,这2个实例的hashcode已经不同了,说明是2个不同的实例。

所以,从安全角度来看,enum用作单例毫无破绽,称之为“固若金汤法”名副其实!

等等!这就天下太平,人生圆满了吗?OO的世界中,还有多态呢! 如果这个单例类,需要继承自父类怎么弄?

终于,生活还是对我们下了狠手,人生太艰难了!enum不允许继承父类!!!

正所谓

世间安得双全法,不负如来不负卿

既然如此,那就... 洗洗睡吧,梦里什么都有!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 世间安得双全法,不负如来不负卿
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档