设计模式-Singleton
程序员文章站
2022-07-06 11:34:30
...
单例模式:
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
单例模式的特点:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
单例模式是最简单的设计模式,但是完全使用java构造一个线程安全的高效的单例,是需要有一定线程安全理论基础的,甚至需要理解java的内存模型!
JDK中的使用案例:
java.lang.Runtime
java.lang.NumberFormat
下面是三种最常用的构造单例的形式,并一一分析使用场景!
1、非延迟加载的单例类(饿汉式单例类)。
2、二次检查的方式(DCL)。
此处重点要理解 为什么instance 要用volitile修饰,大家都知道volitile 实现的效果有:内存可见性,代码重排序。那么此处是用到那个效果呢?
其实此处最重要的是重排序,而非内存可见性:即使没有保证内存可见性的效果,最多也就是再次进入synchronized 块,内部仍然是可以确保逻辑的正确性的。当然内存可见性可以确保写线程写入后,读线程立即可以看到而不用再次获取SingletonDesignDoubleCheck.class锁 ,效率更高。
此处的重排序关键是搞清楚到底是那段代码的重排序,以及导致什么样的一个效果?
答案是:instance 赋值指令 和 SingletonDesignDoubleCheck 构造函数里成员常量的重排序,非法的效果是:赋值在前,初始化在后,这样赋值的instance是没有完全初始化的,其他线程就会看到不完整的instance实例!!!
这样的分析,不知道您是否理解~
3、利用了java的类加载机制的一个实现(这个是实际应用中最推荐的方式,但是这种方式在某些需要定期更新的需求,是做不到的,必须求救于DCL的方式)
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
单例模式的特点:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
单例模式是最简单的设计模式,但是完全使用java构造一个线程安全的高效的单例,是需要有一定线程安全理论基础的,甚至需要理解java的内存模型!
JDK中的使用案例:
java.lang.Runtime
java.lang.NumberFormat
下面是三种最常用的构造单例的形式,并一一分析使用场景!
1、非延迟加载的单例类(饿汉式单例类)。
/** * 非延迟加载(这种是比较建议的,但是会造成启动较慢,不过线程安全,思路也简单) * @author xinchun.wang * */ public class SingletonDesign { private static SingletonDesign instance = new SingletonDesign(); /** * 必须为私有 */ private SingletonDesign(){ } /** * 必须为静态 * @return */ public static SingletonDesign getInstance() { return instance; } }
2、二次检查的方式(DCL)。
/** * 延迟加载(二次检查的方式) * @author xinchun.wang * */ public class SingletonDesignDoubleCheck { /** * 此处必须由volatile 修饰 */ private volatile static SingletonDesignDoubleCheck instance = null; /** * 必须为私有 */ private SingletonDesignDoubleCheck() { } /** * 必须为静态 * * @return */ public static SingletonDesignDoubleCheck getInstance() { if (instance == null) { synchronized (SingletonDesignDoubleCheck.class) { //必须再次判断,已防止多次实例化 if(instance == null){ instance = new SingletonDesignDoubleCheck(); } } } return instance; } }
此处重点要理解 为什么instance 要用volitile修饰,大家都知道volitile 实现的效果有:内存可见性,代码重排序。那么此处是用到那个效果呢?
其实此处最重要的是重排序,而非内存可见性:即使没有保证内存可见性的效果,最多也就是再次进入synchronized 块,内部仍然是可以确保逻辑的正确性的。当然内存可见性可以确保写线程写入后,读线程立即可以看到而不用再次获取SingletonDesignDoubleCheck.class锁 ,效率更高。
此处的重排序关键是搞清楚到底是那段代码的重排序,以及导致什么样的一个效果?
答案是:instance 赋值指令 和 SingletonDesignDoubleCheck 构造函数里成员常量的重排序,非法的效果是:赋值在前,初始化在后,这样赋值的instance是没有完全初始化的,其他线程就会看到不完整的instance实例!!!
这样的分析,不知道您是否理解~
3、利用了java的类加载机制的一个实现(这个是实际应用中最推荐的方式,但是这种方式在某些需要定期更新的需求,是做不到的,必须求救于DCL的方式)
/** * holder方式(这种方式主要是利用了java的类加载机制) * @author xinchun.wang * */ public class SingletonDesignByHolder { private static class SingletonDesignHolder{ private static final SingletonDesignByHolder instance = new SingletonDesignByHolder(); } /** * 必须为私有 */ private SingletonDesignByHolder(){ } /** * 必须为静态 * @return */ public static SingletonDesignByHolder getInstance() { return SingletonDesignHolder.instance; } }