Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 299|回复: 0

[设计模式学习]单例模式完全解析(java)

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-11-4 00:01:13 | 显示全部楼层 |阅读模式
    本文将探讨单例模式的各种情况,并给出相应的建议。单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
    首先看最原始的单例模式。


      
      1
    package
      xylz.study.singleton;

      2


      3
    public
      
    class
      Singleton {

      4


      5
         
    private
      
    static
      Singleton instance
    =
      
    null
    ;

      6


      7
         
    private
      Singleton() {

      8
         }

      9


    10
         
    public
      
    static
      Singleton getInstance() {

    11
             
    if
      (instance
    ==
      
    null
    ) {

    12
                 instance
    =
      
    new
      Singleton();

    13
             }

    14
             
    return
      instance;

    15
         }

    16
    }

    17

    显然这个写法在单线程环境下非常好,但是多线程会导致多个实例出现,这个大家都能理解。
    最简单的改造方式是添加一个同步锁。


      
      1
    package
      xylz.study.singleton;

      2


      3
    public
      
    class
      SynchronizedSingleton {

      4


      5
         
    private
      
    static
      SynchronizedSingleton instance
    =
      
    null
    ;

      6


      7
         
    private
      SynchronizedSingleton() {

      8
         }

      9


    10
         
    public
      
    static
      
    synchronized
      SynchronizedSingleton getInstance() {

    11
             
    if
      (instance
    ==
      
    null
    ) {

    12
                 instance
    =
      
    new
      SynchronizedSingleton();

    13
             }

    14
             
    return
      instance;

    15
         }

    16
    }

    17

    显然上面的方法避免了并发的问题,但是由于我们只是在第一次构造对象的时候才需要同步,以后就不再需要同步,所以这里不可避免的有性能开销。于是将锁去掉采用静态的属性来解决同步锁的问题。


      
      1
    package
      xylz.study.singleton;

      2


      3
    public
      
    class
      StaticSingleton {

      4


      5
         
    private
      
    static
      StaticSingleton instance
    =
      
    new
      StaticSingleton();

      6


      7
         
    private
      StaticSingleton() {

      8
         }

      9


    10
         
    public
      
    static
      StaticSingleton getInstance() {

    11
             
    return
      instance;

    12
         }

    13
    }

    14

    上面的方法既没有锁又解决了性能问题,看起来已经满足需求了。但是追求“完美”的程序员想延时加载对象,希望在第一次获取的时候才构造对象,于是大家非常聪明的进行改造,也即非常出名的双重检查锁机制出来了。


      
      1
    package
      xylz.study.singleton;

      2


      3
    public
      
    class
      DoubleLockSingleton {

      4


      5
         
    private
      
    static
      DoubleLockSingleton instance
    =
      
    null
    ;

      6


      7
         
    private
      DoubleLockSingleton() {

      8
         }

      9


    10
         
    public
      
    static
      DoubleLockSingleton getInstance() {

    11
             
    if
      (instance
    ==
      
    null
    ) {

    12
                
    synchronized
      (DoubleLockSingleton.
    class
    ) {

    13
                     
    if
      (instance
    ==
      
    null
    ) {

    14
                         instance
    =
      
    new
      DoubleLockSingleton();

    15
                     }

    16
                 }

    17
             }

    18
             
    return
      instance;

    19
         }

    20
    }

    21

    双重锁机制看起来非常巧妙的避免了上面的问题。但是真的是这样的吗?文章《双重检查锁定及单例模式》中谈到了非常多演变的双重锁机制带来的问题,包括比较难以理解的指令重排序机制等。总之就是双重检查锁机制仍然对导致错误问题而不是性能问题。
    于是继续改造,某个牛人利用JVM的特性来解决上述问题,具体哪个牛人我忘记了,但是不是下面文章的作者。
    (1)《Java theory and practice: Fixing the Java Memory Model, Part 2
    (2)《Initialize-On-Demand Holder Class and Singletons


      
      1
    package
      xylz.study.singleton;

      2


      3
    public
      
    class
      HolderSingleton {

      4


      5
         
    private
      
    static
      
    class
      HolderSingletonHolder {

      6


      7
             
    static
      HolderSingleton instance
    =
      
    new
      HolderSingleton();

      8
         }

      9


    10
         
    private
      HolderSingleton() {

    11
             
    //
    maybe throw an Exception when doing something


    12
         }

    13


    14
         
    public
      
    static
      HolderSingleton getInstance() {

    15
             
    return
      HolderSingletonHolder.instance;

    16
         }

    17
    }

    18




      



    上述代码看起来解决了上面单例模式遇到的所有问题,而且实际上工作的很好,没有什么问题。但是却有一个致命的问题,如果第11行抛出了一个异常,也就是第一次构造函数失败将导致永远无法再次得到构建对象的机会。
    使用下面的代码测试下。


      
      1
    package
      xylz.study.singleton;

      2


      3
    public
      
    class
      HolderSingletonTest {

      4


      5
         
    private
      
    static
      
    class
      HolderSingletonHolder {

      6


      7
             
    static
      HolderSingletonTest instance
    =
      
    new
      HolderSingletonTest();

      8
         }

      9


    10
         
    private
      
    static
      
    boolean
      init
    =
      
    false
    ;

    11
         

    12
         
    private
      HolderSingletonTest() {

    13
             
    //
    maybe throw an Exception when doing something


    14
             
    if
    (
    !
    init) {

    15
                 init
    =
    true
    ;

    16
                
    throw
      
    new
      RuntimeException(
    "
    fail
    "
    );

    17
             }

    18
         }

    19


    20
         
    public
      
    static
      HolderSingletonTest getInstance() {

    21
             
    return
      HolderSingletonHolder.instance;

    22
         }

    23
         
    public
      
    static
      
    void
      main(String[] args) {

    24
             
    for
    (
    int
      i
    =
    0
    ;i
    <
    3
    ;i
    ++
    ) {

    25
                
    try
      {

    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
    很不幸将得到以下输出:


      
      1
    two
    ->
    0


      2
    java.lang.ExceptionInInitializerError

      3
         at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:
    21
    )

      4
         at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:
    26
    )

      5
    Caused 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
    )

      9
          
    2
      more

    10
    three
    ->
    1


    11
    java.lang.NoClassDefFoundError: Could not initialize
    class
      xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder

    12
         at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:
    21
    )

    13
         at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:
    26
    )

    14
    three
    ->
    2


    15
    java.lang.NoClassDefFoundError: Could not initialize
    class
      xylz.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的类加载特性就必须保证类加载一定正确,否则此问题将比并发和性能更严重。如果我们的类需要初始话那么就需要想其它办法避免在构造函数中完成。看起来像是又回到了老地方,难道不是么?

    总之,结论是目前没有一个十全十美的单例模式,而大多数情况下我们只需要满足我们的需求就行,没必有特意追求最“完美”解决方案。




      
      
       
       

         
       

         
       
      
    复制代码
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 14:03 , Processed in 0.359764 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表