IT阔论

创建型模式-单例模式

2018-03-04  本文已影响11人  七佰

单例模式是一种自己生成对象的全局实例。当访问这个类的对象的时候,不需要在创建。

单例模式一共有6种衍生。

  1. 非线程安全的懒汉单例模式,它是最基础的单例模式实现,由于非线程安全,不推荐使用。
public class Singleton_1 {
    private int i = 0;

    private static Singleton_1 instance;

    private Singleton_1(){}

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

    public void setValue(int i){
        this.i = i;
    }
}

2.线程安全的懒汉单例模式
与非线程安全的懒汉单例模式相比,加了synchronized锁,因此是线程安全的。可以在多线程下工作.由于使用了synchronized锁,之前,由于synchronized锁的效率的低下,不推荐使用,但是JDK1.6以后,synchronized锁与
LOCK一样使用了CAS(?),效率得到优化,真正应用场景的使用待考证.

public class Singeton_2{
    private static Singeton_2 instance;

    private Singeton_2(){};

    public static synchronized Singeton_2 getInstance(){
        if(instance==null){
            instance = new Singeton_2();
        }
        return instance;
    }
}
  1. 饿汉式单例模式
    由于饿汉单例模式在类加载时就初始化,避免了多线程的出现.但是由于不是懒加载机制,造成内存浪费.
public class Singleton_3{
    private static Singleton_3 instance = new Singleton_3();
    private Singleton_3(){};

    public static Singleton_3 getInstance() {
        return instance;
    }
}
  1. 双重锁单例模式
    哪双重锁?使用volatile关键字,保证了实域的可见性.使用过了synchronized,保证了操作的原子性.
public class Singleton_4 {
    private static volatile Singleton_4 instance;

    private Singleton_4(){};

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

5.静态单例模式
由于饿汉单例不能实现延迟加载的功能,我们利用内部类不会与外部类绑定初始化的机制,只有被调用时才会初始化对象,并且由JVM加载时负责线程安全.

public class Singleton_5 {
    private static class SingletonHolder {
        private static final Singleton_5 INSTANCE = new Singleton_5();
    }

    private Singleton_5(){};

    public static Singleton_5 getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
  1. 枚举单例模式
    这种写法太他妈的优雅了.自JDK1.5开始,这种方式被网上推举成最佳写法.这里引申一个概念,

在JDK5 中提供了大量的语法糖,枚举就是其中一种。
所谓 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是但是更方便程序员使用。只是在编译器上做了手脚,却没有提供对应的指令集来处理它。

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

这段代码被反实例化后,

public final class Singleton extends Enum<Singleton> {
      public static final Singleton INSTANCE;
      public static Singleton[] values();
      public static Singleton valueOf(String s);
      static {};
}

是不是很像饿汉单例,虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确的加锁、同步。所以,枚举实现是在实例化时是线程安全。
接下来看看序列化问题:

Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。
也就是说,以下面枚举为例,序列化的时候只将 DATASOURCE 这个名称输出,反序列化的时候再通过这个名称,查找对于的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

最佳实践:
参见Spring源码的版本4.3.4,Spring依赖注入Bean实例的实现方式就是通过单例.
源码如下:

/**
     * Return the (raw) singleton object registered under the given name.
     * <p>Checks already instantiated singletons and also allows for an early
     * reference to a currently created singleton (resolving a circular reference).
     * @param beanName the name of the bean to look for
     * @param allowEarlyReference whether early references should be created or not
     * @return the registered singleton object, or {@code null} if none found
     */
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

拓展内容:
在写这篇的时候,发现对于多线程的锁的概念有点模糊不清.这里稍加说明,以后会专门写一篇多线程的文章.


image.png

上图中JAVA的内存模型 会产生多线程编程中的数据“脏读”等问题.
它具有三重特性.

  1. 可见性,指的是线程内部的状态对于外部是可见的.用volatile,synchronized,final修饰的变量,就具有可见性.

  2. 原子性,这个大家应该比较熟悉,在数据库操作中也会有.就是任何操作都是最小细度操作,额,也就是具有原子性.使用Synchronized和Lock\Condition都可以使操作具有原子性.

  3. 有序性,volatile和synchronize都可以使操作具有有序性.volatile禁止指令重新排列,synchronized是通过同一变量同一时刻只允许一条线程对其进行lock操作.

源码地址:https://github.com/walker0828/DesignPatterns.git
参考文档:
http://www.runoob.com/design-pattern/singleton-pattern.html
https://www.cnblogs.com/chengxuyuanzhilu/p/6404991.html
https://www.cnblogs.com/zhengbin/p/5654805.html
http://www.importnew.com/24082.html

上一篇下一篇

猜你喜欢

热点阅读