单例模式的6种实现方式
大家好,我是尚影嫣🌷,一名Java后端程序媛,热爱技术,更热爱分享。如果您喜欢我的文章,欢迎点赞➕关注💖,让我们一起持续学习,持续改进,成为更好的我们~😉
前言
最近在温习设计模式,设计模式就是一种最佳实践方式,它提供了软件开发过程中面临的一般问题的最佳解决方案。我们先来看最简单最常用的单例模式。
什么是单例模式
单例模式(Singleton Pattern) 是 Java 中最简单,也是最常用的设计模式之一。
单例模式的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
数学与逻辑学中,将 singleton 定义为 “有且仅有一个元素的集合”。
单例模式最早的定义出现在《设计模式》一书中:
“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
Java中单例模式的定义是:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
单例模式的特性:
- 通过单例模式的方法创建的类在当前进程中只有一个实例。
- 在类内部创建自己的实例。
- 对外提供一个返回其单例对象的方法。
类图如下: 👇
访问类
指的是使用单例类对象的类。
单例模式的6种实现方式
单例模式的实现方式有懒汉式
、饿汉式
和枚举
。
对比 | 懒汉式 | 饿汉式 | 枚举 |
---|---|---|---|
特点 | 使用时检查是否有无实例,有则返回,没有则进行实例化。 | 类加载到内存后,就实例化出一个单例,由JVM保证线程安全,简单实用。 | 保证线程安全,而且可以防止反序列化。 |
缺点 | 为保证线程安全,需要加上synchronized关键字,会影响程序执行效率。 | 不论用不用,类加载时都会进行实例化,浪费内存空间。 | 较为完美 |
1、饿汉式-静态变量
类一旦加载就会创建一个单例,保证在调用getInstance()方法之前,单例已经存在了。这种方式比较常用,线程安全,可直接用于多线程场景,但容易产生垃圾对象。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
/**
* 构造函数私有化,以保证该类不会被实例化
*/
private Singleton() { }
public static Singleton getInstance() {
return INSTANCE;
}
}
2、饿汉式-静态代码块
和第一种方式类似。基于 classloader 机制避免了多线程同步问题。
public class Singleton {
private static final Singleton INSTANCE;
//静态代码块
static {
INSTANCE = new Singleton();
}
/**
* 构造函数私有化
*/
private Singleton() { }
public static Singleton getInstance() {
return INSTANCE;
}
}
3、懒汉式-线程安全
在方法上加synchronized关键字,支持多线程,但效率低。
在第一次调用时进行初始化,避免了内存浪费。只能靠加锁(synchronized)来保证单例,加了锁就影响了效率。
public class SingleLazy {
private static SingleLazy INSTANCE;
private SingleLazy() { }
/**
* 加上synchronized实现线程同步
*/
public synchronized static SingleLazy getInstance() {
if (INSTANCE == null) {
INSTANCE = new SingleLazy();
}
return INSTANCE;
}
}
4、双检锁校验(DCL)
将synchronized加在对象上,提高执行效率,需要加上双重校验。这种方式线程安全,满足懒加载,在多线程情况下也可以保证高性能。
public class SingleLazy {
/**
* 加上volatile防止指令重排序
*/
private static volatile SingleLazy INSTANCE;
private SingleLazy() { }
public static SingleLazy getInstance() {
if (INSTANCE == null) {
synchronized (SingleLazy.class) {
//加上双重校验
if (INSTANCE == null) {
INSTANCE = new SingleLazy();
}
}
}
return INSTANCE;
}
}
5、登记式-静态内部类
加载外部类时,不加载内部类,实现 lazy loading 懒加载
,同饿汉式一样利用了 classloader 机制保证初始化 instance 时只有一个线程,由JVM保证单例
。
这种方式也能满足双检锁校验方式的效果,且更简单一些。
只有 getInstance() 方法被显示调用时,才会显示装载 SingleManager 类,实现 instance 的实例化。
public class SingleLazyInner {
private SingleLazyInner() { }
/**
* 静态内部类,外部类加载时不会加载内部类
*/
private static class SingleManager {
private static final SingleLazyInner INSTANCE = new SingleLazyInner();
}
public static SingleLazyInner getInstance() {
return SingleManager.INSTANCE;
}
}
6、枚举
枚举可以说是单例模式的最佳实践
,代码非常简洁,保证线程安全
,自动支持序列化机制
,而且可以防止反序列化
。
由于枚举是从 JDK1.5
起才开始引入的特性,所以使用这种方式的人较少。
public enum SingleEnum {
/**
* 枚举没有构造方法,可以防止反序列化
*/
INSTANCE;
}
总结
饿汉式
最为简单实用,且线程安全。不要求懒加载的情况下,推荐使用
;登记式-静态内部类
的方式实现了懒加载
,比饿汉式更节省内存空间;懒汉式
如果不加锁就无法保证线程安全,如果加锁就会影响效率,较为复杂,不推荐使用
;枚举式
比较简洁,线程安全,且防止反序列化,设计上最为完美
。
转载自:https://juejin.cn/post/7140266949962891277