前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >01.创建型:单例设计模式

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

原创
作者头像
杨充
修改2022-09-08 19:27:06
3470
修改2022-09-08 19:27:06
举报
文章被收录于专栏:lib库lib库

创建型:单例设计模式1

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

01.单例模式介绍

  • 单例模式是应用最广的模式
    • 也是最先知道的一种设计模式,在深入了解单例模式之前,每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式的实现。
  • 单例模式特点
    • 构造函数不对外开放,一般为private
    • 通过一个静态方法或者枚举返回单例类对象
    • 确保单例类的对象有且只有一个,尤其是在多线程的环境下
    • 确保单例类对象在反序列化时不会重新构造对象

02.单例模式定义

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

03.单例使用场景

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

04.思考几个问题

  • 网上有很多讲解单例模式的文章,但大部分都侧重讲解,如何来实现一个线程安全的单例。重点还是希望搞清楚下面这样几个问题。
    • 为什么要使用单例?
    • 单例存在哪些问题?
    • 单例与静态类的区别?
    • 有何替代的解决方案?

05.为什么要使用单例

  • 单例设计模式(Singleton Design Pattern)理解起来非常简单。
    • 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
  • 重点看一下,为什么我们需要单例这种设计模式?它能解决哪些问题?接下来我通过两个实战案例来讲解。
    • 第一个是处理资源访问冲突;
    • 第二个是表示全局唯一类;

06.处理资源访问冲突

  • 实战案例一:处理资源访问冲突
    • 先来看第一个例子。在这个例子中,我们自定义实现了一个往文件中打印日志的 Logger 类。具体的代码实现如下所示:
代码语言:txt
复制
``` java
代码语言:txt
复制
public class Logger {
代码语言:txt
复制
  private FileWriter writer;
代码语言:txt
复制
  public Logger() {
代码语言:txt
复制
    File file = new File("/Users/wangzheng/log.txt");
代码语言:txt
复制
    writer = new FileWriter(file, true); //true表示追加写入
代码语言:txt
复制
  }
代码语言:txt
复制
  public void log(String message) {
代码语言:txt
复制
    writer.write(mesasge);
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
// Logger类的应用示例:
代码语言:txt
复制
public class UserController {
代码语言:txt
复制
  private Logger logger = new Logger();
代码语言:txt
复制
  public void login(String username, String password) {
代码语言:txt
复制
    // ...省略业务逻辑代码...
代码语言:txt
复制
    logger.log(username + " logined!");
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
public class OrderController {
代码语言:txt
复制
  private Logger logger = new Logger();
代码语言:txt
复制
  public void create(OrderVo order) {
代码语言:txt
复制
    // ...省略业务逻辑代码...
代码语言:txt
复制
    logger.log("Created an order: " + order.toString());
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
```
  • 看完代码之后,先别着急看我下面的讲解,你可以先思考一下,这段代码存在什么问题。
  • 在上面的代码中,我们注意到,所有的日志都写入到同一个文件 /Users/wangzheng/log.txt 中。在 UserController 和 OrderController 中,我们分别创建两个 Logger 对象。在 Web 容器的 Servlet 多线程环境下,如果两个 Servlet 线程同时分别执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息互相覆盖的情况。
  • 为什么会出现互相覆盖呢?我们可以这么类比着理解。在多线程环境下,如果两个线程同时给同一个共享变量加 1,因为共享变量是竞争资源,所以,共享变量最后的结果有可能并不是加了 2,而是只加了 1。同理,这里的 log.txt 文件也是竞争资源,两个线程同时往里面写数据,就有可能存在互相覆盖的情况。
  • 那如何来解决这个问题呢?我们最先想到的就是通过加锁的方式:给 log() 函数加互斥锁(Java 中可以通过 synchronized 的关键字),同一时刻只允许一个线程调用执行 log() 函数。具体的代码实现如下所示:
代码语言:txt
复制
``` java
代码语言:txt
复制
public class Logger {
代码语言:txt
复制
  private FileWriter writer;
代码语言:txt
复制
  public Logger() {
代码语言:txt
复制
    File file = new File("/Users/wangzheng/log.txt");
代码语言:txt
复制
    writer = new FileWriter(file, true); //true表示追加写入
代码语言:txt
复制
  }
代码语言:txt
复制
  public void log(String message) {
代码语言:txt
复制
    synchronized(this) {
代码语言:txt
复制
      writer.write(mesasge);
代码语言:txt
复制
    }
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
```
  • 不过,你仔细想想,这真的能解决多线程写入日志时互相覆盖的问题吗?
    • 答案是否定的。这是因为,这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求顺序执行。但是,不同的对象之间并不共享同一把锁。在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,仍然有可能存在写入日志互相覆盖的问题。

07.表示全局唯一类

  • 从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
  • 比如,配置信息类。在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份。
  • 再比如,唯一递增 ID 号码生成器,如果程序中有两个对象,那就会存在生成重复 ID 的情况,所以,我们应该将 ID 生成器类设计为单例。
代码语言:txt
复制
``` java
代码语言:txt
复制
import java.util.concurrent.atomic.AtomicLong;
代码语言:txt
复制
public class IdGenerator {
代码语言:txt
复制
  // AtomicLong是一个Java并发库中提供的一个原子变量类型,
代码语言:txt
复制
  // 它将一些线程不安全需要加锁的复合操作封装为了线程安全的原子操作,
代码语言:txt
复制
  // 比如下面会用到的incrementAndGet().
代码语言:txt
复制
  private AtomicLong id = new AtomicLong(0);
代码语言:txt
复制
  private static final IdGenerator instance = new IdGenerator();
代码语言:txt
复制
  private IdGenerator() {}
代码语言:txt
复制
  public static IdGenerator getInstance() {
代码语言:txt
复制
    return instance;
代码语言:txt
复制
  }
代码语言:txt
复制
  public long getId() { 
代码语言:txt
复制
    return id.incrementAndGet();
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
// IdGenerator使用举例
代码语言:txt
复制
long id = IdGenerator.getInstance().getId();
代码语言:txt
复制
```
  • 实际上,今天讲到的两个代码实例(Logger、IdGenerator),设计的都并不优雅,还存在一些问题。

更多内容

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建型:单例设计模式1
    • 01.单例模式介绍
      • 02.单例模式定义
        • 03.单例使用场景
          • 04.思考几个问题
            • 05.为什么要使用单例
              • 06.处理资源访问冲突
                • 07.表示全局唯一类
                  • 更多内容
                  相关产品与服务
                  日志服务
                  日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档