码迷,mamicode.com
首页 > 其他好文 > 详细

设计模式1---单例模式

时间:2016-07-15 13:34:52      阅读:170      评论:0      收藏:0      [点我收藏+]

标签:

单例模式Singleton

字数827 阅读224 评论0 

这是我在学习《Android源码设计模式解析和实战》过程中对单例模式作一个记录。方便以后随时翻看。
单例模式是应用最广泛的模式之一,使用的常见场景: 网络请求,访问IO,数据库和线程池等很消耗资源的情况下,可以考虑使用单例模式。

中心原则就是:

  • 确保单例类的对象有且只有一个,尤其是在多线程环境下。
  • 确保反序列化的时候不会重新构建对象。

饿汉模式

单例实例在类装载时构建。将构造函数私有化,使得外部程序不得通过构造函数创建对象,只能通过静态方法返回一个唯一的静态对象。

  public class Singleton{
      private static  final Singleton sInstance = new Singleton();
      //构造函数私有化
      private Singleton(){}

      public static Singletoon getInstance() {
          return sInstance;
      }
  }

懒汉模式

单例实例在第一次被调用时进行初始化。

懒汉模式的实现如下:

public class Singleton {
    private static Singleton instance;
    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在获取单例对象的静态方法中添加了synchronized,这可以在多线程下保证单例对象的唯一性。

缺点:

  • 第一次加载需要进行实例化,反应稍慢。
  • 每次调用getInstance()都要进行同步,造成了很多不必要的同步开销。
    因此,一般不建议使用懒汉模式。

基于volatile的双重检查锁(double-checked locking)

public class Singleton {
    //private static Singleton instance = null;
    //防止DCL失效问题,保证instance对象每次都是从住内存中读取
    private static volatile Singleton instance = null;
    private Singleton() {}

    public static Singleton getInstance() {
            //避免不必要的同步
        if (instance == null) {
            synchronized (Singleton.class) {
                //在instance = null下创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

缺点:

  • JDK1.5以上版本才有volatile。

优点:

  • 资源利用率高,第一次执行getInstance()时才会被实例化,效率高。

只是看起来很完美

这种看起来很完美的优化技巧就是double-checked locking,但遗憾地告诉你,根据JLS规范,上面的代码不可靠!线程有可能得到一个不为null,但是构造不完全的对象。
Why?
造成不可靠的原因是编译器为了提高执行效率的指令重排。只要认为在单线程下是没问题的,它就可以进行乱序写入!以保证不要让cpu指令流水线中断。


指令重排

为了提高代码的执行效率,JVM会将执行频率高的代码编译成机器码;而对于频率不高的代码则仍然采用解释执行。
常见的编译优化方式有:
方法内联:免去参数、返回值传递过程
去虚拟化:接口的方法只有一个实现类,进行方法内联
冗余消除:运行时去掉无用代码
还有一些编译优化根据“逃逸分析”技术
标量替换:User u=new User(“zhang3”,18)
String n=“zhang3” int age=18,节省内存
栈上分配:逃逸对象直接在栈上分配,快速,GC及时
同步削除:去掉不必要的同步


new Instance()到底发生了什么?

memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上面的伪代码中2、3步可能重排



技术分享        技术分享


解决方案1

Java5以后的版本,可以利用volatile关键字。
Why?
在java5以前,volatile原语不怎么强大,只能保证对象的可见性
但在java5之后,volatile语义加强了,被volatile修饰的对象,将禁止该对象上的读写指令重排序
这样,就保证了线程B读对象时,已经初始化完全了


解决方案2

这也是官方比较推荐的一种方案(effective java 2nd)

点击(此处)折叠或打开

  1. public class InstanceHolder{
  2. private Instance(){}
  3. //Lazy initialization holder class idiom for static fields
  4. private static class Inner{
  5.   private static final Instance ins = new Instance()
  6. }
  7. public static Instance getInstance(){
  8.   return Inner.ins;
  9. }

 

原理:一个类只有在被使用时才会初始化,而类初始化过程是非并行的,这些都由JLS能保证。
 

静态内部类单例模式

在《Java 并发编程实践》中提及了DCL失效的问题(上面基于volatile的双重检查锁例子中注释的部分),建议使用如下代码替换:

public class Singleton {
    private static final Singleton sInstance;

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonHolder.sInstance;

    }

    private static class SingletonHolder {
         sInstance = new Singleton();
    }
}

优点:

  • 资源利用率高,第一次执行getInstance()时才会被实例化,效率高。
  • 不仅保证线程安全,而且也保证了单例对象的唯一性,同时也延迟了单例的实例化,所以这才是推荐使用的单例模式实现方式。

枚举单例

public class SingletonEnum {
    INSTANCE;
    public void doSomething() {
        //do something;
    }
}

优点:

  • 枚举在Java中于普通的类一样,可以有字段,方法。 默认枚举创建的实例是线程安全的。
  • 使用enum实现的单例自带防序列化。

然而上述的几种单例模式实现中在反序列化时会重新创建对象。要杜绝单例对象重新生成对象,必须要加入如下方法:

private Object readResolve() throws ObjectStreamException {
    return sInstance;
}

参考资料

  • [1]《Android源码设计模式解析和实战》
  • [2]《Java 并发编程实践》

设计模式1---单例模式

标签:

原文地址:http://www.cnblogs.com/linghu-java/p/5673114.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!