今天,我们主要讨论的问题有,
如何理解单例模式的唯一性
如何实现线程唯一的单例
如何实现集群环境下的单例式
如何实现一个多例
1.如何理解单例模式下的唯一性
一个类只能创建一个对象,那这就是一个单例类,这个设计模式就是单例式设计模式
那么一个类只能创建一个对象,这个唯一对象的范围是什么呢?是一个线程还是一个进程呢?自然是一个进程
对于进程来说,就可以认为是一个jar的整体执行范围
也是因为进程之间是不共享内存地址的,所以不会出现进程间的全局唯一的问题,所以一般单例式的作用范围是进程内唯一的,在线程也是唯一的,因为一个进程内有多个线程
2.如果实现一个线程唯一的单例呢?
因为一个进程内有多个线程,而要做到线程唯一就只要保证在这个进程内可以重复就可以了,例如使用Java提供的ThreadLocal,简单点的实现就是利用HashMap来存储对象,key是线程ID,value是对象,那么这样就可以保证不同线程对应的不同的对象,实现的代码基本如下
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(); } } |
3.如何实现集群环境下的单例呢?
集群唯一可以说,就是多个进程的集合,多个进程的唯一,不同进程间共享一个对象,不能创建一个类的多个对象,
有一种思路就是,有一个外部文件,可以让不同的进程去读取,具体点,就是我们将一个单例化的对象序列化存储到一个外部存储区中,不同进程去读取这个外部文件,将其反序列号到内存中使用,并且给这个文件加上锁,使用完成后,将对象从内存中删除,并且释放对文件的锁,实现可以如下
public class IdGenerator {
private AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略,比如文件地址*/); private static DistributedLock lock = new DistributedLock(); private IdGenerator() {} public synchronized static IdGenerator getInstance() if (instance == null) { lock.lock(); instance = storage.load(IdGenerator.class); } return instance; } public synchroinzed void freeInstance() { storage.save(this, IdGeneator.class); instance = null; //释放对象 lock.unlock(); } public long getId() { return id.incrementAndGet(); } } // IdGenerator使用举例 IdGenerator idGeneator = IdGenerator.getInstance(); long id = idGenerator.getId(); IdGenerator.freeInstance(); |
4.如何实现一个多例模式?
单例式只能创建一个对象,多例是指,一个类可以创建多个对象,但是个数是有限的,类似对象池,可以简单的实现如下
public class BackendServer {
private long serverNo; private String serverAddress; private static final int SERVER_COUNT = 3; private static final Map<Long, BackendServer> serverInstances = new HashMap<>(); static { serverInstances.put(1L, new BackendServer(1L, “192.134.22.138:8080”)); serverInstances.put(2L, new BackendServer(2L, “192.134.22.139:8080”)); serverInstances.put(3L, new BackendServer(3L, “192.134.22.140:8080”)); } private BackendServer(long serverNo, String serverAddress) { this.serverNo = serverNo; this.serverAddress = serverAddress; } public BackendServer getInstance(long serverNo) { return serverInstances.get(serverNo); } public BackendServer getRandomInstance() { Random r = new Random(); int no = r.nextInt(SERVER_COUNT)+1; return serverInstances.get(no); } } |
或者说,一个类型的对象只能创建一个,不同类型的对象,可以创建多个,这个类型怎么理解
这一认为是不同的类,传入不同的类来获取到不同的实例独享
public class Logger {
private static final ConcurrentHashMap<String, Logger> instances = new ConcurrentHashMap<>(); private Logger() {} public static Logger getInstance(String loggerName) { instances.putIfAbsent(loggerName, new Logger()); return instances.get(loggerName); } public void log() { //… } } //l1==l2, l1!=l3 Logger l1 = Logger.getInstance(“User.class”); Logger l2 = Logger.getInstance(“User.class”); Logger l3 = Logger.getInstance(“Order.class”); |
这种多例模式的理解类似工厂模式,但是多例模式创建的都是同一个类的对象,工厂模式创建的是不同对象子类的对象
本章的重点很简单
1.如何理解单例模式的唯一性
单例模式的唯一性是一个进程唯一的,进程唯一表示的是进程内唯一,也表示线程唯一
2.如何实现你线程唯一的单例
可以利用HashMap来实现,将不同线程作为key,对象作为value,做到不同线程对应不同的对象
3.如何实现集群环境下的单例,将单例对象序列化并存储到外部共享区域,然后使用的时候,从外部区域去读到内存,反序列化为对象,并给外部共享加上锁,然后使用完成,显式的将对象从中删除,并且释放掉锁
4,如何实现一个多例模式
可以认为是一个类可以创建多个对象,但是数量有限
也可以认为利用Map来创建对象类型和对象之间的关系,控制个数