单例设计模式原理_设计模式有必要学吗「建议收藏」

单例设计模式原理_设计模式有必要学吗「建议收藏」一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式

单例设计模式原理_设计模式有必要学吗「建议收藏」"

目录

单例模式的定义

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

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

如何实现一个多例模式?

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

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

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<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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注