单例模式你真的会了吗?(下篇)

距离《单例模式上篇》写出去已经很久了,竟然久久没有更新下篇,这是庸俗人的普遍表现,只有开始,没有继续,也没有结束;干什么事都没有恒心,只有三天热度。要坚持啊!

《单例模式上篇》描述了单例的几个核心问题:

为什么要有单例?

正确单例应该怎么写?

典型的单例模式写法?

接下来,我们来进阶一下,拓展一下单例的高级用法,所谓开拓思路,不亦乐乎嘛!

  • 单例模式的唯一性如何理解?
  • 线程唯一的单例怎么实现?
  • 如何实现集群模式下的单例?
  • 怎么实现“多例”模式?

看着是不是有些头大,不要急,听我慢慢道来。

单例模式的唯一性怎么理解

我们先来看下单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。”

一个类只允许创建唯一一个对象,这个唯一性的范围是什么?我们都知道程序运行的最小单位是线程,平时编写的一个java程序,最终打包成一个jar包,通过jvm解释执行,对于计算机系统来说,一个程序的运行单位就是一个进程。进程内可包含多个线程,同一个进程内的线程共享进程的内存空间;不同进程间的内存互相隔离。所以这里说的单例模式的唯一性,指的是同一个进程内,只能创建一个单例对象。
在这里插入图片描述

能实现线程唯一的单例吗?

既然平时我们编写的程序都是进程唯一的单例,那么问题来了?可以编写出线程唯一的单例吗?什么叫线程唯一的单例呢?

比如说一个单例类SingleTon,在同一个进程内,有多个线程,假设分别是线程A、线程B、线程C…,线程A内只能创建一个SingleTon的对象,线程B内只能创建另一个SingleTon的对象…各线程间的单例对象各不相同。

你可能会问,这有什么意义呢?还记得ThreadLocal吗?这里就有些类似隔离线程间的对象。废话少说,放码过来吧!

public class SingleTon {

    private static final Map<Long, SingleTon> singleTonMap = new ConcurrentHashMap<>();

    private SingleTon() {}

    public static SingleTon getInstance() {
        long threadId = Thread.currentThread().getId();
        singleTonMap.putIfAbsent(threadId, new SingleTon());
        return singleTonMap.get(threadId);
    }
    
    public void method() {}
}

集群下唯一单例

首先什么叫集群下唯一的单例呢?

对比kafka、redis集群,我们知道一个集群可能包含多个机器,那肯定也是包含多个进程(多个线程)的了。连机器都跨越了,进程肯定都不一样了。这实现起来好像有点难度了。因为我们不仅要保证线程间唯一,还要保证单例对象在进程间唯一。

要保证集群内各进程访问单例的唯一性,首先需要保证同一时刻只有进程或线程可以获取到单例对象,这个可以通过redis或zookeeper来实现分布式锁。那如何保证单例的唯一性呢?有可能是多台机器执行同样的程序,那单纯的SingleTon单例已经无法保证唯一了,既然组成了一个集群,那么必然有集群的共享存储,如果我们将单例对象存储到集群的共享存储,具体来说,进程使用单例对象时,需要将外部存储区的实例对象读取到内存,反序列化成对象然后使用,使用完之后,需要释放对象,存储回外部存储区。

public class DistributedLock {

    public void lock() {

    }

    public void unlock() {

    }
}
public class FileSharedStorage implements SharedStorage {
    private String fileName;

    public FileSharedStorage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public SingleTon load(Class cls) {
        return null;
    }

    @Override
    public void save(SingleTon sharedStorage, Class cls) {

    }
}
public interface SharedStorage {

    SingleTon load(Class cls);

    void save(SingleTon sharedStorage, Class cls);
}
public class SingleTon {
    private static final String sharedFileName = "file_name";
    private static SingleTon instance;
    private static DistributedLock lock = new DistributedLock();
    private static SharedStorage storage = new FileSharedStorage(sharedFileName);

    private SingleTon() {}

    public static SingleTon getInstance() {
        if (instance == null) {
            lock.lock();
            instance = storage.load(SingleTon.class);
        }
        return instance;
    }
    
    public synchronized void freeInstance() {
        storage.save(this, SingleTon.class);
        instance = null;
        lock.unlock();
    }

    public void method() {}
}

多例模式怎么实现?

“单例模式”指一个类只能创建一个对象,那么类比多例模式,就是指一个类可以创建多个对象,但是这时候创建的对象个数,一般是有限制的。

类似于,我们要实现一个随机获取提供服务的后台服务器程序,每次返回的都是固定服务器对象列表中的某一个,达到将负载均衡的目的。

public class BeServer {
    private int serverSequence;
    private String serverAddr;
    
    private static final int MAX_SERVER_COUNT = 5;
    private static final Map<Integer, BeServer> serverMap = new HashMap<>();
    
    static {
        serverMap.put(1, new BeServer(1, "192.168.1.111:10001"));
        serverMap.put(2, new BeServer(2, "192.168.1.112:10001"));
        serverMap.put(3, new BeServer(3, "192.168.1.113:10001"));
    }
    
    private BeServer(int serverSequence, String serverAddr) {
        this.serverSequence = serverSequence;
        this.serverAddr = serverAddr;
    }
    
    public static BeServer getRandomBeServer() {
        Random random = new Random();
        int num = random.nextInt(MAX_SERVER_COUNT) + 1;
        return serverMap.get(num);
    }
}

这里,我们可以扩展一下,平时使用的logger是怎么实现的?针对同样的logger name,返回的是同一个logger对象实例,如果是不同的logger name,获取到的logger对象则是不同的。这其实也类似一种多实例模式。

public class Logger {
    private static final Map<String, Logger> map = new ConcurrentHashMap<>();
    
    private Logger() {
        
    }
    
    public static Logger getInstance(String loggerName) {
        map.putIfAbsent(loggerName, new Logger());
        return map.get(loggerName);
    }
    
    public void log() {
        
    }
}

这种多例模式有点类似工厂模式,区别在于工厂模式创建的对象是不同子类的对象,多例模式创建的对象时同一个类的对象。

总结

单例模式看起来简单,但是想写出一个无bug的单例模式也不易。另外从单例模式,还可以扩展出线程单例,集群单例,多例模式等。

另外单例模式其实并不推荐使用,因为单例对OOP的特性支持并不友好,隐藏类之间的依赖关系,扩展性差,可测试性也不好,也不支持有参数的构造函数(一般的单例构造函数都是私有的)。

每一种设计模式,并不是是用的越多越好,不要为了使用设计模式而过渡滥用设计模式,不要为了设计而设计,要明白每一种设计模式是为了解决什么问题,为什么用,如何用,怎么用对用好。

通过举一反三,也可以应用到在学习和工作中,不是为了工作而工作,要抱着解决问题,提升自己的态度去工作,每一件事都不好做,舍我其谁!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值