• 【转】单例模式完全解析


    本文将探讨单例模式的各种情况,并给出相应的建议。 单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
    首先看最原始的单例模式。

    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

    很不幸将得到以下输出:

    two->0
    java.lang.ExceptionInInitializerError
    at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
    at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
    Caused by: java.lang.RuntimeException: fail
    at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:16)
    at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:12)
    at xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder.<clinit>(HolderSingletonTest.java:7)
    … 2 more
    three->1
    java.lang.NoClassDefFoundError: Could not initialize class xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
    at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
    at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
    three->2
    java.lang.NoClassDefFoundError: Could not initialize class xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
    at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
    at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)

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

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

  • 相关阅读:
    Python面向对象的魔术方法
    Python面向对象基础
    Python异常处理
    Python装饰器实现函数动态类型检查
    Python装饰器
    Python IO
    HTTP协议
    应用层常用协议
    读写分离
    MySQL优化三之MySQL配置
  • 原文地址:https://www.cnblogs.com/bjanzhuo/p/3575913.html
Copyright © 2020-2023  润新知