一文带你彻底搞懂设计模式之单例模式!!由浅入深,图文并茂,超超超详细的单例模式讲解!!

一文带你彻底搞懂设计模式之单例模式!

  • 一、什么是单例模式?
      • 单例模式分类
        • 饿汉式创建单例对象
        • 懒汉式创建单例对象
      • 多问一个为什么?
  • 二、为什么要有单例模式?
      • 使用单例模式的原因
      • 单例模式的应用场景
  • 三、多线程下的单例模式
      • 饿汉式
      • 懒汉式
        • 懒汉加锁
        • 加锁优化(双重检验加锁)
      • 使用volatile防止指令重排
  • 四、破坏懒汉式单例与饿汉式单例
      • 反射破坏
      • 序列化与反序列化破坏
        • 原因分析
        • 问题解决
  • 五、枚举实现
  • 六、总结

一、什么是单例模式?

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。——《大话设计模式》

单例模式是在内存中仅会创建一次对象的设计模式
在这里插入图片描述

简单来说单例模式的简单实现就是
成员是 私有的静态的
构造方法是 私有的
对外暴露的获取访问是 公有的静态的

单例模式分类

  • 饿汉式:类加载就会导致该单实例对象被创建

  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建

饿汉式创建单例对象

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。

饿汉式简单实现代码如下:

package org.example;

/**
 * @Author:wjy
 */
 
public class Singleton{

    //在该类中创建一个该类的对象供外界去使用
    private static Singleton instance= new Singleton();

    // 构造方法 private 化
    private Singleton(){

    }

    // 得到 Singleton 的实例(唯一途径)
    public static Singleton getInstance() {
        return instance;
    }
}
懒汉式创建单例对象

懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作。

懒汉式简单实现代码如下:

package org.example;

/**
 * @Author:wjy
 */
public class Singleton {

    //在该类中创建一个该类的对象供外界去使用
    private static Singleton instance;

    // 构造方法 private 化
    private Singleton(){ 

    }

    // 得到 Singleton 的实例(唯一途径)
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

客户端代码:

public class Main {
    public static void main(String[] args) {
        // Singleton s0 = new Singleton(); // 原先的实例化方法
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        if (s1 == s2){
            System.out.println("两个对象是相同的实例");
        }
    }
}

可以看到打印结果是同一对象

在这里插入图片描述

多问一个为什么?

此处,我们可能在潜意识中就这样让代码过去,也许是因为认为这就是规定或者认为是很基础的内容而无需解释过多,在许多文章中,也并未更加详细的解释这个问题.
但是,如果是作为一个可能基础不那么稳固的初学者(比如本人T-T),在学习的时候,为加深理解,我们不妨多问一个为什么?为什么是这样设计呢?

  1. 成员变量 instance 为什么是私有的?
    最直接的原因就是 防止外部直接访问,如果不声明为 private ,其他类将能直接访问和修改它,而这就违反了单例模式的原则,因为单例模式要求类只有一个实例化
  2. 成员变量 instance 为什么是静态的?
    使用 static 修饰后,意味着该变量是属于类的,而不是类的实例
    你可以通过 类名.变量名 的方式直接访问
    同时,将只存在一个 instance
    你可以像
    Singleton s1 = Singleton.getInstance();
    Singleton s2 = Singleton.getInstance();
    这样创建两个实例化对象,但他们都共享一个 instance 对象,确保只有一个单例
  3. 构造方法 Singleton() 为什么是私有的?
    与成员变量相似,是为了防止外部实例化 ,防止其他类通过 new 关键字直接创建该类的实例(new 关键字本质是调用了类的构造方法),而应该通过特定途径(通常是类提供的静态方法,也就是 getInstance()
  4. getInstance() 方法为什么是公有的?
    很显然,这是我们暴露给其他类调用来创建实例的方法,因此必须是公有的,如果是私有那不就是成黑盒搁这里圈地自萌了~
  5. getInstance() 方法为什么是静态的?
    与成员变量相似,因为构造方法被私有化了,我们无法通过 new 关键字来实例化对象,而通过 类名.方法名 的方式可以直接访问

好的,我们已经明白了一个简单的单例模式的基本实现,了解了单例模式是什么,而在我们继续深入单例模式的各种实现之前,我们加入一个小插曲,你也许会有这样的疑问,我们为什么要有单例模式呢?单例模式应用场景有哪些?

二、为什么要有单例模式?

使用单例模式的原因

  1. 资源控制:单例模式可以用来控制系统中的资源,例如数据库连接池或线程池,确保这些关键资源不会被过度使用。

  2. 内存节省:当需要一个对象进行全局访问,但创建多个实例会造成资源浪费时,单例模式可以确保只创建一个实例,节省内存

  3. 共享:单例模式允许状态或配置信息在系统的不同部分之间共享,而不需要传递实例。

  4. 延迟初始化:单例模式支持延迟初始化,即实例在首次使用时才创建,而不是在类加载时。

  5. 一致的接口:单例模式为客户端提供了一个统一的接口来获取类的实例,使得客户端代码更简洁。

  6. 易于维护:单例模式使得代码更易于维护,因为所有的实例都使用相同的实例,便于跟踪和修改变更。

单例模式的应用场景

  1. 配置管理器:在应用程序中,配置信息通常只需要读取一次,并全局使用。单例模式用于确保配置管理器只被实例化一次。

  2. 日志记录器:一个系统中通常只需要一个日志记录器来记录所有的日志信息,使用单例模式可以避免日志文件的重复写入。

  3. 数据库连接池:数据库连接是一种有限的资源,使用单例模式可以确保数据库连接池的唯一性,并且能够重用连接,减少连接创建和销毁的开销。

  4. 线程池:类似于数据库连接池,线程池也是有限的资源,使用单例模式可以避免创建过多的线程,提高应用程序的并发性能。

  5. 任务调度器:在需要全局调度和管理的场景下,如定时任务调度器,单例模式提供了一个集中的管理方式。

  6. 网站的计数器:一般也是采用单例模式实现,否则难以同步。

值得注意的是,在许多框架中,单例模式也有广泛的应用,比如Spring,可以看看这篇文章

说一说Spring中的单例模式

总之,单例模式在确保资源有效管理、减少不必要的开销、以及提供全局访问控制方面有着广泛的应用。当然,滥用单例模式也可能导致代码的灵活性和可测试性降低,因此应当在适当的场景下谨慎使用。


好的我们在知晓单例模式的强大之处后,继续深入探讨一下单例模式的实现问题吧!

三、多线程下的单例模式

让我们编写一个测试,让三个线程去尝试实例化:

package org.example;

/**
 * @Author:wjy
 */
public class Main1 {
    public static void main(String[] args) {
        // 创建两个线程,尝试创建两个 Singleton 实例
        Thread t1 = new Thread(() -> {
            Singleton s = Singleton.getInstance();
            System.out.println("Thread 1 created instance: " + s);
        });

        Thread t2 = new Thread(() -> {
            Singleton s = Singleton.getInstance();
            System.out.println("Thread 2 created instance: " + s);
        });

        Thread t3 = new Thread(() -> {
            Singleton s = Singleton.getInstance();
            System.out.println("Thread 3 created instance: " + s);
        });

        // 启动线程
        t1.start();
        t2.start();
        t3.start();


        // 等待线程结束
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

饿汉式

因为饿汉式单例模式下,单例实例在类加载的时候就已经创建,并在静态化容器中完成初始化,所以他通常是线程安全的

在这里插入图片描述

可以看到,三个线程获取到的实例是相同的

懒汉式

接着是懒汉式单例模式
首先,让我们回顾一下核心的 getInstance 方法实现:

    // 得到 Singleton 的实例(唯一途径)
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }

测试一下,会发现出现问题,创建了三个实例!!

在这里插入图片描述

这是因为,如果三个线程在调用 getInstance() 方法时
同时判断 instance 为空,那么它们都会去实例化一个 instance 对象,这就变成三例了。如图所示:

在这里插入图片描述
在这里插入图片描述
所以,我们要解决的是线程安全问题。

懒汉加锁

当然,我们最容易想到的呢就是在方法上加锁,或者对类对象进行加锁


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

// 或者

public static Singleton getInstance() {
    synchronized(Singleton.class) {   
        if (instance== null) {
            instance = new Singleton();
        }
    }
    return instance ;
}

通过 synchronized 关键字,可以让每个线程在进入方法之前,都要等到别的线程都离开此方法,不会有两个线程同时进入此方法

在这里插入图片描述

可以看到,此时三个线程获取到的实例都是同样的了

但是这带来了新的问题:每次去获取对象都需要先获取锁,并发性能非常地差

加锁优化(双重检验加锁)

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

在这里插入图片描述

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

所以优化的关键点其实是在于 加锁的时机

    public static Singleton getInstance(){
        if(instance == null){ // 检查实例是否存在,不存在则进入下一步
            synchronized (Singleton.class){  // 防止多个线程同时进入创建实例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

上面的代码已经完美地解决了并发安全+性能低效问题:

  • 第2行代码,如果 instance 不为空,则直接返回对象,不需要获取锁;而如果多个线程发现 instance 为空,则进入分支;
  • 第3行代码,多个线程尝试争抢同一个锁,只有一个线程争抢成功,第一个获取到锁的线程会再次判断 instance 是否为空,因为 instance 有可能已经被之前的线程实例化
  • 其它之后获取到锁的线程在执行到第4行校验代码,发现 instance 已经不为空了,则不会再 new 一个对象,直接返回对象即可
  • 之后所有进入该方法的线程都不会去获取锁,在第一次判断 instance 对象时已经不为空了

一图胜千言,如图,假设这是初次实例化

  1. 三个线程都进行实例化判断,发现实例不存在,进入三线程的三国争霸(抢锁)!!

在这里插入图片描述

  1. 线程 C 抢赢了,获取到了锁,然后进行判断实例是否存在,发现不存在,直接 new 一个 —— 该单例就被线程C创建成功!
    在这里插入图片描述

  2. 线程 C 退出了,然后归还了锁,此时在锁外面双眼红光,饥渴难耐的线程B立马就抢了锁,在他正准备大干一场创建实例时,判断发现已经创建过了,实例 instance 存在,大败而归!

在这里插入图片描述

  1. 线程A 同理,接着线程 ABC就都退出了本次的 getInstance() 实例争霸赛

这里再着重讲述一下,为什么需要在加锁后再次进行实例化的判断呢?
根据上述步骤3,可以发现如果不进行判断,刚拿到锁的线程B会再次创建一个实例,此时就会创建两个实例,违背了单例模式的要求

因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)

使用volatile防止指令重排

创建一个对象,在JVM中会经过三步:

(1)为 instance 分配内存空间

(2)初始化 instance 对象

(3)将 instance 指向分配好的内存空间

而指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能

通俗点来讲,就是先给 instance 找到住的地方,然后把 instance 安排入住,最后给 instance 贴上门牌号
而指令重排序也就是为了提高效率,可能在为 instance 找到住的地方后,先不让他住进去,而是先贴门牌号再让他住进去

在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤时,

线程B判断 instance 已经不为空,获取到还未初始化的 instance 对象并返回,这就会报 NPE 异常。

注意在 if(instance == null)== 操作符判断的是该对象的内存地址,也就是步骤 3
还是门牌号的例子,== 就相当于检查门牌号,然后线程 B 发现你家有门牌号,就把你物业费账单提交了,但其实这时候你甚至还没入住!!

文字较为晦涩,可以看流程图:

在这里插入图片描述
使用 volatile 关键字可以防止指令重排序,​其原理较为复杂,这篇博客不打算展开,可以这样理解:使用 volatile 关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换,这样在多线程环境下就不会发生NPE异常了。

最终的代码如下:

public class Singleton {

    private static volatile Singleton instance;

    // 构造方法 private 化
    private Singleton(){

    }

    // 得到 Singleton 的实例(唯一途径)
    public static Singleton getInstance(){
        if(instance == null){ // 检查实例是否存在,不存在则进入下一步
            synchronized (Singleton.class){  // 防止多个线程同时进入创建实例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

四、破坏懒汉式单例与饿汉式单例

无论是完美的懒汉式还是饿汉式,终究敌不过反射和序列化,它们俩都可以把单例对象破坏掉(产生多个对象)。

反射破坏

  1. 演示利用反射破坏单例模式
  public static void main(String[] args) throws Exception {
        //获取Singletion类的字节码;
        Class<Singleton> singletonClass = Singleton.class;
        //获取无参构造方法,用来创建对象。
        Constructor con = singletonClass.getDeclaredConstructor();
        //由于是private修饰,所以使用暴力破解;
        con.setAccessible(true);
        //创建对象
        Singleton s1 = (Singleton) con.newInstance();
        Singleton s2 = (Singleton) con.newInstance();
        System.out.println(s1 == s2); // 返回 false
    }

上述的代码一针见血了:利用反射,强制访问类的私有构造器,去创建另一个对象

序列化与反序列化破坏

  1. 利用序列化与反序列化破坏单例模式
public static void main(String[] args) {
    // 创建输出流
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
    // 将单例对象写到文件中
    oos.writeObject(Singleton.getInstance());
    // 从文件中读取单例对象
    File file = new File("Singleton.file");
    ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
    Singleton newInstance = (Singleton) ois.readObject();
    // 判断是否是同一个对象
    System.out.println(newInstance == Singleton.getInstance()); // false
}

两个对象地址不相等的原因是:readObject() 方法读入对象时,它必定会返回一个新的对象实例,必然指向新的内存地址。

原因分析

实际上对于序列化与反序列化破坏单例模式的问题,主要是通过readObject()方法,出现了破坏单例模式的现象,主要是因为这个方法最后会通过反射调用无参数的构造方法创建一个新的对象,从而每次返回的对象都不一致。

对于反射破坏单例模式是因为单例模式通过 setAccessible(true) 指示反射的对象在使用时,取消了 Java 语言访问检查,使得私有的构造函数能够被访问,

而单例模式的设计在于只保留一个公有静态函数来获取唯一的实例,其他方法(构造函数)或字段为私有,外界不能访问。

而反射破坏了这一原则,它突破了构造函数私有的限制,可以获取单例类的私有构造函数并使用其创建多个对象。

问题解决

● 序列化、反序列方式破坏单例模式的解决方法
readObject()方法的调用栈的底层方法中有这么两个方法:
hasReadResolveMethod:
表示如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true

invokeReadResolve :通过反射的方式调用要被反序列化的类的readResolve方法。

详细讲解可以看这篇文章:
设计模式|序列化、反序列化对单例的破坏、原因分析、解决方案及解析

所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

public class Singleton implements Serializable {
    private static volatile Singleton singleton;

    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

	// 防止序列化
    private Object readResolve() {
        return singleton;
    }

}

● 反射方式破解单例的解决方法
因为反射是一种暴力获取对象实例的方法,因为他可以直接访问private修饰的构造函数,所以在对于反射方式破坏单例模式的问题上我们只能采取被动的防御,

既然你能访问我的构造函数,我就在我的构造函数中建立防御机制,不让你通过我的构造函数创建多个实例对象。

在构造方法来添加一些限制,如果存在实例,那么就抛出异常!

public class Singleton{
    private static volatile Singleton singleton;

    private Singleton() {
        if (Singleton.singleton != null) {
            throw new RuntimeException("不允许创建多个单例!");
        }
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

五、枚举实现

我们已经掌握了懒汉式与饿汉式的常见写法了,在《图解设计模式》以及《大话设计模式》中,并没有再做更多的涉及。但是,追求极致的我们,怎么能够止步于此,在《Effective Java》书中,给出了终极解决方法。

在 JDK1.5 后,使用 Java 语言实现单例模式的方式又多了一种:枚举

我们先来看看枚举如何实现单例模式的,如下代码:

public enum Singleton {
     INSTANCE;
     public void businessMethod() {
          System.out.println("我是一个单例!");
     }
}

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

我们可以简单地理解枚举实现单例的过程:在程序启动时,会调用Singleton的空参构造器,实例化好一个Singleton对象赋给INSTANCE,之后再也不会实例化

  1. 防止反射攻击
  • 枚举类型的实例在编译后会被编译成静态常量,并且在运行时通过Enum.valueOf()方法来获取实例。这个方法在内部会检查传递的名称是否与枚举定义中的名称匹配,并且直接返回对应的枚举实例,而不允许创建新的枚举实例。
  • 因此,即使调用者尝试使用反射创建新的枚举实例,也会因为名称不匹配而失败。例如,Enum.valueOf(MyEnum.class, "NEW_VALUE")将会抛出IllegalArgumentException
  1. 防止序列化/反序列化攻击
  • 枚举实例在序列化时会被转换为其名称,而不是实例本身。这意味着,即使反序列化时提供了枚举实例的字节流,也会根据名称来查找对应的枚举实例,而不是创建一个新的实例。
  • 由于枚举实例是静态的,反序列化时并不会创建新的实例,而是恢复之前序列化时的那个实例。这样,即使通过序列化/反序列化机制,也无法创建新的枚举实例。
  1. 防止直接实例化
  • 枚举类型默认有一个私有构造器,这意味着不能直接实例化枚举类型,只能通过枚举的静态方法来获取实例。
  • 例如,对于public enum MyEnum { INSTANCE },你不能写代码MyEnum myEnum = new MyEnum();,因为这会编译错误,因为MyEnum的构造器是私有的。

通过这些机制,枚举类有效地阻止了通过反射、序列化和反序列化机制来破坏单例的行为。这使得枚举类成为实现单例模式的一种非常安全和简洁的方法。


六、总结

(1)单例模式常见的写法有两种:懒汉式、饿汉式

(2)懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:Double Check + Lock,解决了并发安全和性能低下问题

(3)饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

(4)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;

(5)如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

(6)为了防止多线程环境下,因为指令重排序导致变量报NPE,需要在单例对象上添加volatile关键字防止指令重排序

(7)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例

参考文章:
我给面试官讲解了单例模式后,他对我竖起了大拇指!
设计模式之单例模式(七种方法超详细)
设计模式|序列化、反序列化对单例的破坏、原因分析、解决方案及解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/753136.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【高级篇】InnoDB引擎深入:核心机制与实战优化(十五)

引言 在探索了MySQL集群与分布式技术之后,我们进入了数据库引擎的核心地带——InnoDB。作为MySQL的默认存储引擎,InnoDB凭借其对事务的支持、行级锁定、高效的恢复机制以及复杂的内存管理,成为众多应用场景的首选。本章,我们将深入InnoDB的内部机制,透彻理解锁管理、事务…

【C++】动态内存管理new和delete

文章目录 一、C的内存管理方式二、new和delete的用法1.操作内置类型2.操作自定义内置类型 三、new和delete的底层实现1.operator new和operator delete函数2.new和delete的实现原理 四、定位new表达式五、malloc/free和new/delete的区别 一、C的内存管理方式 之前在C语言的动态…

找不到vcomp140.dll怎么办,总结多种解决方法

​在日常使用电脑的过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“vcomp140.dll丢失”。那么&#xff0c;vcomp140.dll是什么&#xff1f;它为什么会丢失&#xff1f;丢失后对电脑有什么影响&#xff1f;又该如何解决呢&#xff1f;本文将详细介绍vc…

leetcode 动态规划(基础版)单词拆分

题目&#xff1a; 题解&#xff1a; 一种可行的dp做法是基于完全背包问题&#xff0c;将s看成是一个背包&#xff0c;wordDict看作是物品&#xff0c;然后往s中放入物品判断最终是否可以变为给定的s即可。这道题和上一题都用到了在dp如何枚举连续子串和状态表示&#xff1a;枚…

一个 API 客户端和一份 TS 学习手册

第75期&#xff1a; Insomnia&#xff1a;超好看的 API 客户端 项目介绍&#xff1a; 一款适用于 GraphQL、REST、WebSockets 和 gRPC 的开源 API 客户端&#xff0c;颜值超高。 跨平台&#xff0c;支持 Mac、Windows 和 Linux。但不支持网页版&#xff0c;需要下载客户端。…

如何借助ai(文心一言)获取tushare的数据

1. 准备工作 确保已安装python &#xff0c;安装Tushare库 和文心一言的地址&#xff08;文心一言&#xff09;&#xff1a; 注册Tushare账号并获取Token&#xff1a;在Tushare官方网站注册账号&#xff0c;并获取个人Token。如下 tushare地址&#xff1a;&#xff08;点击即…

PD快充诱骗芯片工作原理,USB-C充电器出不来电压是什么原因?

一般使用Type-C接口的充电器基本上都是采用新的快充协议——PD快充协议&#xff0c;它不同于以前的USB-A的QC协议&#xff0c;这种协议&#xff0c;默认是没有快充电压输出的&#xff0c;VBUS和GND是0V。 所以&#xff0c;我们可以使用电阻的方式&#xff08;电流小&#xff09…

【Apache Doris】如何实现高并发点查?(原理+实践全析)

【Apache Doris】如何实现高并发点查&#xff1f;&#xff08;原理实践全析&#xff09; 一、背景说明二、原理介绍三、环境信息四、Jmeter初始化五、参数预调六、用例准备七、高并发实测八、影响因素九、总结 本文主要分享 Apache Doris 是如何实现高并发点查的&#xff0c;以…

突破SaaS产品运营困境:多渠道运营如何集中管理?

随着数字化时代的到来&#xff0c;SaaS&#xff08;软件即服务&#xff09;产品已成为企业日常运营不可或缺的工具。然而&#xff0c;在竞争激烈的市场环境下&#xff0c;SaaS产品运营越来越重视多渠道、多平台布局&#xff0c;以更广泛地触及潜在用户&#xff0c;然而&#xf…

《昇思25天学习打卡营第10天 | 昇思MindSporeFCN图像语义分割》

第10天 本节学习了FCN图像语义分割。全卷积网络是用于图像语义分割的一种框架。FCN是首个端到端&#xff08;end to end&#xff09;进行像素级&#xff08;pixel level&#xff09;预测的全卷积网络。FCN有两大明显的优点&#xff1a;一是可以接受任意大小的输入图像&#xff…

2024年跨境电商关键数据统计:市场规模将达到1.976万亿美元

预计2024年跨境电商消费市场规模将达到1.976万亿美元&#xff0c;占全球网上销售总额的31.2%。这一数据无疑展示了跨境电商市场的巨大潜力和迅猛增长趋势。 全球跨境电商的现状与未来 现状 2023年&#xff0c;全球跨境电商市场规模预计达到1.56万亿美元&#xff0c;占全球电子…

JAVA毕业设计145—基于Java+Springboot+vue+uniapp的驾校预约小程序(源代码+数据库+15000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvueuniapp的驾校预约小程序(源代码数据库15000字论文)145 一、系统介绍 本项目前后端分离&#xff0c;分为用户、教练、管理员三种角色 1、用户&#xff1a; …

PHP爬虫类的并发与多线程处理技巧

PHP爬虫类的并发与多线程处理技巧 引言&#xff1a; 随着互联网的快速发展&#xff0c;大量的数据信息存储在各种网站上&#xff0c;获取这些数据已经成为很多业务场景下的需求。而爬虫作为一种自动化获取网络信息的工具&#xff0c;被广泛应用于数据采集、搜索引擎、舆情分析…

unity-特效-雷达扫描效果

使用后处理方式制作 using System; using System.Collections; using System.Collections.Generic; using UnityEngine;public class GlobalScanEffect : MonoBehaviour {public float startScanRange 0;public float maxScanRange 20;public float scanWidth 3;public flo…

洁盟超声波清洗机怎么样?横向测评希亦、洁盟、苏泊尔超声波清洗机谁是实力派

生活中大多数人戴眼镜&#xff0c;但是很多人都不注意眼镜的保养&#xff0c;导致镜片越来越模糊&#xff0c;从而引发多边的状况发生&#xff0c;比如长久戴模糊不清的眼镜&#xff0c;视力会受到影响随之下降。甚至是眼镜长期不清洗&#xff0c;上面的灰尘、细菌会影响眼部健…

极限竞速地平线4卡顿?这样做快速解决地平线4卡顿问题

极限竞速地平线4全新开放式剧情的设计让玩家的每一次行动都能推动游戏的进程。时间、天气和四季的变化&#xff0c;都将在极限竞速地平线4这里得到真实的呈现。玩家将有机会在壮丽的原生4K和HDR画质下&#xff0c;欣赏到英国那湖泊、山谷、城堡和无数美景&#xff0c;体验一段从…

使用 Rustup 管理 Rust 版本

文章目录 安装 Rustup配置镜像源安装 Rustup 安装 RustVS Code插件创建项目代码示例 Rust 官网&#xff1a;https://www.rust-lang.org/zh-CN/Crates 包管理&#xff1a;https://crates.io/Rust 程序设计语言&#xff1a;https://kaisery.github.io/trpl-zh-cn/通过例子学 Rust…

docker 搭建 AI大数据模型 --- 使用GPU

docker 搭建 AI大数据模型 — 使用GPU方式 搭建本地大模型&#xff0c;最简单的方法&#xff01;效果直逼GPT 服务器GPU系统HP580 G8P40Rocky9.2 安装程序AnythingLLM前端界面Open WebUIChatOllamaollama 一、AnythingLLM 介绍 AnythingLLM 是 Mintplex Labs Inc. 开发的一…

根据后端返回的省市区重新封装树结构(省市区通过children表示)

对比图&#xff08;截取部分&#xff09;&#xff1a; 注&#xff1a;先看分步&#xff0c;最后会附上完整代码&#xff08;如果有用&#xff0c;可以给小编点个赞吗&#xff1f;十分感谢&#xff09; 1.首先将前端返回相同的省份只展示一次 const obj {}; let keyList []r…

2024HVV最新POC/EXP,目前有8000+个POC/EXP

点击"仙网攻城狮”关注我们哦~ 不当想研发的渗透人不是好运维 让我们每天进步一点点 简介 都是网上收集的POC和EXP&#xff0c;最新收集时间是2024年五月&#xff0c;需要的自取。 表里没有的可以翻翻之前的文章&#xff0c;资源比较零散没有整合起来。 文件链接&#xff…