如何理解单例模式中的唯一性?
单例模式创建的对象是进程唯一的。
单例类在老进程中存在且只能存在一个对象,在新进程中也会存在且只能存在一个对 象。而且,这两个对象并不是同一个对象,这也就说,单例类中对象的唯一性的作用范围是 进程内的,在进程间是不唯一的。
如何实现一个线 程唯一的单例呢?
“进程唯一”指的是进程内唯一,进程间不唯一。
“线程唯一”指的是线程内唯 一,线程间可以不唯一。
我 们通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。Java 语言本 身提供了 ThreadLocal
工具类,可以更加轻松地实现线程唯一单例。
ThreadLocal 底层实现原理也是基于下面代码中所示的 HashMap。
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final ConcurrentHashMap 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();
}
}
如何实现集群环境下的单例?
“集群唯一”就相当于是进程内唯一、进程间也唯一。也就是说,不同的进程间 共享同一个对象,不能创建同一个类的多个对象。
我们需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在 使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对 象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对 象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
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();
如何实现一个多例模式?
“多例”指的就是,一个类可以创建 多个对象,但是个数是有限制的,比如只能创建 3 个对象。
public class BackendServer {
private long serverNo;
private String serverAddress;
private static final int SERVER_COUNT = 3;
private static final Map 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 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");
这种多例模式的理解方式有点类似工厂模式。