Spring-单例bean小记

1.什么是单例bean

单例bean是指在Spring框架中,容器范围内只有一个共享实例的bean对象,是spring默认的bean作用域
简单来说就是:每次获取到的对象都是同一个

2.单例bean的实现方式

1.饿汉式

饿汉式是指在spring容器启动时就创建了对应的bean实例,会增加容器初始化的时间

示例代码:
@Component // 默认就是饿汉式单例
public class EagerSingleton {
    public EagerSingleton() {
        System.out.println("EagerSingleton实例被创建");
    }
}

// 或者使用@Bean方式
@Configuration
public class AppConfig {
    @Bean // 默认是饿汉式
    public MyEagerBean myEagerBean() {
        return new MyEagerBean();
    }
}

2.懒汉式

懒汉式是在第一次被请求触发时才创建的实例,可以减少容器初始化的时间

示例代码:
@Component
@Lazy // 添加Lazy注解实现懒加载
public class LazySingleton {
    public LazySingleton() {
        System.out.println("LazySingleton实例被创建");
    }
}

// 或者使用@Bean方式
@Configuration
public class AppConfig {
    @Bean
    @Lazy
    public MyLazyBean myLazyBean() {
        return new MyLazyBean();
    }
}

3.静态内部类

通过私有化构造函数,public获取实例方法控制单例

示例代码:
public class HolderSingleton {
    private HolderSingleton() {}
    
    private static class Holder {
        static final HolderSingleton INSTANCE = new HolderSingleton();
    }
    
    public static HolderSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

4.双重检查锁(DCL)

通过加锁的方式进行保证创建出来的为单例对象

第一次检查:避免不必要的同步,如果有就直接返回已有的对象

第二次检查:防止多线程同时绕过第一次检查后重复创建实例

为什么要volatile

在多线程环境下,防止指令重排,由于编译器和处理器可能会对指令进行优化重排。导致对象半初始化(如,int a = 10,int半初始化为默认值0),获取不到实际的对象值。

volatile还有一个特点,保证可见性,多个线程对同一个共享变量的操作是可见的。一个线程对变量进行了修改,其他线程会立刻看到这个操作

示例代码:
public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;
    
    private DoubleCheckedSingleton() {}
    
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

5.枚举

枚举具有天然单例的特性,可以从以下几个方面保证

1.枚举值是private static final的,在类加载时就被初始化
2.构造器私有化,不能通过new关键字创建实例
3.防止继承,枚举类隐式继承java.lang.Enum(final)
4.防止序列化攻击,序列化只存储枚举名称,反序列化通过Enum.valueOf()查找已有实例,不会新建实例
5.防止反射攻击,有对枚举类的特殊检查,通过newInstance()反射创建实例会直接报错
示例代码:
public enum Color {
    RED, GREEN, BLUE;
}

//实际对应的是
public final class Color extends Enum<Color> {
    public static final Color RED = new Color("RED", 0);
    public static final Color GREEN = new Color("GREEN", 1);
    public static final Color BLUE = new Color("BLUE", 2);
    
    private static final Color[] VALUES = {RED, GREEN, BLUE};
    
    private Color(String name, int ordinal) {
        super(name, ordinal);
    }
    
    public static Color[] values() {
        return VALUES.clone();
    }
    
    public static Color valueOf(String name) {
        // 查找逻辑...
    }
}

3.单例bean是否线程安全

单例bean只有一个实例,无论哪个线程获取都是同一个,从这个角度是线程安全的,但是还需要看这个实例是不是有状态的

1.有状态的单例bean(线程不安全)

有可变变量的bean,多线程环境下变量的值可能会导致线程不安全

2.无状态的单例bean(线程安全)

无可变变量的bean,所有数据都从方法传入,可以保证天然的单例线程安全

4.序列化/反序列化会破坏单例吗

序列化/反序列化会对单例进行破坏,但是可以通过特定方式解决

首先反序列化的步骤:

1.从字节流中读取对象数据

2.为对象分配内存空间

3.调用类的无参构造器(实际上不调用用户定义的构造器)

4.填充对象的字段值

5.检查类是否定义了readResolve()方法

        1.如果定义了,则调用该方法

        2.用该方法的返回值替换新创建的对象

6.返回最终的引用对象

//代码1
public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static SerializableSingleton instance = new SerializableSingleton();
    
    private SerializableSingleton() {}
    
    public static SerializableSingleton getInstance() {
        return instance;
    }
}


//代码2 解决方式
public class SerializableSingleton implements Serializable {
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();
    
    private SerializableSingleton() {}
    
    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }
    
    protected Object readResolve() {
        return INSTANCE; // 返回现有的单例实例
    }
}

代码1就是在反序列化的时候没有调用到getInstance()这个方法的。而代码2就是通过readResolve()调用了getInstance()方法,所以替换了序列化生成的新对象,用了getInstance()这个单例的对象

5.单例bean的适用场景

1.工具类服务

仅包含方法,无可变变量,线程安全,无需每次创建实例

2.工厂类服务

创建对象的工厂类,避免重复创建工厂实例

3.配置信息

全局统一访问,加载后配置不变

4.数据库连接池

避免重复创建连接池

5.日志记录

统一日志处理策略等等

java小白,错漏之处,请多指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值