设计模式之美 - 如何理解单例模式中的唯一性?
创始人
2024-01-31 00:36:07
0

目录

单例模式的定义

如何实现线程唯一的单例?

如何实现集群环境下的单例?

如何实现一个多例模式?

第一种:一个类可以创建有限的多个对象

第二种: 同一类型只能创建一个对象,不同类型可以创建多个对象

Java 中的单例模式的唯一性


单例模式的定义

一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式。

这里提到的唯一性是针对进程而非线程,我们编写的代码通过编译、链接后组织在一起,构成了一个操作系统可以执行的文件,也就是我们平时所说的 “可执行文件” (比如Windows下的exe文件)。可执行文件实际上就是代码被翻译成操作系统可理解的一组指令。

当使用命令行或者双击运行这个可执行文件时,操作系统会启动一个进程,将这个执行文件从磁盘加载到自己的进程地址空间(可以理解成操作系统为进程分配的内存存储区,用来存储代码和数据)。接着,进程就一条一条地执行可执行文件中包含的代码。比如,当进程读到代码中的 User user = new User(); 这条语句的时候,它就在自己的地址空间中创建一个user临时变量和一个User对象。

进程之间是不共享地址空间的,如果我们在一个进程中创建另外一个进程,操作系统会给新进程分配新的地址空间,并且将老进程地址空间的所有内容,重新拷贝一份到新进程的地址空间中,这些内容包括代码、数据等。

所以,单例类在老进程中存在且只能存在一个对象,在新进程中也会存在且只能存在一个对象。而且,这两个对象并不是同一个对象,这也就是说,单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。


如何实现线程唯一的单例?

先理解下面这段话

线程唯一 != 进程唯一

进程唯一 => 线程唯一 

可以使用 ConcurrentHashMap 作为底层数据结构存储对象,其中 key 是线程Id,value是对象。

public class IdGenerator {/** id生成器 */private AtomicLong id = new AtomicLong(1);/** 存储每个线程id生成器的map容器 */private static final ConcurrentHashMap instances= new ConcurrentHashMap<>();/** 私有构造器,不允许外部 new对象 */private IdGenerator() {}/** 获取当前线程的id生成器对象 */public static IdGenerator getInstance() {long threadId = Thread.currentThread().getId();instances.putIfAbsent(threadId, new IdGenerator());return instances.get(threadId);}/** 获取id */public long getId() {return id.getAndIncrement();}}

这样就可以做到不同的线程对应不同的对象,同一个线程只能对应一个对象。实际上,Java语言本身提供了 ThreadLocal 工具类,可以更加轻松地实现线程唯一单例。


如何实现集群环境下的单例?

经典的单例模式是进程内唯一的。所谓集群环境下的单例也就是进程间唯一,这里可以采用将单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。

为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要进行对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。 

伪代码如下:

public class DistributedIdGenerator {/** id生成 */private AtomicLong id = new AtomicLong(1);private static DistributedIdGenerator instance;/** 进程间共享对象外部存储 */@Resourceprivate SharedObjectStorage storage;/** 分布式锁 */@Resourceprivate DistributedLock lock;private DistributedIdGenerator() {}/** 获取进程间共享的DistributedIdGenerator对象 */public synchronized static DistributedIdGenerator getInstance() {if (instance == null) {lock.lock();instance = storage.load(DistributedIdGenerator.class);}return instance;}/** 当前进程使用完后需释放当前实例 */public synchronized void freeInstance() {storage.save(this, DistributedIdGenerator.class);instance = null;lock.unlock();}public long getId() {return id.getAndIncrement();}}

如何实现一个多例模式?

这里的多例模式可以理解成两种:

  • 一个类可以创建有限的多个对象
  • 同一类型只能创建一个对象,不同类型可以创建多个对象

第一种:一个类可以创建有限的多个对象

可以采取随机数或者是根据特征id进行分片。

public class BackupServer {private long serverNo;private String serverAddress;public BackupServer(long serverNo, String serverAddress) {this.serverNo = serverNo;this.serverAddress = serverAddress;}/** 服务器数量 */private static final int SERVER_COUNT = 3;/** 存储服务器实例 */private static final Map SERVER_MAP = new HashMap<>();/** 初始化服务器实例 */static {for (long i = 1; i <= SERVER_COUNT; i++) {SERVER_MAP.put(i, new BackupServer(i, "192.168.22." + i +":8080"));}}public BackupServer getInstance(long serverNo) {return SERVER_MAP.get(serverNo);}/** 随机获取服务器 */public BackupServer getRandomInstance() {Random random = new Random();long no = random.nextInt(SERVER_COUNT) + 1;return SERVER_MAP.get(no);}}

第二种: 同一类型只能创建一个对象,不同类型可以创建多个对象

public class LogInstanceDemo {private static final ConcurrentHashMap instances= new ConcurrentHashMap<>();private LogInstanceDemo() {}public static LogInstanceDemo getInstance(String loggerName) {instances.putIfAbsent(loggerName, new LogInstanceDemo());return instances.get(loggerName);}}// log1 == log2, log1 != log3 && log2 != log3
LogInstanceDemo log1 = LogInstanceDemo.getInstance("User.class");
LogInstanceDemo log2 = LogInstanceDemo.getInstance("User.class");
LogInstanceDemo log3 = LogInstanceDemo.getInstance("Person.class");

这种多例模式类似工厂模式,但它跟工厂模式的不同之处在于,多例模式创建的对象都是同一个类的对象,而工厂模式创建的是不同子类的对象。


Java 中的单例模式的唯一性

上文中讲述单例唯一性的作用范围是进程,实际上,对于Java语言来说,单例类对象的唯一性的作用范围并非进程,而是类加载器(ClassLoader),这是因为不同类加载器之间命名空间不一样,不同的类加载器加载出来的类实例是不一样的,所以Java语言是类加载器内唯一

相关内容

热门资讯

【中国那些事儿】外媒:便利化政...   中国日报网12月18日电 国际旅游媒体《旅行与旅游世界》(Travel and Tour wor...
大看台丨撬动万亿,冰雪经济何以...   新华社北京12月18日电(记者高萌、李春宇、杨帆)近日召开的中央经济工作会议将“坚持内需主导,建...
【世界说】日本防卫费“野蛮生长...   中国日报网12月19日电 近期,日本高市早苗政府一系列激进防卫政策调整引发国际社会广泛担忧与批评...
脱贫村的这五年|从“门票经济”...   一说起西藏自治区,人们往往会想到雪山巍峨、朔风凛冽,很难将其与娇柔的桃花联系在一起。但是在林芝市...
新华时评|海南自贸港封关开启中...   新华社海口12月18日电 题:海南自贸港封关开启中国对外开放新篇章  新华社记者王晖余、吴茂辉 ...
我国加速布局未来产业抢占发展先...   “十五五”规划建议提出,要前瞻布局量子科技、生物制造、氢能和核聚变能等六大未来产业。今年以来,国...
突发!台北地铁发生砍人事件已致...   据界面新闻报道,今日(12月19日)17时30分左右,台北捷运地铁有一名头戴“专业级防毒面具”的...
春华章•青春问答 | 87岁影...   从话剧舞台到影视镜头,她对每个角色倾尽心力。熟读全剧、揣摩细节、为角色学技能,坚信 “每个角色都...
世界首个原生电力专业大模型正式...   为新型电力系统装上“智慧大脑” 首个原生电力专业大模型正式发布  12月19日,世界首个原生电力...
油价即将调整,12月油价或“二...   2025年12月18日,国内成品油计价周期进入第8个统计日,10个工作日的调价周期已完成80%,...