006单例模式
创始人
2025-05-29 08:42:18
0

目录

      • what 是单例
      • how 定义
      • why 需要它
      • how 实现
        • 一、懒汉式
        • 二、饿汉式
        • 三、静态方法
        • 四、枚举
        • why 有时候不使用单例类会不会更好

what 是单例

保证一个类只允许创建一个对象 ,这个类就是单例类,这中设计模式就叫单例模式。

how 定义

  • 构造方法私有,避免外部访问
  • 创建对象的安全问题考虑
  • 是否支持延迟加载
  • 考虑 getinstance的性能是否合适

why 需要它

  • 处理资源访问冲突

比如一个打印日志的例子,2个人都new一个日志对象,同时打印一个问题,如果超过一个对象,行为不正确


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(message);}
}// 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());}
}
  • 表示全局唯一

比如,项目中配置文件,一个项目只需要有一份就可以了,不需要每次用到还有创建它。或者 ,唯一递增 ID 号码生成 ,如果有2个对象,还存在重复生成id情况 、或者数据库连接池


import java.util.concurrent.atomic.AtomicLong;
public class IdGenerator {// AtomicLong是一个Java并发库中提供的一个原子变量类型,// 它将一些线程不安全需要加锁的复合操作封装为了线程安全的原子操作,// 比如下面会用到的incrementAndGet().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();}
}// IdGenerator使用举例
long id = IdGenerator.getInstance().getId();

how 实现

一、懒汉式

饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。

对于一些创建对象比较耗时,建议使用懒汉式,在程序启动时尽早创建对象,也可以暴露问题


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();}
}

二、饿汉式

  • 懒汉式相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
  • 双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。

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 static IdGenerator instance;private IdGenerator() {}public static IdGenerator getInstance() {if (instance == null) {synchronized(IdGenerator.class) { // 此处为类级别的锁if (instance == null) {instance = new IdGenerator();}}}return instance;}public long getId() { return id.incrementAndGet();}
}//   instance 成员变量添加 volatile 关键字来禁止指令重排序即

三、静态方法

内部SingletonHolder 在使用到的时候,才会进行创建,jvm保证线程安全


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 enum IdGenerator {INSTANCE;private AtomicLong id = new AtomicLong(0);public long getId() { return id.incrementAndGet();}
}

why 有时候不使用单例类会不会更好

一、对单元测试不是很友好
当我们需要改变是实现的时候


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();}
}

二、隐藏类和类之间的依赖关系
三、对扩展性不好

怎么替换单例模式

  1. 使用工厂模式
  2. ioc容器注入

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...