Design Pattern 1: Singleton

来源:互联网 发布:淘宝联盟卖家能看见吗 编辑:程序博客网 时间:2024/05/27 21:48

概念:单例模式确保某一个类只有一个实例,并且自行实例化向整个系统提供这个实例。
实现要点

  • 构造器声明为private来隐藏,防止其他类调用
  • private static Singleton实例
  • 获取实例方法声明为public来供系统调用

设计单例类的目标

  • 延迟加载-lazy loading
  • 调用效率
  • 线程安全

懒汉

可以达到lazy-loading,线程不安全,实际不会使用

public class Singleton1 {    private static Singleton1 instance;    private Singleton1(){}    public static Singleton1 getInstance(){        if(instance == null){            instance = new Singleton1();        }        return instance;    }}

懒汉,线程安全

Singleton1的线程安全版本,虽然线程安全但是效率太低,实际中也不会使用,因为对象只有在初始化时需要同步,多数情况下并不需要互斥的获得对象,这种加锁方式会造成巨大无意义的资源消耗,Singleton7的双重校验锁方式对此进行了优化。

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

饿汉

没有做到lazy-loading,实际中这种方式会造成资源浪费,因为系统中很多的类的实例都不会用到,加载类时就实例化太浪费!但是这种方式是线程安全的。

public class Singleton3 {    private static Singleton3 instance = new Singleton3();    private Singleton3(){}    public static Singleton3 getInstance(){        return instance;    }}//另一种方式public class Singleton4 {    private static Singleton4 instance = null;    static {        instance = new Singleton4();    }    private Singleton4(){}    public static Singleton4 getInstance(){        return instance;    }}

静态内部类

与上种方式的区别是加了个内部类,这样加载类时并不会实例化instance,只有显式的调用getInstance方法的时候才会实例化,巧妙利用了Java虚拟机的类加载机制,既做到了线程安全,有做到了lazy-loading,实际应用中这种方式是比较推荐的。

public class Singleton5 {    private static class SingletonHolder{        private static Singleton5 INSTANCE = new Singleton5();    }    private Singleton5(){}    public static Singleton5 getInstance(){        return SingletonHolder.INSTANCE;    }}

枚举方式

这种方式不仅避免多线程同步问题,还防止反序列化重新创建新的对象

public enum  Singleton6 {    INSTANCE;    public void whateverMethod(){}}

双重校验锁

该方式确保了只有在初始化的时候需要同步,当初始化完成后,再次调用getInstance不会再进入synchronized块。(对Singleton2的改进)但这种方式也被很多人诟病,实际中依然不会采用。
注意这里的instance声明为volatile的必要性
防止指令重排
由于初始化操作 instance=new Singleton()是非原子操作的,主要包含三个过程
1. 给instance分配内存
2. 调用构造函数初始化instance
3. 将instance指向分配的空间(instance指向分配空间后,instance就不为空了)
虽然synchronized块保证了只有一个线程进入同步块,但是在同步块内部JVM出于优化需要可能进行指令重排,例如(1->3->2),instance还没有初始化之前其他线程就会在外部检查到instance不为null,而返回还没有初始化的instance,从而造成逻辑错误。

变量可见性
volatile类型变量可以保证写入对于读取的可见性,JVM不会将volatile变量上的操作与其他内存操作一起重新排序,volatile变量不会被缓存在寄存器,因此保证了检测instance状态时总是检测到instance的最新状态。

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

以上是实现单例的几种方式汇总,其实综上也能发现在实际中能够付诸使用的方式很少,我们这里更多的是从线程安全和lazy-loading的角度更好的理解和实现单例模式。
在Spring使用ThreadLocal解决线程安全问题。我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,为每个线程提供一个独立的变量副本——”以空间换时间”,这样有状态的Bean就可以在多线程中共享了。 在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简便,且拥有更高的并发性。
参考链接
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://blog.csdn.net/neo_ustc/article/details/7913066
http://blog.csdn.net/jq_ak47/article/details/54894793