目录与学习目标

  1. 1:什么是单例设计模式
  2. 2:单例模式的应用场景
  3. 3:尝试添加 synchronized 对象级别的锁 解决
  4. 4:尝试添加 synchronized 类级别的锁 解决
  5. 5:使用单例设计模式解决

1:什么是单例设计模式

  1. 单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),
  2. 那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

2:单例模式的应用场景

  1. 处理资源访问冲突
  2. UserController OrderController 中,
  3. 我们分别创建两个 Logger 对象。在 Web 容器的 Servlet 多线程环境下,
  4. 如果两个 Servlet 线程同时分别执行 login() create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息互相覆盖的情况。

image.png

  1. @Slf4j
  2. public class Logger {
  3. private FileWriter writer;
  4. public Logger() throws IOException {
  5. File file = new File("A:\\log.txt");
  6. //true表示追加写入
  7. writer = new FileWriter(file, true);
  8. }
  9. public void log(String message) throws IOException {
  10. log.info("写入数据:{}",message);
  11. writer.write(message);
  12. writer.write("\n");
  13. writer.close();
  14. }
  15. }
  1. public class UserController {
  2. private Logger logger = new Logger();
  3. public UserController() throws IOException {
  4. }
  5. public void login() throws IOException {
  6. // ...省略业务逻辑代码...
  7. logger.log("HSJ User logined!");
  8. }
  9. }
  1. public class OrderController {
  2. private Logger logger = new Logger();
  3. public OrderController() throws IOException {
  4. }
  5. public void create() throws IOException {
  6. // ...省略业务逻辑代码...
  7. logger.log("Created an order");
  8. }
  9. }

3:尝试添加 synchronized 对象级别的锁 解决

  1. 这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求顺序执行。
  2. 不同的对象之间并不共享同一把锁。
  3. 在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,
  1. @Slf4j
  2. public class Logger {
  3. private FileWriter writer;
  4. public Logger() throws IOException {
  5. File file = new File("A:\\log.txt");
  6. //true表示追加写入
  7. writer = new FileWriter(file, true);
  8. }
  9. public void log(String message) throws IOException {
  10. log.info("写入数据:{}",message);
  11. //添加this 为对象级别的锁 锁无效
  12. synchronized (this) {
  13. writer.write(message);
  14. }
  15. writer.write("\n");
  16. writer.close();
  17. }
  18. }

4:尝试添加 synchronized 类级别的锁 解决

  1. @Slf4j
  2. public class Logger {
  3. private FileWriter writer;
  4. public Logger() throws IOException {
  5. File file = new File("A:\\log.txt");
  6. //true表示追加写入
  7. writer = new FileWriter(file, true);
  8. }
  9. public void log(String message) throws IOException {
  10. log.info("写入数据:{}",message);
  11. //添加Logger.class 为类级别的锁 锁生效
  12. synchronized (Logger.class) {
  13. writer.write(message);
  14. }
  15. writer.write("\n");
  16. writer.close();
  17. }
  18. }

5:使用单例设计模式解决

  1. 我们将 Logger 设计成一个单例类,程序中只允许创建一个 Logger 对象,
  2. 所有的线程共享使用的这一个 Logger 对象,共享一个 FileWriter 对象,
  3. 同时FileWriter 本身是对象级别线程安全的,也就避免了多线程情况下写日志会互相覆盖的问题。
  4. 执行顺序:
  5. //类加载仅执行一次
  6. //1:静态常量在类加载时初始化 调用方法new LoggerSingleton()
  7. //2:被步骤1调用 执行new LoggerSingleton()
  8. //3:在原本方法执行 new LoggerSingleton();
  9. //注意此处final修饰仅表示 LoggerSingleton instance 不可变
  10. 1private static final LoggerSingleton instance = new LoggerSingleton();
  11. 2public LoggerSingleton() {}
  12. 3public LoggerSingleton() {}
  13. //由于getInstance添加了static修饰 因此这里是直接调用getInstance方法
  14. 4 public static LoggerSingleton getInstance() { return instance; }
  15. 5 public static LoggerSingleton getInstance() { return instance; }
  1. @Slf4j
  2. public class LoggerSingleton {
  3. //static 静态变量 在类加载时初始化
  4. //final 变量变常量 只能被赋值一次 在类加载时初始化
  5. private static final LoggerSingleton instance = new LoggerSingleton();
  6. public static LoggerSingleton getInstance() { return instance; }
  7. private FileWriter writer;
  8. public LoggerSingleton() {
  9. File file = new File("A:\\log.txt");
  10. //true表示追加写入
  11. try {
  12. writer = new FileWriter(file, true);
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. public void log(String message) throws IOException {
  18. log.info("写入数据:{}", message);
  19. writer.write(message);
  20. writer.write("\n");
  21. writer.close();
  22. }
  23. }
  24. public class LoggerSingletonDemoStart {
  25. public static void main(String[] args) throws IOException {
  26. //如果没有该 new LoggerSingleton(); 则上面第三步去掉 其他一致
  27. new LoggerSingleton();
  28. //这样就只能获取该单例的Logger对象
  29. LoggerSingleton.getInstance().log("我是测试单例的Logger啊");
  30. LoggerSingleton.getInstance().log("我是测试单例的Logger啊");
  31. }
  32. }

项目连接

  1. 请配合项目代码食用效果更佳:
  2. 项目地址:
  3. https://github.com/hesuijin/hesujin-design-pattern
  4. Git下载地址:
  5. https://github.com.cnpmjs.org/hesuijin/hesujin-design-pattern.git
  6. demo-study模块 build_design_pattern singleton包下 loggerDemo