1、单例
单例设计模式(Singleton Design Pattern)理解起来非常简单
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式
2、处理资源访问冲突
2.1、示例
自定义实现了一个往文件中打印日志的 Logger 类,具体的代码实现如下所示
| public class Logger { |
| |
| private FileWriter writer; |
| |
| public Logger() { |
| File file = new File("/Users/wangzheng/log.txt"); |
| writer = new FileWriter(file, true); |
| } |
| |
| public void log(String message) { |
| writer.write(message); |
| } |
| } |
| 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()); |
| } |
| } |
2.2、问题
在上面的代码中我们注意到,所有的日志都写入到同一个文件 /Users/wangzheng/log.txt 中
在 UserController 和 OrderController 中,我们分别创建两个 Logger 对象,在 Web 容器的 Servlet 多线程环境下
如果两个 Servlet 线程同时分别执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息互相覆盖的情况

2.3、解决 1
那如何来解决这个问题呢,我们最先想到的就是通过加锁的方式
给 log() 函数加互斥锁(Java 中可以通过 synchronized 的关键字),同一时刻只允许一个线程调用执行 log() 函数
| public class Logger { |
| private FileWriter writer; |
| |
| public Logger() { |
| File file = new File("/Users/wangzheng/log.txt"); |
| writer = new FileWriter(file, true); |
| } |
| |
| public void log(String message) { |
| synchronized (this) { |
| writer.write(message); |
| } |
| } |
| } |
这真的能解决多线程写入日志时互相覆盖的问题吗:答案是否定的
因为这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求顺序执行,但是不同的对象之间并不共享同一把锁
在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,仍然有可能存在写入日志互相覆盖的问题


2.4、解决 2
我这里稍微补充一下,在刚刚的讲解和给出的代码中,我故意 "隐瞒" 了一个事实:我们给 log() 函数加不加对象级别的锁,其实都没有关系
因为 FileWriter 本身就是线程安全的,它的内部实现中本身就加了对象级别的锁,因此在外层调用 write() 函数的时候,再加对象级别的锁实际上是多此一举
因为不同的 Logger 对象不共享 FileWriter 对象,所以 FileWriter 对象级别的锁也解决不了数据写入互相覆盖的问题
那我们该怎么解决这个问题呢:我们只需要把对象级别的锁,换成类级别的锁就可以了
让所有的对象都共享同一把锁,这样就避免了不同对象之间同时调用 log() 函数,而导致的日志覆盖问题
| public class Logger { |
| private FileWriter writer; |
| |
| public Logger() { |
| File file = new File("/Users/wangzheng/log.txt"); |
| writer = new FileWriter(file, true); |
| } |
| |
| public void log(String message) { |
| |
| synchronized (Logger.class) { |
| writer.write(message); |
| } |
| } |
| } |
2.5、解决 3
我们将 Logger 设计成一个单例类,程序中只允许创建一个 Logger 对象
所有的线程共享使用的这一个 Logger 对象,共享一个 FileWriter 对象,而 FileWriter 本身是对象级别线程安全的,也就避免了多线程情况下写日志会互相覆盖的问题
| public class Logger { |
| |
| private FileWriter writer; |
| |
| private Logger() { |
| File file = new File("/Users/wangzheng/log.txt"); |
| writer = new FileWriter(file, true); |
| } |
| |
| public void log(String message) { |
| writer.write(message); |
| } |
| |
| private static final Logger instance = new Logger(); |
| |
| public static Logger getInstance() { |
| return instance; |
| } |
| } |
| public class UserController { |
| public void login(String username, String password) { |
| |
| Logger.getInstance().log(username + " logined!"); |
| } |
| } |
| |
| public class OrderController { |
| private Logger logger = new Logger(); |
| |
| public void create(OrderVo order) { |
| |
| Logger.getInstance().log("Created a order: " + order.toString()); |
| } |
| } |
3、表示全局唯一类
从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类
- 配置信息类:在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份
- 唯一递增 ID 号码生成器,如果程序中有两个对象,那就会存在生成重复 ID 的情况,所以我们应该将 ID 生成器类设计为单例
| public class IdGenerator { |
| |
| |
| |
| |
| private AtomicLong id = new AtomicLong(0); |
| |
| private IdGenerator() { |
| } |
| |
| public long getId() { |
| return id.incrementAndGet(); |
| } |
| |
| private static final IdGenerator instance = new IdGenerator(); |
| |
| public static IdGenerator getInstance() { |
| return instance; |
| } |
| } |
| |
| |
| long id = IdGenerator.getInstance().getId(); |
4、如何实现一个单例
4.1、饿汉式
| public class IdGenerator { |
| |
| private IdGenerator() { |
| } |
| |
| private static final IdGenerator INSTANCE = new IdGenerator(); |
| |
| public static IdGenerator getInstance() { |
| return INSTANCE; |
| } |
| |
| private final AtomicLong id = new AtomicLong(0); |
| |
| public long getId() { |
| return id.incrementAndGet(); |
| } |
| } |
4.2、懒汉式
| public class IdGenerator { |
| |
| private IdGenerator() { |
| } |
| |
| private static IdGenerator instance; |
| |
| public static synchronized IdGenerator getInstance() { |
| if (instance == null) { |
| instance = new IdGenerator(); |
| } |
| return instance; |
| } |
| |
| private final AtomicLong id = new AtomicLong(0); |
| |
| public long getId() { |
| return id.incrementAndGet(); |
| } |
| } |
4.3、双重检查
| public class IdGenerator { |
| |
| private IdGenerator() { |
| } |
| |
| private static volatile IdGenerator instance; |
| |
| public static IdGenerator getInstance() { |
| if (instance == null) { |
| synchronized (IdGenerator.class) { |
| if (instance == null) { |
| instance = new IdGenerator(); |
| } |
| } |
| } |
| return instance; |
| } |
| |
| private final AtomicLong id = new AtomicLong(0); |
| |
| public long getId() { |
| return id.incrementAndGet(); |
| } |
| } |
4.4、静态内部类
| public class IdGenerator { |
| |
| private IdGenerator() { |
| } |
| |
| public static IdGenerator getInstance() { |
| return InstanceHolder.INSTANCE; |
| } |
| |
| private static class InstanceHolder { |
| private static final IdGenerator INSTANCE = new IdGenerator(); |
| } |
| |
| private final AtomicLong id = new AtomicLong(0); |
| |
| public long getId() { |
| return id.incrementAndGet(); |
| } |
| } |
4.5、枚举
| public enum IdGenerator { |
| |
| INSTANCE; |
| |
| private final AtomicLong id = new AtomicLong(0); |
| |
| public long getId() { |
| return id.incrementAndGet(); |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步