01.创建型:单例设计模式1

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 单例设计模式

创建型:单例设计模式1

目录介绍

  • 01.单例模式介绍
  • 02.单例模式定义
  • 03.单例使用场景
  • 04.思考几个问题
  • 05.为什么要使用单例
  • 06.处理资源访问冲突
  • 07.表示全局唯一类

01.单例模式介绍

  • 单例模式是应用最广的模式

    • 也是最先知道的一种设计模式,在深入了解单例模式之前,每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式的实现。
  • 单例模式特点

    • 构造函数不对外开放,一般为private
    • 通过一个静态方法或者枚举返回单例类对象
    • 确保单例类的对象有且只有一个,尤其是在多线程的环境下
    • 确保单例类对象在反序列化时不会重新构造对象

02.单例模式定义

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点

03.单例使用场景

  • 应用中某个实例对象需要频繁的被访问。
  • 应用中每次启动只会存在一个实例。如账号系统,数据库系统。

04.思考几个问题

  • 网上有很多讲解单例模式的文章,但大部分都侧重讲解,如何来实现一个线程安全的单例。重点还是希望搞清楚下面这样几个问题。

    • 为什么要使用单例?
    • 单例存在哪些问题?
    • 单例与静态类的区别?
    • 有何替代的解决方案?

05.为什么要使用单例

  • 单例设计模式(Singleton Design Pattern)理解起来非常简单。

    • 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
  • 重点看一下,为什么我们需要单例这种设计模式?它能解决哪些问题?接下来我通过两个实战案例来讲解。

    • 第一个是处理资源访问冲突;
    • 第二个是表示全局唯一类;

06.处理资源访问冲突

  • 实战案例一:处理资源访问冲突

    • 先来看第一个例子。在这个例子中,我们自定义实现了一个往文件中打印日志的 Logger 类。具体的代码实现如下所示:
    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());
      }
    }
  • 看完代码之后,先别着急看我下面的讲解,你可以先思考一下,这段代码存在什么问题。
  • 在上面的代码中,我们注意到,所有的日志都写入到同一个文件 /Users/wangzheng/log.txt 中。在 UserController 和 OrderController 中,我们分别创建两个 Logger 对象。在 Web 容器的 Servlet 多线程环境下,如果两个 Servlet 线程同时分别执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息互相覆盖的情况。
  • 为什么会出现互相覆盖呢?我们可以这么类比着理解。在多线程环境下,如果两个线程同时给同一个共享变量加 1,因为共享变量是竞争资源,所以,共享变量最后的结果有可能并不是加了 2,而是只加了 1。同理,这里的 log.txt 文件也是竞争资源,两个线程同时往里面写数据,就有可能存在互相覆盖的情况。
  • 那如何来解决这个问题呢?我们最先想到的就是通过加锁的方式:给 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); //true表示追加写入
      }
      
      public void log(String message) {
        synchronized(this) {
          writer.write(mesasge);
        }
      }
    }
  • 不过,你仔细想想,这真的能解决多线程写入日志时互相覆盖的问题吗?

    • 答案是否定的。这是因为,这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求顺序执行。但是,不同的对象之间并不共享同一把锁。在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,仍然有可能存在写入日志互相覆盖的问题。

07.表示全局唯一类

  • 从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
  • 比如,配置信息类。在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份。
  • 再比如,唯一递增 ID 号码生成器,如果程序中有两个对象,那就会存在生成重复 ID 的情况,所以,我们应该将 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();
  • 实际上,今天讲到的两个代码实例(Logger、IdGenerator),设计的都并不优雅,还存在一些问题。

更多内容

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
6月前
|
设计模式
单例设计模式步骤
单例设计模式步骤
34 1
|
6月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
142 0
|
6月前
|
设计模式 安全 Java
最简单的设计模式是单例?
单例模式可以说是Java中最简单的设计模式,但同时也是技术面试中频率极高的面试题。因为它不仅涉及到设计模式,还包括了关于线程安全、内存模型、类加载等机制。所以说它是最简单的吗?
79 3
最简单的设计模式是单例?
|
6月前
|
设计模式 安全 Java
【设计模式】2、设计模式分类和单例设计模式
【设计模式】2、设计模式分类和单例设计模式
57 0
|
6月前
|
设计模式 消息中间件 安全
多线程编程设计模式(单例,阻塞队列,定时器,线程池)(二)
多线程编程设计模式(单例,阻塞队列,定时器,线程池)(二)
60 1
|
6月前
|
设计模式 Java
26、Java 简单实现单例设计模式(饿汉式和懒汉式)
26、Java 简单实现单例设计模式(饿汉式和懒汉式)
55 2
|
6月前
|
设计模式 安全 Java
在Java中即指单例设计模式
在Java中即指单例设计模式
40 0
|
2月前
|
设计模式 存储 安全
设计模式——设计模式介绍和单例设计模式
饿汉式(静态常量)、饿汉式(静态代码块)、懒汉式(线程不安全)、懒汉式(线程安全,同步方法)、懒汉式(线程不安全,同步代码块)、双重检查(推荐,线程安全、懒加载)、静态内部类(推荐)、枚举(推荐)
设计模式——设计模式介绍和单例设计模式
|
3月前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
50 1
|
6月前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式