1. 如何理解单例模式的唯一性
我们常理解的单例模式是一个类只允许创建唯一一个对象。这个对象唯一性的作用范围是创建该对象所属的进程。
而进程之间是不共享地址空间的。换句话说,如果在一个进程中创建另一个进程,操作系统会给新的进程分配地址空间,将原进程地址空间中的所有内容拷贝一份到新进程中,这些内容包括代码、数据。所以,单例类在老进程中存在一个对象,在新进程中也存在一个对象,但是这两个对象并不是同一个对象。单例对象在进程之间是不唯一的。
2. 实现一个线程唯一的单例
我们知道单例对象是进程唯一的,这就表明其在线程内、线程间都是唯一的。但是如果我们想实现线程唯一的单例应该怎么做呢?
我们可以通过一个HashMap来存储这些不同线程的单例对象,其中key是线程的ID,value是单例对象。这样就可以做到,不同线程对应不同的单例对象,线程之内单例对象是唯一的,而线程间不唯一。事实上,Java语言本身就提供了ThreadLocal工具类,可以实现线程唯一单例,但是其底层的实现原理也是基于上述HashMap的思想。
具体的实现代码如下:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private IdGenerator(){};
private static final ConcurrentHashMap<Long,IdGenerator> instances = new ConcurrentHashMap<Long, 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. 如何实现集群环境下的单例
进程唯一指的是进程内唯一,进程间不唯一;线程唯一指的是线程内唯一,线程间不唯一。集群相当于多个进程构成的集合。集群唯一相当于进程内唯一,进程间也唯一。也就是说,不同的进程之间共享一个单例对象。
具体来说,实现集群唯一的方式是需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部供词昂存储区中将它读取到内存,并反序列化成对象,然后才能进行使用。使用完成后还要将修改后的对象写到外部共享存储区,并在内存中删掉这个对象,进一步释放所持有的锁。
实现流程的伪代码如下:
import java.util.concurrent.atomic.AtomicLong;
public class IdGenerator1 {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator1 instance;
private static SharedObjectStorage storage = FileSharedObjectStorage();
private static DistributedLock lock = new DistributedLock();
private IdGenerator1(){}
public synchronized static IdGenerator1 getInstance(){
if(instance == null){
lock.lock();
instance = storage.load(IdGenerator1.class);
}
return instance;
}
public synchronized void freeInstance(){
storage.save(this,IdGenerator1.class);
instance = null;
lock.unlock();
}
public Long getId(){
return id.incrementAndGet();
}
}
4. 如何实现一个多例模式
多例指的是一个类可以创建多个对象,但是个数是有限制的,比如只能创建3个对象。
实例代码实现如下:
import java.util.HashMap;
import java.util.Random;
public class BackendServer {
private long serverNo;
private String serverAddress;
private static final int SERVER_COUNT = 3;
private static final HashMap<Long,BackendServer> serverInstances = new HashMap<Long, BackendServer>();
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 static BackendServer getInstance(long serverNo){
return serverInstances.get(serverNo);
}
public static BackendServer getRandomInstance(){
Random r = new Random();
int no = r.nextInt(SERVER_COUNT)+1;
return serverInstances.get(no);
}
}
多例模式的实现逻辑其实和工厂模式非常相似。他们之间的不同是多例模式创建的是同一个类的多个对象;而工厂模式创建的是不同类的多个对象。