从源码看单例模式的应用

或许你曾为了应对面试而对设计模式问题做了单点突破,甚至可以做到徒手写单例;那么今天的你是否还记得如何写一个单例,什么样的写法是安全可靠的单例呢?作为设计模式的入门级模式,我们如何避免死记硬背,做到深刻理解并掌握它的用法。

通过本场免费 Chat 希望带给大家如下内容:

  1. 禁止任何人 new 你的单例;
  2. 控制单例就是控制线程;
  3. 通过 Java 源码学单例书写;
  4. 你不需要知道的单例写法;
  5. 如何控制多实例部署下的单例问题。

单例模式是一种非常简单的模式,实现的成本非常小,而从中获得收益却是巨大的。在现实生活中有些事情只能由一个人来做,比方说工作上你应该只有一个直接上级,否则你的工作将很难做;常规交通工具只有一个驾驶才能保证正确的方向……在计算机的世界里存在同样的情况。

单例最重要的特性:

一个 JVM 中只存在一个实例,伴随着应用的结束而结束。

禁止任何人 new 你的单例

类和实例是 Java 学习者面对的最基本概念,想想你是如何创建一个类的?最基本的操作是通过 new 关键字即可随心所欲的创建类的实例。

为了防止我们的单例被人随心所欲地创建,我们首先要做的就是禁止任何人 new 我们的单例类。

public class Singleton {        //禁止任何人new你的单例        private Singleton(){};}

至此,我们已经完成了单例类变成的第一步。

紧接着你要回答一个问题,既然任何人都无法 new 你的单例类,那么他们如何获得这个类的实例呢?

一般会有三种回答:

  • 第一,提供一个静态公有方法获取实例
  • 第二,通过反射机制创建
  • 第三,借助 Spring 框架注入

我们来实现第一种,单例类本身应该对外提供的实例获取方法。

public class Singleton {       public static Singleton getInstance(){                                      return new Singleton();       };}

上述代码的问题在于,每当 getInstance() 方法被调用时就会 new 一个 Singleton 实例,显然这是在糊弄自己——这只是把别人随心所欲的创建变成了自己随心所欲的创建。

问题到这里的时候你应该想到了,自己在 new Singleton 实例之前应该先判断是否已经 new 过了。对的,如果已经 new 过了就应该把当前 JVM 中的 Singleton 实例直接返回。

此时,我们显然需要一个变量来接受 new 出来的 Singleton 实例,而这个变量显然应该是 static 的,否则它无法被 getInstance() 方法引用。

于是你完整的代码变成了下面这样:

public class Singleton {    static Singleton instance;    public static Singleton getInstance(){        if(null ==instance) {            instance = new Singleton();        }        return instance;    };    //禁止任何人new你的单例    private Singleton(){};}

恭喜,你已经动手写了一个单例。抓紧测试一下吧。

public class ForSingleton {    public static void main(String[] args) throws Exception{        Singleton s1 = Singleton.getInstance();        Singleton s2 = Singleton.getInstance();        Singleton s3 = Singleton.getInstance();        System.out.println(s1);        System.out.println(s2);        System.out.println(s3);        System.out.println(s1==s2);        System.out.println(s2==s3);    }}

控制台输出如下,这意味着无论从内存地址还是 Boolean 判断看,s1、s2、s3 都是同一个实例。

[email protected]@1b6d3586co.topc.chat.Singleton@1b6d3586truetrueProcess finished with exit code 0

请仔细想一下,我们是从什么样的出发点把代码写成这样的,这很重要

这个出发点就是禁止任何人 new 你的单例。记住这一点我相信你随时都能写出这个单例来,直到某天有人跟你说这样的单例代码存在很大的漏洞,像个毕业生写出来的。

控制单例就是控制线程

既然说单例的特性是在 JVM 中只存在一个实例,那么上面的测试场景似乎远远不够;我们还没有考虑在高并发、多线程的应用场景下这样的控制逻辑是否能够 hold 住压力。

理论上假设有 A、B 两个线程调用 getInstance() 方法,当线程 A 执行完 if(null ==instance) 被中断,此时线程 B 恰好也执行到此 if(null ==instance) 依然成立。所以线程 B 得到了一个 new 出来的实例,此时 CPU 让线程 A 继续执行,问题由此而发生了——线程 A 也拿到了一个 new 出来的实例。

赶紧测试一下吧:

public class MultiThreadTest implements Runnable{    @Override    public void run() {        Singleton s1 = Singleton.getInstance();        //理论上只要出现一次输出的内存地址不一样,就说明getInstance()返回了不同的实例        System.out.println(s1);    }    public static void main(String[] args) {        Thread[] tArray=new Thread[1000];        /**        *线程中断不是必然出现,需要尽可能多的线程来模拟或多尝试几次        */        for (int i = 0; i < 1000; i++) {            Thread t1 = new Thread(new MultiThreadTest());            t1.setName("name"+i);            tArray[i] = t1;        }        for (int i = 0; i < 1000; i++) {            tArray[i].start();        }    }}

从源码看单例模式的应用_第1张图片

看到这样的结果,意味着不想发生的事情已经发生了。

你应该心服口服别人说你的单例代码存在漏洞,像是毕业生写出来的。我猜你也会立即寻找弥补的方法,百度会教你很多种写法解决单例的线程安全问题。诸如双重检查、Synchronized 同步等。 但是请你不要看,不要看,不要看,往下面看

通过 Java 源码学单例书写

当你空闲的时候随便看点源码,这里的源码指平台级的代码,如 JDK、Tomcat、Spring Framework 等。这些代码都是经过大量验证和调优的,无论是书写风格还是逻辑思维上都有很多可以学习的地方。看看 JDK 1.8 的 java.lang.Runtime:

public class Runtime {    private static Runtime currentRuntime = new Runtime();    public static Runtime getRuntime() {        return currentRuntime;    }    /** Don't let anyone else instantiate this class */    private Runtime() {}}

请认真仔细地阅读并记住上面 8 行代码,与我们开篇自己写的代码有何区别?这种源码写法是否存在线程安全问题?请在读者圈发表你的看法。

你不需要知道的单例写法

有很多百度文章都会罗列大概 8 至 9 种的单例写法,并非常清晰地表明每一种写法存在什么样的优缺点,笔者认为这很容易把人搞晕。笔者认为你没有必要去研究那些本身就是错误写法的代码,你需要知道是自己能否徒手写一段单例,并用线程安全来检验这段代码就足够了。

如何控制多实例部署下的单例问题

目前我们的应用大多是集群多实例部署,那么会有人关心单例模式在多台主机上并存时的应用场景。即每个 JVM 中都有一个实例,对于集群来说还是多实例的存在,而我们又需要保证整个集群中只有一个实例提供服务。该场景为延伸问题,笔者在应用层面的开发中也没有具体场景实际运用过。思路是这类服务类似于分部署的 master 节点,需要经过一些列状态同步选定一个节点来提供全局唯一性服务。

总结

单例是非常简单的模式,请记住单例书写的出发点【禁止任何人 new 你的单例】和单例的注意事项【线程安全】。


本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook.cn/gitchat/activity/5bf2a725186f104f04c24a72

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

你可能感兴趣的:(从源码看单例模式的应用)