1、单例模式(Singleton)
新人小白,担心学个半桶水,灵机一动,边学边写博文吧,毕竟这里有好多大佬,希望有技术大牛不吝赐教,指点迷津,哈哈。同时也记录一下自己的学习历程,说不定可以给同样是小白的你一点启发、一丝灵感。一起共勉吧,为兴趣也为美好生活
本次学习基于Java语言
- 【单例】一个类只允许创建一个实例
- 【单例模式】是【创建者模式】的一种,创建者模式的主要关注点是 ‘如何创建对象’,核心思想是 ‘将对象的创建与使用分离’,让使用者无需关心对象创建的细节,从而降低系统的耦合度。
单例模式的结构:
- 单例类:要求私有构造方法,创建本类实例,最后提供公共接口方便外界获取单例实例
单例模式有 2 种实现方式:
- 饿汉式:类加载阶段就会创建单例类对象,如果只是加载类而没有使用该对象,会造成内存浪费,太不环保了~
- 懒汉式:类加载阶段不会创建单例类对象,而是首次使用到该对象时才会创建
饿汉式–静态成员变量方式
public class Singleton {
// 1、私有化改造方法,禁止外部直接创建对象
private Singleton(){}
// 2、创建单例对象并初始化提供给外界使用
private static Singleton instance = new Singleton();
// 3、提供对外访问接口,供使用者获取单例对象
public static Singleton getInstance() {
return instance;
}
}
饿汉式–静态代码块方式
与静态变量方式差别不大,好处就是静态代码块中可以继续写代码进行判断,玩出新花样
public class Singleton {
// 1、私有化改造方法,禁止外部直接创建对象
private Singleton(){}
// 2、创建单例对象提供给外界使用
private static Singleton instance;
static {
instance = new Singleton();
}
// 3、提供对外访问接口,供使用者获取单例对象
public static Singleton getInstance(){
return instance;
}
}
饿汉式–枚举方式
枚举方式实现的单例模式是线程安全的,是唯一不会被破坏的单例模式,不考虑内存浪费的情况下极力推荐使用
public enum Singleton {
INSTANCE
}
懒汉式–双重检查锁方式(线程安全–推荐使用)
public class Singleton {
// 1、私有化改造方法,禁止外部直接创建对象
private Singleton(){}
// 2、创建单例对象提供给外界使用
/*
JVM实例化对象时会进行指令重排序(可能会导致单例类实例化时出现空指针异常)
使用volatile关键字可保证有序性
*/
private static volatile Singleton instance;
// 3、提供对外访问接口,供使用者获取单例对象
public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
懒汉式–静态内部类方式(线程安全的方式–推荐使用)
静态内部类单例模式中,单例实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性或方法被调用时才会加载,并初始化其静态属性。而静态属性由于被static修饰,可以保证只被实例化一次,并且严格保证了实例化顺序
public class Singleton {
// 1、私有化改造方法,禁止外部直接创建对象
private Singleton(){}
private static class SingletonHolder {
// 2、内部类中声明并初始化外部类实例
private static final Singleton INSTANCE = new Singleton();
}
// 3、提供对外访问接口,供使用者获取单例对象
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
被破坏的单例模式–反射破坏
通过 getInstanceByReflection() 方法体中反射机制,可以获取到多个单例类的实例,单例模式就被破坏了,解决方案就是通过 new构造器实例化对象时,进行逻辑判断
private static Singleton getInstanceByReflection() throws Exception{
// 获取单例类字节码对象
Class<Singleton> aClass = Singleton.class;
// 获取单例类构造器对象
Constructor<Singleton> c = aClass.getDeclaredConstructor();
// 由于单例类构造器被private修饰,需要取消访问检查,俗称暴力反射
c.setAccessible(true);
return d.newInstance();
}
解决方法:在单例类构造器中进行判断
private static boolean flag = false;
private Singleton(){
synchronized (Singleton.class) {
if (flag) {// 第一次实例化对象时,flag为false,允许创建对象
throw new RuntimeException("该类为单实例,不能创建多个对象");
}
flag = true;
}
}
被破坏的单例模式–反序列化破坏
序列化单例类需要实现 Serializable 接口。
解决方案是:在单例类中定义 readResolve() 方法,通过解读对象输入流的 readObject() 方法的源码,可以发现,在执行反序列化时,会判断类中有没有定义 readResolve() 方法,如果有则自动调用该方法,并使 readObject() 方法直接返回 readResolve() 方法的返回值
private static Singleton readObjectFromFile() throws Exception{
// 创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
// 从指定文件读取对象进内存(反序列化)
Singleton instance = (Singleton) ois.readObject();
// 释放资源
ois.close();
return instance;
}
private static void writeObjectToFile() throws Exception{
// 获取单例对象
Singleton instance = Singleton.getInstance();
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
// 序列化对象到指定文件
oos.writeObject(instance);
// 释放资源
oos.close();
}
解决方法:在单例类中定义readResolve()方法,原理是在反序列化时会自动调用该方法,直接返回该方法的返回值
public Object readResolve(){
return SingletonHolder.INSTANCE;
}