单例模式学习笔记


单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例
常见的单例模式:Spring框架中的ApplicationContext,数据库连接池等

饿汉式单例

public class HungrySingleton { 
	private static final HungrySingleton hungrySingleton = new HungrySingleton(); 
	private HungrySingleton(){} 
	public static HungrySingleton getInstance(){ 
		return hungrySingleton; 
	} 
}

懒汉式单例,在类加载时就创建了单例对象,绝对线程安全
优点:不需要加锁就能保证线程安全,效率高
缺点:不管有没有调用都会创建单例对象,可能造成空间的浪费

懒汉式单例

线程不安全的懒汉式

public class LazySimpleSingleton { 
	private LazySimpleSingleton(){} 
	private static LazySimpleSingleton lazy = null; 
	public static LazySimpleSingleton getInstance(){ 
		if(lazy == null){ 
			lazy = new LazySimpleSingleton(); 
		}
		return lazy; 
	} 
}

饿汉式单例,需要调用时才创建
这种方式无法保证线程安全,当多个线程同时访问时,可能会创建一个以上的单例对象

同步方法单例模式

我们采用synchronized关键字对方法进行修饰,使其变为同步方法

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

通过这种方式,可以解决线程安全的问题,但是在加了锁之后,如果多线程同时调用该方法,会造成阻塞,降低执行效率

双重锁单例

public class LazyDoubleCheckSingleton { 
	private volatile static LazyDoubleCheckSingleton lazy = null; 						       		private LazyDoubleCheckSingleton(){} 
	public static LazyDoubleCheckSingleton getInstance(){ 
		if(lazy == null){ 
			synchronized (LazyDoubleCheckSingleton.class){ 
				if(lazy == null){ 
					lazy = new LazyDoubleCheckSingleton(); 
					//1.分配内存给这个对象 
					//2.初始化对象 
					//3.设置 lazy 指向刚分配的内存地址 
				} 
			} 
		}
		return lazy; 
	} 
}

通过volatile关键字修饰单例对象的引用,防止创建时因为指令重排导致失败的情况
当获取的单例对象为空,需要创建单例对象时,调用同步方法创建单例对象,防止重复创建。在对象被创建了以后,调用时,不触发同步方法,直接获取单例对象,提高了效率

静态内部类单例

public class LazyInnerClassSingleton { 
//默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类 
//如果没使用的话,内部类是不加载的 
private LazyInnerClassSingleton(){} 
//每一个关键字都不是多余的 
//static 是为了使单例的空间共享 
//保证这个方法不会被重写,重载 
public static final LazyInnerClassSingleton getInstance(){ 
	//在返回结果以前,一定会先加载内部类 
	return LazyHolder.LAZY; 
}

这种方法效率更高,不需要使用锁,也避免了未使用就创建造成的空间浪费
当访问获取,访问时,该内部类才初始化

反射破坏单例

public class LazyInnerClassSingletonTest { 
	public static void main(String[] args) { 
		try{
			//获取对象
			Class<?> clazz = LazyInnerClassSingleton.class; 
			//通过反射拿到私有的构造方法 
			Constructor c = clazz.getDeclaredConstructor(null); 		
			//强制访问
			c.setAccessible(true); 
			//创建对象
			Object o1 = c.newInstance(); 
			//调用了两次构造方法,相当于 new 了两次 
			Object o2 = c.newInstance(); 
			System.out.println(o1 == o2); 
		}catch (Exception e){ 
			e.printStackTrace(); 
		} 
	} 
}

为了防止被反射破坏,我们修改它的构造方法,当单例对象已经存在时,抛出异常

public class LazyInnerClassSingleton { 
	//默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类 
	//如果没使用的话,内部类是不加载的 
	private LazyInnerClassSingleton(){ 
		if(LazyHolder.LAZY != null){ 
			throw new RuntimeException("不允许创建多个实例"); 
		} 
	}
	//每一个关键字都不是多余的 
	//static 是为了使单例的空间共享 
	//保证这个方法不会被重写,重载 
	public static final LazyInnerClassSingleton getInstance(){ 
		//在返回结果以前,一定会先加载内部类 
		return LazyHolder.LAZY; 
	}
	//默认不加载 
	private static class LazyHolder{ 
		private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); 
	}
}	

序列化破坏单例

//反序列化时导致单例破坏 
public class SeriableSingleton implements Serializable { 
	//序列化就是说把内存中的状态通过转换成字节码的形式 
	//从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO) 
	//内存中状态给永久保存下来了 
	//反序列化 
	//讲已经持久化的字节码内容,转换为 IO 流 
	//通过 IO 流的读取,进而将读取的内容转换为 Java 对象 
	//在转换过程中会重新创建对象 new public final static 
	SeriableSingleton INSTANCE = new SeriableSingleton(); 
	private SeriableSingleton(){} 
	public static SeriableSingleton getInstance(){ 
		return INSTANCE; 
	} 
}

//调用
public class SeriableSingletonTest { 
	public static void main(String[] args) { 
		SeriableSingleton s1 = null; 
		SeriableSingleton s2 = SeriableSingleton.getInstance(); 	
		FileOutputStream fos = null; 
		try {
			fos = new FileOutputStream("SeriableSingleton.obj"); 
			ObjectOutputStream oos = new ObjectOutputStream(fos); 
			oos.writeObject(s2); 
			oos.flush(); 
			oos.close(); 
			FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); 
			ObjectInputStream ois = new ObjectInputStream(fis); 
			s1 = (SeriableSingleton)ois.readObject(); 
			ois.close(); 
			System.out.println(s1); 
			System.out.println(s2); 
			System.out.println(s1 == s2); 
		} catch (Exception e) { 
			e.printStackTrace(); 
		} 
	} 
}		

反序列化后的对象和手动创建的对象是不一样的,为了防止这种情况,我们只需增加一个readResolve()方法:

public class SeriableSingleton implements Serializable { 
	public final static SeriableSingleton INSTANCE = new SeriableSingleton(); 
	private SeriableSingleton(){} 
	public static SeriableSingleton getInstance(){ 
		return INSTANCE; 
	}
	private Object readResolve(){ 
		return INSTANCE; 
	} 
}

在ObjectInputStream 类的 readObject()方法中,会判断是否有无参构造方法,如果有,则会实例化。通过反射获取readResolve()方法,返回实例,但是在该过程中,虽然返回的是一个对象,但是仍然实例化了两次

注册式单例

是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例
注册式单例有两种:一种是枚举注册,一种是容器注册

枚举单例

public enum EnumSingleton { 
	INSTANCE; 
	private Object data; 
	public Object getData() { 
		return data; 
	}
	public void setData(Object data) { 
		this.data = data; 
	}
	public static EnumSingleton getInstance(){ 
		return INSTANCE; 
	} 
}

由于枚举语法的特殊性,可以防止反序列化破坏单例,也可以防止反射破坏,具体原因,有兴趣可以研究一下源码

容器缓存单例

public class ContainerSingleton { 
	private ContainerSingleton(){} 
	private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>(); 
	public static Object getBean(String className){ 
		synchronized (ioc) { 
			if (!ioc.containsKey(className)) { 
				Object obj = null; 
				try {
					obj = Class.forName(className).newInstance(); 	
					ioc.put(className, obj); 
				} catch (Exception e) { 
					e.printStackTrace(); 
				}return obj; 
			} else { 
				return ioc.get(className); 
			} 
		} 
	} 
}

这种方式可以存储多个单例对象,在Spring中就有使用容器式单例

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { 
/** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */ 
	private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16); 
	... 
}

ThreadLocal 线程单例

这种方式是一种特殊的单例模式,无法保障全局对象唯一,可以保证各个线程中的对象唯一

public class ThreadLocalSingleton { 
	private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){ 
		@Override protected ThreadLocalSingleton initialValue() { 
			return new ThreadLocalSingleton(); 
		} 
	}; 
	private ThreadLocalSingleton(){} public static ThreadLocalSingleton getInstance(){ 
		return threadLocalInstance.get(); 
	} 
}

PS:参考自咕泡学院设计模式学习笔记

python023基于Python旅游景点推荐系统带vue前后端分离毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
JSP基于SSM网上医院预约挂号系统毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值