设计模式的学习和部分应用源码分析(单例模式)

首先是饿汉式(非延迟加载单例类)

public class HungrySinglePatterns {

    /**
     * 类进行初始化的时候,就立即加载这个对象。没有延时加载的优势。加载类时,线程是安全的。
     */
    private static HungrySinglePatterns instance = new HungrySinglePatterns();

    private HungrySinglePatterns() {	}

    /**
     * 方法没有同步,调用率高
     */
    public static HungrySinglePatterns getInstance() {
        return instance;
    }
}

应用:待补充

2.懒汉式(同步延迟加载)

public class LazyLoadSinglePatterns {
    private static LazyLoadSinglePatterns instance;
    /**
     * 私有化构造器
     */
    private LazyLoadSinglePatterns() {	}

    /**
     * 因为是延时加载所以有可能出现线程同步的问题。所以要加上 同步块。
     * 如果A线程执行 方法。 instance为null的时候, B线程又要执行方法,那么这个时候,两个线程都判断instance为null
     * 就会创建出两个对象。 所以要加上synchronized。 导致调用效率不高。
     * @return
     */
    public static synchronized LazyLoadSinglePatterns getInstance() {
        if (instance == null) {
            return new LazyLoadSinglePatterns();
        }
        return instance;
    }
}

应用:待补充

3.双重检测锁

  /**
     * 双重检测锁
     * 懒加载,调用率高。没有同步问题。但是因为jvm底层问题,容易出现问题,不推荐使用
     */
    public class DoubuCheckLockedSinglePatterns {
        private volatile static DoubuCheckLockedSinglePatterns instance = null;
        private DoubuCheckLockedSinglePatterns() {}
    
        public static DoubuCheckLockedSinglePatterns getInstance() {
            if (instance == null) {  // 1. 第一次检查
                synchronized (DoubuCheckLockedSinglePatterns.class) {  // 2
                    if (instance == null) {   // 3. 第二次检查
                        instance = new DoubuCheckLockedSinglePatterns();  // 4
                    }
                }
            }
            return instance;
        }
    }

双重检测锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“重排序”,这也是失败的一个主要原因。所以这里要加volatile才能成功。

我们假设有两个线程 a 和 b 调用 getInstance() 方法,假设 a 先走,一路走到 4 这一步,执行 instance = new Singleton() 这句代码。

instance = new Singleton() 这句代码首先会申请一段空间,然后将各个属性初始化为零值(0/null),执行构造方法中的属性赋值[1],将这个对象的引用赋值给 instance[2]。在这个过程中,[1] 和 [2] 可能会发生重排序。

此时,线程 b 刚刚进来执行到 1(看上面的代码块),就有可能会看到 instance 不为 null,然后线程 b 也就不会等待监视器锁,而是直接返回 instance。问题是这个 instance 可能还没执行完构造方法(线程 a 此时还在 4 这一步),所以线程 b 拿到的 instance 是不完整的,它里面的属性值可能是初始化的零值(0/false/null),而不是线程 a 在构造方法中指定的值。

应用:

/**
     * 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);
    }

Spring的依赖注入(包括lazy-init方式)都是发生在 AbstractBeanFactory 的 getBean 里。 getBean 的 doGetBean 方法调用 getSingleton 进行bean的创建。lazy-init方式(lazy-init=“true”),在用户向容器第一次索要bean时进行调用;非lazy-init方式(lazy-init=“false”),在容器初始化时候进行调用。
从上面代码可以看到,spring依赖注入时,使用了双重检测锁的单例模式。也许是现在的jvm不会有双重检测锁失败的问题了?并没有查到相关资料,希望有大佬补充。

4.静态内部类

/**
 * 静态内部类,实现懒加载单例模式
 * 线程安全,懒加载,并且实现了延时加载。调用效率高
 */
public class StaticInteriorSinglePatterns {

    private static class SingletonClassInstance{
        private static final StaticInteriorSinglePatterns instance = new StaticInteriorSinglePatterns();
    }
    
    private StaticInteriorSinglePatterns() {}

    public static StaticInteriorSinglePatterns getInstance() {
        return StaticInteriorSinglePatterns.SingletonClassInstance.instance;
    }
}

5.枚举类

这里我用了一个获取数据库连接的例子

public enum DataSourceEnum {
    /**
     * mysql的Connection
     */
    DATASOURCE();

    private Connection connection = null;
    private DataSourceEnum() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url="jdbc:mysql://localhost/youDatabase?useUnicode=true&characterEncoding=utf-8";
            String user="root";
            String password="root";
            connection = DriverManager.getConnection(url,user,password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public Connection getConnection() {
        return connection;
    }
}
posted @ 2019-05-14 16:04  肥宅快乐码  阅读(126)  评论(0编辑  收藏  举报