基于渡一教育拓哥单例设计模式课程讲解,及大佬文章、个人理解。
应用背景:解决对象创建的问题,让当前类只能创建一个对象
在当前类的内部创建一个对象,构造方法放置的位置分析
-
属性 :可取
-
方法:多次调用会创建多个对象,无意义
-
构造方法:构造方法内嵌套构造方法,无意义
-
代码块:没有返回值,不可取
1. 构造方法私有
2. 创建一个私有的静态的当前类对象作为属性,用来存储唯一的一个对象
3.公有的静态方法,用来将唯一的这个对象返回
注意:如果属性不添加static关键字,开辟对象空间时会调用构造方法,同时将该类对象作为属性,造成递归调用构造方法,由于对象的方法调用是在栈内存中,会产生StackOverFlowError,栈内存溢出问题。
1.饿汉式(立即加载的形式)
为避免通过反射改变该属性的值,添加final关键字修饰。
public class SingleTon {
private static final SingleTon singleTon=new SingleTon();
private SingleTon(){}
public static SingleTon getSingleTon(){
return singleTon;
}
}
分析:对象提前加载,可能存在内存消耗。
2.懒汉式(延迟加载的形式)
public class SingleTon {
private static SingleTon singleTon=null;
private SingleTon(){}
public static SingleTon getSingleTon(){
if (singleTon==null){ //代码1
singleTon=new SingleTon(); //代码2
}
return singleTon;
}
}
分析:如果两个线程A,B同时访问,线程A执行代码1的同时,B线程执行代码2。A线程会看到singleTon的引用对象还未完成初始化(线程并发的可见性)。
public class SingleTon {
private static SingleTon singleTon=null;
private SingleTon(){}
public static synchronized SingleTon getSingleTon(){
if (singleTon==null){
singleTon=new SingleTon();
}
return singleTon;
}
}
分析:采用线程锁,避免并发访问,虽然保证安全,但多次调用,性能低。
3.基于类初始化(延迟加载的形式)
public class InstanceFactory{
/**
*类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
*没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载
*/
private static class InstanceHolder{
public static Instance instance = new Instance();
}
public static Instance getInstance(){
return InstanceHolder.instance;//这里将导致InstanceHolder类被初始化
}
}
4.双重检测模式
public class SingleTon {
private static SingleTon singleTon=null;
private SingleTon(){}
public static SingleTon getSingleTon(){
if (singleTon==null){
synchronized (SingleTon.class){
if (singleTon==null){
singleTon=new SingleTon(); //代码1
}
}
}
return singleTon; //代码2
}
}
分析:代码1可以分解为如下的3行伪代码。
memory=allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上面3行伪代码中的2和3,可能会重排序。2和3重排序的执行时序如下。
memory=allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
如果两个线程A,B同时访问,由于双重检测不会产生冲突,但可能线程A执行代码1的同时,线程执行代码2,但此时singleTon引用的对象可能还未完成初始化。
引入voatile修饰符,修饰属性,避免jvm指令重排序,规定执行顺序,同时保证线程的可见性。
public class SingleTon {
private volatile static SingleTon singleTon=null;
private SingleTon(){}
public static SingleTon getSingleTon(){
if (singleTon==null){
synchronized (SingleTon.class){
if (singleTon==null){
singleTon=new SingleTon();
}
}
}
return singleTon;
}
}
5.生命周期托管方式
利用spring框架的IOC控制权翻转,以及DI依赖注入的思想,使用反射技术,以及hashmap作为缓存存储实现。
MyManager.java
import java.util.HashMap;
/**
* 为了管理对象的产生
* 对象的控制权给当前类负责
* 生命周期托管实现对象的单例
* IOC控制反转
*/
public class MyManager {
// 存储所有被管理的对象
private static HashMap<String, Object> beanMap = new HashMap<>();
// 获取任何类的一个对象
// className : 类名
// return: (泛型T)
public static <T> T getBean(String className) {
T obj = null;
try {
// 去beanBox看有没有存在了
obj = (T) beanMap.get(className);
if (obj == null) {
// 1. 通过类名获取类
Class clazz = Class.forName(className);
// 2. 通过反射产生一个对象
obj = (T) clazz.newInstance();
// 3. 加入集合
beanMap.put(className, obj);
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
Sign.java
// 单例模式 IOC
// 生命周期给别人托管
public class Sign {
public Sign() {
System.out.println("创建了一个实例");
}
}
6.枚举类型实现
枚举类型实现极其简单,只需编写一个包含单个元素的枚举类型即可,不过多赘述。