设计模式-单例模式进阶

news2024/11/27 19:56:00

在前面的文章(设计模式-单例模式)中,我们分别介绍了四种单例设计模式,包括普通恶汉式单例、双重检查锁单例(DCL)、静态内部类单例以及枚举单例。但是,这四种模式还有一些问题我们没有仔细分析,以至于我们无法深入分析他们的优点以及可能存在的问题,更无法确定我们应该在什么场景下使用,在使用的时候我们有应该注意哪些方面。这些重要问题包括如下(但肯定不止,欢迎读者评论补充):

  1. JVM是如何保证并发加载类时只加载一次?
  2. DCL为什么需要使用volatile关键字修饰私有静态成员变量?
  3. 依赖私有构造方法为什么无法控制对象为单例?
  4. 枚举单例是如何保证绝对单例的?

一、类加载-创建唯一性

在普通饿汉式单例以及静态内部类单例,我们都利用JVM在并发加载类时肯定只会加载一次的特性,来保证单例对象有且仅有一次初始化动作。那有个问题是JVM类的加载真的只会加载一次吗?要回答这个问题,我们先回顾下类加载的过程。
类加载,简单来说就是根据字节流创建类型的过程。更加Javanic的可描述为将类的.class文件中的二进制数据读入内存中,将其放在方法区内存中(类信息),并且在堆中创建了该类的Class对象。JVM是对class信息是按需加载的,当使用到该类时,jvm才会将其对应的class文件加载到内存中(具体什么叫“使用”,本文不展开)。
JVM使用什么加载类呢?类加载器!JVM有以下几种类加载器:① 启动类加载器、② 扩展类加载器、③ 应用类加载器、④ 自定义类加载器。既然是回顾,这里也不会对类加载器过细介绍,这里仅点出对本文比较重要的点:

  1. 启动类加载器是C++实现的,没有Java对象。其余类加载器均为java.lang.ClassLoader的子类。
  2. 四个类加载器是由等级的,等级按序号顺序依次降低。根据双亲委派机制,类加载有核心的两点:自底向上检查类是否已加载、自顶向下尝试加载类。
  3. 每个类加载器都有命名空间来保存已加载的类。即,类加载器命名空间+类全限定名唯一确立了JVM中的一个类型。

启动类加载器一般用于加载rt.jar下的核心类,基本不涉及加载单例类。其他类加载器加载类的过程可以通过其父类ClassLoader的loadClass方法进行加载,源代码贴图如下:
在这里插入图片描述
可以看到,类加载器加载类是会根据要加载的类名获取同步锁。获取锁的过程实际上是通过ClassLoader的私有变量维护了一个ConcurrentHashMap,其中Key就是加载的类名,Value是Object对象。代码这里就不贴了,要说的是不同类加载器的并发锁Map是不一样的,因此这里的synchronized不会影响多个类加载器之间的并发加载。即使如此,由于双亲委派机制、类加载锁机制基本能够保证类只会被某个类加载器加载一次。
也就是说,类只会被类加载器加载一次是由双亲委派机制、类加载锁机制共同实现的。那我们自然能够想到,利用打破了双亲委派机制的自定义加载器就能够获取不同的单例对象。测试代码如下:

/**
 * 饿汉式单例模式
 */
public class HungrySingleton {
    // 类初始化时对象实例化
    private static final HungrySingleton hungrySingleton = new HungrySingleton();
    static {System.out.println("HungrySingleton, 饿汉式单例初始化....");}

    // 构造器私有化
    private HungrySingleton() {}

    // 向外暴露获取单例对象的方法
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}
/**
 * 客户端场景类
 */
public class Client {
    public static void main(String[] args) {
        /* 普通饿汉式单例获取对象实例 start */
        System.out.println(HungrySingleton.getInstance());
        System.out.println(HungrySingleton.getInstance());
        /* 普通饿汉式单例获取对象实例 end */

        CustomClassLoader CustomClassLoader = new CustomClassLoader("{项目路径}\\target\\classes\\");
        try {
            Class<?> clazz = CustomClassLoader.loadClass("com.design.单例模式.hungry.HungrySingleton");
            Constructor<?> constructor = clazz.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            Object obj = constructor.newInstance();   // 不能强转为HungrySingleton
            System.out.println(obj);
        } catch (Exception e) {
            System.out.println("自定义加载失败, msg=" + e.getMessage());
        }
    }
}
/**
 * 自定义类加载器-打破双亲委派机制
 */
public class CustomClassLoader extends ClassLoader {

    private final String baseUrl;

    public CustomClassLoader(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    @Override
    public Class<?> loadClass(String name) {
        try {
            String className = name.replace('.', '\\');
            String fileName = this.baseUrl + className + ".class";
            InputStream is = null;
            try {
                is = new FileInputStream(fileName);
            } catch (Exception ex) {
                // no handle
            }

            if (is == null) {   // 必须委派给应用加载器
                return super.loadClass(name);
            }

            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        } catch (Exception ex) {
            System.out.println("加载失败");
        }
        return null;
    }
}

运行Client.main方法,运行结果:
在这里插入图片描述
从上面结果上来看,破坏了双亲委派机制的自定义类加载器可以破坏JVM的类加载的唯一性,继而也破坏了饿汉式的单例性。因此,在我们使用饿汉式单例的时候,也需注意自定义类加载器对其唯一性的影响,以免出现及其难以排查的问题

二、DCL之Volatile思考

双重检查锁单例模式(DCL)可能是我们日常开发中经常会使用的用于创建单例对象的方案。前面在讲解DCL时,我们说到需要使用volatile关键字来解决指令重排带来的影响。这里我会先详细分析下使用volatile关键字的比较性,再发散分析其他可能存在的问题以及使用注意事项。

/**
 * 懒汉式单例模式-双重检查锁
 */
public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton instance;

    private LazyDoubleCheckSingleton() {}

    public static LazyDoubleCheckSingleton getInstance() {
        if (instance == null) {     // 第一重检查:保证性能不受锁的影响
            synchronized (LazyDoubleCheckSingleton.class){  // 不存在单例对象时,才进入同步区
                if(instance == null) {      // 第二重检查:进入同步区再次检查保证确实不存在单例对象
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }

        return instance;
    }
}

在这里插入图片描述
如上为DCL经典代码示范&核心流程示意图,为了保证多线程环境下高效获取单例对象的需求,DCL设计了两重检查来实现单例模式,具体有四个步骤:

  1. 判断单例对象是否存在?存在则返回
  2. 不存在则进入同步代码区
  3. 判断单例对象是否存在?存在则返回
  4. 不存在,则进行初始化对象,并返回

要理解DCL这四个步骤的意义,本文将从对象状态进行分析,由于是懒加载过程,对象的创建&初始化过程将会在大量流量请求getInstance方法中进行,因此getIntance的处理逻辑必然要和对象的状态相关。一个对象的创建大致可以分为以下几个状态,① 对象尚未被创建、② 对象已创建但未初始化、③ 对象已创建且被初始化。
我们先看第①个状态,对象尚未被创建,大量请求getInstance尝试获取单例对象,为防止多线程环境下对象被创建多次,必须使用synchronized关键字来控制对象的创建过程同时间只能有一个线程执行。另外,虽然同一时间不能有多个线程进入同步代码区,但这也并不意味着仅有一个线程执行同步区代码。只要同步锁不被占用就会有线程执行同步区代码,因此在同步区内部也需要判断单例对象是否已经存在。
再看第③个状态,对象已创建并且初始化完成。此时对象处理直接可用状态,请求getInstance方法获取对象可以直接返回,并不需要等待synchronized同步锁,影响服务性能。因此,我们需要在进入同步区之前通过对象是否已存在来判断直接返回OR尝试进去同步区。
先看前面这两个状态,你是否已经发现问题了呢。明显地,DCL并没有考虑到对象还有第②个状态,第②个状态实际是对象不可用状态。这就意味着第③状态的判断要以对象可不可用为准,而不是仅仅通过instance==null来判断。
好,如果你认同上面一点,我们就继续看下去。在大部分的DCL分析资料中,大家对于DCL中使用volatile关键字的原因是由于new关键字非原子性且存在指令重拍的可能。我认为是但并非简单如此,加上volatile关键字就没有问题吗?确实,还是有问题的。对象可不可用状态并非能够以私有成员变量是否为null、或对象构造函数执行完成为准绳。如下所示单例对象初始化过程,你猜测下是否存在问题?

/**
 * 懒汉式单例模式-双重检查锁
 */
public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton instance;

    @Getter@Setter
    private Object obj;

    private LazyDoubleCheckSingleton() {}

    public static LazyDoubleCheckSingleton getInstance() throws InterruptedException {
        if (instance == null) {     // 第一重检查:保证性能不受锁的影响
            synchronized (LazyDoubleCheckSingleton.class){  // 不存在单例对象时,才进入同步区
                if(instance == null) {      // 第二重检查:进入同步区再次检查保证确实不存在单例对象
                    instance = new LazyDoubleCheckSingleton();
                    instance.setObj(new Object());
                }
            }
        }
        return instance;
    }
}

如上示例代码,投入生产线上使用会不会存在问题?存在,在特殊并发场景下还是会出现获取的单例对象的属性obj为null,后续流程仍然会有出现空指针异常的问题。所以原罪并非new关键字的指令重拍,原罪其实是流程、方案!
怎么改正呢?回归本质,第一重检查应该是对象可不可用状态的判断,可用才能直接返回!因此问题找到了,第一重判断的含义存在误解。如何判断对象可用状态?看看你的同步区如何初始化的,哪些初始化步骤未完成会带来严重业务问题,那这些初始化状态在第一重就一定要检查。【题外话,防御性编程的重要性,以后获取的单例对象你还能完全信赖嘛】

public static LazyDoubleCheckSingleton getInstance() throws InterruptedException {
    if (!checkForSingletonStatus(instance)) {     // 第一重检查:保证性能不受锁的影响
        synchronized (LazyDoubleCheckSingleton.class){  // 不存在单例对象时,才进入同步区
            if(instance == null) {      // 第二重检查:进入同步区再次检查保证确实不存在单例对象
                instance = new LazyDoubleCheckSingleton();
                instance.setObj(new Object());
            }
        }
    }
    return instance;
}

// 检查单例对象的可用状态-用于第一重检查
private static boolean checkForSingletonAccessStatus(LazyDoubleCheckSingleton singleton) {
    if(singleton == null) { // 对象实例化检查
        return false;
    }

    if(singleton.getObj() == null) {    // obj属性检查
        return false;
    }
    // ...其他核心属性检查

    return true;
}

以上为改动后的DCL部分代码(volatile关键字也不需要使用),这才是双重检查锁单例模式完全正确理解。

三、私有构造-防君子设计

在大部分的单例模式设计思路上,都是利用了私有构造方法来保障类不会被在其他地方实例化。因为私有了嘛,只能我自己用,其他人不会用。大部分人应该也都知道了,这个前提是不正确的,私有实在是防君子不防小人呐。这个问题也就导致了私有构造的枚举类也可以被一些小手段给实例化了。这些手段包括反射、序列化等。

3.1 反射

下面给出一个反射破坏单例的示例:

public static void main(String[] args) {
        // 使用单例破坏了私有构造类型单例
        try {
            Class<LazyStaticInnerClassSingleton> clazz = LazyStaticInnerClassSingleton.class;

            // 通过反射获取类的私有构造方法
            Constructor constructor = clazz.getDeclaredConstructor(null);
            constructor.setAccessible(true);    // 强制访问

            Object objV1 = constructor.newInstance();
            Object objV2 = constructor.newInstance();
            System.out.println(objV1 == objV2);     // false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如上,通过简单的反射就实例化了多个单例类。针对这个问题,也有人提出在构造方法中判断对象是否已经存在,如果已经存在就抛出异常,避免类型的再次实例化。这是个很好的方案,仅仅对静态内部类或饿汉式单例来说是可以的,对于其他类型私有构造单例来说是无效的。
为什么这么说呢,静态内部类单例模式中,单例类的私有构造方法是供静态内部类使用的,内部判断对象是否存在来决定构造方法是否抛出异常,这是非常合适的。对象存在,那就说明静态内部类已经被加载过了不允许再次实例化,相反对象不存在,那就说明本次实例化动作是由静态内部类加载并初始化产生的。饿汉式单例而言,类加载过程已经初始化了单例对象,直接在构造方法中抛出异常即可,不需要判断。而对于DCL懒加载单例模式,只要在懒加载之前使用反射调用构造方法获取实例仍然是没有问题的。

3.2 反序列化

获取对象的另外一种方法也可以是通过反序列化的方式。如果单例类实现了Serializable接口,那么该单例就会被反序列化破坏。理解起来应该十分简单,这里给出测试代码:

public static void main(String[] args) {
 LazyStaticInnerClassSingleton instance = LazyStaticInnerClassSingleton.getInstance();

 try {
     String fileName = "SerializableSingleton.obj";
     FileOutputStream fos = new FileOutputStream(fileName);
     ObjectOutputStream oos = new ObjectOutputStream(fos);
     oos.writeObject(instance);
     oos.flush();oos.close();fos.close();

     FileInputStream fis = new FileInputStream(fileName);
     ObjectInputStream ois = new ObjectInputStream(fis);
     LazyStaticInnerClassSingleton o = (LazyStaticInnerClassSingleton) ois.readObject();
     ois.close();fis.close();

     System.out.println(o == instance);      // false
 } catch (Exception e) {
     // skip
 }
}

因此,在使用单例类时需注意尽可能不要实现Serializable接口,否则就需要考虑反序列化带来的影响。然而,我们也可以给单例类增加readResolve方法强制返回单例对象,这时反序列化出来的对象就符合预期了。

public class Singleton implements Serializable {
	...
	public static Singleton getInstance() {
        ...
    }
    private Object readResolve() {
        return getInstance();
    }
}

为什么增加了readResolve方法就可以呢,我们来看下ObjectInputStream#readObject()方法是如何反序列化的。
在这里插入图片描述readObject()方法会根据类型标记来判断是普通对象还是其他,比如枚举等,并分别用不同的方法来进行处理。进入readOrdinaryObject()方法中,这个方法内部会进行实例化对象。
在这里插入图片描述
在这里插入图片描述
从上述源码上来看,反序列化流程中还是会创建一个新的对象,但是后续会判断是否存在resolveObject方法,如果存在则调用返回并覆盖之前创建的新对象。
返序列化是如何判断是否存在readResolve()方法呢?答案是在ObjectOutputStream#writeObject时会找到方法名为readResolve、返回值为Object类型且不含入参的方法。找到后记录在readResolveMethod属性中,反序列化时会判断是否存在这个属性,即resolve方法。

四、枚举单例

前面提到了很多可以破坏单例模式的方法,比如自定义类加载器、反射、序列化等方式均可以破坏单例。这个章节我们就一起来看下枚举是如何保证绝对单例的。

4.1 枚举本质

先来看看枚举类的本质。枚举实际上还是一个类,只不过由于其特殊性,在写法上进行了简化。如下是一个普通枚举类字节码信息:
在这里插入图片描述
从字节码信息中,我们看到枚举实际上还有一些我们看不到的东西,比如这里有2个成员变量,有values、valueOf、类静态方法等。我们通过jad工具反编译其class文件得到内容如下:

public final class EnumSingleton extends Enum {

	public static final EnumSingleton INSTANCE;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }
}

上面就是编译后的枚举类,从其内容上来看有以下几个特点:

  1. 枚举类使用final修饰,因此该类不会被继承
  2. 枚举类都继承自Enum抽象类。
  3. 枚举类的实例都是在枚举类加载时就初始化,因此枚举本身也属于懒加载多单例对象(不同对象区别且唯一)
  4. 枚举类的所有实例都存储在其数组类型的类变量中。

4.2 反射无效

自定义类加载器也是通过反射来获取实例化对象的,因此这里一起说明。反射是通过获取构造函数,然后通过newInstance方法创建实例。根据编译后的枚举类,我们知道枚举是有一个有参构造方法的,第一个参数为枚举实例名称,第二个参数为枚举实例序号。既然有构造方法为啥不会被反射出实例呢?答案就在newInstance方法中。
在这里插入图片描述

4.3 序列化无效

枚举类也无法序列化出实例吗?我们在前文讲解ObjectInputStream#readObject()方法是如何反序列化时截图中给出不同对象实际反序列化的方法。对于枚举类,实际是通过readEnum()方法来进行反序列化的,其内部逻辑如下:
在这里插入图片描述
从代码可以看出,枚举类的反序列化实际上是通过valueOf()方法获取枚举类变量中的实例的,这个函数返回的就是之前枚举类加载时初始化的对象,因此能够保证序列化出来的对象也是之前的同一对象。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【面试题】万字总结MYSQL面试题

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 1、三大范式 2、DML 语句和 DDL 语句区别 3、主键和外键的区别 4、drop、delete、truncate 区别 5、基础架构 6、MyISAM 和 InnoDB 有什么区别&#xff1f; 7、推荐自增id作为…

【mac系统】mac系统调整妙控鼠标速度

当下环境&#xff1a; mac系统版本&#xff0c;其他系统应该也可以&#xff0c;大家可以自行试下&#xff1a; 鼠标 mac妙控鼠标&#xff0c;型号A1657 问题描述&#xff1a; 通过mac系统自带的鼠标速度调节按钮&#xff0c;调到最大后还是感觉移动速度哦过慢 问题解决&…

若依微服务整合activiti7.1.0.M6

若依微服务3.6.3版本整合activiti7&#xff08;7.1.0.M6&#xff09; 目前有两种办法集成activiti7 放弃activiti7新版本封装的API&#xff0c;使用老版本的API&#xff0c;这种方式只需要直接集成即可&#xff0c;在7.1.0.M6版本中甚至不需要去除security的依赖。不多介绍&a…

日常问题记录-Android-Bug-OOM

大家好哇&#xff0c;我是梦辛工作室的灵&#xff0c;最近的项目中&#xff0c;我又遇到了一个bug&#xff0c;就是我写了一个类 将app会用到的Bitmap缓存起来进行管理&#xff0c;防止OOM嘛&#xff0c;不过莫名奇妙的事情还是发生了&#xff0c;内存依旧上涨&#xff0c;且没…

数据结构day7(2023.7.23)

一、Xmind整理&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;结点之间的关系 练习2&#xff1a;二叉树的特殊形态 练习3&#xff1a;满二叉树的形态 练习4&#xff1a;完全二叉树的形态 满二叉树一定是完全二叉树&#xff0c;完全二叉树不一定是满二叉树 练习5&am…

Windows系统自检中断导致存储文件系统损坏的服务器数据恢复案例

服务器数据恢复环境&#xff1a; 一台挂载在Windows server操作系统服务器上的v7000存储&#xff0c;划分了一个分区&#xff0c;格式化为NTFS文件系统&#xff0c;该分区存放oracle数据库。 服务器故障&#xff1a; 服务器在工作过程中由于未知原因宕机&#xff0c;工作人员重…

机器学习深度学习——线性回归

之前已经介绍过线性回归的基本元素和随机梯度下降法及优化&#xff0c;现在把线性回归讲解完&#xff1a; 线性回归 矢量化加速正态分布与平方损失从线性回归到深度网络神经网络图生物学 矢量化加速 在训练模型时&#xff0c;我们常希望能够同时处理小批量样本&#xff0c;所以…

涵子来信——自己的电脑——谈谈想法

大家好&#xff1a; 上一次谈论了苹果的那些事&#xff0c;今天我们来聊聊电脑。 我的第一台电脑现在成了这样子&#xff1a; 很多人以为是我自己拆了电脑做研究&#xff0c;其实是我的第一台电脑&#xff0c;真的坏了。 2021年&#xff0c;我有了属于我自己的第一台电脑&am…

STM32 HAL库串口重映射printf

添加代码 #include "stdio.h" int fputc(int ch, FILE *f) {HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);return ch; }keil设置 实现效果&#xff1a; 打印变量 printf("Hello, I am %s\r\n", "iii"); // printf输出字符…

Kubernetes pv-pvc-nfs-service综合实验

目录 实验&#xff1a;pv-pvc-nfs-service综合实验 实验环境 实验描述 实验拓扑图&#xff1a; 实验步骤&#xff1a; 1、修改nfs服务器的主机名&#xff1a; 2、搭建nfs服务器&#xff1a;(131条消息) 搭建NFS服务器_搭建nfs存储_Claylpf的博客-CSDN博客 3、测试k8s上…

极速跳板机登陆服务器

目录 一&#xff1a;简单登陆跳板器二&#xff1a;一键申请相关的服务器权限三&#xff1a;简化登陆 一&#xff1a;简单登陆跳板器 登陆公司提供的网址&#xff0c; 下载自己的专属RSA密钥。在密钥文件处&#xff0c; 执行登陆指令&#xff1a; ssh -p 36000 -i id_rsa 用户跳…

【MATLAB】 二维绘图,三维绘图的方法与函数

目录 MATLAB的4种二维图 1.线图 2.条形图 3.极坐标图 4.散点图 三维图和子图 1.三维曲面图 2.子图 MATLAB的4种二维图 1.线图 plot函数用来创建x和y值的简单线图 x 0:0.05:30; %从0到30&#xff0c;每隔0.05取一次值 y sin(x); plot(x,y) %若(x,y,LineWidth,2) 可…

mac 移动硬盘未正常退出,再次链接无法读取(显示)

&#xff08;1&#xff09;首先插入自己的硬盘&#xff0c;然后找到mac的磁盘工具 &#xff08;2&#xff09;打开磁盘工具&#xff0c;发现自己的磁盘分区在卸载状态&#xff1b;点击无法成功装载。 &#xff08;3&#xff09;打开终端&#xff0c;输入 diskutil list查看自…

Spring Cloud【Resilience4j(重试机制、异常比例熔断降级、信号量隔离实现、线程池隔离实现、限流 ) 】(五)

目录 服务断路器_Resilience4j重试机制 服务断路器_Resilience4j异常比例熔断降级 服务断路器_Resilience4j慢调用比例熔断降级 服务断路器_Resilience4j信号量隔离实现 服务断路器_Resilience4j线程池隔离实现 服务断路器_Resilience4j限流 服务网关Gateway_微服务中…

Docker 的数据管理、容器互联、镜像创建

目录 一、数据管理 1.数据卷 2. 数据卷容器 二、容器互联&#xff08;使用centos镜像&#xff09; 三、Docker 镜像的创建 1.基于现有镜像创建 1.1首先启动一个镜像&#xff0c;在容器里修改 1.2将修改后的容器提交为新的镜像&#xff0c;需使用该容器的id号创建新镜像 …

JAVA设计模式——模板设计模式(itheima)

JAVA设计模式——模板设计模式(itheima) 文章目录 JAVA设计模式——模板设计模式(itheima)一、模板类二、子类2.1 Tom类2.2 Tony类 三、测试类 一、模板类 package _01模板设计模式;public abstract class TextTemplate{public final void write(){System.out.println("&…

使用 Docker 快速上手官方版 LLaMA2 开源大模型

本篇文章&#xff0c;我们聊聊如何使用 Docker 容器快速上手 Meta AI 出品的 LLaMA2 开源大模型。 写在前面 昨天特别忙&#xff0c;早晨申请完 LLaMA2 模型下载权限后&#xff0c;直到晚上才顾上折腾了一个 Docker 容器运行方案&#xff0c;都没来得及写文章来聊聊这个容器怎…

c# 使用socket进行tcp通信

服务端开启监听代码示例&#xff1a; //服务端开启监听示例Socket ListenSocket;private void btnStartListen_Click(object sender, EventArgs e){if (ListenSocket null){ListenSocket new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPAd…

【cef】2023最新windows编译全流程

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ 准备工作环境准备确定要编译的cef版本 2️⃣ 搭建编译环境保证可以访问chrome源码创建代码目录下载自动构建脚本创建.boto文件checkout 版本号创建2个编译脚本 3️⃣ 开始编译下载代码真正的拉取代码修改.gclient运行build.ba…

Debezium日常分享系列之:在 OpenShift 上部署 Debezium

Debezium日常分享系列之&#xff1a;在 OpenShift 上部署 Debezium 一、先决条件二、部署 Strimzi Operator三、为数据库创建机密四、部署Apache Kafka五、部署数据源六、部署 Debezium 连接器七、创建 Debezium 连接器八、验证部署 此过程用于在 Red Hat 的 OpenShift 容器平台…