设计模式之单例模式

目录

一.设计模式概述

1.什么是设计模式?

2.设计模式有哪些?

3.GoF设计模式的分类?

二.单例模式

[1. 单例模式的核心思想](#1. 单例模式的核心思想)

[2. 单例模式的实现方式](#2. 单例模式的实现方式)

引入:怎样算单例

[(1) 饿汉式(Eager Initialization)](#(1) 饿汉式(Eager Initialization))

[(2) 懒汉式(Lazy Initialization)(线程不安全)](#(2) 懒汉式(Lazy Initialization)(线程不安全))

[(3) 线程安全的懒汉式(Synchronized Method)](#(3) 线程安全的懒汉式(Synchronized Method))

[(4) 双重检查锁(Double-Checked Locking)](#(4) 双重检查锁(Double-Checked Locking))

[(5) 静态内部类(Static Inner Class)](#(5) 静态内部类(Static Inner Class))

[(6) 枚举(Enum)](#(6) 枚举(Enum))

[3. 单例模式的应用场景](#3. 单例模式的应用场景)

[4. 单例模式的优缺点](#4. 单例模式的优缺点)

优点

缺点

[5. 总结](#5. 总结)


一.设计模式概述

1.什么是设计模式?

设计模式(Design Pattern)是一套被广泛接受的、经过试验验证的、可反复使用的基于面向对象的软件设计经验总结,它是软件开发人员在软件设计中,对常见问题的解决方案的总结和抽象。设计模式是针对软件开发中常见问题和模式的通用解决方案

大白话:可以重复利用的解决方案(即有个模版可以直接套用)

2.设计模式有哪些?

①.GoF设计模式:《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为四人组(Gang of Four)。

②.架构设计模式(Architectural Pattern):主要用于软件系统的整体架构设计,包括多层架构、MVC架构、微服务架构、REST架构和大数据架构等。

③.企业级设计模式(Enterprise Pattern):主要用于企业级应用程序设计,包括基于服务的架构(SOA)、企业集成模式(EIP)、业务流程建模(BPM)和企业规则引擎(BRE)等。

④.领域驱动设计模式(Domain Driven Design Pattern):主要用于领域建模和开发,包括聚合、实体、值对象、领域事件和领域服务等。

⑤.并发设计模式(Concurrency Pattern):主要用于处理并发性问题,包括互斥、线程池、管道、多线程算法和Actor模型等。

⑥.数据访问模式(Data Access Pattern):主要用于处理数据访问层次结构,包括数据访问对象(DAO)、仓库模式和活动记录模式等。

3.GoF设计模式的分类?

①.创建型:主要解决对象的创建问题

②.结构型:通过设计和构建对象之间的关系,以达到更好的重用性、扩展性和灵活性

③.行为型:主要用于处理对象之间的算法和责任分配

二.单例模式

(单例即一个实例(一个对象)即使用这种模式可以保证只创建一个对象)

单例模式(Singleton Pattern) 是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。 在程序中多次使用同一个对象且作用相同 时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

单例模式通常用于管理共享资源,如数据库连接、线程池、配置管理等。


1. 单例模式的核心思想

  • 唯一实例 :确保一个类只有一个实例。

  • 全局访问:提供一个全局访问点,方便其他对象访问该实例。


2. 单例模式的实现方式

引入:怎样算单例

Student类

public class Student {

}

StudentTest类

public class StudentTest {
    public static void main(String[] args) {
        Student s1=new Student();
        System.out.println(s1);


        Student s2=new Student();

        System.out.println(s2);//输出结果:lianxi.oop11.Student@4eec7777
                               //其中@4eec7777为16进制数
                                // 比较 s1 和 s2 的引用,结果为 false,因为它们是不同的对象

        System.out.println(s1==s2);//输出结果:false
                                   //所以这不是单例,因为有2个对象
                                


        int a=10;
        int b=10;
        System.out.println(a==b);  //输出结果:true
                   // 比较基本数据类型 a 和 b 的值,结果为 true,因为它们的值相等

    }
}

输出结果:

解释:

**public static void main(String[] args):**程序的入口方法。

Student s1 = new Student();Student s2 = new Student();:创建了两个 Student 类的对象 s1 和 s2。

System.out.println(s1);System.out.println(s2);:打印 s1 和 s2 的对象引用。输出结果类似于 lianxi.oop11.Student@4eec7777,这里 @ 后面的十六进制数字是对象的哈希码,它是对象的内存地址的一种表示方式,通常由 Java 的 Object 类的 hashCode() 方法生成。由于 s1 和 s2 是两个不同的对象,它们存储在不同的内存位置,所以哈希码不同。

System.out.println(s1 == s2);:使用 == 运算符比较 s1 和 s2。在 Java 中,对于对象,== 比较的是对象的引用(即它们是否指向相同的内存地址),而不是对象的内容。因为 s1 和 s2 是两个不同的对象,所以结果为 false,这表明这里创建的不是单例模式,因为单例模式要求在整个程序中只有一个对象实例。

int a = 10; 和 int b = 10;:定义了两个整型变量 a 和 b,并初始化为 10。

System.out.println(a == b);:对于基本数据类型(如 int),== 比较的是它们的值。由于 a 和 b 的值都是 10,所以结果为 true。

所以这不是单例,因为有2个对象

以下是几种常见的单例模式实现方式:

(1) 饿汉式(Eager Initialization)

复制代码
单例模式怎么实现?
       第一步:构造方法私有化
 private Singleton(){
    }
复制代码
      第二步:对外提供一个公开的静态的方法,用这个方法获取单个实例
  //实例方法
    //(实例方法必须创建对象才能使用)
    //因为对象都私有化了,所以使用静态
    public static Singleton get(){//返回Singleton类型
                                   //因为要通过这个方法获取Singleton对象
        return s;
复制代码
       第三步:定义一个静态变量,在类加载的时候,初始化静态变量。(只初始化一次)
 private static Singleton s=new Singleton();

/*    单例模式怎么实现?
       第一步:构造方法私有化
       第二步:对外提供一个公开的静态的方法,用这个方法获取单个实例
       第三步:定义一个静态变量,在类加载的时候,初始化静态变量。(只初始化一次)

* */

public class Singleton {
    private static Singleton s=new Singleton();
    private Singleton(){
    }
    //实例方法
    //(实例方法必须创建对象才能使用)
    //因为对象都私有化了,所以使用静态
    public static Singleton get(){//返回Singleton类型
                                   //因为要通过这个方法获取Singleton对象
        return s;



    }
}
  • 特点:在类加载时创建实例。

  • 优点:实现简单,线程安全。

  • 缺点:如果实例未被使用,会造成内存浪费。

    public class Singleton {
    // 在类加载时创建实例
    private static final Singleton s = new Singleton();

      // 私有构造方法,防止外部创建实例
      private Singleton() {}
    
      // 提供全局访问点
      public static Singleton get() {
          return s;
      }
    

    }


(2) 懒汉式(Lazy Initialization)(线程不安全)

  • 特点 :在第一次调用 getInstance() 时创建实例。

  • 优点:延迟加载,节省内存。

  • 缺点:线程不安全,需要额外处理多线程问题。

    public class Singleton {
    private static Singleton instance;//其默认值为null
    //相当于 private static Singleton instance=null;

      private Singleton() {}
    
      public static Singleton getInstance() {
          if (instance == null) {
              instance = new Singleton();
          }
          return instance;
      }
    

    }

测试:

Singleton类:

/**
 * Singleton是单例的。
 *
 * 懒汉式的单例模式:用到这个对象的时候再创建对象。别在类加载的时候创建对象。
 *
 * 第一步:构造方法私有化。
 * 第二步:对外提供一个静态方法,通过这个方法可以获取到Singleton对象。
 * 第三步:提供一个静态变量,但是这个变量值为null。
 */
public class Singleton {

    private static Singleton s;

    private Singleton(){}

    public static Singleton get(){
        if (s == null) {
            s = new Singleton();
            System.out.println("对象创建了");
        }
        return s;
    }
}

SingletonTest类:

public class SingletonTest {
    public static void main(String[] args) {
        Singleton s1 = Singleton.get();
        Singleton s2 = Singleton.get();
        Singleton s3 = Singleton.get();
        Singleton s4 = Singleton.get();
        Singleton s5 = Singleton.get();
        Singleton s6 = Singleton.get();

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
        System.out.println(s5);
        System.out.println(s6);

        System.out.println(s1 == s2);
    }
}

运行结果:

可见,此时只有一个对象。但这个方法其实是存在问题的,试想一下,如果两个线程同时判断singleton为空,那么它们都会去实例化一个Singleton对象,这就变成双例了。所以,我们要解决的是线程安全问题。


(3) 线程安全的懒汉式(Synchronized Method)

最容易想到的解决方法就是在方法上加锁,或者是对类对象加锁,程序就会变成下面这个样子

  • 特点 :通过 synchronized 关键字确保线程安全。

  • 优点:线程安全,延迟加载。

  • 缺点 :每次调用 getInstance() 都会加锁,性能较差。

    public class Singleton {
    private static Singleton singleton;

      private Singleton() {}
    

    public static synchronized Singleton getInstance() {
    if (singleton == null) {
    singleton = new Singleton();
    }
    return singleton;
    }
    // 或者
    public static Singleton getInstance() {
    synchronized(Singleton.class) {
    if (singleton == null) {
    singleton = new Singleton();
    }
    }
    return singleton;
    }

这样就规避了两个线程同时创建Singleton对象的风险,但是引来另外一个问题:每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。


接下来要做的就是优化性能,目标是:如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例

所以直接在方法上加锁的方式就被废掉了,因为这种方式无论如何都需要先获取锁

(4) 双重检查锁(Double-Checked Locking)

  • 特点:在加锁前后分别检查实例是否为空,减少加锁次数。

  • 优点:线程安全,延迟加载,性能较好。

  • 缺点:实现稍复杂。

    public class Singleton {
    // 使用 volatile 关键字确保可见性
    private static volatile Singleton singleton;

      // 私有构造方法,防止外部创建实例
      private Singleton() {}
    
      // 公共静态方法,提供获取单例实例的全局访问点
      public static Singleton getInstance() {
          if (singleton == null) {  // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
              synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
                  if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
                      singleton = new Singleton();
                  }
              }
          }
          return singleton;
      }
    

    }

public static Singleton getInstance():

这是获取单例实例的公共静态方法,作为全局访问点。

**if (singleton == null) {...}:**第一次检查,避免不必要的同步开销,如果 singleton 已经实例化,直接返回实例,不需要进入同步块。

**synchronized(Singleton.class) {...}:**同步块,确保只有一个线程可以进入该代码块,防止多个线程同时创建实例。

**if (singleton == null) {...}:**第二次检查,确保只有一个线程可以创建实例,因为可能有多个线程同时通过第一次检查,但只有一个线程能进入同步块并创建实例。


(5) 静态内部类(Static Inner Class)

  • 特点:利用类加载机制确保线程安全,同时实现延迟加载。

  • 优点:线程安全,延迟加载,实现简单。

  • 缺点:无法传递参数。

    public class Singleton {
    private Singleton() {}

      private static class SingletonHolder {
          private static final Singleton instance = new Singleton();
      }
    
      public static Singleton getInstance() {
          return SingletonHolder.instance;
      }
    

    }


(6) 枚举(Enum)

  • 特点:利用枚举的特性实现单例。

  • 优点:线程安全,防止反射和序列化破坏单例。

  • 缺点:不够灵活,无法延迟加载。

    public enum Singleton {
    INSTANCE;

      public void doSomething() {
          System.out.println("Doing something...");
      }
    

    }


3. 单例模式的应用场景

  • 资源共享:如数据库连接池、线程池、缓存等。

  • 配置管理:如全局配置对象。

  • 日志记录:如日志管理器。

  • 设备驱动:如打印机驱动程序。


4. 单例模式的优缺点

优点
  • 唯一实例:确保一个类只有一个实例,避免资源浪费。

  • 全局访问:提供全局访问点,方便其他对象访问。

缺点
  • 难以扩展:单例类的职责过重,违反了单一职责原则。

  • 难以测试:单例类的全局状态可能影响测试结果。

  • 线程安全问题:需要额外处理多线程环境下的实例化问题。


5. 总结

  • 单例模式 确保一个类只有一个实例,并提供一个全局访问点。

  • 实现方式:包括饿汉式、懒汉式、双重检查锁、静态内部类和枚举等。

  • 应用场景:适用于资源共享、配置管理、日志记录等场景。

相关推荐
ekskef_sef2 分钟前
Spring Boot——日志介绍和配置
java·数据库·spring boot
智驾12 分钟前
C++,设计模式,【目录篇】
c++·设计模式
理想青年宁兴星31 分钟前
【RabbitMQ】rabbitmq广播模式的使用
java·rabbitmq·java-rabbitmq
北京_宏哥37 分钟前
《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(详解教程)
java·selenium·前端框架
V+zmm1013443 分钟前
基于微信小程序的中国各地美食推荐平台的设计与实现springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计
magic 2451 小时前
JVM体系结构
java·开发语言·jvm·intellij-idea·idea
计算机-秋大田1 小时前
基于微信小程序的购物系统设计与实现(LW+源码+讲解)
java·后端·微信小程序·小程序·课程设计
GGBondlctrl1 小时前
【Spring Boot】掌握 Spring 事务:隔离级别与传播机制解读与应用
java·spring boot·spring·事务隔离级别·spring事务传播机制
DanceDonkey1 小时前
SpringCloud -根据服务名获取服务运行实例并进行负载均衡
java·spring cloud·负载均衡
呼啦啦啦啦啦啦啦啦1 小时前
【Redis】在Java中以及Spring环境下操作Redis
java·redis·spring