设计模式-单例模式
什么是单例模式
单例模式是设计模式中被问到最多的,分属于创建型。其目的是使项目中用到的类都是同一个,即实例只有一个的模式。其结构简单,代码简洁,在框架中又使用广泛,作为学习设计模式的首个是最为合适的。
饿汉式
饿汉式是指只要还没创建就创建一个,那我们可以先理一下逻辑:
1.首先肯定不能开放构造器,不然调用构造器就能new一个实例了,不符合单例的思想。
2.我们需要给别人一个实例,所以得有一个私有的实例和一个开放的提供实例方法。
就这么两个条件,现在看一下代码
/**
* 单例模式 饿汉式
* 饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,
* 所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
*/
public class SingletonDemo01 {
//1.私有化实例方法
private SingletonDemo01(){}
//2.定义一个私有的静态全局对象
private static SingletonDemo01 instance = new SingletonDemo01();
//3.开放一个获取私有对象的方法
public static SingletonDemo01 getInstance(){
return instance;
}
}
懒汉式
懒汉式是指不在运行时创建了,而是在使用时还没创建就创建一个,那我们再理一下逻辑:
1.首先构造器依旧不能开放,这是单例最基本的要求。
2.我们依旧需要给别人一个实例,但这个实例不再是在全局对象这new,而是在给别人实例时做判断,是null再创建。
ok我们继续看代码
/**
* 单例模式 懒汉式 线程不安全
* 相对于饿汉式的优势是支持延迟加载。
* 这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
*/
public class SingletonDemo02 {
//1.私有化实例方法
private SingletonDemo02(){}
//2.定义一个私有的静态全局对象,但不创建实例
private static SingletonDemo02 instance;
//3.开放一个获取私有对象的方法
public static SingletonDemo02 getInstance(){
//4.判断是否存在实例
if (instance == null)
instance = new SingletonDemo02();
return instance;
}
}
这存在一个问题,看似是实例,但其实在多线程情况下,会创建多个实例,这怎么能算单例呢。
懒汉式-线程安全
非常简单,线程安全不就加个 synchronized吗
public static synchronized SingletonDemo03 getInstance(){
if (instance == null)
instance = new SingletonDemo03();
return instance;
}
但还是存在问题,我们对方法加锁,这个并发压力就很高,我们每次调用方法都需要做一次锁的争夺,不合理
懒汉式-线程安全 细化锁粒度
public static SingletonDemo03 getInstance(){
if (instance == null)
synchronized (SingletonDemo03.class){
instance = new SingletonDemo03();
}
return instance;
}
现在会先做判断,没有实例才进锁,线程安全了,没问题了?
不还有问题,如果,现在线程0进入锁,但还没创建实例,线程1此时过了判断,进入等待,在线程0创建实例后,线程1还会创建实例,不符合单例。
懒汉式-线程安全 双重校验
既然问题出自进入锁后没做判断,那叫一个判断就好了
public static SingletonDemo05 getInstance(){
if (instance == null){
synchronized (SingletonDemo05.class){
if (instance == null)
instance = new SingletonDemo05();
}
}
return instance;
}
好像是没问题了,并发压力不大,还做两次校验,单例线程安全实现啦!
但可惜,还是还是存在问题,jvm存在指令重排的问题,通过反编译工具我们可以看到
instance = new SingletonDemo05(); 这一部在jvm中是分三步:
- 1.分配内存空间
- 2.初始对象
- 3.instance指向该内存地址
jvm存在指令重排的特性,可能会132的顺序执行,所以还不完美。
我们可以添加volatile关键字解决这个问题
public class SingletonDemo05 {
private SingletonDemo05(){}
//添加 volatile 禁止重排
private volatile static SingletonDemo05 instance;
public static SingletonDemo05 getInstance(){
if (instance == null){
synchronized (SingletonDemo05.class){
if (instance == null)
instance = new SingletonDemo05();
}
}
return instance;
}
}
补充:volatile详解不在这里讲,但我们要清除volatile的两个特性:
- 代码可见性
- 禁止指令重排
懒汉式-线程安全 静态内部类式
我们上面那个看起来已经是终极形态了,但解决问题的方式不只有一种,下面介绍一个对基础理解好的
/**
* 单例模式 懒汉式静态内部类
* 利用 Java 的静态内部类来实现单例。
* 这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。
*/
public class SingletonDemo04 {
private SingletonDemo04(){
if(SingletonHandler.instance != null){
throw new RuntimeException("不允许非法访问!");
}
}
//创建静态内部类
private static class SingletonHandler{
private static SingletonDemo04 instance = new SingletonDemo04();;
}
public static SingletonDemo04 getInstance(){
return SingletonHandler.instance;
}
}
静态内部类的单例对象只会在装载内部类时才会创建,所以只有调用 getInstance() 方法时才会创建。
反射和序列化对单例的破坏
反射对单例的破坏
Constructor<SingletonDemo05> constructor= SingletonDemo05.class.getDeclaredConstructor();
constructor.setAccessible(true);
如果Accessible设置true,那就可以访问类中所有的私有成员。这样就可以调用私有的构造器,破坏了单例。
解决办法就是在构造器中加判断
//其他类似
private SingletonDemo04(){
if(SingletonHandler.instance != null){
throw new RuntimeException("不允许非法访问!");
}
}
序列化对单例的破坏
这个问题要看一下原码
new ObjectInputStream(new FileInputStream(new File("demo.txt"))).readObject();
我们进入readObject()方法,继续进入readObject0 方法
我们关注这个 switch
其中有一个 TC_OBJECT,他会检测是否有readResolve方法
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
继续进入 readOrdinaryObject 方法
此处的obj,就是最后返回的对象
isInstantiable():是判断实现了序列号接口
desc.newInstance():是反射调用无参构造器实例化对象
这就是序列化对单例的破坏,但解决方法很简单,在下面还有个判断
hasReadResolveMethod:会判断是否实现了readResolve方法,如果实现了就返回true
之后就会执行invokeReadResolve方法,这个方法会反射调用readResolve方法,我们在这个方法中返回实例就能保证单例了。
private Object readResolve(){
return instance;
}
最后介绍一个最简单的单例实现方式
枚举式
/**
* 单例模式 枚举
* 最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,
* 保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏)。
*/
public enum SingletonDemo06 {
INSTANCE;
private Object data;
public Object getData(){
return data;
}
public void setData(Object data){
this.data = data;
}
public static SingletonDemo06 getInstance(){
return INSTANCE;
}
}