没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
43 | 单例模式(下):如何设计实现一个集群环境下的分布式单例模
式?
2020-02-10 王争
设计模式之美
进入课程
讲述:冯永吉
时长 10:21 大小 8.31M
上两节课中,我们针对单例模式,讲解了单例的应用场景、几种常见的代码实现和存在的问
题,并粗略给出了替换单例模式的方法,比如工厂模式、IOC 容器。今天,我们再进一步
扩展延伸一下,一块讨论一下下面这几个问题:
如何理解单例模式中的唯一性?
如何实现线程唯一的单例?
如何实现集群环境下的单例?
如何实现一个多例模式?
下载APP
今天的内容稍微有点“烧脑”,希望你在看的过程中多思考一下。话不多说,让我们正式开
始今天的学习吧!
如何理解单例模式中的唯一性?
首先,我们重新看一下单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这
个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。”
定义中提到,“一个类只允许创建唯一一个对象”。那对象的唯一性的作用范围是什么呢?
是指线程内只允许创建一个对象,还是指进程内只允许创建一个对象?答案是后者,也就是
说,单例模式创建的对象是进程唯一的。这里有点不好理解,我来详细地解释一下。
我们编写的代码,通过编译、链接,组织在一起,就构成了一个操作系统可以执行的文件,
也就是我们平时所说的“可执行文件”(比如 Windows 下的 exe 文件)。可执行文件实
际上就是代码被翻译成操作系统可理解的一组指令,你完全可以简单地理解为就是代码本
身。
当我们使用命令行或者双击运行这个可执行文件的时候,操作系统会启动一个进程,将这个
执行文件从磁盘加载到自己的进程地址空间(可以理解操作系统为进程分配的内存存储区,
用来存储代码和数据)。接着,进程就一条一条地执行可执行文件中包含的代码。比如,当
进程读到代码中的 User user = new User(); 这条语句的时候,它就在自己的地址空间中创
建一个 user 临时变量和一个 User 对象。
进程之间是不共享地址空间的,如果我们在一个进程中创建另外一个进程(比如,代码中有
一个 fork() 语句,进程执行到这条语句的时候会创建一个新的进程),操作系统会给新进
程分配新的地址空间,并且将老进程地址空间的所有内容,重新拷贝一份到新进程的地址空
间中,这些内容包括代码、数据(比如 user 临时变量、User 对象)。
所以,单例类在老进程中存在且只能存在一个对象,在新进程中也会存在且只能存在一个对
象。而且,这两个对象并不是同一个对象,这也就说,单例类中对象的唯一性的作用范围是
进程内的,在进程间是不唯一的。
如何实现线程唯一的单例?
刚刚我们讲了单例类对象是进程唯一的,一个进程只能有一个单例对象。那如何实现一个线
程唯一的单例呢?
我们先来看一下,什么是线程唯一的单例,以及“线程唯一”和“进程唯一”的区别。
“进程唯一”指的是进程内唯一,进程间不唯一。类比一下,“线程唯一”指的是线程内唯
一,线程间可以不唯一。实际上,“进程唯一”还代表了线程内、线程间都唯一,这也
是“进程唯一”和“线程唯一”的区别之处。这段话听起来有点像绕口令,我举个例子来解
释一下。
假设 IdGenerator 是一个线程唯一的单例类。在线程 A 内,我们可以创建一个单例对象
a。因为线程内唯一,在线程 A 内就不能再创建新的 IdGenerator 对象了,而线程间可以
不唯一,所以,在另外一个线程 B 内,我们还可以重新创建一个新的单例对象 b。
尽管概念理解起来比较复杂,但线程唯一单例的代码实现很简单,如下所示。在代码中,我
们通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以
做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。实际上,Java 语言本
身提供了 ThreadLocal 工具类,可以更加轻松地实现线程唯一单例。不过,ThreadLocal
底层实现原理也是基于下面代码中所示的 HashMap。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final ConcurrentHashMap<Long, IdGenerator> instances
= new ConcurrentHashMap<>();
private IdGenerator() {}
public static IdGenerator getInstance() {
Long currentThreadId = Thread.currentThread().getId();
instances.putIfAbsent(currentThreadId, new IdGenerator());
return instances.get(currentThreadId);
}
public long getId() {
return id.incrementAndGet();
}
}
剩余10页未读,继续阅读
资源评论
雨后的印
- 粉丝: 14
- 资源: 288
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功