public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final IdGenerator instance = new IdGenerator();
private IdGenerator() {}
public static IdGenerator getInstance() {
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
如果实例占用资源多,按照 fail-fast 的设计原则(有问题及早暴露),那我们也希望在程序启动时就将这个实例初始化好。如果资源不够,就会在程序启动的时候触发报错(比如 Java 中的 PermGen Space OOM),我们可以立即去修复。这样也能避免在程序运行一段时间后,突然因为初始化这个实例占用资源过多,导致系统崩溃,影响系统的可用性
懒汉模式
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static synchronized IdGenerator getInstance() {
if (instance == null) {
instance = new IdGenerator();
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private IdGenerator() {}
private static class SingletonHolder{
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance() {
return SingletonHolder.instance;
}
public long getId() {
return id.incrementAndGet();
}
}
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private IdGenerator() {}
private static class SingletonHolder{
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance() {
return SingletonHolder.instance;
}
public long getId() {
return id.incrementAndGet();
}
}
优势
最简单的实现
例子
处理资源访问冲突
public class Logger {
private FileWriter writer;
public Logger() {
File file = new File("/Users/wangzheng/log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}
public void log(String message) {
writer.write(mesasge);
}
}
// Logger类的应用示例:
public class UserController {
private Logger logger = new Logger();
public void login(String username, String password) {
// ...省略业务逻辑代码...
logger.log(username + " logined!");
}
}
public class OrderController {
private Logger logger = new Logger();
public void create(OrderVo order) {
// ...省略业务逻辑代码...
logger.log("Created an order: " + order.toString());
}
}
public class Order {
public void create(...) {
//...
long id = IdGenerator.getInstance().getId();
//...
}
}
public class User {
public void create(...) {
// ...
long id = IdGenerator.getInstance().getId();
//...
}
}
如果未来某一天,我们希望针对不同的业务采用不同的 ID 生成算法。比如,订单 ID 和用户 ID 采用不同的 ID 生成器来生成。为了应对这个需求变化,我们需要修改所有用到 IdGenerator 类的地方,这样代码的改动就会比较大。
public class Order {
public void create(...) {
//...
long id = IdGenerator.getInstance().getId();
// 需要将上面一行代码,替换为下面一行代码
long id = OrderIdGenerator.getIntance().getId();
//...
}
}
public class User {
public void create(...) {
// ...
long id = IdGenerator.getInstance().getId();
// 需要将上面一行代码,替换为下面一行代码
long id = UserIdGenerator.getIntance().getId();
}
}
如果单例类持有成员变量(比如 IdGenerator 中的 id 成员变量),那它实际上相当于一种全局变量,被所有的代码共享。如果这个全局变量是一个可变全局变量,也就是说,它的成员变量是可以被修改的,那我们在编写单元测试的时候,还需要注意不同测试用例之间,修改了单例类中的同一个成员变量的值,从而导致测试结果互相影响的问题。
单例不支持有参数的构造函数
单例不支持有参数的构造函数,比如我们创建一个连接池的单例对象,我们没法通过参数来指定连接池的大小
单例的替代方案
静态方法
// 静态方法实现方式
public class IdGenerator {
private static AtomicLong id = new AtomicLong(0);
public static long getId() {
return id.incrementAndGet();
}
}
// 使用举例
long id = IdGenerator.getId();
public class StudentIdGenerator implements IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final StudentIdGenerator instance = new StudentIdGenerator();
@Override
public long getId() {
if (id.incrementAndGet()%2 == 0){
return id.get();
}else {
return id.incrementAndGet();
}
}
public static StudentIdGenerator getInstance() {
return instance;
}
}
public class UserIdGenerator implements IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final UserIdGenerator instance = new UserIdGenerator();
@Override
public long getId() {
return id.incrementAndGet();
}
public static UserIdGenerator getInstance() {
return instance;
}
}
public class Application {
public static void main(String[] args) {
test1(StudentIdGenerator.getInstance());
test1(UserIdGenerator.getInstance());
}
public static void test1(IdGenerator idGenerator){
while (true){
System.out.println(String.format("%s -------> %d",idGenerator.getClass().getName(),idGenerator.getId()));
}
}
public static void test2(IdGenerator idGenerator){
while (true){
System.out.println(String.format("%s -------> %d",idGenerator.getClass().getName(),idGenerator.getId()));
}
}
}
终极解决方案
们可能要从根上,寻找其他方式来实现全局唯一类。
实际上,类对象的全局唯一性可以通过多种不同的方式来保证。
我们既可以通过单例模式来强制保证
也可以通过工厂模式、IOC 容器(比如 Spring IOC 容器)来保证
还可以通过程序员自己来保证(自己在编写代码的时候自己保证不要创建两个类对象)。这就类似 Java 中内存对象的释放由 JVM 来负责,而 C++ 中由程序员自己负责,道理是一样的
public class BackendServerHungery {
private long serverNo;
private String serverAddress;
private static final int SERVER_COUNT = 3;
private static final Map<Long, BackendServerHungery> serverInstances = new HashMap<>();
static {
serverInstances.put(1L, new BackendServerHungery(1L, "192.134.22.138:8080"));
serverInstances.put(2L, new BackendServerHungery(2L, "192.134.22.139:8080"));
serverInstances.put(3L, new BackendServerHungery(3L, "192.134.22.140:8080"));
}
private BackendServerHungery(long serverNo, String serverAddress) {
this.serverNo = serverNo;
this.serverAddress = serverAddress;
}
public BackendServerHungery getInstance(long serverNo) {
return serverInstances.get(serverNo);
}
public BackendServerHungery getRandomInstance() {
Random r = new Random();
int no = r.nextInt(SERVER_COUNT)+1;
return serverInstances.get(no);
}
}
懒汉模式
public class BackendServerLazy {
private static final int SERVER_COUNT = 3;
private static final Map<Integer, BackendServerLazy> serverInstances = new HashMap<>();
private BackendServerLazy() {
}
public static synchronized BackendServerLazy getRandomInstance() {
Random r = new Random();
int no = r.nextInt(SERVER_COUNT)+1;
BackendServerLazy instance= serverInstances.get(no);
if (instance==null){
serverInstances.put(no, new BackendServerLazy());
}
instance=serverInstances.get(no);
return instance;
}
public static void main(String[] args) {
Set<BackendServerLazy> instances=new HashSet<> ();
int times = 1;
while (instances.size() < 3){
instances.add(BackendServerLazy.getRandomInstance());
System.out.println(String.format("第 %d 次",times));
times++;
}
System.out.println("三个实例已经全部初始化");
}
}
扩展
进程下的单例
平时我们说的都是进程下的单例
单例类对象的唯一性的作用范围并非进程,而是类加载器(Class Loader)
线程下的单例
自定义实现
/**
* 在代码中,我们通过一个 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());
return instances.get(currentThreadId);
}
public long getId() {
return id.incrementAndGet();
}
}
ThreadLocal 实现
import java.util.concurrent.atomic.AtomicLong;
public class IdgeneratorThreadLocal {
private AtomicLong id = new AtomicLong(0);
static ThreadLocal<IdgeneratorThreadLocal> instances = new ThreadLocal<>();
private IdgeneratorThreadLocal() {}
public static IdgeneratorThreadLocal getInstance() {
instances.set(new IdgeneratorThreadLocal());
return instances.get();
}
public long getId() {
return id.incrementAndGet();
}
}