单例模式,你知道的和你所不一定知道的一切

本文详细解析单例模式的常见实现方式及优化策略,包括懒汉式、恶汉式和双重检查锁定(DCL)方法,强调线程安全与延迟加载的重要性,并探讨了枚举单例作为替代方案的优劣。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

写这篇文章的目的呢,其实很简单,就是让更多的人明白,更加透彻的理解单例模式,或许大家以为单例模式嘛,大家都会些,简简单单,就那么两种,懒汉式或者说恶汉式,在多了解一点的,懒汉式和恶汉式的结合版,呵呵,貌似好像没有什么卵用,接下来,请看正解。


单例模式-情景分析

常见的单例模式代码(1/3)

这里写图片描述

这个呢,其实大家都知道,这是最为常见的单例模式,也就是单例中的懒汉式,就是在你需要使用对象的时候,在去new 一个出来,那么这样下有什么特点:

特点:延迟加载,需要的时候才使用。
缺点:单线程使用是没问题的,但是我们的web开发都是需要并发的,所以这个代码适用场景较小


常见的单例模式代码(2/3)

这里写图片描述

这种,也就是单例模式中的恶汉式,就是在一开始,我就给new 出对象,就像一个饿了的人,太饿了,先拿着东西吃,那么这个有什么特点了

优点:线程安全
缺点:不是延迟加载的,当构造这样的单例非常重量级,但是这个单例可能很久也用不到一次的时候比较受伤。(这样的场景比较少)


常见的单例模式代码(3/3)

这里写图片描述

那么这种情况下的单例模式,你们是不是就觉得少见了,对于大部分人来说,知道前面两种是必然,知道以下的那么就可能没多少人了吧,我来解释下这个代码

使用内部类方式(static holder),既支持了延迟加载,也是线程安全的,较完美的解决方案。

这种呐,一般情况下,绝大多数人都是这样去写的,这样的解决方案的确算是接近完美了。

是不是大家以为,单例模式到这里基本是就接近于结束了,over了,那你就大错特错了,加下来带你看看其他的东西


思考点

第一种单例模式

回过头看来第一种单例模式的代码,要让他支持多线程环境,有其他改动方法么?
这里写图片描述

上面我们知道这种代码的特点了,那么针对这种情况,我们有上面好的方法解决了呐

常见的解决方法
这里写图片描述

我们在这个地方增加synchronized关键字,这样是线程安全的,但是这样做有什么缺点呢?

因为在方法上使用了synchronized关键词,每次获取单例都要同步,而同步的成本较高,所有讲同步的书籍都告诉我们,要缩小synchronized使用的范围

那么我们如何解决?

lowB一些的会这样改
这里写图片描述

这样又会出现什么问题?

前述代码存在多线程bug,有可能会创建多个对象

那要怎么改进呢?

牛逼一些的会这样写:
这里写图片描述

这样写的原因是什么:

1.这样的代码有个术语,叫:双重检查锁定(DCL),前述代码貌似解决了多线程bug,把synchronized限定在最小的范围,拥有不错的性能,而且是懒加载的,好像无可挑剔,实际上我也看到过不少人是这么写的。

2.表面看起来确实是这样,然而不幸的是:这个代码仍然有问题

问题在那?

一、使用DCL能确保只生成单例,他的问题在于可能让其他线程看到这个单例的未构建完全的样子。
二、DCL背后的理论是完美的,他失败的原因不是jvm的bug,而是java的内存模型导致的,关键原因在于:new操作不是原子的,而java内存模型允许指令重排。

一:大家认为的new对象的过程

比如instance = new Instance();这样的代码,编译器可以翻译为三步:
1).mem= allocate();//分配内存
2).callConstructor(mem);//调用构造函数
3). Instance=mem;//把内存指针赋值给instance。

如果是这样的方式,DCL可以正常工作。

二:指令重排

但是编译器也可能按这样的顺序执行:
1).mem= allocate();//分配内存
2).Instance=mem;//把内存指针赋值给instance,注意此时instance已经是非null了。
3).callConstructor(instance);//调用构造函数

这种情况下,DCL会失效。

三:了解了指令重排,有人会这样改

这里写图片描述

Will it work?

三:甚至这样改(事情将变的复杂)

这里写图片描述

fuck了,估计到这里,我们就会想,真是日了狗了,有必要嘛,一个单例搞这么复杂,搞飞机啊,哎。。。。。。

前面两种代码同样存在指令重排的问题。

DCL的另一个问题

上述DCL代码还存在另一个问题,并发编程的经验告诉我们:必须对同一个变量的所有读写同步,才能保证不读取到陈旧的数据,仅仅同步读或写是不够的。就是说一个线程调用单例方法生成单例后,另一个线程刚进入方法时仍然可能看到是null的,直到进入同步块才能看到那个对象,存在浪费。

顺着这个思路,难道就没有办法了吗?
在JDK5之前,答案:有更曲折的解决方案,使用ThreadLocal代替外层的null检查,当然效率较低。(感兴趣的可以自行google,这里不讨论)

庆幸的是,java一直在改进其内存模型
在JDK5及其之后,已经有办法稍微改下代码,使DCL可用。

how?

答案是在类变量上加入volatile关键词

but why ?

在JDK5之前,使用volatile往往不能得到正确的结果,JDK5对volatile的语义做了重大改变。
其中有两个比较关键:
1.禁止指令重排序
2.内存可见性
规则1可以确保外层null检查要么看到的是null,要么看到的是一个构造完整的对象,规则2可以解决之前浪费的问题。

综上所述,用DCL来实现单例并不是一种很好的方法,因为过于复杂,容易出错。更好的方式是使用之前说到的static holder方式,或者枚举单例。

枚举单例

这里写图片描述

业界非常推崇用此法实现单例,因为它的简单,以及如下好处:
好处:
1.线程安全;
2.不会因为序列化而产生新的实例(因为它自己实现了readResolve方法);
3.防止反射攻击。(因为enum实际上是abstract的)

http://segmentfault.com/q/1010000000646806

相关资料

关于volatile关键字:
http://www.cnblogs.com/dolphin0520/p/3920373.html

关于DCL:
http://www.iteye.com/topic/260515
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

<think>好的,用户现在问的是:“我的意思是元类的单例模式输出就是类是吗”。首先,我需要回顾之前的对话历史,确保准确理解用户的问题。用户之前询问的是通过元类实现单例是否意味着一个元类对应一个类,因为Python中一切皆为对象,符合单例对象要求。我之前已经详细解释了元类实现单例的核心原理,强调单例模式控制的是类的实例,而不是类本身。现在用户可能对之前的解释有些混淆,需要进一步澄清。 用户现在的提问似乎在确认是否元类的单例模式输出的是类本身。这可能存在误解,需要明确区分元类、类实例之间的关系。首先,元类是类的类,控制类的创建行为。当使用元类实现单例时,单例指的是类的实例只有一个,而不是类本身是单例。用户可能将元类的作用与单例的目标混淆了,认为元类创建的是单例类,但实际上元类的作用是确保某个类的实例是唯一的。 接下来,我需要考虑用户可能的背景。用户可能对Python的元类单例模式有一定的了解,但可能对两者的结合使用存在困惑。他们可能误认为元类本身的实例(即类对象)是单例,但实际并非如此。需要明确元类的作用是控制类的实例化过程,而不是控制类本身的创建次数。 然后,我需要分析用户的问题中的关键词:“元类的单例模式输出就是类”。这里的“输出”可能指的是元类生成的结果,即类本身是否是单例。需要澄清,元类本身可以创建多个类,每个类可以独立地成为单例,或者每个类的实例是单例。例如,使用同一个元类创建的不同类,每个类都有自己的单例实例,而不是元类限制只能创建一个类。 此外,用户可能混淆了类作为对象的概念。在Python中,类本身也是对象,由元类创建。但单例模式通常指的是类的实例是单例,而不是类对象本身。例如,模块导入的方式实现单例,是因为模块在导入时只执行一次,生成一个类对象,但这也是一种单例类的方式。不过用户的问题可能涉及到元类是否让类对象成为单例,即一个元类只能创建一个类,但根据Python的机制,元类可以创建多个类,每个类都是元类的实例,但这些类本身并不是单例,除非特别设计。 接下来,需要明确回答用户的问题:元类的单例模式输出是否是类本身。答案是否定的。元类实现的单例模式是确保某个类的实例是唯一的,而类本身仍然可以被多次引用或作为父类,但每次实例化该类时,只会返回同一个实例。而元类本身可以创建多个不同的类,每个类都有自己的单例实例。 可能需要用代码示例来进一步说明。例如,定义一个元类SingletonMeta,然后用它创建两个不同的类ClassAClassB。这两个类都是SingletonMeta的实例,但它们是不同的类对象。每个类的实例化过程由元类的__call__方法控制,确保每个类自己的实例是唯一的。因此,元类的作用是控制各个类的实例化,而不是限制元类只能创建一个类。 同时,需要指出用户可能的误区:认为元类作为单例意味着每个元类只能有一个类实例。实际上,元类可以生成多个类,每个类在实例化时都是单例,但类本身是独立的。例如,元类类似于工厂,生产不同的类,每个类有自己的单例实例管理。 最后,总结时强调元类实现单例的核心在于控制类的实例化过程,而不是类本身的创建,因此元类的单例模式输出不是类本身,而是类的实例是单例。确保用户理解类实例的区别,以及元类在其中的作用。</think>--- ### **元类实现单例模式的输出本质** #### **核心结论** **元类实现单例模式的输出是类的实例(对象),而不是类本身**。 元类的作用是控制类的实例化逻辑(即 `__call__` 方法),确保每次通过 `ClassName()` 创建对象时返回唯一实例。而类本身(作为元类的实例)仍可以正常定义复用,元类并不会限制类的数量。 --- ### **1. 关键概念澄清** #### **元类(Metaclass)** - 元类是“类的类”,控制**类对象**的创建过程(如 `class MyClass(...)` 的定义行为)。 - 元类的实例是**类对象**(如 `MyClass`),而非类的实例(如 `obj = MyClass()`)。 #### **单例模式(Singleton)** - 单例模式的目标是确保**类的实例对象唯一**(如 `obj1 = MyClass()` `obj2 = MyClass()` 始终指向同一内存地址)。 - 与类本身的创建无关(即使类本身是元类的实例,也可以定义多个不同的类)。 --- ### **2. 元类实现单例的代码验证** #### **代码示例** ```python class SingletonMeta(type): _instances = {} # 存储所有类的单例实例 def __call__(cls, *args, **kwargs): if cls not in cls._instances: # 首次实例化时创建对象 cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] class ClassA(metaclass=SingletonMeta): pass class ClassB(metaclass=SingletonMeta): pass # 验证单例实例 a1 = ClassA() a2 = ClassA() print(a1 is a2) # 输出: True(ClassA的实例唯一) b1 = ClassB() b2 = ClassB() print(b1 is b2) # 输出: True(ClassB的实例唯一) # 验证类本身是否唯一 print(ClassA is ClassB) # 输出: False(两个不同的类对象) ``` #### **输出结果分析** - `ClassA` `ClassB` 是**两个不同的类对象**(均通过 `SingletonMeta` 元类创建)。 - 每个类的实例化结果独立实现单例:`a1` `a2` 是同一对象,`b1` `b2` 是另一对象。 - 元类 `SingletonMeta` 控制的是类的实例化过程,而非类本身的唯一性。 --- ### **3. 常见误解与正解** #### **误解** “元类实现单例时,元类本身是单例的,因此它生成的类也是单例的。” **正解**: - 元类的单例逻辑作用于其生成的**类的实例**,而非类本身。 - 即使多个类共享同一个元类(如 `ClassA` `ClassB`),每个类仍可独立管理自己的实例单例。 #### **图解关系** ``` 元类(SingletonMeta) │ ├── 类对象 ClassA(单例逻辑控制 ClassA 的实例) │ └── 实例对象 a1/a2(唯一) │ └── 类对象 ClassB(单例逻辑控制 ClassB 的实例) └── 实例对象 b1/b2(唯一) ``` --- ### **4. 元类与类的生命周期** #### **类对象的创建** - 通过元类 `SingletonMeta` 定义的类(如 `ClassA`、`ClassB`)在代码加载时创建,且**每个类都是独立的元类实例**。 - 元类不会限制类的数量,即使使用同一元类,也可以定义任意多个类。 #### **实例对象的创建** - 当调用 `ClassA()` 或 `ClassB()` 时,元类的 `__call__` 方法被触发,确保每个类仅生成一个实例。 --- ### **5. 总结:元类单例模式的输出本质** | 对象类型 | 是否单例? | 说明 | |----------------|----------------------------|------------------------------------------| | **元类** | 否 | 元类本身可被多个类共用 | | **类对象** | 否 | 每个类(如 `ClassA`、`ClassB`)独立存在 | | **实例对象** | 是 | 每个类的实例唯一(由元类 `__call__` 控制) | --- 通过元类实现单例的核心是**控制实例化过程**,而非限制类的数量。这种设计保留了类的完整定义能力,同时精准实现了实例对象的唯一性需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值