单例模式——最全写法汇总
来源:互联网 发布:淘宝直播电脑端怎么看 编辑:程序博客网 时间:2024/06/06 04:21
一、意图与动机
保证一个类仅有一个实例,并且提供一个全局访问点。
二、效果
1、对唯一实例的受控访问。
2、缩小命名空间。单例模式是对全局变量的一种改进,避免污染全局变量空间
3、允许可变数目的实例。可以改进单例模式,精确控制实例个数
三、实现
1、线程不安全的实现(lazy init)
package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/27. */public class Singleton1 { private static Singleton1 INSTANCE; private Singleton1(){} public static Singleton1 Instance() { if (INSTANCE == null) { INSTANCE = new Singleton1(); } return INSTANCE; }}
缺陷:多线程下不可用,无法确保只创建一个实例
2、同步方法(lazy init)
针对1中多线程不可用的问题,最直观手段是将访问方法做同步处理,从而实现线程安全的延迟加载
package com.yangyi.singleton;/** * 同步处理,线程安全,延迟加载 * Created by yangjinfeng02 on 2016/9/27. */public class Singleton2 { private static Singleton2 INSTANCE; private Singleton2(){} public static synchronized Singleton2 Instance() { if (INSTANCE == null) { INSTANCE = new Singleton2(); } return INSTANCE; }}
由于对Instance()方法做了同步处理,能够保证多线程可用,但synchronized将导致性能开销,如果多个线程频繁调用该方法,将会导致程序执行性能的下降。反之,如果该方法调用不频繁,这种实现则是可以使用的。
3、双重检查锁定(lazy init)——不正确的一种实现
为了解决2中同步带来的性能开销,一种思路是双重锁检查,只在第一次创建的时候做同步操作
package com.yangyi.singleton;/** * 双重锁检查,线程不安全,延迟加载 * Created by yangjinfeng02 on 2016/9/27. */public class Singleton3 { private static Singleton3 INSTANCE; private Singleton3(){} public static Singleton3 Instance() { // 第一次检查 if (INSTANCE == null) { // 枷锁 synchronized (Singleton3.class) { // 第二次检查 if (INSTANCE == null) { // 创建 INSTANCE = new Singleton3(); } } } return INSTANCE; }}
这种实现看起来比较完美,但这是一个错误的优化!
错误的地方:上述代码中第一次判断INSTANCE不为null,读取到的对象可能尚未初始化完毕。
问题的根源
代码中new创建实例的那一行,在jvm中实际做的事情大致可以归结为三点:
(1)、分配对象内存
(2)、初始化对象
(3)、设置INSTANCE指向分配的内存地址
而这3点中,2和3是可能发生重排序的,如果2和3的顺序被重排后,那么执行过程如下
(1)、分配对象内存
(3)、设置INSTANCE指向分配的内存地址
(2)、初始化对象
之所以会发生这种重排序,是因为java语言规范指定,所有线程在执行程序的时候必须遵循intra-thread semantics. intra-thread semantics保证重排序不会改变单线程内的执行结果,换句话说,intra-thread semantics允许那些单线程内不会改变程序执行结果的重排序。
接着来看这种重排序会导致的后果,如果线程A完成了(1)和(3),此时线程B去判断INSTANCE是否为null,此时INSTANCE不为null,但是所指向的内存地址空间尚未初始化完毕,所以此时线程B取到的实例是并未初始化完成的。
解决方案
知晓了问题的根源,我们来看如何解决,有两个思路:
方案一、不允许(2)和(3)重排序
方案二、允许(2)和(3)重排序,但是不允许其它线程看到这个重排序
根据这两种思路,于是有下面的两种实现方案
4、基于volatile的方案
我们知道java中的volatile关键字的作用就是限制指令的重排序,所以相对于3,我们只需要做一个小小的改动就可以将3中的方案变为正确无误、线程安全的实现方式。我们只需要将INSTANCE声明为volatile即可
package com.yangyi.singleton;/** * 基于volatile的双重锁检查,线程安全,延迟加载 * Created by yangjinfeng02 on 2016/9/27. */public class Singleton4 { private volatile static Singleton4 INSTANCE; private Singleton4(){} public static Singleton4 Instance() { // 第一次检查 if (INSTANCE == null) { // 枷锁 synchronized (Singleton4.class) { // 第二次检查 if (INSTANCE == null) { // 创建 INSTANCE = new Singleton4(); } } } return INSTANCE; }}
5、基于JVM类初始化的解决方案
jvm在类的初始化阶段(即在class被加载后,且被线程使用之前),会执行类的初始化,在初始化期间,JVM会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。
利用这个特性,我们可以实现另外一种线程安全的单例模式
package com.yangyi.singleton;/** * 基于jvm类加载机制的单例实现 * Created by yangjinfeng02 on 2016/9/27. */public class InstanceFactory { private static class InstanceHolder { public static Instance INSTANCE = new Instance(); } public static Instance Instance() { // 此处将触发InstanceHolder类的初始化 return InstanceHolder.INSTANCE; }}
另外一种更加简洁的写法,利用静态常量
package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/28. */public class Singleton5 { private static final Singleton5 INSTANCE = new Singleton5(); private Singleton5() {}; public static Singleton5 getInstance() { return INSTANCE; }}
另外一种实现方式,通过静态代码块
package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/28. */public class Singleton6 { private static Singleton6 INSTANCE; private Singleton6() {}; static { INSTANCE = new Singleton6(); } public static Singleton6 getInstance() { return INSTANCE; }}
上述三种实现均是基于jvm类初始化的解决方案,没有禁止指令重排,但是通过jvm类初始化时候的锁,将指令重排对第二个线程不可见
6、基于enum的实现方式
在effective java中介绍了一种基于enum实现单例的简洁方法
package com.yangyi.singleton;/** * Created by yangjinfeng02 on 2016/9/28. */public enum Singleton7 { INSTANCE; public void leaveTheBuilding() { }}
四、总结
如此多的实现方案,思路不一样,但是目的是一致的。实际使用中可更具场景选择合适的方案,整体来说,基于类初始化的方案比较受推荐。
- 单例模式——最全写法汇总
- 单例模式最全
- C++单例模式的最实用写法
- 单例模式写法
- 单例模式写法
- 单例模式写法
- 单例模式写法
- 关于单例模式的最全总结
- Java SE-最全的单例模式详解
- 单例模式汇总
- 单例模式汇总
- 单例模式的写法
- 模版单例模式写法
- 单例模式的写法
- 单例模式最佳写法
- 单例模式的写法
- 单例模式的写法
- 单例模式枚举写法
- powerdesigner
- Linux 统计当前文件夹、文件的数目
- 【bzoj 3670】 [Noi2014]动物园 KMP变式
- android的listview,怎么改变item内容?
- linux vi E325错误,不能正常编辑退出
- 单例模式——最全写法汇总
- swift中UI适配,即视图控件在导航栏下面开始显示
- Linux cp命令拷贝 不覆盖原有的文件
- Gii 生成添加页面里面的图片表单样式修改
- linux wget(下载命令)
- Qt显示系统标准时间
- linux xargs命令一(与find ls等命令组合)
- spring mvc 返回html
- axure7 制作选项卡