SpringBoot + Disruptor实现高并发内存消息队列

news2024/10/2 4:45:23

1. 简介

  Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注。
  Disruptor区别于Kafka、RabbitMQ等消息队列,它是一个高性能的线程间异步通信的框架,即在同一个JVM进程中的多线程间消息传递。和ArrayBlockingQueue比较类似,但是它是一个有界无锁的高并发队列,如果项目中使用ArrayBlockingQueue在多线程之间传递消息,可以考虑是用Disruptor来代替。
  Github:https://github.com/LMAX-Exchange/disruptor
  文档:https://lmax-exchange.github.io/disruptor/
  资料及测试结论:https://lmax-exchange.github.io/disruptor/disruptor.html#_overview

2. 官方测试

  • 吞吐量性能测试

  参考ArrayBlockingQueue(简称ABQ)在不同机器、不同消费模式(下文详细介绍)环境下的性能对比,详情浏览官方测试报告:https://lmax-exchange.github.io/disruptor/disruptor.html#_throughput_performance_testing

Nehalem 2.8Ghz – Windows 7 SP1 64-bitSandy Bridge 2.2Ghz – Linux 2.6.38 64-bit
ABQDisruptorABQDisruptor
Unicast: 1P – 1C5,339,25625,998,3364,057,45322,381,378
Pipeline: 1P – 3C2,128,91816,806,1572,006,90315,857,913
Sequencer: 3P – 1C5,539,53113,403,2682,056,11814,540,519
Multicast: 1P – 3C1,077,3849,377,871260,73310,860,121
Diamond: 1P – 3C2,113,94116,143,6132,082,72515,295,197
  • 延迟性能测试

  参考ArrayBlockingQueue(简称ABQ)在相同机器、相同同消费模式环境下的对比,详情浏览官方测试报告:https://lmax-exchange.github.io/disruptor/disruptor.html#_latency_performance_testing

Array Blocking Queue (ns)Disruptor (ns)
Min Latency14529
Mean Latency32,75752
99% observations less than2,097,152128
99.99% observations less than4,194,3048,192
Max Latency5,069,086175,567

3. 相关博客

  【面试专栏】Java 阻塞队列【面试专栏】Java常用并发工具类【面试专栏】JAVA锁机制【面试专栏】JAVA CAS(Conmpare And Swap)原理【面试专栏】Java并发编程:volatile关键字

4. Java阻塞队列存在的弊端

  常用的Java阻塞队列:

名称是否有界是否加锁数据结构队列类型
ArrayBlockingQueue有界加锁数组阻塞
LinkedBlockingQueue可选有界加锁链表阻塞
PriorityBlockingQueue无界加锁数组阻塞
DelayQueue无界加锁数组阻塞
LinkedTransferQueue无界无锁链表阻塞
LinkedBlockingDeque可选有界有锁链表阻塞

  在使用中,为了防止生产过快而消费不及时导致的内存溢出,或垃圾回收频繁导致的性能问题,一般会使用有界且数据结构为数组的阻塞队列,即:ArrayBlockingQueue。但是,ArrayBlockingQueue使用加锁的方式保证线程安全,在低延迟的场景中表现悲观,且存在伪共享的问题,因此结果不尽人意。

5. 伪共享

5.1 CPU内部存储结构

  现在的CPU都是多个CPU核心,如下图。为了提高访问效率,都有缓存行。每个核中都有L1 Cache和L2 Cache,L3 Cache则在多核之间共享。CPU在执行运算时,首先从L1 Cache查找数据,找不到则以一次从L2 Cache、L3 Cache查找,如果还是没有,则会去内存中查找,路径越长,耗时越长,性能越低。当数据被修改后,通过主线通知其他CPU将读取的数据标记为失效状态,下次访问时从内存重新读取数据到Cache。
  对于计算机的存储设备而言,除了CPU之外,外部还有内存和磁盘。如下图,存储容量越来越大,成本越来越大,但访问速度却越来越慢。

5.2 缓存行

  CPU从内存中加载数据时,并不是一个字节一个字节的加载,而是一块一块的的加载数据,这样的一块称为:缓存行(Cache Line),即缓存行是CPU读取数据的最小单位。
  CPU的缓存行一般为32 ~ 128字节,常见的CPU缓存行为64字节。

# 查询CPU的Cache Line大小
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size

  对于数组而言,CPU每次会加载数据中多个数据到Cache中。所以,如果按照物理内存地址分布的顺序去访问数据,Cache命中率最高,从而减少从内存加载数据的次数,提高性能。
  但对于单个变量而言,会存在Cache伪共享的问题。假设定义衣蛾Long类型的变量A,占用8个字节,则CPU每次从内存中读取数剧是,会连同连续地址内的其余7个数据一并加载到Cache中。

5.3 伪共享

  多个CPU缓存遵循MESI协议
  假设,定义两个变量A、B,线程1绑定Core1,读取变量A,线程2绑定Core2,读取变量B。
  Core1和Core2分别读取变量A、B到Cache中,但变量A、B在同一Cache Line,因此,Core1和Core2都会把数据A、B加载到Cache中。
  线程1通过Core1修改变量A。首先通过主线发送消息给Core2将存放变量A的Cache Line标记为失效状态,然后将Core1中的Cache Line的状态标为已修改,并修改数据。
  线程2修改变量B时,发现Cache Line的状态为失效,并且Core1中的Cache Line为已修改状态,则先把Core1中的变量A、B写回内存,然后从内存中重新读取变量A、B。然后通过主线发送消息给Core1将存放变量B的Cache Line标记为失效状态,然后将Core2中的Cache Line的状态标为已修改,并修改数据。
  如果线程1、2频繁交替修改变量A、B,则会重复以上步骤,导致Cache没有意义。虽然变量A、B之间没有任何关系,但属于同一Cache Line。
  这种多个线程同时读写同一个Cache Line的不同变量而导致CPU Cache失效的现象称为伪共享(False Sharing)。

5.4 ArrayBlockingQueue中的伪共享

  查看ArrayBlockingQueue源码,有三个核心变量:

# 出队下标
/** items index for next take, poll, peek or remove */
int takeIndex;

# 入队下标
/** items index for next put, offer, or add */
int putIndex;

# 队列中元素数量
/** Number of elements in the queue */
int count;

  这三个变量很容易放到同一Cache Line,这也是影响ArrayBlockingQueue性能的重要原因之一。

6. Disruptor如何避免伪共享

  ArrayBlockingQueue因为加锁和伪共享的问题影响性能,那Disruptor是如何避免这两个问题来提供性能的呢?主要表现在以下几个方面:

  • 采用环形数组结构
  • 采用CAS无锁方式
  • 添加额外的信息避免伪共享
6.1 环形数组结构

  环形数组(RingBuffer)结构是Disruptor的核心。官网对环形数据结构的剖析:Dissecting the Disruptor: What’s so special about a ring buffer?
优势:

  • 数组结构:当CPU Cache加载数据时,相邻的数据也会被加载到Cache中,避免CPU频繁从内存中获取数据。
  • 避免GC:实质还是一个普通的数组,当数据填满队列时(2^n - 1)时,再次添加数据回覆盖之前的数据。
  • 位运算:数组大小必须为2的n次方,通过位运算提高效率。
6.2 CAS无锁方式

        Disruptor与传统的队列不同,分为队首指针和队尾指针,而是只有一个游标器Sequencer,它可以保证生产的消息不回覆盖还未消费的消息。Sequencer分为两个实现类:SingleProducerSequencerMultiProducerSequencer,即单生产者和多生产者。当单个生产者时,生产者每次从RingBuffer中获取下一个可以生产的位置,然后存放数据;消费者先获取最大的可消费的位置,再读取数据进行消费。当多个生产者时,每个生产者下通过CAS竞争获取可以生产的位置,然后存放数据;每个消费者都需要先获取最大可消费的下标,然后读取数据进行消费。

6.3 添加额外信息

        RingBuffer的下标是一个volatile变量,即不用加锁就能保证多线程安全,同时每个long类型的下标还会附带7个long的额外变量,避免伪共享的问题。

class LhsPadding
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding
{
    protected volatile long value;
}

class RhsPadding extends Value
{
    protected long p9, p10, p11, p12, p13, p14, p15;
}

7. 示例代码

  • 创建项目
  • 修改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.c3stones</groupId>
    <artifactId>springboot-disruptor-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  • 创建消息模型类
      用于在Disruptor之间传递消息。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 消息
 *
 * @author CL
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {

    private Object data;

    @Override
    public String toString() {
        return data.toString();
    }

}
  • 创建生产者
import com.c3stones.model.Message;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import lombok.RequiredArgsConstructor;

/**
 * 生产者
 *
 * @author CL
 */
@RequiredArgsConstructor
public class Producer {

    private final Disruptor disruptor;

    /**
     * 发送数据
     *
     * @param data 数据
     */
    public void send(Object data) {
        RingBuffer<Message> ringBuffer = disruptor.getRingBuffer();
        // 获取可以生成的位置
        long next = ringBuffer.next();
        try {
            Message msg = ringBuffer.get(next);
            msg.setData(data);
        } finally {
            ringBuffer.publish(next);
        }
    }

}
  • 创建分组模式消费者
import com.c3stones.model.Message;
import com.lmax.disruptor.WorkHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 分组消费
 *
 * @author CL
 */
@Slf4j
@RequiredArgsConstructor
public class GroupConsumer implements WorkHandler<Message> {

    /**
     * 消费着编号
     */
    private final Integer number;

    /**
     * 分组消费:每个生产者生产的数据只能被一个消费者消费
     *
     * @param message 消息
     */
    @Override
    public void onEvent(Message message) {
        log.info("Group Consumer number: {}, message: {}", number, message);
    }

}
  • 创建重复模式消费者
import com.c3stones.model.Message;
import com.lmax.disruptor.EventHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 重复消费
 *
 * @author CL
 */
@Slf4j
@RequiredArgsConstructor
public class RepeatConsumer implements EventHandler<Message> {

    /**
     * 消费着编号
     */
    private final Integer number;

    /**
     * 重复消费:每个消费者重复消费生产者生产的数据
     *
     * @param message    消息
     * @param sequence   当前序列号
     * @param endOfBatch 批次结束标识(常用于将多个消费着的数据依次组合到最后一个消费者统一处理)
     */
    @Override
    public void onEvent(Message message, long sequence, boolean endOfBatch) {
        log.info("Repeat Consumer number: {}, message: {}, curr sequence: {}, is end: {}",
                number, message, sequence, endOfBatch);
    }

}
  • 创建通用模式消费者
      一般采用这种更加通用的模式。
import com.c3stones.model.Message;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.WorkHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 消费者
 *
 * @author CL
 */
@Slf4j
@RequiredArgsConstructor
public class Consumer implements WorkHandler<Message>, EventHandler<Message> {

    private final String desc;

    /**
     * 重复消费:每个消费者重复消费生产者生产的数据
     *
     * @param message    消息
     * @param sequence   当前序列号
     * @param endOfBatch 批次结束标识(常用于将多个消费着的数据依次组合到最后一个消费者统一处理)
     */
    @Override
    public void onEvent(Message message, long sequence, boolean endOfBatch) {
        this.onEvent(message);
    }

    /**
     * 分组消费:每个生产者生产的数据只能被一个消费者消费
     *
     * @param message 消息
     */
    @Override
    public void onEvent(Message message) {
        log.info("Group Consumer describe: {}, message: {}", desc, message);
    }

}

8. 单元测试

  • 测试分组消费:每个生产者生产的数据只能被一个消费者消费
import com.c3stones.consumer.GroupConsumer;
import com.c3stones.model.Message;
import com.c3stones.producer.Producer;
import com.lmax.disruptor.dsl.Disruptor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.concurrent.Executors;

/**
 * 分组消费 单元测试
 *
 * @author CL
 */
public class GroupConsumerTest {

    private Disruptor<Message> disruptor;

    @Before
    public void init() {
        GroupConsumer a = new GroupConsumer(1);
        GroupConsumer b = new GroupConsumer(2);

        disruptor = new Disruptor(Message::new, 1024, Executors.defaultThreadFactory());

        disruptor.handleEventsWithWorkerPool(a, b);

        disruptor.start();
    }

    @After
    public void close() {
        disruptor.shutdown();
    }

    @Test
    public void test() {
        Producer producer = new Producer(disruptor);

        Arrays.asList("aaa", "bbb").forEach(data -> producer.send(data));
    }

}

  单元测试结果:

[pool-1-thread-1] INFO com.c3stones.consumer.GroupConsumer - Group Consumer number: 1, message: bbb
[pool-1-thread-2] INFO com.c3stones.consumer.GroupConsumer - Group Consumer number: 2, message: aaa
  • 测试重复消费:每个消费者重复消费生产者生产的数据
import com.c3stones.consumer.RepeatConsumer;
import com.c3stones.model.Message;
import com.c3stones.producer.Producer;
import com.lmax.disruptor.dsl.Disruptor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.concurrent.Executors;

/**
 * 重复消费 单元测试
 *
 * @author CL
 */
public class RepeatConsumerTest {

    private Disruptor<Message> disruptor;

    @Before
    public void init() {
        RepeatConsumer a = new RepeatConsumer(1);
        RepeatConsumer b = new RepeatConsumer(2);

        disruptor = new Disruptor(Message::new, 1024, Executors.defaultThreadFactory());

        disruptor.handleEventsWith(a, b);

        disruptor.start();
    }

    @After
    public void close() {
        disruptor.shutdown();
    }

    @Test
    public void test() {
        Producer producer = new Producer(disruptor);

        Arrays.asList("aaa", "bbb").forEach(data -> producer.send(data));
    }

}

  单元测试结果:

[pool-1-thread-1] INFO com.c3stones.consumer.RepeatConsumer - Repeat Consumer number: 1, message: aaa, curr sequence: 0, is end: false
[pool-1-thread-2] INFO com.c3stones.consumer.RepeatConsumer - Repeat Consumer number: 2, message: aaa, curr sequence: 0, is end: false
[pool-1-thread-1] INFO com.c3stones.consumer.RepeatConsumer - Repeat Consumer number: 1, message: bbb, curr sequence: 1, is end: true
[pool-1-thread-2] INFO com.c3stones.consumer.RepeatConsumer - Repeat Consumer number: 2, message: bbb, curr sequence: 1, is end: true
  • 测试链路模式
/**
 * 测试链路模式
 * <p>
 * p => a -> b -> c
 * </p>
 */
@Test
public void testChain() throws InterruptedException {
    Consumer a = new Consumer("a");
    Consumer b = new Consumer("b");
    Consumer c = new Consumer("c");

    Disruptor<Message> disruptor = new Disruptor(Message::new, 1024, Executors.defaultThreadFactory());

    disruptor.handleEventsWith(a).then(b).then(c);

    disruptor.start();

    Producer producer = new Producer(disruptor);

    producer.send("Chain");

    Thread.sleep(1000);

    disruptor.shutdown();
}

  单元测试结果:

[pool-1-thread-1] INFO com.c3stones.consumer.Consumer - Group Consumer describe: a, message: Chain
[pool-1-thread-2] INFO com.c3stones.consumer.Consumer - Group Consumer describe: b, message: Chain
[pool-1-thread-3] INFO com.c3stones.consumer.Consumer - Group Consumer describe: c, message: Chain
  • 测试钻石模式
/**
     * 测试钻石模式
     * <p>
     *           a
     *        ↗     ↘
     *  p =>            c
     *        ↘     ↗
     *           b
     * </p>
     */
    @Test
    public void testDiamond() throws InterruptedException {
        Consumer a = new Consumer("a");
        Consumer b = new Consumer("b");
        Consumer c = new Consumer("c");

        Disruptor<Message> disruptor = new Disruptor(Message::new, 1024, Executors.defaultThreadFactory());

        disruptor.handleEventsWithWorkerPool(a, b).then(c);

        disruptor.start();

        Producer producer = new Producer(disruptor);

        producer.send("Diamond1");
        producer.send("Diamond2");

        Thread.sleep(1000);

        disruptor.shutdown();
    }

  单元测试结果:

[pool-1-thread-1] INFO com.c3stones.consumer.Consumer - Group Consumer describe: a, message: Diamond2
[pool-1-thread-2] INFO com.c3stones.consumer.Consumer - Group Consumer describe: b, message: Diamond1
[pool-1-thread-3] INFO com.c3stones.consumer.Consumer - Group Consumer describe: c, message: Diamond1
[pool-1-thread-3] INFO com.c3stones.consumer.Consumer - Group Consumer describe: c, message: Diamond2

9. 项目地址

  spring-boot-disuptor-demo

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

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

相关文章

【思维模型】概率思维的价值:找到你的人生算法,实现阶级跃迁!

把同样公平的机会放在放在很多人面前,不同的人生算法,会得到迥然不同的结果。 概率思维是什么? 【ChatGPT】概率思维是一种通过使用数学模型来思考和评估不确定性事件的方法。它通过计算不同可能性的概率来预测事件的结果,并评估风险和机会。 概率思维的价值在于它可以帮…

ChatYuan元语AI: 类似ChatGPT功能型对话大模型

ChatYuan元语AI 元语智能开发团队训练了一个类似ChatGPT的功能型对话大模型ChatYuan. 类似ChatGPT模型, 中文开源版,功能型对话大语言模型. 功能有:支持训练端到端文本生成文本生成情感分析句子相似度零样本分类命名实体识别翻译自然语言推理问答文本纠错文本摘要FAQ问答文本…

终于体验了一下ChatGPT

再次尝试 隔了一天&#xff0c;今天&#xff08;2023-2-11&#xff09;再试一下。真的是一下。。。&#xff08;如果没有境外环境的&#xff0c;大家还是在网上找个共享账号试一下吧。网上有人分享的&#xff0c;大家细找一下就可以&#xff0c;我就不在这里发出来了。。。&…

微信小程序 Springboot校运会高校运动会管理系统

3.1小程序端 小程序登录页面&#xff0c;用户也可以在此页面进行注册并且登录等。 登录成功后可以在我的个人中心查看自己的个人信息或者修改信息等 在广播信息中我们可以查看校运会发布的一些信息情况。 在首页我们可以看到校运会具体有什么项目运动。 在查看具体有什么活动我…

“笨办法”学Python 3 ——练习 37. 复习各种符号

练习 37. 复习各种符号 关键词 可参考&#xff1a;https://www.knowledgedict.com/tutorial/python-keyword.html 数据类型 可参考&#xff1a;https://www.knowledgedict.com/tutorial/python-data-type.html 如果需要查看变量的类型。可以使用Python的内置类type。 例如…

【Python小游戏】通过这款专为程序员设计的《极限车神》小游戏,你的打字速度可以赢过专业录入员,这个秘密98%的人都不知道哦~(爆赞)

导语 哈喽&#xff0c;我是你们的木木子&#x1f478;&#xff01; 今天小编要为大家介绍一款小编自己用代码码出来的赛车风格的打字小游戏 取名暂定为《&#x1f697;极限车神&#x1f697;》打字小游戏。 这款Pygame小游戏在玩法上可以说十分创新&#xff0c;不仅能游戏还…

内存对齐计算方法(偏移量)

内存对齐简单来讲就是把一个数据存放到内存中&#xff0c;其内存的地址要与数据自己大小为整数倍。 处理器在执行指令去操作内存中的数据&#xff0c;这些数据通过地址来获取。 当一个数据所在的地址和它的大小对齐的时候&#xff0c;就说这个数据对齐了&#xff0c;否则就是没…

【GO】29.go-gin支持ssl/tls,即https示例

本文为演示采用自签名证书一.生成证书通过openssl工具生成证书1.1 安装opensslmacos通过brew安装brew install openssl1.2 生成跟证书私钥openssl genrsa -out ca.key 40961.3 准备配置文件vim ca.conf内容如下[ req ] default_bits 4096 distinguished_name req_disti…

光耦合器的定义与概述

光耦合器或光电耦合器是一种电子元件&#xff0c;基本上充当具有不同电压电平的两个独立电路之间的接口。光耦合器是可在输入和输出源之间提供电气隔离的常用元件。它是一个 6 引脚器件&#xff0c;可以有任意数量的光电探测器。 在这里&#xff0c;光源发出的光束作为输入和输…

设计模式第7式:适配器模式与外观模式

前言 前面讲的装饰者模式是将对象包装起来&#xff0c;并赋予新的职责。适配器模式同样是包装对象&#xff0c;但是目的不一样&#xff0c;它要让某些对象的接口看起来不像自己而是像别的东西。为什么要这样做&#xff0c;因为可以将类的接口转换成想要的接口。还会讲一个适配…

C++中的枚举与位域

枚举在传统 C中&#xff0c;枚举类型并非类型安全&#xff0c;枚举类型会被视作整数&#xff0c;则会让两种完全不同的枚举类型可以进行直接的比较&#xff08;虽然编译器给出了检查&#xff0c;但并非所有&#xff09;&#xff0c;甚至同一个命名空间中的不同枚举类型的枚举值…

GPR后期功能整理

基金本子写得太困难了&#xff0c;学术水平不够&#xff0c;好的想法未想到好的科学问题&#xff0c;难以下笔。和龙工沟通后&#xff0c;得到了大量impulse radar的数据&#xff0c;后期需要进行分析&#xff0c;从而能让GPR智能识别走得更远。从数据解译角度&#xff0c;找到…

配置CMAKE编译环境:VSCODE + MinGW

一. MinGW安装 MinGW(Minimalist GNU For Windows)是个精简的Windows平台C/C、ADA及Fortran编译器&#xff0c;相比Cygwin而言&#xff0c;体积要小很多&#xff0c;使用较为方便。 MinGW最大的特点就是编译出来的可执行文件能够独立在Windows上运行。 MinGW的组成&#xff…

[Linux]进程替换

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

HashMap设计思想学习

HashMap设计思想学习引言树化与退化红黑树的优势索引计算put流程扩容&#xff08;加载&#xff09;因子为何默认是 0.75fhashMap并发丢失数据问题jdk 1.7并发死链问题key 的设计引言 hashmap在jdk 1.7之前是数组链表结构&#xff0c;而jdk1.8之后变为是数组(链表|红黑树) 树化…

【第38天】不同路径数问题 | 网格 dp 入门

本文已收录于专栏&#x1f338;《Java入门一百例》&#x1f338;学习指引序、专栏前言一、网格模型二、【例题1】1、题目描述2、解题思路3、模板代码4、代码解析5.原题链接三、【例题2】1、题目描述2、解题思路3、模板代码4、代码解析5.原题链接三、推荐专栏四、课后习题序、专…

大型物流运输管理系统源码 TMS源码

大型物流运输管理系统源码 TMS是一套适用于物流公司的物流运输管理系统&#xff0c;涵盖物流公司内部从订单->提货->运单->配车->点到->预约->签收->回单->代收货款的全链条管理系统。 菜单功能 一、运营管理 1、订单管理&#xff1a;用于客户意向订…

ChatGPT 到底强大在哪里?(文末有彩蛋)

ChatGPT 是由 OpenAI 开发的一个人工智能聊天机器人程序&#xff0c;于2022年11月推出。该程序使用基于 GPT-3.5 架构的大型语言模型并通过强化学习进行训练。ChatGPT 以文字方式交互&#xff0c;而除了可以通过人类自然对话方式进行交互&#xff0c;还可以用于相对复杂的语言工…

一定要收藏的面试思维导图,粉丝分享面试经验

一位粉丝分享面试经验&#xff1a;1.常见面试题有哪些&#xff1f;主要从以下一些知识点做了准备&#xff1a; 常用的分析方法、Excel、SQL、 A/B测试、产品分析。然后每份面试针对职位要求&#xff0c;还有前期和HR聊天一点点了解这个职位之后&#xff0c;定向准备。 Excel、S…

OpenMMLab 实战营打卡 - 第 7 课

OpenMMLab MMSegmentation内容概要MMSegmentation统一超参MMSegmentation 的项目结构分割模型的模块化设计分割模型的配置文件主干网络的配置ResNet v1c主解码头的配置辅助解码头的配置数据集配置数据处理流水线常用训练策略参考资料内容概要 • MMSegmentation 项目概述 • M…