详解单例模式及其在Sping中的最优实践

简介: 在程序中,每new() 一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。 抽象到设计模式中,这种只生成一个实例的模式就是单例模式(Singleton)。

听说微信搜索《Java鱼仔》会变更强哦!


本文收录于githubgitee ,里面有我完整的Java系列文章,学习或面试都可以看看哦


(一)什么是单例模式


在程序中,每new() 一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。 抽象到设计模式中,这种只生成一个实例的模式就是单例模式(Singleton)。


(二)单例模式的实现


单例模式的实现有很多种方式,归根到底是保证new Class()的操作只做一次,在大多数的实现上,会将需要实现单例的类的构造方法改为private修饰,使得无法通过new命令创建对象实例。


(三)单例模式的代码实现


代码模式有很多种实现方式,主要有饿汉式、懒汉式以及有点特殊的单例注册表(Spring的实现方式)。下面会针对上面的这三种方式各自给出一种实现方式。


3.1 饿汉式


饿汉式简单来说就是在项目启动时就将对象实例创建出来,可以用来做需要执行一次的操作:


publicclassHungrySingleton {
publicstaticfinalHungrySingletonINSTANCE;
static {
INSTANCE=newHungrySingleton();
    }
privateHungrySingleton(){
    }
}

调用时只需要直接调用INSTANCE变量就行:

HungrySingletoninstance=HungrySingleton.INSTANCE;

静态代码块饿汉式适合从外部文件中获取数据时使用,我在项目下新建一个info.properties,里面包含一条内容info=hello。饿汉式单例代码如下:

publicclassHungrySingleton2 {
publicstaticfinalHungrySingleton2INSTANCE;
privateStringinfo;
static {
Propertiesproperties=newProperties();
try {
properties.load(HungrySingleton2.class.getClassLoader().getResourceAsStream("info.properties"));
        } catch (IOExceptione) {
e.printStackTrace();
        }
INSTANCE=newHungrySingleton2(properties.getProperty("info"));
    }
privateHungrySingleton2(Stringinfo){
this.info=info;
    }
publicStringgetInfo() {
returninfo;
    }
publicvoidsetInfo(Stringinfo) {
this.info=info;
    }
@OverridepublicStringtoString() {
return"Singleton3{"+"info='"+info+'\''+'}';
    }
publicstaticvoidmain(String[] args) {
HungrySingleton2instance=HungrySingleton2.INSTANCE;
System.out.println(instance.getInfo());
    }
}

3.2 懒汉式


懒汉式是指当第一次调用时才会生成这个实例,后面再调用就只会使用之前生成的实例。懒汉式是实际编写单例模式代码时用的比较多的方案,下面给出一段十分经典的懒汉式单例模式代码案例:

publicclassLazySingleton {
privatestaticvolatileLazySingletonInstance;
privateLazySingleton(){};
publicstaticLazySingletongetInstance(){
if (Instance==null){
synchronized (LazySingleton.class){
if(Instance==null){
Instance=newLazySingleton();
                }
            }
        }
returnInstance;
    }
}

在面试中,往往这道题能引出volatile和synchorized这两个知识点。


3.3 单例注册表


我看网上很少有人介绍这种单例模式,单例注册表是Spring中Bean单例的核心实现方案。可以通过一个ConcurrentHashMap存储Bean对象,保证Bean名称唯一的情况下也能保证线程安全。下面是单例注册表的简单实现:


publicclassRegSingleton {
privatefinalstaticMap<String, Object>singletonObjects=newConcurrentHashMap<>(16);
privateRegSingleton(){}
publicstaticObjectgetInstance(StringclassName){
if (StringUtils.isEmpty(className)) {
returnnull;
        }
if (singletonObjects.get(className) ==null) {
try {
singletonObjects.put(className, Class.forName(className).newInstance());
            } catch (Exceptione) {
e.printStackTrace();
            }
        }
returnsingletonObjects.get(className);
    }
}

新建一个测试类:

publicclassUser {
privateStringid;
publicvoidsetId(Stringid) {
this.id=id;
    }
publicStringgetId() {
returnid;
    }
@Overridepublicbooleanequals(Objecto) {
if (this==o) returntrue;
if (!(oinstanceofUser)) returnfalse;
Useruser= (User) o;
returnObjects.equals(getId(), user.getId());
    }
@OverridepublicinthashCode() {
returnObjects.hash(getId());
    }
}

最后测试单例是否生效:

publicclassMain {
publicstaticvoidmain(String[] args) {
Useruser1= (User) RegSingleton.getInstance("com.javayz.singleton.User");
Useruser2= (User) RegSingleton.getInstance("com.javayz.singleton.User");
System.out.println(user1==user2);
    }
}

(四)单例模式在Spring中的最佳实践


单例模式的最佳实践就是Spring中的Bean了,Spring中的Bean默认都是单例模式,Spring实现单例的方式就采用了单例注册表的方式。


首先在Bean工厂中,如果设置了Spring的Bean模式为单例模式,Spring就会通过getSingleton的方式去获取单例Bean对象。


接着就会进


网络异常,图片无法展示
|


入到DefaultSingletonBeanRegistry类的getSingleton方法中,忽略掉其他代码,只看单例Bean生成相关代码


publicclassDefaultSingletonBeanRegistryextendsSimpleAliasRegistryimplementsSingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */privatefinalMap<String, Object>singletonObjects=newConcurrentHashMap<>(256);
publicObjectgetSingleton(StringbeanName, ObjectFactory<?>singletonFactory) {
//一系列处理操作         }
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions=null;
            }
afterSingletonCreation(beanName);
         }
if (newSingleton) {
//如果实例对象不存在,注册到单例注册表中addSingleton(beanName, singletonObject);
         }
      }
returnsingletonObject;
   }
}
}

对应的addSingleton方法就是将对象加入到单例注册表中:


网络异常,图片无法展示
|


(五)总结


至此,单例模式的内容差不多就结束了,结合源码看设计模式每次都有新收获。我是鱼仔,我们下期再见!

相关文章
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
3月前
|
设计模式 Java Spring
spring源码设计模式分析(七)-委派模式
spring源码设计模式分析(七)-委派模式
|
2月前
|
IDE Java 编译器
java反射机制原理
java反射机制原理
59 0
|
5月前
|
XML 监控 Java
Spring框架的核心原理与应用实践
Spring框架的核心原理与应用实践
|
6月前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
68 0
|
7月前
|
设计模式 安全 编译器
c++单例模式-6种单例层层迭代优化
6种单例模式,层层迭代优化
55 1
|
7月前
|
Java Spring 容器
Spring框架讲解笔记:spring框架学习的要点总结
Spring框架讲解笔记:spring框架学习的要点总结
|
XML Java Go
《Spring框架原理》
《Spring框架原理》
106 0
|
缓存 Java Spring
Spring源码学习:三级缓存的必要性
Spring源码学习:三级缓存的必要性
623 0
|
Java Spring 容器
【框架源码】Spring源码核心注解@Conditional原理及应用
【框架源码】Spring源码核心注解@Conditional原理及应用
【框架源码】Spring源码核心注解@Conditional原理及应用