设计模式——单例模式
创始人
2025-05-28 18:41:10
0

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创
建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供
了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一、饿汉单例模式

单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

1.静态变量实现饿汉式单例模式

package 单例模式.饿汉式_静态成员变量;
/*** @author Watching* * @date 2023/3/14* * Describe:饿汉模式:静态成员变量方式*/
public class HungerSingleton {//1.私有构造方法private HungerSingleton() {}//2.静态私有成员private static HungerSingleton instance = new HungerSingleton();//3.提供一个静态的方法获取唯一实例public static HungerSingleton getInstance() {return instance;}
}

2.静态代码块实现饿汉式单例模式

package 单例模式.饿汉式_静态代码块;
/*** @author Watching* * @date 2023/3/14* * Describe:饿汉式:静态代码块*/
public class HungerSingleton {//1.私有构造方法private HungerSingleton() {}//2.私有静态成员private static HungerSingleton instance;//3.静态代码块static {instance = new HungerSingleton();}//4.提供公有静态方法访问唯一对象public static HungerSingleton getInstance() {return instance;}
}

3.最简单的饿汉式单例模式——枚举类

1.创建一个枚举类

public enum Singleton {INSTANCE;
}

2.获取这个对象

public class Client {public static void main(String[] args) {Singleton instance1 = Singleton.INSTANCE;Singleton instance2 = Singleton.INSTANCE;System.out.println(instance1 == instance2);}
}

两次获得对象是同一个对象

总结

饿汉式单例模式的三个要点
①私有构造方法
②私有静态成员
③直接初始化静态成员,或者在静态代码块中初始化
④提供静态方法获取单例

二、懒汉单例模式

1.静态内部类实现懒汉单例模式

1.使用静态内部类创建单例类

public class LazySingleton {//1.创建一个私有构造器private LazySingleton() {}//2.创建一个静态内部类,并在其中初始化成员常量(变量也行)static class SingletonHolder {public static final LazySingleton instance = new LazySingleton();}//3.提供方法获取静态内部类中的成员常量(或者变量public static LazySingleton getInstance() {return SingletonHolder.instance;}
}

2.测试

public class Client {public static void main(String[] args) {LazySingleton instance1 = LazySingleton.getInstance();LazySingleton instance2 = LazySingleton.getInstance();System.out.println(instance1 == instance2);}
}

两次获取的对象都是同一个

为何静态内部类中的成员变量是单例的?
因为静态代码块只有在第一次被调用的时候才会加载。

静态内部类实现单例的优点

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
具体来说当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,
只有当getInstance()方法第一次被调用时,使用INSTANCE的时候,才会导致虚拟机加载SingleTonHoler类。
这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

那么他是如何实现线程安全的?
首先要了解类加载过程中的最后一个阶段:即类的初始化,类的初始化阶本质就是执行类构造器的方法。
方法:这不是由程序员写的程序,而是根据代码由javac编译器生成的。
它是由类里面所有的类变量的赋值动作和静态代码块组成的。
JVM内部会保证一个类的方法在多线程环境下被正确的加锁同步,也就是说如果多个线程同时去进行“类的初始化”,
那么只有一个线程会去执行类的方法,其他的线程都要阻塞等待,直到这个线程执行完方法。
然后执行完方法后,其他线程唤醒,但是不会再进入()方法。
也就是说同一个加载器下,一个类型只会初始化一次。

2.同步锁实现懒汉单例模式

为了保证懒汉式在多线程的环境中是线程安全的,只需要在基本的懒汉式的方法签名上加synchronized同步锁

public class LazySingleton {private LazySingleton() {}private static LazySingleton instance;//只需要在方法签名上添加一个synchronized关键字就可以保证该方法的线程安全性public static synchronized LazySingleton getInstance() {//判断instance是否为null 为null才创建,保证唯一性if (instance == null) {instance = new LazySingleton();}return instance;}
}

这样可以保证并发中懒汉单例模式的线程安全,但是由于整个方法都被同步锁锁住,导致方法效率变低,每个线程的每次请求都会阻塞等待上一个线程判断是否存在单例。

3.双重检查锁实现懒汉式单例※

使用同步锁实现懒汉式单例模式导致性能下降,为了避免,我们是可以使用双重检查锁实现懒汉式单例模式。

public class LazySingleton {private LazySingleton() {}// 1.添加volatile关键字后可以保证instance的有序性,不添加volatile关键字可能会出现空指针异常(由于jvm的指令重排)private static volatile LazySingleton instance;public static  LazySingleton getInstance() {//2.判断instance是否为null 为null就锁住后创建实例,保证安全性,双重检查锁解决了同步锁导致的多个线程进入需要排队取锁而影响性能的问题if (instance == null) {synchronized (LazySingleton.class){if(instance == null){instance = new LazySingleton();}}}return instance;}
}

实现思路:是当一个线程已经进入instance = new LazySingleton();语句后,其他线程就要等待,如果当一个线程已经成功创建单例后,其余的线程进入就只会在第一条if语句处跳出,而不需要等待锁的释放。且之后的每条线程都会直接从第一个if语句处跳出,从而保证了线程安全,大大的提高了性能。
注意点:需要添加volatile关键字修饰静态成员变量,防止空指针异常。(详情请了解jvm的指令重排)

三、破坏单例模式与预防方法

1.反射破坏单例模式

思路:通过反射获取单例类的构造器,通过构造器创建实例

public class Client {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {//1.获取单例类的类对象Class hungerSingletonClass = HungerSingleton.class;//2.获取单例类的构造器并修改访问权限Constructor constructor = hungerSingletonClass.getDeclaredConstructor();constructor.setAccessible(true);//设置访问权限,可以访问私有构造方法//3.使用获取到的构造器创建实例HungerSingleton hungerSingleton1 = constructor.newInstance();HungerSingleton hungerSingleton2 = constructor.newInstance();//4.测试发现两个实例不是同一个对象,单例被破坏了。System.out.println(hungerSingleton1.toString());System.out.println(hungerSingleton2.toString());/*** 两次对象结果的地址不同,说明他们不是同一个对象,单例被破坏了*/}
}

预防方法

在构造方法中判断是否已经创建单例,如果已经创建,则直接手动抛出异常

public class HungerSingleton {//提供一个flag用于判断对象是否已经创建private static boolean flag = false;//1.私有构造方法private HungerSingleton() {//加锁保证多线程安全问题synchronized (HungerSingleton.class){//在无参构造中提供一个判断,如果已经创建了对象则抛出异常if(flag){throw new RuntimeException("禁止创建多个对象");}flag = true;}}//2.私有静态成员private static HungerSingleton instance;//3.静态代码块static {instance = new HungerSingleton();}//4.提供公有静态方法访问唯一对象public static HungerSingleton getInstance() {return instance;}
}

使用一个boolean类型的flag初始化为false,当flag为true是说明已经存在单例,再通过构造器创建实例就直接抛出异常。
第一次调用构造器即第一次创建单例会将flag置为true
测试一下
在这里插入图片描述
发现想利用反射创建两个实例,直接抛出了我们设置的异常。

2.反序列化破坏单例与预防方法

思路:将单例类序列化通过流存储在文件中,再通过反序列化将文件中的数据生成对象

public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObjectToFile(HungerSingleton.getInstance());readObjectFromFile();readObjectFromFile();/*** 输出结果显示两个对象的地址不同,即代表两个对象不是同一个,这就破坏了单例模式了*/}//将对象序列化并存入文件public static void writeObjectToFile(HungerSingleton hungerSingleton) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\d.txt")));oos.writeObject(hungerSingleton);oos.close();}//将文件中的内容反序列化为对象public static void readObjectFromFile() throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\d.txt")));HungerSingleton hungerSingleton = (HungerSingleton) ois.readObject();System.out.println(hungerSingleton.toString());}
}

此时两次反序列化生成的对象就是不同的两个对象。
注意:被序列化的类需要实现Serializable接口

预防方法

在单例类中添加一个readResolve()方法,在该方法内调用获取单例的静态方法

public class HungerSingleton implements Serializable {//1.私有构造方法private HungerSingleton() {}//2.静态私有成员private static HungerSingleton instance = new HungerSingleton();//3.提供一个静态的方法获取唯一实例public static HungerSingleton getInstance() {return instance;}//4.提供readResolve()方法,保证反序列化为同一个对象public Object readResolve(){return HungerSingleton.getInstance();}
}

ObjectInputStream 类中,会做一个判断,判断序列化的类中是否存在 readResolve() 方法,如果有,则会在反序列化的时候调用 readResolve() 方法。
有兴趣可以查看 ObjectInputStream 源码第2243行

四、jdk源码解析

在JDK中,Runtime类也是使用了饿汉式单例模式

public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class Runtime are instance* methods and must be invoked with respect to the current runtime object.** @return  the Runtime object associated with the current*          Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}.............
}

可以看到Runtime也初始化了一个静态成员变量,和一个静态方法获取该成员变量,并且只有一个私有构造方法

相关内容

热门资讯

班级元旦晚会策划方案 班级元旦晚会策划方案  为了确保事情或工作能无误进行,我们需要提前开始方案制定工作,方案指的是为某一...
女神节祝福文案 2022女神节祝福文案(精选285句)  无论是身处学校还是步入社会,越来越多人钟情于在朋友圈发布文...
物业前期工程介入方案 物业前期工程介入方案XXXXXX项目物业前期介入方案1.0目的协助项目工程部对前期施工安装过程进行过...
年夜饭主题活动策划优秀方案 年夜饭主题活动策划优秀方案(精选6篇)  为了确保活动取得实效,常常需要预先制定活动方案,活动方案是...
幼儿园国庆活动主题方案 幼儿园国庆活动主题方案(精选16篇)  为了保障事情或工作顺利、圆满进行,时常需要预先开展方案准备工...
营销策划方案范本 营销策划方案范本  一、什么是策划方案  策划方案,是策划成果的表现形态,通常以文字或图文为载体,策...
公司年会策划方案制定注意事项 公司年会策划方案制定注意事项  公司年会庆典活动通常出席的嘉宾、参加活动的工作人员都比较多。所以组织...
办公室招新策划书 办公室招新策划书  办公室是学生会重要的一个部门,下面unjs小编整理了办公室招新策划书,欢迎阅读!...
服装营销策划方案 服装营销策划方案  为了确保事情或工作有效开展,通常会被要求事先制定方案,方案是阐明具体行动的时间,...
暑期社会实践策划书 暑期社会实践策划书  时光如箭,转眼一划而过,一段时间的工作已经结束了,我们又将接触新的知识,学习新...
高中主题班会策划 高中主题班会策划  高中主题班会策划方案应该怎么样策划?看看下面为大家整理推荐的高中主题班会策划方案...
立春温柔的文案 立春温柔的文案(精选95句)  无论是身处学校还是步入社会,越来越多人喜欢在闲暇时发布文案,文案用以...
新年年会策划方案 最新年会策划方案推荐度:年会策划方案推荐度:公司年会策划方案推荐度:年会策划方案推荐度:年会晚会策划...
药品活动策划方案 药品活动策划方案  为保障活动顺利开展,常常需要提前准备一份具体、详细、针对性强的活动方案,活动方案...
新闻媒体的策划书 新闻媒体推荐的策划书  一、前言。  现代广告的迅猛发展,已成为社会经济增长的一大优势。广告收入增长...
主题活动策划 主题活动策划15篇主题活动策划1  泼水节是傣族人民最盛大的节日,傣语称“桑康比迈”,意为六月(傣历...
运动会活动策划方案 运动会活动策划方案(精选6篇)  为确保活动顺利开展,时常需要预先开展活动方案准备工作,活动方案是阐...
真相永远只有一个“推理之绊”... 真相永远只有一个“推理之绊”系列活动策划  一、活动背景:  “真相永远只有一个”,“ 除所有不可能...
学校总务主任述职报告   述职报告是各级机关、企事业单位和社会团体的工作人员向本单位的组织部门、上级领导机关或本单位员工陈...