单例模式
来源:互联网 发布:人工智能的理论意义 编辑:程序博客网 时间:2024/04/28 16:00
文/程序亦非猿(简书作者)
原文链接:http://www.jianshu.com/p/eebcb81b1394
单例模式(Singleton Pattern)
当我们有这样的需求:某一些类应该只存在一个实例 的时候,我们就可以用单例模式来应对.
单例模式:确保一个类只有一个实例,并提供一个全局访问点.
单例模式是所有设计模式中最简单的一个,也是大部分人最早知道的一个设计模式.
但是即使是最简单的,也有很多可以推敲的细节,要做得对也不简单.
经典的单例
相信大家一定写过这样类似的单例模式代码:
public class Singleton { private static Singleton ins; private Singleton() {} public static Singleton getIns() { if (null == ins) { ins = new Singleton(); } return ins; }}
简单总结一下这样的写法:
- 提供一个全局静态的getIns方法,使得易于使用.
- 延迟Singleton的实例化,节省资源(所谓的懒汉式).
- 缺点是线程不安全.当多个线程同时进入if (null == ins) {}的时候就会创建多个实例.
OK,接下来我们看来是要解决多线程不安全的问题了.
多线程安全
这个时候可能有的人就说了,这个太简单了,加一个synchronized不就结了吗?
public static synchronized Singleton getIns() { if (null == ins) { ins = new Singleton(); } return ins;}
确实,增加synchronized之后能够迫使每个线程在进入这个方法之前,要先等别的线程离开该方法.也即避免了多个线程同时进入getIns方法.
诚然这个能解决问题,但是我们知道synchronized是非常耗性能的.
更何况:
我们只需要在第一次执行这个方法的时候同步,也就是说当ins实例化后,我们不再需要同步了
而如果我们加了synchronized,那么实例化后的每次调用getIns都是一种多余的消耗操作,是累赘
PS:当然,如果哪些额外的负担你能接受(比如用的很少),那么添加synchronized
的方法也是可以接受的,毕竟这是最简单的方式.
那么问题来了,如何改善?如何确保单例,而又不影响性能?
性能进阶
接下去介绍一种更优秀的,线程安全的单例写法—双重检查锁模式(double check locking pattern)
public class DoubleCheck { private volatile static DoubleCheck ins; private DoubleCheck() {} public static DoubleCheck getIns() { if (null==ins){ //检查 synchronized (DoubleCheck.class){ if (null == ins) { //又检查一次 ins = new DoubleCheck(); } } } return ins; }}
注意这里的ins用了volatile关键字来修饰,为什么呢?
因为执行ins = new DoubleCheck()做了很多事情:
- 给ins分配内存
- 调用构造函数来初始化成员变量(可能会很久)
- 将ins对象指向分配的内存空间(执行完这步 ins才不为null)
上面的操作并不是原子操作,而jvm也可能重排序指令,导致第二三两步的执行顺序可能会被打乱,当第3步先于第2步完成,那么会导致有线程拿到了初始化未完毕的ins,那么就会错误,而这里利用了volatile的禁止指令重排序优化特性,用来解决这个问题.
注:volatile 在java 5 后才有效,原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,当然现在不需要担心这个啦!
小结
双重检查非常适用于高并发,我们熟知的开源库Eventbus,ImageLoader等都是用的双重检查锁方式实现单例
不过它,写起来稍微复杂了些,有没有简单点的呢?答案是:有!
饿汉式
直接上代码吧
public class Early { private static final Early ins = new Early(); public static Early getIns() { return ins; }}
饿汉式的原理其实是基于classloder机制来避免了多线程的同步问题
饿汉式与之前提到的懒汉式不同,它在我们调用getIns之前就实例化了(在类加载的时候就实例化了),所以不是一个懒加载,这样就有几个缺点:
延长了类加载时间
如果没用到这个类,就浪费了资源(加载了但是没用它)
不能传递参数(很显然适用的场景会减少)
静态内部类
静态内部类原理同上,另外虽然它看上去有点恶汉式,但是与之前的恶汉有点不同,它在类Singleton加载完毕后并没有实例化,而是当调用getIns去加载Holder的时候才会实例化,静态内部类的方式把实例化延迟到了内部类的加载中去了!所以它比饿汉式更优秀!(偷偷告诉你Effective Java中也推荐这个方式)
例子:
public class Singleton { private static class Holder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getIns(){ return Holder.INSTANCE; }}
枚举
最后介绍一种,也是我在 EffectiveJava中看到的,但是在开发中我重来没看到过!
public enum Singleton{ INSTANCE;}
优点:简单,线程安全,防反序列化.
本人不太了解,也没用过,所以不敢乱说了~~~
值得一提的是 EffectiveJava 也提倡这个方式
这样真的就能保证单例是个单例了吗?
我们采用了很多方式来保证实例的唯一性,但是真的够了吗?
如果采用反射呢?
如果使用多个ClassLoder呢?
那是不是会实例化多个了呢?
思考一下吧~
总结
单例的实现方式有好多种,实际开发中看需求而定,个人比较推荐双重检查锁和静态内部类,这两个我比较常用,其他的说实话我一般不用.
同时也可以看到单例也有很多需要学习以及思考的地方,完全弄懂也不容易~
给自己定了一个每周一个设计模式的学习计划,希望能够坚持下去吧.
以上代码可以在我的Github上找到,好了,就这样,下次见.
More
聊完了单例的实现方式,其实单例还有一些比较有趣的,值得思考讨论的地方,下面推荐几个,有兴趣的可以去看看~
单例还是Util?
什么时候该使用单例?
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- tomcat redis nginx 实现session共享
- Hibernate 常见的注解及使用方法
- java.io.FileNotFoundException: File does not exist: hdfs://mycluster/home/sqoop- 1.4.6/lib/commons-
- Android 的Swift-->Kotlin
- 任意数二进制转化
- 单例模式
- 230转化成二进制
- 将230转化为16进制数
- 10137 - The Trip
- 解压缩版的mysql的安装和配置
- 十六进制表示
- sql左右连接的区别
- [C++]关于接口的设计与声明--对封装性的理解
- 2进制