⚠️ 区分: 【设计模式:单例模式】自己写代码控制类只能产生一个对象 【Spring scope singleton】Spring容器帮你控制bean只创建一份,不是同一个东西
一、单例模式(Singleton)
核心:保证一个类在整个程序中,有且仅有一个实例对象,提供全局访问点。 使用场景:工具类、连接池、线程池、配置文件对象、日志对象,不需要多个实例。
分类
1. 饿汉式(静态常量,简单、线程安全,考试最常写)
package com.qcby;
//单例模式-饿汉式 传统实现代码
public class Singleton2 {
//私有构造方法
private Singleton2(){ };
//类加载时就实例化对象 加static
private static Singleton2 obj=new Singleton2();
public static Singleton2 getInstance(){
return obj;
}
}
通过static关键字,在类加载时创建对象。
特点:
- 类加载的时候直接实例化对象 能够在第一时间实例化对象,供其他方法使用,是拿空间换时间的方案
- 优点:写法简单、天然线程安全,没有并发问题
- 缺点:类加载就创建,不用它也会占用内存,没有懒加载
优化实现:
上述传统方式中,由于类加载时就实例化对象,因此当我们调用这个类的其它静态方法时,也会触发类加载,从而实例化单例独享,会导致空间的暂时浪费。
由于静态内部类中的对象不会默认加载,直到调用了获取该内部类属性的方法。因此可用静态内部类封装静态实例变量。
优化实现代码:
class Singleton{
// 私有构造函数
private Singleton() {}
// 静态内部类
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
种种实现方式中,最推荐这种优化后的饿汉式实现,利用static保证线程安全,利用静态内部类节约了空间,实现lazy-loading(懒加载),而且代码非常简短,可谓一箭三雕。
2. 懒汉式(双重检查锁 DCL,开发常用,懒加载+线程安全)
传统实现代码:
package com.qcby;
//单例模式-懒汉式 传统实现代码
class Singleton{
//声明一个Singleton对象为obj
private static Singleton obj;
//设置私有构造方法
private Singleton(){}
//加锁保证obj只能实例化一次,时间换空间
public static synchronized Singleton getInstance(){
if(obj == null){
obj=new Singleton();
}
return obj;
}
}
传统实现方式中,每次获取实例都要被synchronized关键字串行化,即使已经生成了对象实例。
而我们加锁的目的是为了防止生成多个实例,因此其实只需要对生成实例的代码加锁,生成实例后,可支持并发访问,提高性能。
优化实现代码:
package com.qcby;
//单例模式-懒汉式 优化实现代码
public class Singleton1 {
private volatile static Singleton1 obj;
//获取实例对象的方法
public static Singleton1 getInstance(){
//如果已有实例则直接返回,不走锁
if(obj==null){
//仅在没生成实例时加锁控制,使并发访问串行化
synchronized(Singleton1.class){
//多个线程会按序执行到此处,需要再次检查是否已经实例化
if(obj==null){
obj = new Singleton1();
}
}
}
return obj;
}
}
优化实现 由于检查了两次对象是否已经实例化,因此该方法又被称为“双检法”,能够保证线程安全的同时提升对象实例化后的调用性能。
特点:
- 懒加载:调用getInstance()才创建对象
- volatile关键字:禁止指令重排序(考点!)
- 优点:用时才创建,线程安全,效率高
- 缺点:写法复杂
❌ 不推荐:不加锁的懒汉式,多线程下会创建多个对象,线程不安全
单例模式要点
- 构造方法私有化 private → 外面不能 new
- 静态变量保存唯一实例
- 提供静态方法 getInstance() 获取这个唯一对象
- 破坏单例的方式:反射、序列化反序列化
二、多例模式(Multiton)
核心:限制一个类,只能产生固定数量的多个实例,不是无限new,也不是只能1个。
区别:普通类随便new无数个;多例模式控制实例总数固定。 场景:比如数据库连接固定3个连接、线程池固定几个worker。
示例(固定2个实例)
package com.qcby;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class Multiton {
// map用来存我们提前建好的实例,key标记每个对象
private static Map<String, Multiton> map = new HashMap<>();
// 规定一共只能创建 2 个实例,可以修改这个数字
private static final int COUNT = 2;
// 静态代码块:类加载的时候执行,提前把2个对象一次性创建好放进map
static {
for (int i = 1; i <= COUNT; i++) {
map.put("key"+i, new Multiton());
}
}
// 私有构造方法!重点
// 外面不能 new Multiton(),防止别人随便造新对象,破坏“数量固定”的规则
private Multiton(){}
// 对外获取实例的方法:随机从已经建好的2个里面返回一个
public static Multiton getInstance(){
// 拿到map里所有key,转成数组:["key1","key2"]
String[] keys = map.keySet().toArray(new String[0]);
// 随机生成 0 或者 1
int index = new Random().nextInt(COUNT);
// 返回对应key的对象
return map.get(keys[index]);
}
}
特点:
- 构造方法私有,外部不能随意new
- 在静态代码块提前创建好固定个数对象
- 外部只能从已经创建好的里面拿,不能新增
- 同一个key拿到同一个对象;不同key是不同对象
补充:我们平时随便new User()这种,叫普通无限制实例,不属于多例模式! ✅ 多例 = 实例数量可控、固定;不是“可以new好几个”就叫多例
三、对比表格
|
项目 |
单例模式(Singleton) |
多例模式(Multiton) |
|
实例数量 |
只能 1 个 |
固定有限多个(比如 2、3 个) |
|
构造器 |
private 私有 |
private 私有 |
|
对象创建时机 |
饿汉:类加载;懒汉:调用方法时 |
一般静态代码块提前创建全部 |
|
获取方式 |
getInstance (),永远返回同一个 |
从预先建好的一批里面获取 |
|
目的 |
全局唯一对象,节省资源 |
控制实例上限,避免无限创建 |
四、高频易混区分
- 设计模式 单例模式:自己写Java代码,控制这个类只能造1个对象
- Spring bean scope="singleton":Spring容器里面这个bean只一份,底层不是单例模式,只是容器行为
- Spring bean scope="prototype":每次getBean新建对象,不是设计模式的多例! prototype是无限新建,多例模式是数量固定
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2302_81838100/article/details/169463796



