`
k_lb
  • 浏览: 791984 次
  • 性别: Icon_minigender_1
  • 来自: 郑州
社区版块
存档分类
最新评论
  • kitleer: 据我所知,国内有款ETL调度监控工具TaskCTL,支持ket ...
    kettle调度

单例模式完全解析

 
阅读更多
本文将探讨单例模式的各种情况,并给出相应的建议。单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
首先看最原始的单例模式。
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] 感谢原作者的钻研奉献精神。

分享到:
评论

相关推荐

    MLang:Android 动态化多语言框架,支持语言包的动态下发、升级、删除,一处安装,到处使用

    设计优雅 语言包存储格式为 xml 格式,和 res 下的 strings.xml 一致 零依赖,完全使用系统 api 和系统的 xml 解析器 不持有 context,无内存泄漏 静态方法 + 单例模式,一处安装,到处使用动态化语言包 动态下发...

    report 开源的访问层中间件,基于java平台

    report项目请求通道采用责任链模式设计,扩展性极强,流程完全可控! ankang-report report 是一个开源的访问层框架,基于java平台,依赖于spring,以Mozilla Public License 2.0 协议发布。可用于接口的调试及性能...

    java面试题

    Struts1只是在第一次请求的时候创建一个action实例,以后每次相同的请求都直接从内存中去读取,它是单例模式,安全性较差。 Struts2是如何实现MVC模式的? 答:在Struts2里面是将每次页面的请求进行处理,然后将请求...

    超级有影响力霸气的Java面试题大全文档

    面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 4. 多态性:  多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化...

    java 面试题 总结

    面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 4. 多态性: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多...

    Spring面试题

    3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能 5.容器提供了众多的辅助类,能加快应用的开发 6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    综上所述,“认我测”在线认证检测系统,率先填补了认证检测领域移动端的空缺,提供了Web浏览器+移动端的双端访问模式,给用户提供了多种访问途径,真正实现了用户和检测机构的随时随地在线下单检测。 关键词:...

    一个java正则表达式工具类源代码.zip(内含Regexp.java文件)

    原文如下: 以前写了一个java的正规表达式的java工具类,分享一下,有用到的欢迎下载使用。 如果你有常用的定义好的,且测试通过的正规表达式,欢迎跟贴,也让我享用一下 . 类中用到了 jakarta-oro-2.0.jar 包,请...

Global site tag (gtag.js) - Google Analytics