`
Cages
  • 浏览: 101753 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

单例模式完全解析

阅读更多

 

本文将探讨单例模式的各种情况,并给出相应的建议。单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
首先看最原始的单例模式。
1packagexylz.study.singleton;
2
3publicclassSingleton {
4
5privatestaticSingleton instance=null;
6
7privateSingleton() {
8 }
9
10publicstaticSingleton getInstance() {
11if(instance==null) {
12 instance=newSingleton();
13 }
14returninstance;
15 }
16}
17

显然这个写法在单线程环境下非常好,但是多线程会导致多个实例出现,这个大家都能理解。
最简单的改造方式是添加一个同步锁。
1packagexylz.study.singleton;
2
3publicclassSynchronizedSingleton {
4
5privatestaticSynchronizedSingleton instance=null;
6
7privateSynchronizedSingleton() {
8 }
9
10publicstaticsynchronizedSynchronizedSingleton getInstance() {
11if(instance==null) {
12 instance=newSynchronizedSingleton();
13 }
14returninstance;
15 }
16}
17

显然上面的方法避免了并发的问题,但是由于我们只是在第一次构造对象的时候才需要同步,以后就不再需要同步,所以这里不可避免的有性能开销。于是将锁去掉采用静态的属性来解决同步锁的问题。
1packagexylz.study.singleton;
2
3publicclassStaticSingleton {
4
5privatestaticStaticSingleton instance=newStaticSingleton();
6
7privateStaticSingleton() {
8 }
9
10publicstaticStaticSingleton getInstance() {
11returninstance;
12 }
13}
14

上面的方法既没有锁又解决了性能问题,看起来已经满足需求了。但是追求“完美”的程序员想延时加载对象,希望在第一次获取的时候才构造对象,于是大家非常聪明的进行改造,也即非常出名的双重检查锁机制出来了。
1packagexylz.study.singleton;
2
3publicclassDoubleLockSingleton {
4
5privatestaticDoubleLockSingleton instance=null;
6
7privateDoubleLockSingleton() {
8 }
9
10publicstaticDoubleLockSingleton getInstance() {
11if(instance==null) {
12synchronized(DoubleLockSingleton.class) {
13if(instance==null) {
14 instance=newDoubleLockSingleton();
15 }
16 }
17 }
18returninstance;
19 }
20}
21


双重锁机制看起来非常巧妙的避免了上面的问题。但是真的是这样的吗?文章《双重检查锁定及单例模式》中谈到了非常多演变的双重锁机制带来的问题,包括比较难以理解的指令重排序机制等。总之就是双重检查锁机制仍然对导致错误问题而不是性能问题。

一种避免上述问题的解决方案是使用volatile关键字,此关键字保证对一个对象修改后能够立即被其它线程看到,也就是避免了指令重排序和可见性问题。参考文章

指令重排序与happens-before法则

所以上面的写法就变成了下面的例子。

package xylz.study.singleton;

public class DoubleLockSingleton {

private staticvolatileDoubleLockSingleton instance = null;

private DoubleLockSingleton() {
}

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


于是继续改造,某个牛人利用JVM的特性来解决上述问题,具体哪个牛人我忘记了,但是不是下面文章的作者。
(1)《Java theory and practice: Fixing the Java Memory Model, Part 2
(2)《Initialize-On-Demand Holder Class and Singletons

1packagexylz.study.singleton;
2
3publicclassHolderSingleton {
4
5privatestaticclassHolderSingletonHolder {
6
7staticHolderSingleton instance=newHolderSingleton();
8 }
9
10privateHolderSingleton() {
11//maybe throw an Exception when doing something
12 }
13
14publicstaticHolderSingleton getInstance() {
15returnHolderSingletonHolder.instance;
16 }
17}
18




上述代码看起来解决了上面单例模式遇到的所有问题,而且实际上工作的很好,没有什么问题。但是却有一个致命的问题,如果第11行抛出了一个异常,也就是第一次构造函数失败将导致永远无法再次得到构建对象的机会。
使用下面的代码测试下。
1packagexylz.study.singleton;
2
3publicclassHolderSingletonTest {
4
5privatestaticclassHolderSingletonHolder {
6
7staticHolderSingletonTest instance=newHolderSingletonTest();
8 }
9
10privatestaticbooleaninit=false;
11
12privateHolderSingletonTest() {
13//maybe throw an Exception when doing something
14if(!init) {
15 init=true;
16thrownewRuntimeException("fail");
17 }
18 }
19
20publicstaticHolderSingletonTest getInstance() {
21returnHolderSingletonHolder.instance;
22 }
23publicstaticvoidmain(String[] args) {
24for(inti=0;i<3;i++) {
25try{
26 System.out.println(HolderSingletonTest.getInstance());
27 }catch(Exception e) {
28 System.err.println("one->"+i);
29 e.printStackTrace();
30 }catch(ExceptionInInitializerError err) {
31 System.err.println("two->"+i);
32 err.printStackTrace();
33 }catch(Throwable t) {
34 System.err.println("three->"+i);
35 t.printStackTrace();
36 }
37 }
38 }
39}
40
很不幸将得到以下输出:
1two->0
2java.lang.ExceptionInInitializerError
3 at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
4 at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
5Caused by: java.lang.RuntimeException: fail
6 at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:16)
7 at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:12)
8 at xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder.<clinit>(HolderSingletonTest.java:7)
92more
10three->1
11java.lang.NoClassDefFoundError: Could not initializeclassxylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
12 at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
13 at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
14three->2
15java.lang.NoClassDefFoundError: Could not initializeclassxylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
16 at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
17 at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
18

很显然我们想着第一次加载失败第二次能够加载成功,非常不幸,JVM一旦加载某个类失败将认为此类的定义有问题,将来不再加载,这样就导致我们没有机会再加载。目前看起来没有办法避免此问题。如果要使用JVM的类加载特性就必须保证类加载一定正确,否则此问题将比并发和性能更严重。如果我们的类需要初始话那么就需要想其它办法避免在构造函数中完成。看起来像是又回到了老地方,难道不是么?

总之,结论是目前没有一个十全十美的单例模式,而大多数情况下我们只需要满足我们的需求就行,没必有特意追求最“完美”解决方案。
原文[http://www.imxylz.info/p/177.html] 感谢原作者的钻研奉献精神。

 

分享到:
评论

相关推荐

    c#单例模式示例

    本文将重点介绍C#中两种不同的单例模式实现方法:基于嵌套类的完全懒惰加载和使用.NET 4.0的`Lazy&lt;T&gt;`类型。 #### 二、基于嵌套类的完全懒惰加载 ##### 2.1 实现原理 此实现方式利用了C#中的静态构造函数以及内部...

    java代码-double check单例模式

    **Java代码 - 双重检查锁定(Double-Check Locking)单例模式** 在Java编程中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景下非常...

    Java 23种设计模式全归纳.zip

    设计模式分为清晰类型,共23种创建型模式单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。结构类型模式队列模式、桥接模式、装饰模式、组合模式、外观模式、共享元模式、代理模式。行为类型模式模版方法...

    GoF23种设计模式解析.pdf

    单例模式确保某个类只有一个实例,并提供一个全局访问点。 **应用场景**:当系统中只需要一个实例对象时,比如线程池、缓存等。 **优点**: - 保证了系统内存的有效利用。 - 控制对资源的访问。 **缺点**: - ...

    c++ and Peris of Double Checked Locking

    描述:C++如何解决单例模式的线程安全问题 ### 关键知识点解析: #### 单例模式的线程安全挑战 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。在多线程环境中,传统实现的单例模式可能...

    JAVA常用设计模式详解大全.docx

    5. **单例模式**:单例模式确保一个类只有一个实例,并提供全局访问点。在多线程环境中,单例模式需要正确地处理同步,以防止多个线程同时创建单例对象。 **结构模式** 1. **适配器模式**:适配器模式将一个类的...

    全面解析Activity-的4种启动模式.doc

    这是最严格的单例模式,不仅Activity自身在一个任务栈中唯一,而且这个任务栈中只能有这一个Activity。这意味着任何尝试启动这个Activity的Intent都将在这个Activity所在的任务栈中打开,即使这个Activity已经存在...

    《 php设计模式》

    单例模式确保一个类只有一个实例,并提供全局访问点。在PHP中,这个模式常用于管理共享资源,如数据库连接或缓存系统。通过限制实例化次数,单例模式能有效避免资源浪费,提高程序性能。 二、工厂模式 工厂模式...

    [游戏编程模式].(Game.Programming.Patterns).(美)Robert.Nystrom.pdf

    8. **单例模式(Singleton Pattern)**:在游戏编程中,有些资源或服务需要全局唯一,如游戏管理器、音频系统、网络连接等,这就需要用到单例模式来确保只有一个实例存在。 9. **代理模式(Proxy Pattern)**:游戏...

    设计模式面试题

    - **单例模式(Singleton)**:确保一个类只有一个实例,并提供一个全局访问点。例如,在Java中`Runtime`类就是单例模式的一个应用。 - **工厂模式(Factory)**:定义一个用于创建对象的接口,让子类决定实例化哪一...

    Java和Android源码设计模式

    此外,在Android系统源码中也可以看到多种设计模式的应用,如单例模式在ContextImpl类中的使用,以及观察者模式在BroadcastReceiver中的体现等。 通过学习和实践这些设计模式,不仅可以提高代码质量,还能显著提升...

    89丨开源实战五(下):总结MyBatis框架中用到的10种设计模式1

    单例模式确保在整个应用程序中,只有一个SqlSessionFactory实例存在,从而提高性能并减少资源消耗。在实际代码中,可以通过静态工厂方法或双重检查锁定等策略来实现单例。 接下来,我们讨论代理模式。MyBatis的...

    Android Activity启动模式全面解析

    此外,`taskAffinity`属性可以用来控制Activity所属的任务栈,通常与单例模式配合使用。 通过adb命令,开发者可以查看当前设备上各个任务栈的运行状态,从而更好地理解和调试Activity的启动模式。例如,使用`adb ...

    韩顺平_Java设计模式笔记.docx

    - **实现方式**:单例模式共有8种实现方式,包括饿汉式、懒汉式、双重检查锁、静态内部类、枚举等。 - **饿汉式**:在类加载时就完成了初始化,因此类加载比较慢。 - **懒汉式**:第一次调用时才初始化,提高...

    C++23种设计模式一点就通

    正如商标保护法确保了商标的独特性一样,在软件中使用单例模式可以确保某个类的对象在整个系统中只存在一个实例。 #### 二、结构型模式 **5. 适配器模式 (ADAPTER)** - **应用场景**:当希望将一个类的接口转换成...

    追MM与23种设计模式

    在“追MM”的例子中,单例模式可以比喻为专注于一个人的追求,确保不会出现同时追求多人的情况,保持感情的专一性。 #### 6. 适配器模式(Adapter) 适配器模式是一种结构型设计模式,它作为两个不兼容接口之间的...

    java23种设计模式与追MM

    1. **单例模式**:就像GG只有一个真心爱慕的MM,系统中也常常需要确保一个类只有一个实例,如全局配置对象。通过双重检查锁定或静态内部类方式实现单例。 2. **工厂模式**:GG追求MM时需要制造惊喜,工厂模式则提供...

    中兴公司笔试题目集合

    在C++、Java或Python等语言中,实现单例模式有不同的方式,但核心思想是控制类的实例化过程,防止多实例化导致的问题。单例模式常用于资源管理、日志记录等场景,确保在整个程序中只有一个共享的对象。 第三部分则...

    设计模式之禅(第2版)1

    1. **单例模式(Singleton)**: - 保证一个类只有一个实例,并提供全局访问点。 - 应用于配置中心、缓存管理等场景。 2. **工厂方法模式(Factory Method)**: - 定义一个创建对象的接口,让子类决定实例化哪...

    java设计模式

    在Java中,设计模式分为三类:创建型模式(如工厂模式、抽象工厂模式、单例模式)、结构型模式(如适配器模式、代理模式、装饰器模式)和行为型模式(如观察者模式、责任链模式、策略模式)。 这篇博客链接指向的是...

Global site tag (gtag.js) - Google Analytics