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小白,错漏之处,请多指正