文章目录
前言
Java八股是程序员面试和进阶的必备技能。本专栏旨在每篇文章通过一个八股问题深入钻研,并引申出其相关联的知识点,方便记忆。
本文介绍设计模式的单例模式。将围绕单例模式的概念、实现、应用三个方面展开叙述。
一、单例模式是什么
单例模式(Singleton)是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
二、单例模式的实现
1.饿汉式
public class Singleton {
private static Singleton instance=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
2.懒汉式
线程不安全的实现
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
线程安全的实现
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
双重检查锁实现
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 私有化构造函数
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意
- 构造函数必须是private修饰的,禁止从Singleton类外部调用构造函数创建实例。因为单例类必须自己创建自己的唯一实例,才能确保任何情况下都只能生成一个实例。
- 单例类必须对外提供获取其实例的方法。
饿汉式和懒汉式的比较
- 实例的初始化时间:饿汉式在初始化类的时候就创建了唯一实例;而懒汉式在外部第一次调用getInstance方法的时候才创建唯一实例
- 懒加载的好处是节省资源。但要注意多线程环境下的线程安全问题。
双重检查锁说明
- 双重检查锁是对线程安全的懒汉式单例模式的优化。
- 线程安全的懒汉式单例模式是在getInstance方法直接加锁,在getInstance方法内部进行一次instance是否为null的检查。双重检查锁是在getInstance方法内部进行一次instance是否为null的检查,为null才加锁,之后再进行一次instance是否为null的检查。
- 重点:变量instance必须用volatile修饰。原因:instance = new Singleton();这行代码包含了三步JVM操作。
a. 分配对象的内存空间
b.初始化对象
c.将instance指向刚分配的内存地址
以下并发场景存在问题:线程A进入getInstance方法,执行了步骤1和步骤3,步骤2因为指令重排序的问题还未执行。此时线程B也getInstance,判断instance!=null,直接把当前instance返回了。但由于还未执行步骤2初始化对象,此时instance执行的内存地址是空对象,后续使用会有问题。
如下图,指令重排序导致的问题(绿色表示已执行)

解决:用volatile修饰instance,使得instance = new Singleton();这一步禁用了指令重排序,从而解决了这个问题。
3. 静态内部类
public class Singleton {
private Singleton() {
// 私有化构造函数
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
静态内部类说明
- 静态内部类单例模式是利用java类的加载机制来保证线程安全的。
- 当Singleton类被加载时,内部类SingletonHolder不会被加载。只有当调用getInstance方法时,SingletonHolder类才被加载,此时INSTANCE实例也被创建。
- 这种方式既能保证线程安全,又能避免使用synchronized关键字,减少加锁的开销,性能较好,是目前公认的最优秀的单例模式的实现方式之一。
4. 枚举
public enum EnumSingleton {
INSTANCE;
}
枚举说明
- 枚举单例模式是使用 Java 枚举特性实现的单例模式。通过私有构造函数和枚举的特性,可以确保只有一个实例被创建。
- 这种实现方式非常简单,并且天生就是线程安全的。此外,它还能防止反序列化破坏单例,以及防止通过反射的方式创建新实例。因此,它被认为是最安全和最简单的单例模式实现方式。
三、单例模式的应用
适用场景
- 某些类只应该有一个实例,比如配置类、日志类等
- 当实例化需要消耗大量资源时,如数据库
- 当多个实例会导致问题时,如共享访问修改同一个资源
举例
1. 单例实现DataSource
@Bean
public DataSource dataSource() {
// 初始化连接池(耗时操作)
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost/db");
return ds; // 单例复用连接池
}
2. 集成腾讯云对象存储时使用单例模式初始化客户端
@Bean
public COSClient cosClient() {
// 初始化用户身份信息(secretId, secretKey)。
COSCredentials cosCredentials = new BasicCOSCredentials(ServerConfig.cosSecretId, ServerConfig.cosSecretKey);
ClientConfig clientConfig = new ClientConfig(new Region(ServerConfig.cosBucketRegion));
clientConfig.setHttpProtocol(HttpProtocol.https);
return new COSClient(cosCredentials, clientConfig);
}
@Bean注解不指定其作用域时默认是单例的,相当于@Scope(“singleton”)。
3. Spring用到单例模式的地方
- Bean对象的默认作用域为单例(scope=“singleton”):在Spring中,Bean对象的作用域可以配置为单例模式。当一个Bean配置为单例模式时,Spring容器在启动时会创建该Bean的实例,并在容器的整个生命周期中都使用同一个实例。
- Spring容器本身是单例的:在Spring中,容器是一个单例对象,只会有一个ApplicationContext容器实例。这是为了提高应用程序的性能和效率,避免重复创建和销毁容器实例。
- Spring AOP中的切面类也是单例的:在Spring的AOP中,切面(Aspect)是一个跨越多个对象的通用功能模块,如日志记录、事务管理等。为了保证切面类的效率和性能,Spring默认将切面类配置为单例模式。
- Spring中的代理对象也是单例的:在Spring中,通过代理模式来实现增强目标对象的功能。默认情况下,Spring使用JDK动态代理来创建代理对象,并将代理对象配置为单例模式。
- 单例容器:在Spring中,可以通过SingletonBeanRegistry接口提供的方法来管理单例对象。这个接口定义了一些方法,如registerSingleton()、getSingleton()、containsSingleton()等,用于向容器注册和获取单例对象。

180

被折叠的 条评论
为什么被折叠?



