【线程池】springboot线程池的底层设计原理

【线程池】springboot线程池的底层设计原理

  • 【一】Java 线程池基础
    • 【1】ThreadPoolExecutor 核心参数
    • 【2】ThreadPoolExecutor 工作流程
  • 【二】Spring Boot 中的线程池配置
    • 【1】配置类方式
    • 【2】配置文件方式
  • 【三】Spring Boot 线程池的使用
    • 【1】启用异步支持
    • 【2】异步方法示例
  • 【四】Spring Boot 线程池的底层调度机制
  • 【五】线程池监控和调优
  • 【六】常见底层原理问题
    • 【1】线程池里的线程是怎么创建的?
    • 【2】线程池里的线程是怎么阻塞的?
    • 【3】线程池里的线程是怎么唤醒的?
    • 【4】线程池里的线程是怎么回收的?
    • 【5】线程池如何实现线程复用?
    • 【6】线程池如何知道一个线程的任务已经执行完成

在 Spring Boot 中,线程池是一个重要的组件,用于管理和调度线程,提高应用程序的性能和资源利用率。下面将详细汇总 Spring Boot 线程池的底层原理。

【一】Java 线程池基础

Spring Boot 的线程池底层基于 Java 的 java.util.concurrent 包中的线程池实现,主要涉及到 ThreadPoolExecutor 类。理解 ThreadPoolExecutor 的工作原理是掌握 Spring Boot 线程池的基础。

【1】ThreadPoolExecutor 核心参数

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:空闲时间的时间单位,如 TimeUnit.SECONDS。
(5)workQueue:任务队列,用于存储等待执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
(6)threadFactory:线程工厂,用于创建线程。可以自定义线程工厂来设置线程的名称、优先级等属性。
(7)handler:拒绝策略,当任务队列已满且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy 等。

【2】ThreadPoolExecutor 工作流程

(1)当有新任务提交时,首先检查核心线程数是否已满。如果核心线程数未满,则创建新的核心线程来执行任务。
(2)如果核心线程数已满,将任务放入任务队列中等待执行。
如果任务队列已满,检查线程数是否达到最大线程数。如果未达到最大线程数,则创建新的(3)非核心线程来执行任务。
(4)如果线程数达到最大线程数,且任务队列已满,新提交的任务会触发拒绝策略。

【二】Spring Boot 中的线程池配置

在 Spring Boot 中,可以通过配置类或配置文件来创建和配置线程池。

【1】配置类方式

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;@Configuration
public class ThreadPoolConfig {@Bean("customThreadPool")public Executor customThreadPool() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5); // 核心线程数executor.setMaxPoolSize(10); // 最大线程数executor.setQueueCapacity(20); // 任务队列容量executor.setKeepAliveSeconds(60); // 线程空闲时间executor.setThreadNamePrefix("custom-thread-"); // 线程名称前缀executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略executor.initialize();return executor;}
}

【2】配置文件方式

在 application.properties 或 application.yml 中配置线程池属性:

spring.task.execution.pool.core-size=5
spring.task.execution.pool.max-size=10
spring.task.execution.pool.queue-capacity=20
spring.task.execution.pool.keep-alive=60s
spring.task.execution.thread-name-prefix=custom-thread-

【三】Spring Boot 线程池的使用

在 Spring Boot 中,可以通过 @Async 注解来异步执行方法,使用线程池来管理这些异步任务。

【1】启用异步支持

在主应用类上添加 @EnableAsync 注解来启用异步支持:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;@SpringBootApplication
@EnableAsync
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

【2】异步方法示例

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Service
public class AsyncService {@Async("customThreadPool")public void asyncMethod() {// 异步执行的方法逻辑System.out.println("异步方法执行,线程名称:" + Thread.currentThread().getName());}
}

【四】Spring Boot 线程池的底层调度机制

Spring Boot 的线程池调度主要依赖于 ThreadPoolTaskExecutor 类,它是对 ThreadPoolExecutor 的封装。当调用 @Async 注解的方法时,Spring 会将任务提交给线程池进行处理。

(1)任务提交
ThreadPoolTaskExecutor 提供了 execute 和 submit 方法来提交任务:
execute 方法用于提交 Runnable 任务,没有返回值。
submit 方法可以提交 Runnable 或 Callable 任务,并且可以返回一个 Future 对象,用于获取任务的执行结果。

(2)任务调度
当任务提交到线程池后,线程池会根据 ThreadPoolExecutor 的工作流程进行任务调度。如果核心线程有空闲,会优先使用核心线程执行任务;如果核心线程已满,任务会被放入任务队列;如果任务队列已满,会创建新的非核心线程;如果线程数达到最大线程数,会触发拒绝策略。

【五】线程池监控和调优

在 Spring Boot 中,可以通过 Actuator 来监控线程池的状态,例如线程池的活跃线程数、任务队列大小等。同时,可以根据监控结果对线程池的参数进行调优,以提高应用程序的性能。

(1)线程池监控和调优
在 Spring Boot 中,可以通过 Actuator 来监控线程池的状态,例如线程池的活跃线程数、任务队列大小等。同时,可以根据监控结果对线程池的参数进行调优,以提高应用程序的性能。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

(2)监控线程池
通过访问 /actuator/metrics 端点可以查看线程池的相关指标,例如 executor.active 表示活跃线程数,executor.completed 表示已完成的任务数等。
综上所述,Spring Boot 线程池的底层原理基于 Java 的 ThreadPoolExecutor,通过配置和使用 ThreadPoolTaskExecutor 来实现任务的异步执行和调度。同时,可以通过 Actuator 对线程池进行监控和调优,以提高应用程序的性能和稳定性。

【六】常见底层原理问题

【1】线程池里的线程是怎么创建的?

当有新任务提交到线程池时,线程池会根据当前线程数量和配置参数决定是否创建新线程。若当前线程数小于核心线程数(corePoolSize),会直接创建新的核心线程;若核心线程已满,则将任务放入任务队列;若任务队列已满且线程数小于最大线程数(maximumPoolSize),会创建非核心线程。

public void execute(Runnable command) {if (command == null)throw new NullPointerException();// 获取线程池的控制状态(包含线程数和运行状态)int c = ctl.get();// 如果当前线程数小于核心线程数if (workerCountOf(c) < corePoolSize) { // 尝试创建新线程执行任务if (addWorker(command, true)) return;c = ctl.get();}// 如果线程池在运行且任务能放入队列if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get();// 再次检查线程池状态,如果已停止则移除任务并拒绝if (! isRunning(recheck) && remove(command)) reject(command);// 如果线程数为 0,创建一个无初始任务的线程else if (workerCountOf(recheck) == 0) addWorker(null, false);}// 如果任务队列已满,尝试创建非核心线程else if (!addWorker(command, false)) // 创建失败则拒绝任务reject(command); 
}

addWorker方法,这个方法负责创建新的工作线程。Worker类是继承AQS,同时实现了Runnable,每个Worker持有一个线程,在run方法里不断从队列里取任务执行。

// 线程创建(addWorker)
private boolean addWorker(Runnable firstTask, boolean core) {// 创建 Worker 对象(包含 Thread 和初始任务)Worker w = new Worker(firstTask);Thread t = w.thread;workers.add(w); // 加入线程集合t.start();      // 启动线程
}private final class Worker extends AbstractQueuedSynchronizer implements Runnable {final Thread thread;Runnable firstTask;Worker(Runnable firstTask) {this.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}public void run() {runWorker(this); // 核心执行循环}
}
// 任务执行循环
final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;while (task != null || (task = getTask()) != null) {try {task.run(); // 执行任务} finally {task = null; // 清空任务引用}}processWorkerExit(w, false); // 线程回收处理
}

【2】线程池里的线程是怎么阻塞的?

当线程执行完一个任务后,会通过getTask()方法从任务队列中获取下一个任务。若任务队列为空,阻塞队列的take()会阻塞线程,线程会进入阻塞状态等待新任务。而如果线程池正在关闭,可能就会返回null,导致Worker退出循环,线程结束。

ThreadPoolExecutor 中的线程通过 workQueue.take() 或 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 方法获取任务,这两个方法在队列为空时会使线程阻塞。

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // 允许线程被中断boolean completedAbruptly = true;try {// 循环获取任务并执行while (task != null || (task = getTask()) != null) { w.lock();// 检查线程池状态,确保线程在需要时被中断if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {// 执行任务前的钩子方法beforeExecute(wt, task); Throwable thrown = null;try {// 执行任务task.run(); } catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {// 执行任务后的钩子方法afterExecute(task, thrown); }} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {// 处理线程退出逻辑processWorkerExit(w, completedAbruptly); }
}private Runnable getTask() {boolean timedOut = false; // 记录上次 poll 是否超时for (;;) {int c = ctl.get();int rs = runStateOf(c);// 检查线程池状态和任务队列情况if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// 判断线程是否需要超时控制boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {// 根据是否需要超时控制选择获取任务的方法Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {// 响应中断信号timedOut = false;}}
}

当队列为空时,线程通过workQueue.take()阻塞

【3】线程池里的线程是怎么唤醒的?

当有新任务提交到任务队列时,会唤醒处于阻塞状态的线程。任务队列(如 LinkedBlockingQueue)使用 ReentrantLock 和 Condition 来实现线程的阻塞和唤醒机制。新任务加入队列时,会调用 Condition 的 signal() 或 signalAll() 方法唤醒等待的线程。

以 LinkedBlockingQueue 的 offer 方法为例:

public boolean offer(E e) {if (e == null) throw new NullPointerException();final AtomicInteger count = this.count;// 如果队列已满,返回 falseif (count.get() == capacity) return false;int c = -1;Node<E> node = new Node<E>(e);final ReentrantLock putLock = this.putLock;putLock.lock();try {// 再次检查队列是否未满if (count.get() < capacity) { // 将任务加入队列enqueue(node); c = count.getAndIncrement();// 如果队列还有空间,唤醒等待放入任务的线程if (c + 1 < capacity) notFull.signal();}} finally {putLock.unlock();}// 如果之前队列为空,唤醒等待获取任务的线程if (c == 0) signalNotEmpty();return c >= 0;
}private void signalNotEmpty() {final ReentrantLock takeLock = this.takeLock;takeLock.lock();try {// 唤醒等待获取任务的线程notEmpty.signal(); } finally {takeLock.unlock();}
}

当队列为空时,线程通过workQueue.take()阻塞

【4】线程池里的线程是怎么回收的?

线程池根据线程的空闲时间和配置参数回收线程。当线程空闲时间超过 keepAliveTime,且线程数大于核心线程数时,非核心线程会被回收。在 getTask 方法中,若使用 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 方法获取任务超时,线程会退出 runWorker 循环,最终被回收。

在 getTask 方法中:

private Runnable getTask() {boolean timedOut = false; // 记录上次 poll 是否超时for (;;) {int c = ctl.get();int rs = runStateOf(c);// 检查线程池状态和任务队列情况if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// 判断线程是否需要超时控制boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {// 减少线程计数if (compareAndDecrementWorkerCount(c)) return null;continue;}try {// 根据是否需要超时控制选择获取任务的方法Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}
}

【5】线程池如何实现线程复用?

线程池实现线程复用的核心在于避免频繁地创建和销毁线程,而是让线程在完成一个任务后继续去执行其他任务。下面详细介绍其底层原理:
(1)线程的创建和管理
线程池在初始化时会根据配置创建一定数量的核心线程。这些线程在创建后不会立即销毁,而是进入等待状态,随时准备执行新的任务。线程池使用一个内部的数据结构(通常是一个集合)来管理这些线程。
(2)任务队列
线程池中有一个任务队列,用于存储待执行的任务。当有新任务提交时,如果核心线程都在忙碌,任务会被放入任务队列中等待执行。
(3)线程的执行逻辑
每个线程在启动后会进入一个循环,不断从任务队列中获取任务并执行。当任务队列为空时,线程会进入阻塞状态,等待新任务的到来。一旦有新任务被添加到队列中,线程会被唤醒并继续执行任务。
(4)线程的复用
线程在完成一个任务后,不会终止,而是继续从任务队列中获取下一个任务。这样,同一个线程可以执行多个不同的任务,实现了线程的复用。

【6】线程池如何知道一个线程的任务已经执行完成

(1)基于任务执行逻辑
线程池中的线程在执行任务时,通常会遵循特定的执行逻辑。一般来说,线程会从任务队列中取出一个任务并调用其 run 方法(对于 Runnable 任务)或 call 方法(对于 Callable 任务),当这个方法正常返回或者抛出异常结束时,就意味着该任务执行完成,FutureTask会标记任务为完成,并调用done()方法。可以通过Future的isDone()方法来判断状态。

(2)线程池的状态管理
线程池内部会对线程和任务的状态进行管理。每个线程在执行任务时,线程池会记录该线程的状态,当任务执行完成后,线程池会更新相应的状态信息。

原理分析
1-任务队列操作:线程从任务队列中取出任务执行,当任务完成后,线程池会知道该任务已经从队列中移除并执行完毕。
2-线程状态标记:线程池可以通过内部的状态标记来记录线程是处于忙碌状态(正在执行任务)还是空闲状态(任务执行完成)。

(3)回调机制
有些线程池实现会使用回调机制来通知任务执行完成。例如,Future 接口和 CompletableFuture 类就提供了这样的功能。

bmit 方法返回一个 Future 对象,通过调用 isDone 方法可以检查任务是否执行完成。当 isDone 方法返回 true 时,就表示任务已经执行完成,可以通过 get 方法获取任务的执行结果。

public class FutureTask<V> implements RunnableFuture<V> {private volatile int state;private static final int NEW          = 0;private static final int COMPLETING   = 1;private static final int NORMAL       = 2;private static final int EXCEPTIONAL  = 3;private static final int CANCELLED    = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED  = 6;protected void done() { // 任务完成时回调(可用于异步通知)}public boolean isDone() {return state != NEW; // 任何非NEW状态都表示完成}
}

通过FutureTask的状态机跟踪任务生命周期
isDone()方法检查state是否为非NEW状态

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

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

相关文章

PyTorch 源码学习:GPU 内存管理之深入分析 CUDACachingAllocator

因引入 expandable_segments 机制&#xff0c;PyTorch 2.1.0 版本发生了较大变化。本文关注的是 PyTorch 原生的 GPU 内存管理机制&#xff0c;故研究的 PyTorch 版本为 2.0.0。代码地址&#xff1a; c10/cuda/CUDACachingAllocator.hc10/cuda/CUDACachingAllocator.cpp 更多内…

【PromptCoder】使用 package.json 生成 cursorrules

【PromptCoder】使用 package.json 生成 cursorrules 在当今快节奏的开发世界中&#xff0c;效率和准确性至关重要。开发者们不断寻找能够优化工作流程、帮助他们更快编写高质量代码的工具。Cursor 作为一款 AI 驱动的代码编辑器&#xff0c;正在彻底改变我们的编程方式。但如…

学习路程五 向量数据库Milvus操作

前序 前面安装好了docker且成功拉取Milvus镜像&#xff0c;启动。通过python成功连接上了数据。接下来就继续更多Milvus的操作 在开始之前&#xff0c;先来简单了解一下向量数据库内一些东西的基本概念 概念描述数据库&#xff08;Database&#xff09;类似与MySQL的database…

SpringBoot 热部署

1、添加 DevTools 依赖 <!-- 热部署依赖 --> <dependency> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId> </dependency>2、在IDEA的菜单栏中依次选择“File”→“Settings”&#x…

SOC-ATF 安全启动BL1流程分析(1)

一、ATF 源码下载链接 1. ARM Trusted Firmware (ATF) 官方 GitHub 仓库 GitHub 地址: https://github.com/ARM-software/arm-trusted-firmware 这是 ATF 的官方源码仓库&#xff0c;包含最新的代码、文档和示例。 下载方式&#xff1a; 使用 Git 克隆仓库&#xff1a; git…

汽车无钥匙进入一键启动操作正确步骤

汽车智能无钥匙进入和一键启动的技术在近年来比较成熟&#xff0c;不同车型的操作步骤可能略有不同&#xff0c;但基本的流程应该是通用的&#xff0c;不会因为时间变化而有大的改变。 移动管家汽车一键启动无钥匙进入系统通常是通过携带钥匙靠近车辆&#xff0c;然后触摸门把…

excel单、双字节字符转换函数(中英文输入法符号转换)

在Excel中通常使用函数WIDECHAR和ASC来实现单、双字节字符之间的转换。其中 WIDECHAR函数将所有的字符转换为双字节&#xff0c;ASC函数将所有的字符转换为单字节 首先来解释一下单双字节的含义。单字节一般对应英文输入法的输入&#xff0c;如英文字母&#xff0c;英文输入法…

IP----访问服务器流程

这只是IP的其中一块内容-访问服务器流程&#xff0c;IP还有更多内容可以查看IP专栏&#xff0c;前一段学习内容为IA内容&#xff0c;还有更多内容可以查看IA专栏&#xff0c;可通过以下路径查看IA-----配置NAT-CSDN博客CSDN,欢迎指正 1.访问服务器流程 1.分层 1.更利于标准化…

Ubutu部署WordPress

前言 什么是word press WordPress是一种使用PHP语言开发的建站系统&#xff0c;用户可以在支持PHP和MySQL数据库的服务器上架设WordPress。它是一个开源的内容管理系统&#xff08;CMS&#xff09;&#xff0c;允许用户构建动态网站和博客。现在的WordPress已经强大到几乎可以…

LangChain构建行业知识库实践:从架构设计到生产部署全指南

文章目录 引言:行业知识库的进化挑战一、系统架构设计1.1 核心组件拓扑1.2 模块化设计原则二、关键技术实现2.1 文档预处理流水线2.2 混合检索增强三、领域适配优化3.1 医学知识图谱融合3.2 检索结果重排序算法四、生产环境部署4.1 性能优化方案4.2 安全防护体系五、评估与调优…

Lua的table(表)

Lua表的基本概念 Lua中的表&#xff08;table&#xff09;是一种多功能数据结构&#xff0c;可以用作数组、字典、集合等。表是Lua中唯一的数据结构机制&#xff0c;其他数据结构如数组、列表、队列等都可以通过表来实现。 表的实现 Lua的表由两部分组成&#xff1a; 数组部分…

应对现代生活的健康养生指南

在科技飞速发展的现代社会&#xff0c;人们的生活方式发生了巨大改变&#xff0c;随之而来的是一系列健康问题。快节奏的生活、高强度的工作以及电子产品的过度使用&#xff0c;让我们的身体承受着前所未有的压力。因此&#xff0c;掌握正确的健康养生方法迫在眉睫。 针对久坐不…

使用DeepSeek/chatgpt等AI工具辅助网络协议流量数据包分析

随着deepseek,chatgpt等大模型的能力越来越强大&#xff0c;本文将介绍一下deepseek等LLM在分数流量数据包这方面的能力。为需要借助LLM等大模型辅助分析流量数据包的同学提供参考&#xff0c;也了解一下目前是否有必要继续学习wireshark工具以及复杂的协议知识。 pcap格式 目…

【Linux】CentOS7停服之后配置yum镜像源

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 毛毛张今天分享一个CentOS7系统停服之后&#xff0c;配置yum镜像源的步骤&#xff0c;有坑&#xff01; 文章目录 1.概述2.查看系统架构2.1 查看内核版本2.2 查看lin…

2025-02-26 学习记录--C/C++-C语言 整数格式说明符

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; C语言 整数格式说明符 【例如 】&#x1f380; &#xff1a;在 C 语言中&#xff0c;%ld 是 printf 或 scanf 等格式化输入输出函…

OpenAI开放Deep Research权限,AI智能体大战升级,DeepSeek与Claude迎来新对决

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

个人电脑小参数GPT预训练、SFT、RLHF、蒸馏、CoT、Lora过程实践——MiniMind图文版教程

最近看到Github上开源了一个小模型的repo&#xff0c;是真正拉低LLM的学习门槛&#xff0c;让每个人都能从理解每一行代码&#xff0c; 从零开始亲手训练一个极小的语言模型。开源地址&#xff1a; GitHub - jingyaogong/minimind: &#x1f680;&#x1f680; 「大模型」2小时…

【数据结构】顺序表和链表

线性表 线性表 (linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串 ….. 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时…

一文讲解Redis的内存淘汰和过期策略

Redis 报内存不足怎么处理&#xff1f; Redis 内存不足有这么几种处理方式&#xff1a; 修改配置文件 redis.conf 的 maxmemory 参数&#xff0c;增加 Redis 可用内存 也可以通过命令 set maxmemory 动态设置内存上限 修改内存淘汰策略&#xff0c;及时释放内存空间 使用 R…

游戏引擎学习第125天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾并为今天的内容做准备。 昨天&#xff0c;当我们离开时&#xff0c;工作队列已经完成了基本的功能。这个队列虽然简单&#xff0c;但它能够执行任务&#xff0c;并且我们已经为各种操作编写了测试。字符串也能够正常推送到队…