如何理解单例模式中的唯一性
这个唯一性指的是进程内只允许创建一个对象。
如何实现线程唯一的单例
“进程唯一”指的是进程内唯一,进程间不唯一。“线程唯一”指的是线程内唯 一,线程间可以不唯一。
我们通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以 做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。实际上,Java 语言本 身提供了 ThreadLocal 工具类,可以更加轻松地实现线程唯一单例。不过,ThreadLocal 底层实现原理也是基于下面代码中所示的 HashMap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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(); } }
|
如何实现集群环境下的单例
集群相当于多个进程构成的 一个集合,“集群唯一”就相当于是进程内唯一、进程间也唯一。也就是说,不同的进程间 共享同一个对象,不能创建同一个类的多个对象。
具体来说,我们需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在 使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对 象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对 象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内 存中删除,并且释放对对象的加锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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 idGeneator = IdGenerator.getInstance(); long id = idGenerator.getId(); IdGenerator.freeInstance();
|
参考
《设计模式之美》