单例模式的四种具体写法

1.单例模式的介绍

1.1概念

单例模式就是保证对象只被创建一次,并在整个程序中复用。

1.2如何实现单例

  • 将构造方法私有化
  • 提供一个全局唯一获取该类实例的方法帮助用户获取该类的实例

1.3应用场景

主要被用于一个全局类的对象在多个地方被使用并且对象的状态是全局变化的场景下。

1.4优点

单例模式保障了整个系统只有一个对象能被使用,很好地节约了资源。

1.5单例模式的写法

  • 饿汉模式
  • 懒汉模式
  • 静态内部类
  • 双重校验锁

2、四种模式的使用

2.1饿汉模式

饿汉模式就是在加载类(比如下面加载Singleton)的时候就直接new一个对象,然后在方法中直接将对象返回给用户

java 复制代码
public class Singleton {
    // 使用static修饰,类加载的时候new一个对象
  	private static Singleton INSTANCE = new Singleton();
  
  	// 构造器私有化
  	private Singleton() {}
  	
  	public static Singleton getInstance() {
      	return INSTANCE;
    }
}

2.2懒汉模式

懒汉模式就是在加载类的时候只声明变量,不new对象,后面需要用到的时候再new对象,并把对象赋给变量

java 复制代码
public class Singleton {
    
  	private static Singleton INSTANCE;
  
  	// 构造器私有化
  	private Singleton() {}
  	
  	public static Singleton getInstance() {
      	if (INSTANCE == null) {
          	INSTANCE = new Singleton();
        }
      	return INSTANCE;
    }
}
饿汉模式与懒汉模式的区别

饿汉模式在getInstance方法调用前实例已经被创建,因此实例在类加载的时候就已经存在于JVM中,因此饿汉模式是线程安全 的,而懒汉模式 则是在调用getInstance方法后才创建实例,因此线程是不安全的

2.3静态内部类

通过在类中定义一个静态内部类,将对象实例的创建与初始化放在内部类中完成,我们在getInstance中获取对象直接通过静态内部类调用单例对象

正是因为类的静态内部类在JVM中的唯一性 才保证了单例对象的唯一性,从而静态内部类同样是线程安全的

java 复制代码
public class Singleton {
  
  	private static class SingletonHolder {
      	private static final Singleton INSTANCE = new Singleton();
    }
  
  	private Singleton(){}
  
  	public static final Singleton getInstance(){
      	return SingletonHolder.INSTANCE;
    }
}

2.4双重校验锁

普通的懒汉模式在单线程场景下是线程安全的,但在多线程场景下是非线程安全的。

先来看看普通的懒汉模式

java 复制代码
public class Singleton {
    
  	private static Singleton INSTANCE;
  
  	private Singleton() {}
  	
  	public static Singleton getInstance() {
      	if (INSTANCE == null) {
          	INSTANCE = new Singleton();
        }
      	return INSTANCE;
    }
}

在多线程同时调用getInstance方法时,由于方法没有加锁,可能会出现以下情况:

  1. 这些线程可能会创建多个对象
  2. 某个线程可能会得到一个未完全初始化的对象

对于1的解释

java 复制代码
public static Singleton getInstance() {
    if (INSTANCE == null) {
        /**
         * 由于没有加锁,当线程A刚执行完if判断INSTANCE为null后还没来得及执行INSTANCE = new Singleton()
         * 此时线程B进来,if判断后INSTANCE为null,且执行完INSTANCE = new Singleton()
         * 然后,线程A接着执行,由于之前if判断INSTANCE为null,于是执行INSTANCE = new Singleton()重复创建了对象
         */
        INSTANCE = new Singleton();
    }
    return INSTANCE;
}

对于2的解释

java 复制代码
public static Singleton getInstance() {
    if (INSTANCE == null) {
        /**
         * 由于没有加锁,当线程A刚执行完if判断INSTANCE为null后开始执行 INSTANCE = new Singleton()
         * 但是注意,new Singleton()这个操作在JVM层面不是一个原子操作
         *
         *(具体由三步组成:1.为INSTANCE分配内存空间;2.初始化INSTANCE;3.将INSTANCE指向分配的内存空间,
         * 且这三步在JVM层面有可能发生指令重排,导致实际执行顺序可能为1-3-2)
         *
         * 因为new操作不是原子化操作,因此,可能会出现线程A执行new Singleton()时发生指令重排的情况,
         * 导致实际执行顺序变为1-3-2,当执行完1-3还没来及执行2时(虽然还没执行2,但是对象的引用已经有了,
         * 只不过引用的是一个还没初始化的对象),此时线程B进来进行if判断后INSTANCE不为null,
         * 然后直接把线程A new到一半的对象返回了
         */
        INSTANCE = new Singleton();
    }
    return INSTANCE;
}

因此通过单例模式的双重检验锁模式去解决以上问题

java 复制代码
public class Lock2Singleton {
  	private volatile static Lock2Singleton INSTANCE;    // 加 volatile
  
  	private Lock2Singleton() {}
  
  	public static Lock2Singleton getSingleton() {
      	if (INSTANCE == null) {                         // 双重校验:第一次校验
          	synchronized(Lock2Singleton.class) {        // 加 synchronized
              	if (INSTANCE == null) {                 // 双重校验:第二次校验
                  	INSTANCE = new Lock2Singleton();
                }
            }
        }
      	return INSTANCE;
    }
}

为啥要双重校验?

第一次校验是为了提高效率,避免INSTANCE不为null时仍然去竞争锁

第二次校验是为了避免多个线程重复创建对象

为啥要加volatitle?

加volatitle是为了禁止指令重排,避免某个线程可能会得到一个未完全初始化的对象

具体执行过程:

  1. 判断 INSTANCE 是否为null,检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量;
  2. 不为null,直接返回,不用去竞争锁,为null,获取锁,然后再次判断是否为null,
  3. 为null,创建并返回,不为null直接返回
相关推荐
草履虫建模15 分钟前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq2 小时前
分布式系统安全通信
开发语言·c++·算法
qq_297574673 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
老毛肚3 小时前
MyBatis插件原理及Spring集成
java·spring·mybatis
学嵌入式的小杨同学3 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
lang201509283 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
Re.不晚3 小时前
Java入门17——异常
java·开发语言
缘空如是3 小时前
基础工具包之JSON 工厂类
java·json·json切换
精彩极了吧3 小时前
C语言基本语法-自定义类型:结构体&联合体&枚举
c语言·开发语言·枚举·结构体·内存对齐·位段·联合
追逐梦想的张小年4 小时前
JUC编程04
java·idea