单例模式, 顾名思义, 在程序运行中, 实例化某个类时只实例化一次, 即只有一个实例对象存在. 例如在古代, 一个国家只能有一个皇帝, 在现代则是主席或总统等.
在 Java 语言中单例模式有以下实现方式
1. 饿汉式
- import org.junit.jupiter.API.Test;
- public class Singleton {
- // 静态成员变量
- private static Singleton singleton = new Singleton();
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- // 构造函数私有化
- private Singleton(){
- }
- // 返回静态资源中 Singleton 实例
- public static Singleton getInstance() {
- return singleton;
- }
- /**
- * 单元测试
- */
- @Test
- public void testSingleton() {
- // 通过调用类的静态方法返回类的实例对象
- Singleton s1 = Singleton.getInstance();
- Singleton s2 = Singleton.getInstance();
- s1.setName("zhangsan");
- s2.setName("lisi");
- System.out.println(s1.getName());
- System.out.println(s2.getName());
- }
- }
在类加载时, 直接将实例对象初始化, 并且该实例是静态的, 属于类的成员变量, 通过调用类的静态方法返回该对象.
运行 testSingleton 单元测试, 输出的两行都是 lisi, 因为 s1,s2 指向的同一个实例对象, 这个对象在类创建的时候就存在了.
饿汉式是线程安全的, 不管系统的那一个线程获取这个对象, 他们都是该类同一个对象. 缺点是在程序在一开始就创建了该对象, 占用内存空间. 下面这种实现方式增加判断, 在程序调用时才实力化该对象.
2. 懒汉式
- import org.junit.jupiter.API.Test;
- public class Singleton {
- // 不初始化实例对象
- private static Singleton singleton = null;
- private String name;
- public String getName() {
- return name;
- }
- // 构造函数私有化
- private Singleton(){
- }
- public void setName(String name) {
- this.name = name;
- }
- // 当被调用时才动态实例化
- public static Singleton getInstance() {
- if(singleton == null) {
- singleton = new Singleton();
- }
- return singleton;
- }
- }
懒汉式解决了饿汉式的实例对象初始化占用内存的情况, 但是懒汉式存在线程安全的问题, 当多个线程同时访问 getInstance() 方法时, 有可能在第一个线程进入 if 语句是还没 new Singleton() 时, 这时第二个线程判断 if 的时候就会为真, 则会创建新的实例, 单例模式设计失败.
这时需要在 getInstance() 方法上添加 synchronized 修饰符, 表示线程同步, 即当一个线程访问该方法时, 其他线程需要等待其释放资源.
- public static synchronized Singleton getInstance() {
- if(singleton == null) {
- singleton = new Singleton();
- }
- return singleton;
- }
这样解决了懒汉式的线程不安全的问题, 但是这样会降低系统的性能, 当一个线程占用了该资源, 其他的线程只能等待, 系统的性能大大下降. 于是乎第三种实现模式, 对懒汉式进行了改进.
3. 双重检测
- public static Singleton getInstance() {
- if(singleton == null) {
- synchronized (Singleton.class) {
- if(singleton==null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
双重检测, 线程访问时有两次检测.
这里将 synchronized 放到判断语句里面, 这样当第一个线程调用 getInstance() 方法时, singleton 为空, 进入 synchronized 代码块, 其他的线程即使也判断 singleton 为空, 则需要等待第一个线程完成资源的使用, 这样保证了线程的安全.
当实例化对象完成是, 其他的线程调用 getInstance() 方法时, 直接返回 Singleton 对象即可, 不用再等待, 这一点优化了系统, 提高了系统的性能.
4. 总结
模式来源于生活. 本文用 Java 语言实现了单例模式. 三种实现方式都是线程安全的, 饿汉式比较占用内存空间; 懒汉式则降低了系统的性能; 双重检测是懒汉式的升级, 即能保证线程的安全, 也能以懒加载的方式实现单例模式, 在大规模系统中节省对象的创建时间, 提高系统的性能. 在系统中共享同一个资源或同一个对象, 则可利用单例模式来实现.
参考 B 站视频 https://www.bilibili.com/video/av34162848.
来源: https://www.cnblogs.com/peter-hao/p/Singleton.html