如何实现一个线程单例?
“进程唯一” 指的是进程内唯一, 进程间不唯一。 类比一下, “线程唯一” 指的是线程内唯 一, 线程间可以不唯一。 实际上, “进程唯一” 还代表了线程内、 线程间都唯一, 这也 是“进程唯一”和“线程唯一” 的区别之处。 这段话听起来有点像绕口令, 我举个例子来解 释一下。 假设 IdGenerator 是一个线程唯一的单例类。 在线程 A 内, 我们可以创建一个单例对象 a。 因为线程内唯一, 在线程 A 内就不能再创建新的 IdGenerator 对象了, 而线程间可以 不唯一, 所以, 在另外一个线程 B 内, 我们还可以重新创建一个新的单例对象 b。 尽管概念理解起来比较复杂, 但线程唯一单例的代码实现很简单, 如下所示。 在代码中, 我 们通过一个 HashMap 来存储对象, 其中 key 是线程 ID, value 是对象。 这样我们就可以 做到, 不同的线程对应不同的对象, 同一个线程只能对应一个对象。 实际上, Java 语言本 身提供了 ThreadLocal 工具类, 可以更加轻松地实现线程唯一单例。不过, ThreadLocal 底层实现原理也是基于下面代码中所示的 HashMap。
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()) ; r
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();
public synchronized static IdGenerator getInstance(){
if (instance == null){
lock.lock();
instance = storage.load(IdGenerator.class);
}
return instance;
}
public synchronized void freeInstace(){
storage.save(this,IdGenerator.class);
instance = null;
lock.unlock();
}
public long getid(){
return id.incrementAndGet();
}
//用例
IdGenerator idGenerator = new IdGenerator();
IdGenerator.freeInstace();
}