目录
第二种: 同一类型只能创建一个对象,不同类型可以创建多个对象
单例模式的定义
一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式。
这里提到的唯一性是针对进程而非线程,我们编写的代码通过编译、链接后组织在一起,构成了一个操作系统可以执行的文件,也就是我们平时所说的 “可执行文件” (比如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<Long, IdGenerator> 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;
/** 进程间共享对象外部存储 */
@Resource
private SharedObjectStorage storage;
/** 分布式锁 */
@Resource
private 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<Long, BackupServer> 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<String, LogInstanceDemo> 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语言是类加载器内唯一。
今天的文章单例设计模式原理_设计模式有必要学吗「建议收藏」分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/86337.html