单例模式介绍
1. 描述
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。
单例模式也比较好理解,比如一个人一生当中只能有一个真实的身份证号,一个国家只有一个政府,类似的场景都是属于单例模式。
2. 单例模式要完成的两件事
- 保证一个类只有一个实例
- 为该实例提供一个全局访问节点
3. 单例模式结构
4. 单例模式实现的几种方式
4.1 饿汉式
在类加载期间初始化静态实例,保证 instance 实例的创建是线程安全的 ( 实例在类加载时实例化,有JVM保证线程安全).
特点: 不支持延迟加载实例(懒加载) , 此中方式类加载比较慢,但是获取实例对象比较快
问题: 该对象足够大的话,而一直没有使用就会造成内存的浪费。
java
public class Singleton_01 {
//1. 私有构造方法
private Singleton_01(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_01 instance = new Singleton_01();
//3. 提供一个全局访问点,供外部获取单例对象
public static Singleton_01 getInstance(){
return instance;
}
}
4.2 懒汉式(线程不安全)
这种方式的单例模式实现了懒加载,只有在调用getInstance()方法的时候才会去创建对象,但是如果是多线程情况,则会出现线程安全问题
java
public class Singleton_02 {
//1. 私有构造方法
private Singleton_02(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_02 instance;
//3. 通过判断对象是否被初始化,来选择是否创建对象
public static Singleton_02 getInstance(){
if(instance == null){
instance = new Singleton_02();
}
return instance;
}
}
假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程A在执行完if (instance == null) 后,线程调度机制将 CPU 资源分配给线程B,此时线程B在执行 if (instance == null)时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次。为了防止这种情况发生,需要对 getInstance() 方法同步处理。改进后的懒汉模式.
4.3 懒汉式(线程安全)
使用同步锁 synchronized
锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建
-
即,
getInstance()
方法块只能运行在1个线程中 -
若该段代码已在1个线程中运行,另外1个线程试图运行该块代码,则 会被阻塞而一直等待
-
而在这个线程安全的方法里我们实现了单例的创建,保证了多线程模式下 单例对象的唯一性
java
public class Singleton_03 {
//1. 私有构造方法
private Singleton_03(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_03 instance;
//3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性
public static synchronized Singleton_03 getInstance(){
if(instance == null){
instance = new Singleton_03();
}
return instance;
}
}
懒汉式的缺点也很明显,我们给 getInstance() 这个方法加了一把大锁(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了。
4.4 双重校验
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。
实现步骤:
- 在声明变量时使用了 volatile 关键字,其作用有两个:
保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。
屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果时正确的,但 是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。
- 将同步方法改为同步代码块. 在同步代码块中使用二次检查,以保证其不被重复实例化 同时在调用getInstance()方法时不进行同步锁,效率高。
java
public class Singleton_04 {
//使用 volatile保证变量的可见性
private volatile static Singleton_04 instance = null;
private Singleton_04(){
}
//对外提供静态方法获取对象
public static Singleton_04 getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null){
synchronized (Singleton_04.class){
//抢到锁之后再次进行判断是否为null
if(instance == null){
instance = new Singleton_04();
}
}
}
return instance;
}
}
4.5 静态内部类
- 原理
根据 静态内部类 的特性(外部类的加载不影响内部类),同时解决了按需加载、线程安全的问题,同时实现简洁
- 在静态内部类里创建单例,在装载该内部类时才会去创建单例
- 线程安全:类是由
JVM
加载,而JVM
只会加载1遍,保证只有1个单例
java
public class Singleton_05 {
private static class SingletonHandler{
private static Singleton_05 instance = new Singleton_05();
}
private Singleton_05(){}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
如果对你有帮助,可以关注博主(不定期更新各种技术文档) 给博主一个免费的点赞以示鼓励,谢谢 !
欢迎各位🔎点赞👍评论收藏⭐️