Java
List中存储学生对象,学号重复怎么去重?
- 重写学生类的
<font style="color:#000000;">equals</font>(仅比较学号)和<font style="color:#000000;">hashCode</font>(基于学号计算),定义 "学号相同即重复"。 - 去重方式:
- 用
<font style="color:#000000;">LinkedHashSet</font>:<font style="color:#000000;">new ArrayList<>(new LinkedHashSet<>(list))</font>(保留原顺序)。 - 用 Stream:
<font style="color:#000000;">list.stream().distinct().collect(Collectors.toList())</font>(依赖<font style="color:#000000;">equals</font>和<font style="color:#000000;">hashCode</font>)。
- 用
【P0】事务注解失效的场景?
- 非 Spring 管理的 Bean:未通过 @Component、@Service 等注解或 XML 配置注册到 Spring 容器,AOP 动态代理无法作用,事务切面拦截失效,注解形同虚设。
- 内部方法调用 :类内通过
<font style="color:rgb(0, 0, 0);">this</font>调用本类事务方法,未经过 Spring 生成的代理对象,事务增强逻辑(如开启事务)无法触发。 - 方法非 public:Spring 事务切面(TransactionInterceptor)默认仅拦截 public 方法,非 public 方法上的 @Transactional 不生效。
- 异常类型不匹配 :默认仅回滚 RuntimeException 及其子类;若抛出 checked 异常(如 IOException),需通过
<font style="color:rgb(0, 0, 0);">rollbackFor</font>指定才会回滚,否则事务不触发回滚。 - 手动捕获异常未抛出:方法内捕获异常后未重新抛出,Spring 无法感知异常发生,事务无法触发回滚。
- 传播机制不当 :选择不支持 / 排斥事务的传播行为会导致失效,如:
- PROPAGATION_NOT_SUPPORTED:不支持事务,挂起上游事务以非事务执行;
- PROPAGATION_NEVER:禁止事务,上游有事务则抛异常;
- PROPAGATION_SUPPORTS:仅上游有事务时才加入,无则以非事务执行(无法独立开启事务)。
- 数据源未配事务管理器:未为数据源绑定对应的 PlatformTransactionManager,Spring 无法管理事务。
- 多线程场景:子线程事务与主线程独立,@Transactional 无法跨线程生效,子线程事务需单独配置。
上游事务调用如何保证被层事务生效?事务传播?
用 <font style="color:#000000;">@Transactional(propagation = ...)</font> 定义上下游事务关系,常用生效组合:
- 下层需融入上游事务:默认
<font style="color:#000000;">REQUIRED</font>(上游有事务则加入,无则新建); - 下层需独立事务:
<font style="color:#000000;">REQUIRES_NEW</font>(无论上游有无事务,均新建独立事务,与上游隔离); - 下层必须依赖上游事务:
<font style="color:#000000;">MANDATORY</font>(上游无事务则抛异常)。
上下游事务需使用同一个 <font style="color:#000000;">PlatformTransactionManager</font>(如 Spring 默认的 <font style="color:#000000;">DataSourceTransactionManager</font>),避免因多数据源、多事务管理器导致事务无法协同。
【P0】@Transactional 实现原理
核心:Spring AOP 动态代理 + 事务管理器,实现声明式事务自动管理。
核心逻辑:
- 扫描注解:Spring 启动时解析
<font style="color:#000000;">@Transactional</font>的事务属性(传播行为、隔离级别等); - 生成代理:对标注
<font style="color:#000000;">@Transactional</font>的类,根据是否有接口选择生成 JDK 代理 (有接口)或 CGLIB 代理(无接口),用于拦截目标方法; - 拦截执行:
- 前置:通过
<font style="color:#000000;">PlatformTransactionManager</font>开启事务; - 执行:调用原始业务方法;
- 后置:正常返回→提交;抛未捕获运行时异常(默认)→回滚。
- 前置:通过
关键组件:
- 注解:
<font style="color:#000000;">@Transactional</font>(声明事务规则); - 管理器:
<font style="color:#000000;">PlatformTransactionManager</font>(事务执行核心); - 代理:JDK(有接口)/CGLIB(无接口);
- 拦截器:
<font style="color:#000000;">TransactionInterceptor</font>(封装 begin/commit/rollback)。
核心属性(默认值):
- 传播行为:REQUIRED(无则新建,有则加入);
- 隔离级别:数据库默认;
- 回滚规则:运行时异常 / Error 回滚;
- 其他:无超时、读写事务。
HashMap的put操作?
- 计算 key 哈希(定位) :null→hash=0;非 null→经扰动函数优化,用
<font style="color:#000000;background-color:rgba(0, 0, 0, 0);">(len-1)&hash</font>定位桶位; - 初始化校验 :桶数组未初始化则触发
<font style="color:#000000;background-color:rgba(0, 0, 0, 0);">resize()</font>(默认容量 16,阈值 12); - **桶位为空:**直接插入新节点;
- 桶位非空 :
- 桶首结点完全相同:首节点 key 相同→覆盖 value;
- 桶内红黑树节点:putTreeVal()树内插入;
- 桶内链表节点:尾插新节点,长度≥8 且数组≥64 则转红黑树;
- 统计数量,是否扩容 :
<font style="color:#000000;background-color:rgba(0, 0, 0, 0);">size++</font>,超扩容阈值(容量 ×0.75)→<font style="color:#000000;background-color:rgba(0, 0, 0, 0);">resize()</font>(容量翻倍,迁移节点); - 返回旧 value(覆盖时)或 null(新插入时)。
核心:哈希定位→冲突处理(链表 / 红黑树)→扩容检查,兼顾效率与均匀分布。
JDK8垃圾回收器?
默认 GC 组合:未指定 GC 参数时,默认启用 "Parallel Scavenge(新生代) + Parallel Old(老年代)" 并行垃圾回收器。
新生代(Parallel Scavenge):
- 采用算法:并行复制算法
- 核心特点:多线程并行回收,以 "吞吐量优先" 为核心目标(吞吐量 = 应用运行时间 / 总时间)
- 适配场景:后台任务、批处理等对响应时间无严格要求,追求高吞吐量的场景
老年代(Parallel Old):
- 采用算法:并行标记 - 整理算法
- 核心特点:与新生代 GC 配套,同样支持多线程并行回收,可规避单线程回收的性能瓶颈
函数变量的JVM内存分配?
核心结论:函数内变量的分配位置,由「变量类型(基本类型 / 引用类型)+ 变量修饰符(static / 非 static)」决定,主要分布在虚拟机栈、堆、元空间。
| 变量类型 | 分配位置 | 生命周期 |
|---|---|---|
| 基本类型局部变量 / 参数 | 虚拟机栈:局部变量表 | 方法执行期间(栈帧存在) |
| 引用类型局部变量 / 参数 | 变量(地址): + 栈局部变量表 + 实例:堆 | 变量:栈帧存在 实例:GC 回收 |
| 类级静态变量(函数外) | 堆(Java 8+) | 类加载至类卸载 |
核心记忆:栈存局部变量(值 / 地址),堆存对象实例,静态变量归堆管,函数内无合法静态变量,参数等同于局部变量。
volatile与synchronized如何保证可见性?
- volatile:
- 通过插入内存屏障,强制变量写操作刷回主内存、读操作从主内存加载,直接保证单个变量的内存可见性。
- "单点突破",只盯着单个变量的读写同步,解决"可见性 + 有序性"
- 单点监控变量,修改变量值必须修改主内存,保证其他线程能够对该变量可见,不保证原子性。
- synchronized :
- 依赖锁的获取 / 释放语义,进入同步块时清空 CPU 缓存(从主内存读最新值),退出时强制刷回块内变量到主内存,间接同步块内所有变量可见性。
- 借着锁的独占性,顺带解决 "可见性 + 有序性 + 原子性"
- 线程独占,占用模块变量,不仅保证变量可见性同时保证变量的原子性。
| 维度 | volatile | synchronized |
|---|---|---|
| 控制粒度 | 单个变量(细粒度) | 代码块 / 方法(粗粒度) |
| 同步范围 | 仅目标变量 | 块内所有变量 |
| 附加能力 | 保证有序性,不保证原子性 | 保证有序性 + 原子性 |
| 实现方式 | 直接插入内存屏障 | 锁机制关联内存屏障 |
| 性能开销 | 轻量(无锁) | 偏重量级(JDK1.6 后优化) |
| 适用场景 | 单写多读(如状态标记位) | 多线程读写竞争(如共享数据修改) |
原子类底层原理?会出现ABA问题?
核心:volatile 保证可见性 + Unsafe 调用 CPU 原子指令实现 CAS + 自旋重试处理并发,无锁实现单变量原子操作。
- 内部用
<font style="color:#000000;">volatile int value</font>保证变量内存可见性; - 依赖
<font style="color:#000000;">Unsafe</font>工具类,调用<font style="color:#000000;">compareAndSwapInt</font>映射 CPU 原子指令(如<font style="color:#000000;">cmpxchg</font>),确保 "比较 + 交换" 原子化; - 自增 / 修改等操作通过 "读最新值→算新值→CAS 尝试→失败自旋重试" 实现,无需锁;
- 局限:仅单变量原子性、高并发自旋耗 CPU、不解决 ABA 问题。
结论:会出现 ABA 问题!
- AtomicInteger 未解决 ABA 问题:因它仅用
<font style="color:#000000;">compareAndSwapInt</font>做值校验,无版本控制; - 解决方案:用
<font style="color:#000000;">AtomicStampedReference</font>,其 CAS 方法(<font style="color:#000000;">compareAndSwap</font>)会同时校验 "值 + 版本号",即使值回滚,版本号已递增,CAS 会失败,避免 ABA 误判。
Java的CAS使用了版本控制?
结论:Java 基础 CAS (如 AtomicInteger 的 compareAndSwapInt)不自带版本控制;仅带版本控制的 CAS 实现(如 AtomicStampedReference)才引入版本号解决 ABA 问题。
核心区分:
- 普通 CAS(AtomicInteger/AtomicLong 等):仅校验「变量当前值 == 预期旧值」,无版本控制,会出现 ABA 问题;
- 版本控制 CAS(AtomicStampedReference/AtomicMarkableReference):校验「值 + 版本号(或标记)」,通过版本号递增(或标记位变更),避免 ABA 误判。
Java中Sort用的是什么排序?
Java 8+ 中 <font style="color:#000000;">sort</font> 算法核心结论(精炼版):
- 基本类型数组(int/long/char 等):双轴快速排序(非稳定,O (n log n));
- 对象数组 / 集合(String、自定义类等):TimSort(归并 + 插入混合,稳定,O (n log n));
- 小数据集(长度 < 47):均降级为插入排序(低开销优化);
- 并行排序
<font style="color:#000000;">Arrays.parallelSort()</font>:双轴快排 + 多线程并行处理(超大数据集提速)。
双轴快排是 快速排序的优化版,核心是用「两个基准值(轴)」替代传统快排的「一个基准值」,把数组分成三部分,效率比单轴快排更高,是 Java 8+ 中基本类型数组(int/long 等)排序的默认算法。
equals与hashcode如何重写?
- equals 规则:自反性、对称性、传递性、一致性(多次调用结果一致)、非空性(非 null 对象不与 null 相等);
- hashCode 规则:相等对象(equals 返回 true)必须有相同哈希值;不相等对象哈希值可相同(但需尽量避免,减少哈希冲突)。
- 属性选择 :
- 必须与 equals 参与对比的属性完全一致(避免「equals 相等但 hashCode 不同」);
- 优先选择「不可变属性」(如 accountId),避免可变属性(如 balance)导致哈希值变化,进而集合中找不到对象;
- 空指针防护 :用
<font style="color:#000000;">Objects.equals(a, b)</font>替代<font style="color:#000000;">a.equals(b)</font>,避免空指针; - 集合场景:若对象需存入 HashMap/HashSet,务必复写两者,否则会出现「对象相等但无法去重」「查找不到」问题;
- 工具生成:IDE(IDEA/Eclipse)可自动生成(Alt+Insert → Equals and HashCode),推荐优先使用,减少手动错误。
Java中的String为什么要设置成final?
Java String 设为 final,核心是保障不可变性,支撑四大关键能力:
- 安全:防子类篡改,适配核心类库 / 安全场景;
- 高效:支撑常量池复用(字符串常量池核心前提:字符串不可变),节省内存;
- 稳定:hashCode 固定,适配 HashMap 等哈希表键;
- 线程安全:多线程共享无需同步。
@Transation 事务如何使用的、回滚操作如何实现的?
核心结论:@Transactional 是 Spring 声明式事务注解,回滚通过异常触发或手动控制。
@Transactional 基本使用:
- 加在类 / 方法上,类上表示所有公共方法生效。
- 需开启事务管理(@EnableTransactionManagement 注解)。
- 仅对 public 方法生效,非 public 方法注解无效。
回滚实现方式:
- 自动回滚:默认抛出 unchecked 异常(RuntimeException 及其子类)时触发。
- 手动指定回滚:通过 rollbackFor 属性指定异常类型,如 @Transactional (rollbackFor = Exception.class)。
- 编程式控制:注入 TransactionStatus,调用 setRollbackOnly () 手动标记回滚。
Java是怎么知道创建的对象在内存的哪个地方的?
Java 中对象的内存定位,核心靠 引用(Reference)+ JVM 内存模型 实现 ------ 引用存储对象的「内存地址相关信息」,JVM 通过引用解析出对象在堆中的具体位置,无需开发者手动管理。
- 对象存哪 :所有对象(含成员变量)都在 JVM 堆内存 中(数组也属于对象,同样在堆中)。
- 引用的作用 :变量声明的「引用」(如
<font style="color:rgb(0, 0, 0);">User user = new User()</font>中的<font style="color:rgb(0, 0, 0);">user</font>),存储在栈内存(局部变量)或堆内存(成员变量)中,其本质是「指向堆中对象的 "指针"/ 地址标识」。 - JVM 如何定位 :
- 直接指针式(主流 JVM 如 HotSpot 默认):引用直接存储对象在堆中的起始物理地址,JVM 直接通过引用访问堆中对象;
- 句柄池式(少用):引用存储句柄地址(句柄池在堆中),句柄再关联对象的堆地址,JVM 需多一次间接查找。
【P0】红黑树结构?
红黑树是一种自平衡二叉搜索树,通过维持特定规则保证树的平衡,确保插入、删除、查询的时间复杂度均为 O (log n)。其核心结构特点和规则如下:
结构定义:
- 节点组成 :每个节点包含键值、左子树、右子树、父节点,以及一个颜色标识(红色或黑色)。
- 二叉搜索树特性 :
- 左子树所有节点值 < 父节点值;
- 右子树所有节点值> 父节点值。
核心规则(维持平衡的关键):
- 根节点必为黑色。
- 叶子节点(NIL 节点,空节点)必为黑色。
- 红色节点的子节点必为黑色(避免两个红色节点相邻)。
- 从任意节点到其所有叶子节点的路径中,黑色节点数量相同(黑高相等)。
平衡维护机制:
当插入或删除节点破坏上述规则时,通过以下操作恢复平衡:
- 变色:改变节点的颜色(红 ↔ 黑)。
- 旋转 :分为两种类型:
- 左旋:以某个节点为支点,将其右子树旋转为父节点,自身变为右子树的左子节点。
- 右旋:以某个节点为支点,将其左子树旋转为父节点,自身变为左子树的右子节点。
优势与应用:
- 优势:相比 AVL 树,旋转操作更少,插入删除效率更高,适用于频繁修改的场景。
- 应用 :Java 中的
<font style="color:#000000;">TreeMap</font>、<font style="color:#000000;">TreeSet</font>,C++ STL 中的<font style="color:#000000;">map</font>、<font style="color:#000000;">set</font>,Linux 内核的进程调度等。
简化图示(红黑树结构示例)
plain
10 (黑)
/ \
5 (红) 15 (红)
/ \ / \
NIL NIL NIL NIL (所有叶子节点为黑色)
红黑树通过严格的规则和高效的旋转策略,在保证查询效率的同时,最小化了维护平衡的开销,是工程中应用最广泛的平衡树之一。
什么是线程安全?
线程安全:多线程并发操作共享资源时,行为符合预期(与单线程执行结果一致),无数据损坏、逻辑错乱等问题。
核心根源 :多线程并发操作「共享资源 」+ 操作「非原子性 」,叠加 CPU 调度、缓存、指令重排,导致指令交错。
三大核心原则(并发三要素):
- 原子性:操作不可分割(要么全成,要么全不成)
- 可见性:线程修改共享变量后,其他线程能立即感知
- 有序性:指令执行顺序与逻辑顺序一致,无乱序重排
线程安全级别(从弱到强):
- 不可变(天然安全):如 String、Integer
- 绝对安全:无需额外同步,并发调用行为一致
- 相对安全:单操作安全,复合操作需额外同步(如 Vector)
- 线程兼容:本身不安全,可通过外部同步修复(如 HashMap)
- 线程对立:并发调用必出问题(如 SimpleDateFormat)
常用实现方式:
- 无状态设计(无共享资源)
- 不可变对象(禁止修改共享资源)
- 同步机制(synchronized/Lock、volatile、原子类)
什么是Session?
Session 是 服务器端的临时会话容器,核心用于解决 HTTP 无状态问题,让服务器识别同一用户的多次请求(如登录状态维持)。
核心逻辑:
- 用户首次请求(如登录):服务器创建 Session,生成唯一 SessionID,通过 Cookie 返回给浏览器;
- 后续请求:浏览器自动携带 Cookie 中的 SessionID,服务器通过 SessionID 匹配对应 Session,识别用户。
与Cookie核心区别:
- Session:服务器存、安全高、可存复杂对象;
- Cookie:客户端存、安全低、仅存字符串(4KB 限制)。
为什么用 Guava 而非直接用 Map?
核心:原生 Map 是 "基础存储容器",Guava Map 是 "带工业级特性的增强容器",解决原生痛点,减少重复开发。
核心优势:
- 缓存能力:
<font style="color:rgb(0, 0, 0);">LoadingCache</font>原生支持过期、LRU 淘汰、自动加载(原生需手动实现); - 线程安全:高效并发 Map,性能优于
<font style="color:rgb(0, 0, 0);">Hashtable</font>,支持原子操作; - 安全省心:禁止 null 键 / 值,规避 NPE;提供不可变 Map(
<font style="color:rgb(0, 0, 0);">ImmutableMap</font>),线程安全; - 便捷高效:内置
<font style="color:rgb(0, 0, 0);">computeIfAbsent</font>等批量 / 原子方法,简化代码。
适用边界:
- 简单单线程存储:原生 Map 足够;
- 缓存、并发、复杂操作场景:Guava 更高效省心。
ABA问题及其解决方法?
CAS机制缺陷:
- 线程 1 读取共享变量值为 A;
- 线程 1 被挂起,线程 2 介入,将变量改为 B ,随后又改回 A;
- 线程 1 恢复执行,CAS 比较发现变量仍为 A,认为未被修改,执行交换操作。
GPU 和 CPU 的主要区别是什么?各自适用场景?
| 维度 | CPU(中央处理器) | GPU(图形处理器) |
|---|---|---|
| 设计目标 | 低延迟、强通用性、复杂逻辑控制 | 高吞吐、大规模并行、重复计算 |
| 核心特性 | 核心少(4-64 核)、大缓存、强控制单元(分支预测 / 乱序执行) | 核心多(数千 / 数万核)、小缓存、弱控制单元(共享指令逻辑) |
| 执行模式 | 串行优先 + 有限任务并行 | 单指令多线程(SIMT)海量数据并行 |
| 算力侧重 | 单核心性能强,总浮点算力低 | 并行浮点算力极高(CPU 的 10-100 倍) |
| 编程模型 | 通用编程语言(Java/C++)直接控制 | 并行编程框架(CUDA/OpenCL)拆分任务 |
算法
堆排序理解?Java如何实现?
堆排序是一种基于堆数据结构 的高效排序算法,时间复杂度为 O(n log n) ,空间复杂度为 O(1) (原地排序),稳定性为不稳定排序(相等元素可能改变相对顺序)。
堆是一种完全二叉树,同时满足「堆属性」:
- 大顶堆:每个父节点的值 ≥ 子节点的值(用于升序排序)
- 小顶堆:每个父节点的值 ≤ 子节点的值(用于降序排序)
堆的存储:通常用数组 表示(完全二叉树的特性),对于索引 <font style="color:rgba(0, 0, 0, 0.85) !important;">i</font>(从 0 开始):
- 左子节点索引:
<font style="color:rgb(0, 0, 0);">2i + 1</font> - 右子节点索引:
<font style="color:rgb(0, 0, 0);">2i + 2</font> - 父节点索引:
<font style="color:rgb(0, 0, 0);">(i - 1) / 2</font>(整数除法)
堆排序核心思路(以升序为例):
- 构建大顶堆:将待排序数组转换成大顶堆(根节点是最大值)。
- 堆顶交换与调整 :
- 交换堆顶(最大值)与堆尾元素(将最大值放到最终位置)。
- 缩小堆的范围(排除已排序的堆尾元素),对新堆顶执行「堆化(Heapify)」,重新维护大顶堆属性。
- 重复步骤 2:直到堆的范围缩小到 1,数组排序完成。
关键操作:堆化(Heapify):
堆化是维护堆属性的核心操作,目的是将一个「可能违反堆属性的节点」向下调整,使以该节点为根的子树重新成为堆。
- 输入:堆数组、堆的大小、当前需要调整的节点索引
<font style="color:rgb(0, 0, 0);">i</font>。 - 步骤:
- 找到
<font style="color:rgb(0, 0, 0);">i</font>的左、右子节点,确定三者中的最大值索引<font style="color:rgb(0, 0, 0);">maxIdx</font>。 - 若
<font style="color:rgb(0, 0, 0);">maxIdx != i</font>(说明子节点更大,违反大顶堆属性):- 交换
<font style="color:rgb(0, 0, 0);">i</font>和<font style="color:rgb(0, 0, 0);">maxIdx</font>的元素。 - 递归(或迭代)对
<font style="color:rgb(0, 0, 0);">maxIdx</font>位置的节点继续堆化(因为交换后该位置可能违反堆属性)。
- 交换
- 找到
java
public class HeapSort {
// 堆排序入口:升序(基于大顶堆)
public static void heapSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return; // 边界条件:空数组或长度为1无需排序
}
int n = arr.length;
// 步骤1:构建大顶堆(从最后一个非叶子节点开始向前堆化)
// 最后一个非叶子节点索引 = (n-1 - 1)/2 = n/2 - 1
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 步骤2:堆顶交换+堆化(重复n-1次,每次确定一个最大值)
for (int i = n - 1; i > 0; i--) {
// 交换堆顶(arr[0],最大值)与堆尾(arr[i])
swap(arr, 0, i);
// 堆大小缩小为i(排除已排序的arr[i]),对新堆顶堆化
heapify(arr, i, 0);
}
}
/**
* 堆化操作:将以i为根的子树调整为大顶堆
* @param arr 堆数组
* @param heapSize 当前堆的大小(有效元素范围:0~heapSize-1)
* @param i 当前需要调整的节点索引
*/
private static void heapify(int[] arr, int heapSize, int i) {
int largest = i; // 初始化最大值为当前节点
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 若左子节点大于当前最大值,更新largest
if (left < heapSize && arr[left] > arr[largest]) {
largest = left;
}
// 若右子节点大于当前最大值,更新largest
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
// 若最大值不是当前节点(需要调整)
if (largest != i) {
swap(arr, i, largest); // 交换当前节点与最大值节点
// 递归堆化被交换的子节点(确保子树仍为大顶堆)
heapify(arr, heapSize, largest);
}
}
// 交换数组中两个元素
private static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 测试代码
public static void main(String[] args) {
int[] arr = {5, 2, 9, 3, 7, 6, 1, 8, 4};
System.out.println("排序前:" + Arrays.toString(arr));
heapSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
}
快排思想?优化方案?
快速排序(Quick Sort)是一种分治思想 的排序算法,核心是「选基准、分区间、递归排序」,平均时间复杂度 O(n log n) ,最坏 O(n²) ,空间复杂度 O(log n) (递归栈),属于不稳定排序。
核心步骤(分治流程):
- 选择基准(Pivot):从待排序数组中选一个元素作为「基准」(比如数组第一个、最后一个、中间元素或随机元素)。
- 分区(Partition) :遍历数组,将小于基准 的元素放到基准左侧,大于基准的元素放到右侧(等于基准的元素可左可右,不影响排序正确性),最终基准元素落在「其最终排序位置」。
- 递归排序:对基准左侧的子数组和右侧的子数组,重复「选基准 + 分区」步骤,直到子数组长度 ≤ 1(递归终止条件,此时子数组已有序)。
优化方案:
- 三数取中(Median of Three):从数组「左端、右端、中间」三个位置选中间值作为基准,避免极端情况。
- 随机基准(Random Pivot):随机选择一个元素作为基准,数学期望上避免分区失衡,实际效果优于三数取中(但有微小随机开销)。
JVM
【P0】JVM内存结构?
线程共享:
- 堆(Heap) :
- 核心作用:存储对象实例、数组、字符串常量池(String Pool)、静态变量
- 关键特点:
- JVM 最大内存区域;
- 分代划分(新生代 Eden+Survivor、老年代);
- 垃圾回收的核心区域
- 元空间(Metaspace) :
- 核心作用:实现方法区,存储类元信息(类结构、字段、方法)、运行时常量池
- 关键特点:
- 物理存储在本地内存,默认无固定上限(可通过参数限制);
- 回收目标:无用类、动态常量;
- 替代 Java 8 前的永久代,彻底解决 PermGen OOM
- 运行时常量池 :
- 核心作用:存储编译期常量、符号引用、动态生成的常量
- 关键特点:
- 元空间的一部分;
- 支持动态添加常量,适配运行时类加载场景
线程私有:
- 程序计数器 :
- 核心作用:记录当前线程执行的字节码指令地址
- 关键特点:
- 唯一不会 OOM 的区域;
- 线程切换时恢复执行位置的核心依据
- 虚拟机栈 :
- 核心作用:存储方法调用栈帧(局部变量表、操作数栈、方法出口)
- 关键特点:
- 栈帧随方法调用创建、方法返回销毁;
- 栈深度过大抛 StackOverflowError,内存不足抛 OOM;
- Java 11+ 支持动态扩容 / 缩容
- 本地方法栈 :
- 核心作用:支持 Native 方法(如 JNI 调用)的执行
- 关键特点:
- 与虚拟机栈功能类似,仅服务于本地方法;
- Java 9+ 优化底层实现,降低内存开销
核心总结:
- 堆 + 方法区(元空间):线程共享,是内存溢出(OOM)的高发区,也是 GC 重点关注区域;
- 程序计数器 + 虚拟机栈 + 本地方法栈:线程私有,随线程销毁释放资源,主要风险是栈溢出(StackOverflowError);
- 核心记忆:堆存对象,方法区存类信息,栈存方法调用,计数器定执行位置。
JVM的内存结构,那部分占用内存最大?
默认情况下,JVM 内存结构中堆(Heap) 占用内存最大,是存储对象实例和数组的核心区域。
核心原因:
- 存储内容体量最大:Java 程序运行中创建的所有对象(如自定义类实例、集合对象)均存于堆中,其他区域仅存轻量数据(如局部变量、类元数据)。
- 动态内存需求:对象创建 / 销毁频繁且生命周期差异大,需足够空间容纳,避免频繁内存不足(OOM)。
其他区域对比:
- 方法区(MetaSpace):存类元数据,占比中等,仅在加载大量类时可能膨胀。
- 虚拟机栈 / 本地方法栈:线程私有,单栈内存小(默认几 MB),总占比远低于堆。
- 程序计数器:存指令地址,占比极小,且不会 OOM。
堆的内存空间分配?
堆内存逻辑上分为 新生代(Young Generation) 和 老年代(Old Generation) ,比例默认 1:2(可通过<font style="color:#000000;">-XX:NewRatio</font>调整)。
- 新生代(占堆内存 1/3 左右) 存储新创建的对象 (短命对象),进一步细分为:
- Eden 区(占新生代 80%):新对象优先分配到 Eden 区;
- Survivor 区(共占 20%,分 From 和 To 两块,各占 10%):用于存放 Eden 区 GC 后存活的对象,两块区域始终有一块为空。
- 新生代采用复制算法 回收内存,触发Minor GC(轻量 GC),频率高、速度快。
- 老年代(占堆内存 2/3 左右) 存储存活时间长的对象 (长命对象),如多次经历多次 Minor GC 仍存活的对象(默认 15 次,可通过
<font style="color:#000000;">-XX:MaxTenuringThreshold</font>调整)。老年代采用标记 - 清除 / 标记 - 整理算法 ,触发Major GC/Full GC,频率低、耗时久。
方法区为什么要改成元空间?
JVM 将方法区从永久代改为元空间,核心是解决永久代的内存痛点,具体有三点:
- 避免 OOM:元空间用本地内存,不受堆大小限制,解决永久代因类加载过多导致的 "PermGen space" 溢出。
- 优化内存管理:元空间按类加载器独立分配,支持动态扩缩容,减少永久代的内存碎片问题。
- 提升 GC 效率:元空间回收不依赖 Full GC,可独立触发类型卸载,降低 Full GC 频率,减少性能损耗。
JVM垃圾回收方式?
JVM 垃圾回收分按区域 和按算法两类,核心如下:
- 按区域:
- Minor GC:收新生代(Eden+Survivor),用复制算法,频率高、快。
- Major GC:收老年代,用标记 - 清除 / 整理算法,频率低、慢。
- Full GC:收全堆 + 元空间,触发 STW,影响大。
- 核心算法:
- 复制算法:无碎片、快,需额外空间(适新生代)。
- 标记 - 清除:无需空间,产碎片(少用)。
- 标记 - 整理:无碎片,需移动对象(适老年代)。
类加载机制过程?
- 加载:取类字节流,转方法区数据,生成 Class 对象。
- 验证:字节码验证、文件验证。
- 准备:为静态变量分配内存,设默认初始值(如 int=0)。
- 解析:常量池符号引用(如类名)转直接引用(内存地址)。
- 初始化:执行静态变量赋值和静态代码块,生成() 方法。
多线程之间怎么共享变量?
- 共享成员变量(同一对象引用);
- 共享静态变量(类级别);
- 用 JUC 并发容器(ConcurrentHashMap 等);
- 原子类(AtomicInteger)或锁(synchronized/ReentrantLock)保障安全。
核心:线程访问同一内存地址,配合同步机制防竞态条件。
新生代为什么要用复制算法,为什么不用标记-整理??
核心逻辑:新生代对象 "朝生夕死"(存活率<10%),复制算法仅需复制少量存活对象,时间开销极低;标记 - 整理需移动大量对象 + 更新引用,耗时不匹配。
| 算法 | 核心流程 | 时间开销 | 空间开销 | 适用场景 |
|---|---|---|---|---|
| 复制算法 | 划分 2 块等大空间→标记存活对象→复制到另一块→清空原空间 | 低(仅复制存活对象,无移动 / 排序) | 高(需预留 50% 空闲空间) | 存活对象少的区域(如新生代) |
| 标记整理 | 标记存活对象→移动到空间一端→清空剩余区域 | 高(除标记外,需移动对象 + 更新引用) | 低(无额外空闲空间浪费) | 存活对象多的区域(如老年代) |
标记复制算法:
- 契合新生代 "对象朝生夕死" 的特性
- 无内存碎片,无需额外整理(新生代对象创建频繁,碎片会严重影响内存分配效率),而标记 - 整理虽也能解决碎片,但 "整理" 步骤本身就是时间开销,新生代没必要为了省空间付出这个成本。
- 空间可接受:新生代用 Eden+2 个 Survivor(8:1:1)结构,仅预留 10% 空间,复制算法的空间开销可控,且天然无内存碎片,无需额外整理。
- 新生代核心诉求:"快速 GC、缩短 STW",标记 - 整理的时间成本与该诉求矛盾,且其 "无空间浪费" 的优势在新生代无意义。
新生代的设计核心是 "优先保证 GC 效率",复制算法通过 "少量存活对象复制 + 天然无碎片",完美匹配其 "低存活率、短生命周期" 的特性;而标记 - 整理的 "高时间开销" 与新生代的效率诉求冲突,且空间优势无法发挥,因此不被采用。
线程频繁full gc排查?
核心思路:先止血→定位根因→优化解决
- 紧急止血(10 分钟内)
- 临时调大堆内存(-Xmx),降级非核心功能 / 批量任务;
- 提升缓存命中率,减少对象创建;
- 监控堆使用、Full GC 频率 / 耗时。
- 定位根因(核心)
- 抓数据:导出堆 Dump(jmap)、收集 GC 日志、查线程栈(jstack);
- 找诱因:内存泄漏(大对象 / 集合未清理)、大对象频繁创建、JVM 参数不合理、缓存 / 连接池使用不当。
- 优化解决
- 修泄漏:清理无用引用、关闭未释放资源;
- 优对象:复用对象、拆分大对象;
- 调参数:优化堆配比、新生代比例、晋升阈值;
- 减压力:优化缓存 / 数据库,避免大量数据加载。
导致原因:
- 老年代内存泄漏
- 大对象频繁创建
- 新生代参数不合理
- 缓存使用不当
- 长事务/大事务
G1垃圾回收器怎么实现可配置STW?
核心是通过「软实时目标设定 + 阶段化 STW 拆分 + 自适应调节机制」实现的。
核心机制:
- 堆分区化:将堆划分为等大 Region(1~32MB),每次仅回收部分 Region,控制单次 STW 范围。
- 停顿预测模型:统计历史回收不同 Region 的耗时,反向计算目标时长内可回收的最大 Region 数,优先回收高垃圾占比 Region。
- 自适应调节:对比实际 STW 与目标值,动态调整回收 Region 数、年轻代大小、Mixed GC 次数,平衡延迟与吞吐量。
- 分阶段拆分 STW:将长 STW 拆解为多个短停顿,Final Mark 阶段仅留少量收尾工作在 STW,其余并发处理。
MySQL
某个接口中sql查询很慢,怎么排查?
- 看执行计划:用
<font style="color:#000000;">EXPLAIN</font>查是否走索引、有无全表扫描 / 临时表。 - 查索引:检查查询 / 排序 / 分组字段是否有有效索引,清理冗余索引。
- 析数据:看表数据量是否过大,是否有数据倾斜。
- 观状态:用
<font style="color:#000000;">SHOW PROCESSLIST</font>查锁等待、资源过载(CPU/IO)。 - 优 SQL:避免
<font style="color:#000000;">SELECT *</font>、拆分复杂关联、优化<font style="color:#000000;">NOT IN</font>/<font style="color:#000000;">OR</font>等。 - 检缓存连接:确认缓存命中情况、连接池状态。
【P0】MySQL索引数据结构?
MySQL 索引的核心数据结构是B + 树,不同索引类型基于其衍生,具体如下:
- **主键索引(聚簇索引)**B + 树结构,叶子节点直接存储整行数据,按主键顺序排列,查询效率最高。
- 二级索引(非主键索引) 同样是 B + 树,叶子节点存储主键值,需通过主键回表查询完整数据(覆盖索引可避免回表)。
- 其他特殊索引
- 哈希索引:仅 Memory 引擎支持,适用于精确匹配,不支持范围查询。
- 全文索引:基于倒排索引,用于文本内容模糊搜索。
select where order by group by 等等的执行顺序 起的别名在哪个阶段生效?
执行顺序:
<font style="color:#000000;">FROM</font>:确定查询的表 / 数据源<font style="color:#000000;">JOIN</font>:关联表(若有)<font style="color:#000000;">WHERE</font>:过滤行数据(基于原始字段,未经过聚合 / 排序)<font style="color:#000000;">GROUP BY</font>:按指定字段分组<font style="color:#000000;">HAVING</font>:过滤分组后的结果(仅对聚合结果生效)<font style="color:#000000;">SELECT</font>:选择要返回的字段(含计算、别名定义)<font style="color:#000000;">DISTINCT</font>:去重(基于 SELECT 结果)<font style="color:#000000;">ORDER BY</font>:对最终结果排序(可使用 SELECT 定义的别名)<font style="color:#000000;">LIMIT/OFFSET</font>:限制返回行数
别名生效阶段:
仅在**** **<font style="color:#000000;">SELECT</font>******执行之后的阶段生效,即:
- 可使用别名的阶段:
<font style="color:#000000;">ORDER BY</font>、<font style="color:#000000;">LIMIT</font>(依赖 SELECT 结果) - 不可使用别名的阶段:
<font style="color:#000000;">WHERE</font>、<font style="color:#000000;">GROUP BY</font>、<font style="color:#000000;">HAVING</font>(执行在 SELECT 之前,别名尚未定义。
核心总结:
- 执行顺序核心逻辑:先找表→过滤行→分组→过滤分组→选字段→排序→限制行数
- 别名生效规则:仅
<font style="color:#000000;">ORDER BY</font>/<font style="color:#000000;">LIMIT</font>可使用,<font style="color:#000000;">WHERE</font>/<font style="color:#000000;">GROUP BY</font>/<font style="color:#000000;">HAVING</font>需用原始字段或重复计算逻辑
索引未命中SQL命令?
sql
-- 字段隐式转换(varchar 字段用数值查询,索引失效)
EXPLAIN SELECT * FROM user WHERE name = 123;
-- 前缀模糊查询(%开头,索引失效)
EXPLAIN SELECT * FROM user WHERE name LIKE '%三';
-- 联合索引违背最左前缀原则(idx_dept_age 只查 age,索引失效)
EXPLAIN SELECT * FROM user WHERE age > 30;
什么是最左前缀匹配规则?
最左前缀匹配是联合索引的查询规则:需从索引最左侧列开始依次匹配,不能跳过左侧列,否则索引失效或部分失效。
为什么MySQL要设计最左前缀匹配原则?
本质:是为了兼顾索引查询效率与存储 / 维护成本。
核心设计初衷:
- 适配索引物理存储结构:MySQL 联合索引按 "最左前缀" 有序存储(如字典排序逻辑),查询时按左→右顺序匹配,能快速定位数据范围,避免全索引扫描。
- 平衡索引实用性与开销:无需为 (a,b)、(a) 单独建索引,一个联合索引 (a,b) 可覆盖 "仅查 a""查 a+b" 的场景,减少索引数量,降低存储和更新成本。
- 提升查询匹配效率:优先匹配最左列,能快速缩小数据筛选范围,后续列的匹配基于已筛选结果,进一步提升查询速度。
MySQL一主多从怎么保证数据一致性的?
核心结论 :binlog 日志同步 + 校验补偿,确保主库变更完整有序同步到从库。
核心同步流程(基础保障):
主从通过「二进制日志(binlog)」实现数据同步,流程不可逆且有序:
- 主库:所有写操作(增删改)执行后,记录到 binlog(含操作 SQL / 行数据、事务 ID、时间戳等元数据);
- 从库:IO 线程拉取主库 binlog,写入本地中继日志(relay log);
- 从库:SQL 线程解析 relay log,重演主库操作,最终同步数据。
关键一致性保障手段:
- binlog 格式选对 :优先用
<font style="color:rgb(0, 0, 0);">ROW 格式</font>(记录行级变更,而非 SQL 语句),避免 SQL_mode 差异、函数(如 NOW ())导致的执行结果不一致; - 事务级同步:主库事务提交时,binlog 才会被从库拉取,确保事务原子性;
- 同步校验机制 :
- 主从维护「GTID(全局事务 ID)」或「binlog 文件名 + 偏移量」,标记已同步位置,避免漏同步 / 重复同步;
- 从库 SQL 线程按顺序执行 relay log,不打乱事务执行顺序;
- 参数约束 :
- 主库开启
<font style="color:rgb(0, 0, 0);">sync_binlog=1</font>(每次事务提交强制刷 binlog 到磁盘,避免主库宕机丢失日志); - 从库开启
<font style="color:rgb(0, 0, 0);">innodb_flush_log_at_trx_commit=1</font>(同步后刷盘,避免从库宕机丢失已解析的操作);
- 主库开启
- 延迟与校验补偿 :
- 定期用
<font style="color:rgb(0, 0, 0);">pt-table-checksum</font>工具比对主从数据一致性,发现差异用<font style="color:rgb(0, 0, 0);">pt-table-sync</font>修复; - 业务层面可根据场景选择「强一致性(读主库)」或「最终一致性(读从库 + 容忍短暂延迟)」。
- 定期用
深分页解决方案?
游标分页(最优) :用主键 / 唯一索引有序性(<font style="color:rgb(0, 0, 0);">id > 上一页尾id LIMIT 10</font>),无性能衰减,仅支持滚动加载(不支持跳页);
- 原理:利用主键 / 唯一索引的有序性,以上一页最后一条数据的主键为条件,替代
<font style="color:rgb(0, 0, 0);">OFFSET</font>偏移。
sql
-- 第1页:取前10条
SELECT id, name FROM t WHERE id > 0 LIMIT 10;
-- 第2页:以上一页最后一条id(如10)为起点
SELECT id, name FROM t WHERE id > 10 LIMIT 10;
-- 深分页(第10000页):直接定位到100000之后
SELECT id, name FROM t WHERE id > 100000 LIMIT 10;
覆盖索引 + 延迟关联 :先查主键(<font style="color:rgb(0, 0, 0);">SELECT id FROM t LIMIT 100000,10</font>),再关联原表查详情,支持跳页,性能较优;
原理:先通过覆盖索引(仅包含查询所需字段)快速定位目标数据的主键,再关联原表查询详情,减少扫描数据量。
sql
-- 深分页:先查主键(覆盖索引 idx_name),再关联原表
SELECT t.id, t.name, t.content
FROM t
JOIN (
SELECT id FROM t ORDER BY name LIMIT 100000, 10 -- 覆盖索引扫描,仅查id,速度快
) AS temp ON t.id = temp.id;
MySQL中锁和事务是否有相关性?
答案:强相关
核心关系:事务是锁的作用范围容器,锁是事务隔离性的实现核心:
- 锁的生命周期绑定事务:事务发起加锁,
<font style="color:rgb(0, 0, 0);">commit/rollback</font>释放锁(语句级锁除外); - 事务隔离性依赖锁:隔离级别(读已提交 / RR / 串行化)通过锁的类型、加锁 / 释放时机实现;
- 锁冲突触发事务行为:锁冲突导致事务阻塞等待或死锁回滚,保障并发数据一致性。
总结:事务定义并发操作的原子范围,锁实现资源互斥规则,二者协同保证数据一致性。
B+树与普通多叉树?
普通多叉树(如多路查找树)
- 结构:每个节点存储关键字 + 数据指针,子节点数量无严格限制,节点大小不固定,叶子节点无关联。
- 问题:无磁盘适配设计,节点大小不均导致 IO 次数不可控;范围查询需回溯父节点,效率极低。
B + 树
- 非叶子节点:仅存储关键字 + 子节点指针,不存数据,可容纳更多关键字,树高更低;
- 叶子节点:存储全部关键字 + 数据指针 ,且叶子节点通过双向链表串联;
- 所有关键字都出现在叶子节点,非叶子节点仅作为索引。
MySQL选择B+树的核心原因?
- 降低磁盘 IO 次数(数据库性能的核心瓶颈) :数据库索引存储在磁盘上,每次查询需加载磁盘块到内存,IO 次数 = 树的高度 ,因此降低树高是关键:
- 普通多叉树:节点存储数据 + 关键字,单个节点能存的关键字少,树高更高(比如百万数据可能需要 5-6 层);
- B + 树:非叶子节点仅存关键字和指针,无数据,相同大小的磁盘块(如 4KB)可存储更多关键字,树高极低(百万数据仅需 3-4 层),IO 次数大幅减少。
- 完美适配范围查询(MySQL 高频场景)
- 普通多叉树 / B 树:范围查询需遍历多个分支,回溯父节点,效率低;
- B + 树:叶子节点通过双向链表串联,范围查询只需找到起始叶子节点,沿链表遍历即可,无需回溯,效率接近顺序遍历。
- 非叶子节点的稳定性更高
- B 树:插入 / 删除数据时,非叶子节点可能发生分裂 / 合并,导致结构频繁变动;
- B + 树:插入 / 删除仅影响叶子节点(非叶子节点仅作索引,无数据),结构更稳定,维护成本更低。
如何保证B+树层级3~4层?
核心是控制单节点可存储的关键字数量 (即节点扇出)和数据总量匹配树高的数学逻辑
B + 树的层级(树高 h)和数据量的关系公式:<font style="color:#000000;">总数据量 N ≤ 扇出^(h-1) × 叶子节点单节点数据量</font>
InnoDB 默认配置下,非叶子节点的扇出约1000(后文推导),因此:
- 3 层 B + 树可存储:
<font style="color:#000000;">1000×1000 = 100万</font>条数据; - 4 层 B + 树可存储:
<font style="color:#000000;">1000×1000×1000 = 10亿</font>条数据;
只要数据量在100 万~10 亿 之间,B + 树层级天然稳定在 3~4 层;若数据量超出 / 不足,需通过调整节点扇出或拆分数据来适配。
MySQL深分页如何解决?
基于主键 / 索引的「游标分页」(最优):
利用上一页最后一条数据的主键 / 唯一索引值,替代偏移量,仅扫描目标行:
java
-- 原深分页(低效)
SELECT * FROM t WHERE status=1 LIMIT 100000, 10;
-- 游标分页(高效):假设上一页最后一条id=100000
SELECT * FROM t WHERE status=1 AND id > 100000 LIMIT 10;
- 分页仅需 "下一页"(无需跳页),如 APP 列表翻页;
- 主键 / 索引有序且连续(或允许非连续)。
子查询定位起始位置(优化偏移量):
先通过索引找到偏移量对应的主键,再关联查询目标数据,减少扫描范围:
java
-- 子查询找偏移量对应的id
SELECT * FROM t
JOIN (SELECT id FROM t WHERE status=1 LIMIT 100000, 1) AS tmp
ON t.id = tmp.id
LIMIT 10;
子查询仅扫描索引(覆盖索引),无需回表,比直接 LIMIT 减少全表扫描。
什么是MySQL磁盘碎片?
MySQL 磁盘碎片是数据操作后遗留的 "零散空闲空间",本质是空间利用不连续;
- 产生根源:删除 / 更新导致空间释放后无法高效复用;
- 核心影响:空间浪费 + IO 性能下降;
- 解决关键:定期重建表(OPTIMIZE/ALTER),同时规范数据操作(如顺序插入、批量删除)减少碎片产生。
JUC
【P0】进程与线程区别?
核心本质:
- 进程是操作系统资源分配的最小单位,线程是 CPU 调度(执行)的最小单位;
- 线程依赖进程存在,一个进程可包含多个线程,共享进程资源。
| 对比维度 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 资源分配 | 独立资源空间(代码段、数据段、堆、文件句柄、PID) | 共享所属进程的全部资源(仅私有线程栈、PC 寄存器、线程 ID) |
| 调度单位 | 操作系统调度的基本单位(粒度粗) | CPU 调度的最小单位(粒度细,切换更快) |
| 创建/销毁开销 | 大(需分配资源、初始化 PCB) | 小(仅需初始化 TCB,共享进程资源) |
| 切换开销 | 大(需切换进程上下文、刷新 TLB) | 小(仅切换线程上下文,资源共享无需切换) |
| 通信方式 | 复杂(IPC:管道、消息队列、共享内存等) | 简单(直接读写进程内共享数据,需同步锁) |
| 独立性 | 高(进程崩溃不影响其他进程) | 低(线程崩溃可能导致整个进程退出) |
| 并发/并行支持 | 可并发 / 并行(多进程同时运行) | 可并发 / 并行(多线程共享 CPU 执行) |
【P0】进程间的通信?
| 核心方式 | 核心特点 | 适用场景 |
|---|---|---|
| 管道 / FIFO | 简单易用(文件式读写),本地通信 | 父子进程协作、本地无亲缘进程小数据传输 |
| 共享内存 | 速度最快(无数据拷贝),需同步 | 高频、大数据量本地通信(如实时处理) |
| 信号量 | 仅用于同步互斥,不传输数据 | 保护共享资源(配合共享内存 / 消息队列) |
| 消息队列 | 结构化消息(带类型),异步 | 本地小数据异步通信(如通知推送) |
| 套接字Socket | 跨主机 / 本地通用,全双工 | 跨主机通信(分布式系统)本地高可靠通信 |
- 管道
- 原理:基于内核缓冲区的 "半双工" 通信(数据只能单向流动),仅支持父子进程 / 兄弟进程间通信
- 适用场景:父子进程通信
- 共享内存
- 原理:操作系统在物理内存中开辟一块区域,映射到多个进程的虚拟地址空间,进程直接读写该内存(无需内核转发),是最快的 IPC 方式。
- 场景:大数据量通信(如视频处理、实时数据传输)、对性能要求极高的场景。
- 消息队列
- 原理:内核维护的 "消息链表",进程可向队列发送 "带类型的消息"(如类型 + 数据),接收方按类型筛选消息,无需按顺序读取。
- 场景:进程间异步通信(如后台服务向应用进程推送通知)、数据量较小的结构化消息传输。
- 信号量
- 原理:内核维护的 "计数器",用于进程间同步与互斥 (而非传输数据),通过
<font style="color:#000000;">P/V</font>操作(减 1 / 加 1)控制资源访问 - 场景:保护共享资源(如共享内存、文件)、协调进程执行顺序(如进程 A 完成后再执行进程 B)。
- 原理:内核维护的 "计数器",用于进程间同步与互斥 (而非传输数据),通过
【P0】线程间的通信?
| 方式 | 核心原理 | 适用场景 | 关键注意点 |
|---|---|---|---|
| 共享变量(volatile) | 修饰变量,保证内存可见性(禁止指令重排) | 简单状态同步(如标志位控制线程启停) | 仅保证可见性,不保证原子性(需配合锁) |
| 锁 + 共享对象 | synchronized/Lock 保护共享对象,同步读写 | 复杂数据交换(如共享队列、业务对象) | 需手动保证线程安全,避免死锁 |
| 等待 / 通知机制 | Object.wait ()/notify ()(配合 synchronized) | 线程间顺序协调(如生产者 - 消费者) | 必须在同步块内调用,避免虚假唤醒 |
| Condition 条件变量 | Lock 配套的等待 / 通知(支持多条件) | 精细化顺序控制(如多生产者多消费者) | 绑定 Lock,可精准唤醒指定线程组 |
| 阻塞队列(BlockingQueue) | 自带阻塞 / 唤醒,无需手动同步 | 生产者 - 消费者模型(解耦线程) | 内置线程安全,简化代码(推荐优先用) |
为什么要线程?多线程危害?
线程****的核心价值:「高效利用硬件资源 + 提升程序并发能力」,尤其适配 IO 密集型和多核场景;
多线程的缺点:本质是「并发带来的复杂性 + 资源开销」,需通过合理设计(如线程池、无锁编程、阻塞队列)规避风险。
CyclicBarrier的底层实现?
CyclicBarrier 底层基于 ReentrantLock + Condition 实现,核心是 "计数器 + 条件等待" 机制,支持多线程屏障汇合与重复复用。
- 用
<font style="color:#000000;">ReentrantLock</font>保证计数器操作的线程安全(原子性); - 用
<font style="color:#000000;">Condition</font>(由锁创建)实现线程等待 / 唤醒(替代<font style="color:#000000;">Object.wait()</font>/<font style="color:#000000;">notifyAll()</font>)。
什么是协程?
协程(Coroutine)是用户态的轻量级线程,由程序代码而非操作系统内核调度,兼具 "轻量、高效、可控" 的核心特性,是解决高并发场景的关键技术。
- 本质:非抢占式的执行单元,拥有自己的执行上下文(栈、寄存器、程序计数器),可在指定点主动 "挂起(yield)" 和 "恢复(resume)",切换成本远低于线程。
- 对比线程 / 进程:
| 维度 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 调度者 | 操作系统内核 | 操作系统内核 | 用户程序 |
| 切换成本 | 高(内存映射、上下文切换 | 中(内核态上下文切换) | 极低(用户态,仅保存栈指针) |
| 资源占用 | 大(独立地址空间) | 中(共享进程资源) | 极小(MB 级内存可创建百万协程) |
| 抢占方式 | 抢占式 | 抢占式 | 协作式(主动让出 CPU) |
- 协作式调度 :协程不会被内核强制中断,需自身主动调用
<font style="color:rgb(0, 0, 0);">yield</font>/<font style="color:rgb(0, 0, 0);">await</font>等方法让出执行权,避免线程的 "抢占式调度" 带来的上下文切换开销。 - 轻量级:单个进程可创建数十万甚至百万协程,内存占用仅几 KB / 个(线程约 MB 级),适合高并发场景(如百万级 IO 请求)。
- 上下文复用:挂起时仅保存少量执行状态,恢复时直接复用,无需内核参与,切换耗时仅为线程的 1/1000~1/100。
实现原理:
- M:N 调度:多个虚拟线程绑定少量平台线程(载体线程),阻塞时 JVM 卸载虚拟线程、保存上下文,载体线程切换执行其他虚拟线程,阻塞完成后重新挂载恢复。
- 非阻塞化适配:JVM 对 IO、sleep 等标准阻塞 API 适配,避免虚拟线程 "钉住" 载体线程。
- 分段栈 :基于
<font style="color:rgb(0, 0, 0);">Continuation</font>实现轻量级上下文切换,切换成本仅为平台线程的 1/1000。
进程、线程、协程什么区别?
三者是不同粒度的执行单元,核心差异体现在资源占用、调度方式、切换成本,本质是为了平衡 "资源隔离" 与 "执行效率":
| 维度 | 进程 | 线程(内核线程) | 协程(用户态 / 虚拟线程) |
|---|---|---|---|
| 本质 | 资源分配的最小单位 | CPU 调度的最小单位(内核态) | 程序调度的最小单位(用户态) |
| 资源占用 | 独立地址空间(内存 / 文件句柄),MB 级 / 个 | 共享进程资源,仅占栈 / 寄存器,MB 级 / 个 | 共享线程资源,仅占轻量级上下文,KB 级 / 个 |
| 调度者 | 操作系统内核 | 操作系统内核 | 用户程序 / JVM(无内核参与) |
| 切换成本 | 极高(内存映射 / 页表切换) | 中(内核态上下文切换,保存寄存器 / 栈) | 极低(用户态,仅保存栈指针) |
| 并发能力 | 低(单机数百个) | 中(单机数千个) | 极高(单机百万级) |
| 抢占方式 | 抢占式(内核强制切换) | 抢占式(内核按时间片切换) | 协作式(主动 yield/await 让出) |
| 隔离性 | 强(进程崩溃不影响其他进程) | 弱(线程崩溃导致进程退出) | 极弱(协程崩溃导致线程退出) |
| 通信方式 | IPC(管道 / 套接字 / 共享内存) | 共享变量(需加锁) | 直接共享变量(单线程内无需锁) |
| 典型应用 | 独立程序(如浏览器 / MySQL) | 多任务并行(如线程池处理请求) | 高 IO 并发(如网关 / 爬虫 / 微服务调用) |
进程间通信方式,介绍信号量并发控制的原理?
信号量并非用于传输数据,而是解决进程 / 线程间的竞争条件(如共享资源抢占、执行顺序同步),核心是通过 "原子性的计数器操作" 实现并发控制。
核心概念:
- 计数器(value):内核维护的整数变量,代表 "可用资源数"(可正可负);
- P 操作(wait/down):申请资源 → 计数器 - 1,若结果 < 0 则进程阻塞,直到计数器≥0;
- V 操作(post/up):释放资源 → 计数器 + 1,若有阻塞进程则唤醒一个;
- 原子性:P/V 操作是内核级原子指令,不会被中断,避免竞态条件。
两种核心类型:
互斥信号量(二进制信号量,Mutex)
- 计数器仅取值 0/1,用于实现 "互斥访问"(如共享内存只能被一个进程读写);
- 原理:
- 初始值 = 1(资源可用);
- 进程 A 执行 P 操作:计数器 = 0,占用资源;
- 进程 B 执行 P 操作:计数器 =-1,阻塞等待;
- 进程 A 执行 V 操作:计数器 = 0,唤醒进程 B;
- 进程 B 被唤醒后,重新执行 P 操作:计数器 = 0,占用资源。
计数信号量(通用信号量)
- 计数器可取任意非负整数,用于 "资源限流"(如限制同时访问数据库的进程数为 5);
- 原理:
- 初始值 = N(如 N=5,代表最多 5 个进程同时访问);
- 每个进程申请资源时执行 P 操作,计数器 - 1;
- 计数器 > 0:进程继续执行(资源可用);
- 计数器 = 0:后续进程执行 P 操作后阻塞,直到有进程执行 V 操作释放资源;
- 进程释放资源时执行 V 操作,计数器 + 1,唤醒一个阻塞进程。
僵尸进程是什么?
- 定义 :子进程执行完毕终止,父进程未调用
<font style="color:#000000;">wait()</font>/<font style="color:#000000;">waitpid()</font>回收其进程描述符(PCB) ,残留的进程状态,标记为<font style="color:#000000;">Z</font>。 - 核心特性
- 不占用 CPU、内存等运行资源,仅占用 PID 和少量内核数据结构;
<font style="color:#000000;">kill -9</font>无法杀死(进程已终止);- 大量堆积会耗尽 PID 资源,导致无法创建新进程。
- 解决方法
- 父进程主动调用
<font style="color:#000000;">wait()</font>/<font style="color:#000000;">waitpid()</font>回收子进程状态; - 父进程退出,僵尸进程被
<font style="color:#000000;">init进程(PID=1)</font>接管并自动清理; - 父进程注册
<font style="color:#000000;">SIGCHLD</font>信号处理函数,异步回收子进程。
- 父进程主动调用
- 与孤儿进程区别
- 僵尸进程:子死父未收,残留 PCB;
- 孤儿进程:父死子未死,被 init 接管,正常运行。
什么是管程?
- 通俗理解:管程是 "带锁的房间",多个线程要进房间操作共享数据,必须先拿钥匙(锁),且房间里可以设置 "等待条件"(比如没数据就等,有数据再醒)。
- 核心特征 :
- 管程不是 "执行单元"(进程 / 线程 / 协程是),而是解决多线程同步问题的编程抽象。
- 核心思想:将共享资源和操作共享资源的方法封装起来,内置锁和条件变量,避免裸锁的滥用。
- 语言支持:Java 的
<font style="color:#000000;">synchronized</font>、Python 的<font style="color:#000000;">threading.Condition</font>、Go 的<font style="color:#000000;">sync</font>包本质都是管程的实现。 - 举例:多线程操作一个队列,管程可以封装 "入队""出队" 方法,确保同一时间只有一个线程操作队列,且队列为空时出队线程自动等待。
栈是独立的吗,是线程私有的吗,线程间如何做数据共享,线程共享会带来什么问题?
核心结论 :栈(线程栈 / 虚拟机栈)是线程私有的、完全独立的,每个线程启动时 JVM 会为其分配专属栈空间,生命周期与线程一致。
- 独立性体现:线程栈存储该线程的方法调用栈帧(局部变量、操作数栈、返回地址等),不同线程的栈空间物理隔离,互不访问;
- 对比堆:堆是所有线程共享的内存区域(对象、数组存储于此),这是线程间数据共享的核心载体。
分布式事务
Seata的事务模式?
- 2P****C模式:基于全局锁 + 本地 undo 日志(由 Seata 生成),本地事务提交后释放数据库锁,仅全局锁在二阶段持有。
- TCC 模式 :手动实现
<font style="color:#000000;">Try</font>(资源检查 / 预留)、<font style="color:#000000;">Confirm</font>(确认提交)、<font style="color:#000000;">Cancel</font>(回滚释放)三个接口,适用于非关系型数据库或业务逻辑复杂的场景,侵入性高但灵活性强。 - SAGA 模式:基于状态机实现长事务拆分,按顺序执行子事务,失败时通过补偿事务逆向回滚,适合长流程业务(如订单履约)。
- XA 模式:依赖数据库原生 XA 协议(如 MySQL 的 XA 事务),Seata 作为协调者,事务隔离性强但性能较差,兼容性好但侵入性低。
Seata的2PC比数据库2PC优势?
- 性能更优:AT 模式下,一阶段本地事务提交后释放数据库锁,仅保留轻量级全局锁,减少锁竞争;而数据库原生 2PC 全程持有锁,并发性能差。
- 低侵入性:无需修改业务代码,通过数据源代理自动实现事务拦截、日志生成和回滚,降低开发成本。
- 灵活性高:支持多语言、多数据源(关系型 + 非关系型),可结合 TCC/SAGA 等模式适配复杂场景,而数据库原生 2PC 仅支持 XA 协议兼容的数据库。
- 全局事务可视化:Seata Server 提供事务状态监控、追踪能力,便于问题排查;数据库原生 2PC 缺乏全局视角。
缓存
缓存一致性如何保证?
- 读策略:先查 Redis,未命中查 MySQL,再回写 Redis(设过期时间)。
- 写策略:先更 MySQL,再删 Redis(而非更新,避免并发覆盖)。
- 补充方案 :
- 延迟双删:解决删缓存前旧数据已入缓存的问题。
- 过期时间:兜底保证最终一致。
- 极端场景:用分布式锁或 Canal 监听 binlog 异步同步。
核心:以 DB 为基准,通过删缓存 + 兜底策略平衡一致性与性能。
注意:一定要答到读策略与写策略两点。
本地缓存与Redis缓存区别?
| 维度 | 本地缓存(Caffeine/Guava) | Redis 缓存 |
|---|---|---|
| 核心定位 | 进程内内存缓存 | 分布式独立缓存 |
| 访问速度 | 微秒级(无网络开销) | 毫秒级(需网络请求) |
| 数据共享 | 单实例可见,不跨实例 | 分布式共享,多服务可访问 |
| 可靠性 | 应用重启丢失,无持久化 | 支持持久化,集群高可用 |
| 容量限制 | 受应用内存限制(较小) | 独立扩容,支持大容量 |
| 适用场景 | 高频访问、单实例、非共享数据 | 分布式共享、核心数据、需持久化 |
- 追求极致速度 + 单实例→本地缓存;
- 分布式共享 + 高可靠 + 大容量→Redis;
- 常用组合:本地缓存缓存热点数据,Redis 存核心共享数据。
缓存击穿互斥写并发安全?
核心逻辑:分布式锁串行化缓存重建 + 双重检查防重复 + 超时兜底防死锁,将并发风险转化为可控串行处理
- 分布式锁:同一缓存 Key 仅允许一个线程执行 "查 DB→写缓存",避免多线程重复查 DB、写缓存冲突;
- 双重检查:抢锁前 / 后各查一次缓存,防止锁超时 / 其他线程已完成重建,避免无效操作;
- 锁超时:设置合理超时(略长于重建耗时),防止线程崩溃导致死锁;
- 降级兜底:抢锁失败线程返回默认值 / 旧数据,避免长时间阻塞。
热Key会出现什么问题?
热 Key(高并发集中访问的缓存 Key)会引发 "缓存层压力过载 + 穿透到 DB" 连锁问题,换一种说法就是出现缓存击穿的问题。
直接使用 Redis(如 set nx)和使用 Redisson(或其他封装组件)有什么区别?
| 维度 | Redis 原生(set nx 等) | Redisson 封装组件 |
|---|---|---|
| 核心定位 | 指令级调用,仅基础原子操作 | 场景级封装,面向分布式业务 |
| 易用性 | 手动拼接指令、处理序列化 / 原子性,代码冗余 | 面向对象 API,一行实现分布式锁 / 队列等,自动序列化 |
| 原子性保障 | 多操作需手动写 Lua,易出错 | 内置 Lua,自动保证原子性 |
| 高级特性 | 无(锁续期、红锁、读写锁等) | 支持锁自动续期、红锁、限流器等,开箱即用 |
| 异常处理 | 手动处理断连、超时、死锁 | 自动重连、重试、锁兜底释放 |
| 性能 | 极低开销 | 轻微封装开销,可忽略 |
计网
HTTP3.0为什么基于UDP实现?
HTTP/3.0 基于 UDP 实现,核心是解决 TCP 的性能瓶颈,关键原因:
- 减少连接建立延迟:0-RTT/TLS 握手合一,减少连接建立延迟。
- 解决队头阻塞问题:传输层多路复用,彻底解决队头阻塞。
- 灵活的拥塞控制:拥塞控制灵活,支持多种算法适配网络。
- 连接迁移:连接 ID 标识,实现网络切换无缝迁移。
- 协议升级:用户空间部署,协议升级维护更便捷。
- 增强安全性:头部认证 + Body 加密,安全性更强。
CDN服务器端如何拉取不在本地服务器缓存的页面的具体流程?
- 请求调度与缓存校验:用户请求经 GSLB 路由至就近 CDN 边缘节点,节点通过 Cache Key 查本地缓存,未命中 / 过期则触发回源。
- 回源预处理:校验请求合法性、适配缓存策略(TTL / 压缩规则),合并并发请求避免源站压力,补充防盗链凭证。
- 回源拉取:按配置的源站地址发起请求,源站返回数据(2xx 成功 / 304 未变更 / 5xx 错误)。
- 缓存存储:边缘节点按 TTL 及介质策略(内存 / 磁盘)存储数据与响应元数据(ETag/Last-Modified)。
- 优化响应:可选压缩、格式转换等优化,返回数据给用户(标 X-Cache: MISS)。
- 后续命中:同 URL 再次请求时,直接返回缓存(标 X-Cache: HIT),无需回源。
Linux抓包流程?
- 准备 :sudo 权限 + 用
<font style="color:#000000;">ip addr</font>查网卡(如 eth0/lo),工具选 tcpdump(命令行)或 wireshark(可视化); - 抓包 :基础命令
<font style="color:#000000;">sudo tcpdump -i 网卡 [过滤规则] -w 路径.pcap</font>(过滤规则:host/port/ 协议,如<font style="color:#000000;">port 80</font>); - 停止:Ctrl+C 终止,数据包保存为 .pcap 文件;
- 分析 :wireshark 导入文件可视化解析,或
<font style="color:#000000;">tcpdump -r 路径.pcap</font>命令行查看; - 关键参数:
<font style="color:#000000;">-s 0</font>抓完整包、<font style="color:#000000;">-n</font>禁用 DNS 解析、<font style="color:#000000;">-c 数量</font>限制抓包个数。
URL 输入到页面响应的核心全过程(精炼版)
核心逻辑:"解析 URL→建立连接→请求传输→服务器处理→响应返回→页面渲染",全程涉及 DNS、HTTP、TCP 等多层协议协作,步骤如下:
- URL 解析与预处理
- 提取协议(HTTP/HTTPS)、域名(如 www.baidu.com)、端口(默认 80/443)、路径(如 /index.html)、查询参数(如?id=1);
- 若输入非完整 URL(如 baidu.com),浏览器自动补全协议和端口。
- DNS 域名解析(获取 IP 地址)
- 浏览器缓存→操作系统缓存→本地 DNS 服务器→根 DNS→顶级 DNS→权威 DNS,逐级查询域名对应的服务器 IP(如 14.215.177.38);
- 优化:DNS 缓存(减少重复查询)、DNS 负载均衡(返回就近服务器 IP)。
- 建立网络连接(TCP/HTTPS)
- HTTP 协议:与目标 IP 的对应端口建立 TCP 连接,经历 "三次握手"(SYN→SYN+ACK→ACK);
- HTTPS 协议:TCP 连接建立后,额外执行 TLS 握手(交换密钥、验证证书),建立加密通道。
- 发送 HTTP 请求
- 浏览器构造请求报文(请求行:GET/POST 路径;请求头:Host、User-Agent、Cookie 等;请求体:POST 数据);
- 经加密通道(HTTPS)或直接(HTTP)将请求发送至服务器。
- 服务器处理请求
- 服务器接收请求,解析 URL 路径、参数和请求头;
- 后端处理(如查询数据库、调用接口、渲染页面),生成响应数据(HTML/CSS/JS/ 图片等)。
- 服务器返回响应
- 构造响应报文(状态码:200 成功 / 404 未找到等;响应头:Content-Type、Cache-Control 等;响应体:资源数据);
- 经网络连接将响应返回给浏览器。
- 关闭连接(或复用)
- 若为 HTTP/1.1,默认开启长连接(Keep-Alive),可复用连接处理后续请求;
- 无需复用则执行 TCP "四次挥手" 关闭连接。
- 浏览器渲染页面
- 解析 HTML 生成 DOM 树,解析 CSS 生成 CSSOM 树,结合生成渲染树;
- 布局(计算元素位置大小)、绘制(像素渲染到屏幕);
- 加载并执行 JS,动态修改 DOM/CSSOM,触发重排重绘,最终呈现完整页面。
跨域问题?
问题本质:浏览器同源策略限制:协议、域名、端口任一不同的 AJAX/Fetch 请求,禁止读取响应数据(静态资源加载不受限)。
解决方案:
- 后端 CORS(最推荐) :服务器返回
<font style="color:#000000;">Access-Control-*</font>响应头(如<font style="color:#000000;">Allow-Origin</font>指定域名、<font style="color:#000000;">Allow-Methods</font>允许请求方式),告知浏览器放行,前端无需改动,生产环境首选; - 前端代理:开发环境用 Vue CLI/Webpack 代理,生产环境用 Nginx,前端请求先发代理(同源),代理转发目标服务器,规避跨域限制;
- JSONP :利用
<font style="color:#000000;"><script></font>标签无跨域限制,仅支持 GET,兼容旧浏览器,需前后端配合回调函数; - 补充方案:WebSocket(实时通信,不受同源限制)、postMessage(前端页面间跨域通信)。
TCP第三次握手丢失,服务端接受重复握手如何处理?
- 第三次握手(ACK)丢失:服务端 SYN_RECV 状态下重传 SYN+ACK(默认 3-5 次),超时未收则释放资源;客户端虽认为连接建立,但发数据会被服务端回 RST 拒绝,最终连接未建立。
- 连接建立后收到重复第三次 ACK:服务端通过四元组识别连接已 ESTABLISHED,静默丢弃冗余报文,不影响正常通信。
核心:丢失靠超时重传容错,重复靠状态机 + 四元组静默丢弃,不影响连接稳定性。
DNS劫持与DNS污染?
| 对比维度 | DNS 劫持 | DNS 污染(域名投毒) |
|---|---|---|
| 技术原理 | 拦截 DNS 请求,返回伪造解析结果 | 污染DNS缓存,植入错误IP(无需拦截请求) |
| 实施主体 | 运营商、局域网管理员(如路由器篡改) | 网络攻击者、特定网络环境 |
| 影响范围 | 特定网络(如运营商网段、局域网) | 更广泛(污染公共 DNS 缓存,影响多用户) |
| 触发方式 | 主动拦截用户 DNS 请求 | 向 DNS 服务器发送伪造应答,污染其缓存 |
| 防范方式 | 改用可信 DNS(如 8.8.8.8)、加密 DNS(DoH/DoT) | 加密 DNS(DoH/DoT)、定期刷新 DNS 缓存 |
Ping命令协议?
核心机制:基于网络层 ICMP 协议,通过 "请求 - 应答" 模式验证网络连通性,无需依赖 TCP/UDP 端口。
核心流程:
- 发送端发送含标识、序号、时间戳的 ICMP Echo Request 报文;
- 若目标主机可达,会返回携带原报文信息的 ICMP Echo Reply 报文;
- 发送端通过报文计算往返时延(RTT),若超时未收到应答,则提示 "请求超时"。
关键细节:
- 需依赖 IP 协议实现传输,本身无 "端口" 概念;
- 标识用于匹配发起请求的进程,序号用于标记请求顺序,时间戳是计算 RTT 的关键依据。
http请求可以拼接几个参数?上限是多少?
HTTP 请求参数无统一数量上限,核心看请求方式 + 服务器 / 客户端限制:
- GET:参数拼 URL,受 URL 长度限制(常见 2KB~8KB),对应几十到上百个短参数;
- POST:参数在请求体,协议无限制,实际受服务器配置(如 Tomcat 默认 2MB、Nginx 默认 1MB)约束。
有些网站明明是HTTPS,浏览器还是提示它'不安全'?
HTTPS 仍提示 "不安全",核心是证书验证失败、配置不兼容或内容混合,浏览器判定连接存在风险
- 证书问题(最常见):过期、未信任、域名不匹配、证书链不完整;
- 配置风险:用老旧协议(SSLv3、TLS1.0/1.1)、弱加密套件;
- 内容问题:页面加载 HTTP 资源(混合内容)。
TCP 的 backlog 是什么,backlog满了之后发生了什么?
- TCP backlog:连接建立阶段「全连接等待队列」的容量上限(缓存三次握手完成但应用层未 accept 的连接);
- backlog 满了:服务器丢弃新客户端 SYN 请求,客户端连接超时 / 被拒绝,触发重传后仍失败。
HTTP2服务器推送实现原理?
HTTP/2 服务器推送是服务器主动推送客户端未请求但依赖的资源(如 CSS/JS),核心减少 RTT(往返延迟)。
- 触发流程 :
- 服务器解析主请求(如 /index.html)
- 发送
<font style="color:#000000;">PUSH_PROMISE</font>帧告知客户端即将推送资源 - 客户端未拒绝则发送资源 DATA 帧
- 响应主资源,客户端缓存复用推送资源。
- 客户端可控 :可通过
<font style="color:#000000;">SETTINGS_ENABLE_PUSH</font>禁用推送,或<font style="color:#000000;">RST_STREAM</font>拒绝已推送流。
HTTP协议有哪几部分组成?
| 组成部分 | 结构(请求) | 结构(响应) |
|---|---|---|
| 核心结构 | 请求行 + 请求头 + 空行 + 请求体 | 状态行 + 响应头 + 空行 + 响应体 |
| 必选部分 | 请求行 + 空行 | 状态行 + 空行 |
| 数据载体 | 请求体(POST/PUT) | 响应体(资源内容) |
- 请求行:GET /index.html HTTP/1.1
- 请求头 :可选,键值对形式,传递请求附加信息
- 格式:
<font style="color:#000000;">Header-Name: value</font>
- 格式:
- 空行 :必选,一个单独的
<font style="color:#000000;">\r\n</font>,标记请求头结束 - 请求体 :可选,仅
<font style="color:#000000;">POST/PUT</font>等方法需要,传递请求数据
客户端怎么校验CA证书?
客户端校验 CA 证书的核心是验证证书合法性、身份匹配性、时效性,确保通信对象真实可靠,流程如下:
- 获取证书:建立 SSL/TLS 连接时,服务端发送证书链(服务端证书 + 中间 CA 证书,可选根 CA 证书)。
- 验证证书链
- 以客户端内置根 CA 证书为信任锚点,回溯验证证书链签名:用上级 CA 公钥解密证书签名,对比证书内容的哈希值,一致则未篡改。
- 若证书链不完整(缺中间 CA),校验失败。
- 检查时效性 :验证证书的生效时间(
<font style="color:rgb(0, 0, 0);">Not Before</font>)和过期时间(<font style="color:rgb(0, 0, 0);">Not After</font>),当前时间不在有效期内则失效。 - 匹配身份 :校验证书的
<font style="color:rgb(0, 0, 0);">SAN</font>/<font style="color:rgb(0, 0, 0);">CN</font>字段是否与访问域名一致(支持通配符如<font style="color:rgb(0, 0, 0);">*.example.com</font>),防止域名劫持。 - 核查吊销状态 :通过 CRL(吊销列表) 或OCSP(在线查询) 确认证书未被 CA 吊销(如私钥泄露)。
TCP 拥塞控制的作用与实现原理?
核心作用:解决网络链路拥堵问题(如路由器缓存溢出、数据包丢失),避免 "发送方盲目发数据→网络拥堵→丢包→重传→更拥堵" 的恶性循环,最终实现:
- 充分利用网络带宽;
- 避免网络过载;
- 保障多连接公平共享带宽。
| 阶段 | 触发条件 | 核心策略 | 窗口变化 |
|---|---|---|---|
| 慢启动 | 连接建立 / 重传后 | 初始 cwnd=1~2 个 MSS,每收到一个 ACK,cwnd指数增长 | cwnd *= 2 |
| 拥塞避免 | cwnd ≥ 慢启动阈值(ssthresh) | 每轮 RTT,cwnd线性增长(+1 MSS) | cwnd += 1 |
| 快速重传 | 收到 3 个重复 ACK(判定为丢包,非超时) | 立即重传丢包,不等待超时; 设置 ssthresh=cwnd/2 cwnd=ssthresh+3 | 快速恢复,避免慢启动 |
| 快速恢复 | 进入快速重传后 | 每收到一个重复 ACK,cwnd +=1; 收到新ACK后,cwnd=ssthresh,拥塞避免 |
http响应码301和302的区别,暂时和永久是什么意思?
| 响应码 | 名称 | 核心含义 | 「暂时 / 永久」的本质 |
|---|---|---|---|
| 301 | Moved Permanently | 资源永久移动到新 URL | 客户端需永久记住新 URL,后续请求直接访问新地址 |
| 302 | Found(原 Moved Temporarily) | 资源临时移动到新 URL | 客户端仍需向原 URL 发起请求,由服务端临时重定向 |
Redis
Redis为什么避免大Key?
核心问题本质:大 key 违背 Redis"轻量、高速" 设计初衷,从存储、性能、集群三维度引发连锁问题,是性能瓶颈、资源浪费与可用性风险的关键诱因。
具体问题表现:
- 内存与存储:占用内存多易触发内存淘汰(挤掉热点小 key);持久化时导致快照 / AOF 文件过大、刷盘阻塞,恢复加载耗时久。
- 操作性能:读写消耗更多 CPU/IO,单操作耗时超毫秒级拖慢 QPS;删除 / 过期易阻塞主线程(非惰性删除场景),引发服务卡顿。
- 网络传输:序列化 / 反序列化耗时久,跨节点复制(主从、集群)占用大量带宽致同步延迟;客户端获取等待久,影响业务响应。
- 集群环境:无法均匀分片致节点负载失衡;迁移时耗时久、易失败,影响集群稳定性。
解决方案建议:对大 key 进行拆分,如 hash 分桶、list 分段。
大Key拆分总量一样,怎么算优化了?
核心目标:非减少数据总量,而是将单 Key 的集中压力分散,降低其访问压力,优化存储与查询性能
三大关键优化维度:
- 降低单 Key 内存占用:大 Key 易致 Redis 内存碎片率升高、GC 耗时增加,拆分后子 Key 内存占用变小,内存分配更均衡。
- 减少单 Key 访问阻塞:Redis 单线程下,操作大 Key 易长时间占用线程,拆分后子 Key 操作耗时大幅缩短,提升整体并发能力。
- 提升集群数据分片效率:集群中未拆分的大 Key 易使单个节点成热点,拆分后子 Key 分散到不同节点,实现负载均匀分配,避免单点瓶颈。
实例佐证:100 万元素、占 1GB 内存的大 Hash,拆分后为 100 个 1 万元素子 Hash(总内存不变)。未拆分时操作阻塞 1 秒、节点 CPU 飙升;拆分后单操作仅 10 毫秒,且可分散执行,无阻塞且负载均衡。
关键结论:大 Key 优化核心是拆解集中压力,在数据总量不变的情况下,大幅降低单 Key 的内存占用、操作耗时与节点负载,进而提升 Redis 的并发能力、稳定性和扩展性。
Lua脚本如何优化?
- 精简脚本逻辑:移除冗余操作,拆分长脚本为多个短脚本
- 优化执行效率:批量操作、精准定位数据、选用高效结构
- 12制执行边界:设置超时、异步化处理、杜绝阻塞操作
SDS结构与C++标准字符串区别?
核心:SDS 是Redis 专用、高性能内存优化的二进制安全字符串 ,std::string 是C++ 通用、兼顾易用性的****封装字符串,差异源于专用优化 vs 通用设计。
- SDS为什么是二进制安全的?
答:SDS 靠「长度字段显式标识字符串边界」,不依赖终止符 '\0',因此是二进制安全的。
- buf[]类型是什么结构?
答:SDS 的 <font style="color:#000000;">buf[]</font> 不是复杂结构,就是 用于存储字节数据的 char 数组,其关键价值是:
- 用连续内存保证读写高效;
- 末尾 '\0' 兼容 C 字符串;
- 作为字节容器支持任意二进制数据,配合
<font style="color:#000000;">len</font>字段实现二进制安全。
用Redis或ZK 做分布式锁,性能其实不一定比MySQL的行锁好,你知道为什么?
核心原因:分布式锁(Redis/ZK)的 "跨节点通信开销",在特定场景下会抵消其并发优势,而 MySQL 行锁的 "本地操作特性" 更适配低并发、强一致性需求。
- 开销差异:Redis/ZK 锁需跨节点网络往返(ms 级),还需处理续租、容错;MySQL 行锁是本地内存操作(μs 级),无网络延迟;
- 场景适配:Redis/ZK 适合高并发松一致(如秒杀),MySQL 行锁适合低并发强一致(如订单支付),后者无需额外处理锁超时、一致性补偿,整体更高效。
Redis的Cluster集群如果节点宕机怎么切换?
Redis Cluster 节点宕机切换核心:「主从复制 + 自动投票」。
主节点宕机(核心场景):
- 故障检测:节点超时(默认 15 秒)未响应,经半数主节点确认后标记为 FAIL。
- 从节点竞选:按数据同步进度、优先级、运行时长筛选最优从节点。
- 投票晋升:获半数以上主节点投票后,从节点升为新主节点,接管原哈希槽。
- 服务恢复:集群更新路由表,客户端请求自动路由至新主节点,总耗时 15~30 秒。
从节点宕机(无服务影响):
- 仅失去备份能力,原主节点正常提供服务。
- 恢复后自动同步原主节点数据,重建备份角色。
关键配置与异常处理:
- 核心配置:
<font style="color:rgb(0, 0, 0);">cluster-node-timeout</font>(超时阈值)、<font style="color:rgb(0, 0, 0);">slave-priority</font>(从节点优先级)、<font style="color:rgb(0, 0, 0);">cluster-replica-minimum</font>(最小从节点数≥2)。 - 异常干预:主节点无备份宕机需手动恢复节点;故障转移失败可执行
<font style="color:rgb(0, 0, 0);">cluster failover force</font>强制晋升。
Spring
循环依赖解决方法以及过程?
仅支持:单例 Bean + setter / 字段注入(多例、构造器注入、静态依赖无法自动解决)
| 缓存级别 | 存储的 Bean 类型 (核心状态) | 关键说明 |
|---|---|---|
| 一级缓存 | 「完全初始化完成」单例 Bean(最终可用状态) | 1. 已执行:实例化(new 对象)→ 属性注入 → init 方法 → AOP 代理(若有); 2. 可直接被依赖方使用,无状态问题; 3. 所有单例 Bean 最终都会存入此处。 |
| 二级缓存 | 「提前暴露的半成品代理 Bean」(实例化后,未完成初始化) | 1. 已执行:实例化 → AOP 代理(若有); 2. 未执行:属性注入 → init 方法; 3. 仅用于循环依赖场景,临时存储 "已生成代理的半成品",避免重复生成代理。 |
| 三级缓存 | 「Bean 工厂函数」(而非直接 Bean 引用) | 1. 存储的是生成 Bean 引用的 "工厂方法"(<font style="color:#000000;">() -> getEarlyBeanReference(bean)</font>); 2. 工厂方法的作用:按需生成「原始半成品 Bean」或「半成品代理 Bean」; 3. 核心价值:延迟生成代理,避免 Bean 未初始化就执行代理逻辑。 |
核心逻辑:Bean 实例化后(未初始化),提前暴露引用到缓存,让依赖方先获取半成品,后续补全依赖。
自动解决核心过程(A 依赖 B,B 依赖 A):
- A 实例化 → 存入三级缓存 → 注入 B 时触发 B 创建;
- B 实例化 → 存入三级缓存 → 注入 A 时从三级缓存取 A 的半成品(移至二级缓存);
- B 完成初始化 → 存入一级缓存 → 注入 A;
- A 完成初始化 → 存入一级缓存,循环依赖解决。
手动解决方法(无法自动场景):
- 改注入方式:构造器注入 → setter / 字段注入(最推荐);
- 延迟初始化:@Lazy 注解标记其中一个 Bean;
- 解耦:通过 Spring Event、MQ 等中间件间接依赖;
- 重构:拆分公共逻辑,明确 Bean 职责(根本解)。
可不可以只使用二级缓存?
结论:不可以。仅用二级缓存无法兼容「AOP 动态代理」场景,会导致依赖方拿到「原始 Bean」而非「最终代理 Bean」,破坏 Spring Bean 的一致性。
核心原因:二级缓存无法解决「代理 Bean 的延迟生成」问题
Spring 三级缓存的核心价值,是为了在循环依赖场景中动态生成 AOP 代理对象,而二级缓存不具备这个能力:
- 若 Bean 需 AOP 代理(如加了
<font style="color:#000000;background-color:rgba(0, 0, 0, 0);">@Transactional</font><font style="color:#000000;background-color:rgba(0, 0, 0, 0);">@Aspect</font>),最终注入依赖的应是「代理 Bean」,而非实例化后的「原始 Bean」; - 三级缓存存储的是「Bean 工厂函数」(
<font style="color:#000000;background-color:rgba(0, 0, 0, 0);">singletonFactories</font>),而非直接的 Bean 引用 ------ 只有当依赖方需要时,才执行工厂函数生成代理对象(或原始 Bean),保证代理逻辑不提前执行; - 若仅用二级缓存:需在 Bean 实例化后立即生成代理对象并存入二级缓存,但此时 Bean 还未完成属性注入和初始化,代理对象可能依赖未初始化的状态,导致逻辑异常。
| 场景 | 三级缓存(正常) | 二级缓存(仅用) |
|---|---|---|
| 无 AOP 的循环依赖 | 正常(暴露原始半成品 Bean) | 正常(效果一致) |
| 有 AOP 的循环依赖 | 生成代理 Bean 注入,逻辑一致 | 注入原始 Bean,代理失效(如事务不生效) |
关键结论:
- 二级缓存仅能解决「无 AOP 的简单循环依赖」,但无法兼容 Spring 的 AOP 动态代理机制 ------ 而 AOP 是 Spring 核心特性(事务、日志、权限等均依赖),因此必须保留三级缓存,实现「代理对象延迟生成」与「循环依赖解决」的平衡。
本质:三级缓存的核心是「工厂函数」,而非缓存本身 ------ 它允许 Spring 在需要时才决定暴露「原始 Bean」还是「代理 Bean」,这是二级缓存(直接存 Bean 引用)无法替代的。
Spring&SpringBoot&SpringCloud三者关系?
结论:三者是「基础框架→快速开发工具→微服务架构解决方案」的递进关系,Spring 是核心根基,SpringBoot 简化 Spring 开发,SpringCloud 基于前两者实现微服务治理。
Spring:所有的核心根基:
- 定位:核心是 IOC(控制反转)和 AOP(面向切面编程),企业级开发基础框架;
- 作用:统一开发规范,解决依赖、事务、Bean管理等问题,是SpringBoot/Cloud 的底层支撑
- 特点:灵活但配置繁琐,需手动整合 Spring MVC、MyBatis 等依赖。
SpringBoot:Spring 的 "快速启动器":
- 定位:基于 Spring 的开发脚手架,核心 "约定优于配置";
- 作用:简化 Spring 应用搭建与开发,免XML 配置、自动整合常用依赖、内置服务器,一键启动
- 关系:不替代 Spring,是对 Spring 的封装简化,底层依赖其 IOC、AOP 核心能力。
SpringCloud:微服务的 "全家桶":
- 定位:基于 SpringBoot 的微服务架构解决方案,核心是微服务治理全套组件;
- 作用:解决服务注册发现、负载均衡、熔断降级、配置中心等分布式问题(组件如 Eureka、Ribbon 等);
- 关系:依赖 SpringBoot 快速开发微服务(每个微服务是 SpringBoot 应用),底层基于 Spring,是其分布式场景延伸。
| 框架 | 核心定位 | 依赖关系 | 典型使用场景 |
|---|---|---|---|
| Spring | 基础核心框架 | 无(独立存在) | 传统单体应用开发 |
| SpringBoot | 快速开发脚手架 | 依赖 Spring | 快速搭建单体 / 微服务应用 |
| SpringCloud | 微服务治理解决方案 | 依赖 前两者 | 分布式微服务架构系统 |
Spring如何加载一个Bean?
Spring加载Bean核心**"扫描定位→解析配置→实例化→初始化→注册"**五步流程,依赖IOC容器完成
核心流程(精炼版):
- 扫描定位 :容器启动时,扫描指定包(如
<font style="color:rgb(0, 0, 0);">@ComponentScan</font>配置),识别带<font style="color:rgb(0, 0, 0);">@Component</font>(含<font style="color:rgb(0, 0, 0);">@Service</font>/<font style="color:rgb(0, 0, 0);">@Controller</font>等派生注解)、<font style="color:rgb(0, 0, 0);">@Bean</font>的类 / 方法,确定 Bean 候选者; - 解析配置 :解析 Bean 的元信息(类名、依赖、作用域、初始化方法等),封装为
<font style="color:rgb(0, 0, 0);">BeanDefinition</font>对象; - 实例化:通过构造器 / 工厂方法创建 Bean 实例(默认单例,懒加载除外);
- 初始化 :依赖注入(Autowired 注入依赖 Bean)→ 执行初始化方法(
<font style="color:rgb(0, 0, 0);">@PostConstruct</font>、<font style="color:rgb(0, 0, 0);">InitializingBean</font>、自定义 init-method); - 注册缓存:将初始化完成的 Bean 存入 IOC 容器(单例池),供后续获取使用。
关键补充:
- 触发时机:容器启动(
<font style="color:rgb(0, 0, 0);">refresh()</font>)时加载非懒加载 Bean,懒加载 Bean 首次获取时触发; - 核心载体:
<font style="color:rgb(0, 0, 0);">BeanDefinition</font>(描述 Bean 元信息)、<font style="color:rgb(0, 0, 0);">BeanFactory</font>(Bean 创建管理核心)、<font style="color:rgb(0, 0, 0);">ApplicationContext</font>(扩展 BeanFactory,提供更多功能)。
@Service和@Controller互换可以?
不可以(不推荐,且多数场景会失效),核心原因:两者是 Spring 针对不同分层的 "专用注解",虽底层都基于 @Component,但功能定位、依赖注入逻辑、配套机制完全不同。
- 共性:均派生自 @Component,能被 Spring 扫描为 Bean;
- 核心差异:@Controller 适配 Spring MVC,支持 @RequestMapping 接收请求;@Service 标识业务层,适配事务等逻辑;
- 互换后果:@Controller 放 Service 层会丢事务、注入异常;@Service 放 Controller 层会失去请求映射能力,接口不可用。
Spring 事务了解?
Spring 事务核心:以声明式(@Transactional)为主,保障数据库 ACID,核心要点:
- 两种方式:@Transactional 注解(声明式,推荐)、TransactionTemplate/API(编程式);
- 关键配置:隔离级别(解决脏读 / 不可重复读等)、传播行为(多事务调用规则)、rollbackFor(指定回滚异常);
- 避坑点:仅 public 方法生效、同类内部调用失效、需配事务管理器、忌长事务。
Spring配置加载顺序?
- 内置默认配置(Spring 框架自带默认值,优先级最低);
- XML 配置 (
<font style="color:rgb(0, 0, 0);">applicationContext.xml</font>等,通过<font style="color:rgb(0, 0, 0);">ClassPathXmlApplicationContext</font>加载); - 注解配置 (
<font style="color:rgb(0, 0, 0);">@Configuration</font>类、<font style="color:rgb(0, 0, 0);">@ComponentScan</font>扫描的 Bean 等); - 外部化配置 (优先级从低到高):
- 系统环境变量;
- 命令行参数(
<font style="color:rgb(0, 0, 0);">java -jar xxx.jar --key=value</font>); - 配置文件(
<font style="color:rgb(0, 0, 0);">application-{profile}.yml/properties</font>><font style="color:rgb(0, 0, 0);">application.yml/properties</font>);
- 编程式配置 (
<font style="color:rgb(0, 0, 0);">Environment</font>API 动态设置、<font style="color:rgb(0, 0, 0);">@PropertySource</font>加载的自定义配置,优先级较高)。
Spring 体系在处理网络请求时,底层使用的网络模型是什么?(如阻塞 IO、NIO、容器模型等)
Spring 本身不直接实现网络模型,而是依赖底层 Web 容器(Tomcat/Jetty/Undertow)处理网络 IO,其网络模型的核心由容器决定;同时 Spring 框架自身(如 Spring Web、Spring WebFlux)会基于容器提供的 IO 模型封装上层编程模型。
| Spring 组件 | 底层依赖 | 核心网络模型 | 适用场景 |
|---|---|---|---|
| Spring MVC | Tomcat/Jetty/Undertow(默认 Tomcat) | 主流为NIO(非阻塞 IO)(Tomcat 8 + 默认),兼容 BIO(阻塞 IO) | 同步阻塞编程模型,高 CPU / 低 IO 场景 |
| Spring WebFlux | Netty/Tomcat/Jetty/Undertow(默认 Netty) | NIO + 事件驱动(Reactor),纯异步非阻塞 | 高 IO 并发场景(如微服务网关、长连接) |
- GPU 和 CPU 的主要区别是什么?各自适用场景?
SpringCloud
网关实现注册中心功能可行?
答:可行但不推荐
- 技术可行:网关可通过暴露注册接口、维护服务列表、集成负载均衡,实现服务注册 / 发现核心功能,仅适用于 Demo 或极简微服务。
- 核心不推荐原因 :
- 职责耦合:网关(流量转发)与注册中心(服务治理)功能混杂,开发维护难、故障定位复杂;
- 性能瓶颈:高频服务心跳与高并发流量叠加,占用网关资源,导致转发延迟增加;
- 高可用 / 扩展性差:网关集群难同步服务列表,无注册中心的健康检查、灰度发布等高级能力,扩容成本高。
- 正确架构:网关(负责流量转发)与注册中心(负责服务治理)独立部署,网关从注册中心获取服务列表后转发请求。
线程池
线程池首个任务与核心线程初始化?
线程池处理第一个任务 时,核心线程是否初始化取决于线程池的配置(是否预热核心线程)和任务提交方式,核心结论:
- 默认配置(未预热):第一个任务会触发核心线程的初始化(创建并启动核心线程执行任务);
- 若提前预热核心线程(如调用
<font style="color:#000000;">prestartCoreThread()</font>):首个任务直接复用已初始化的核心线程,无新创建逻辑。
默认逻辑(未预热核心线程):首个任务触发核心线程初始化
- 线程池创建后,
<font style="color:rgb(0, 0, 0);">workerCount</font>(当前工作线程数)= 0; - 提交首个任务,检查
<font style="color:rgb(0, 0, 0);">workerCount < corePoolSize</font>(0 < 5),执行<font style="color:rgb(0, 0, 0);">addWorker(command, true)</font>; <font style="color:rgb(0, 0, 0);">addWorker</font>方法创建<font style="color:rgb(0, 0, 0);">Worker</font>对象(封装核心线程),启动线程并执行首个任务;- 核心线程初始化完成,后续任务优先复用该核心线程(若未空闲则入队)。
核心线程提前初始化(无首个任务创建开销)
<font style="color:#000000;">prestartCoreThread()</font> |
初始化1 个核心线程(即使无任务) | 首个任务直接复用该核心线程 |
|---|---|---|
<font style="color:#000000;">prestartAllCoreThreads()</font> |
初始化所有核心线程(如 corePoolSize=5 则创建 5 个) | 首个任务复用已初始化的核心线程 |
核心总结:
| 线程池配置 | 首个任务是否初始化核心线程 | 核心逻辑 |
|---|---|---|
| 默认(未预热) | 是 | 懒加载,首个任务触发<font style="color:rgb(0, 0, 0);">addWorker</font>创建核心线程 |
<font style="color:rgb(0, 0, 0);">prestartCoreThread()</font> |
否 | 提前初始化 1 个核心线程,首个任务复用 |
<font style="color:rgb(0, 0, 0);">prestartAllCoreThreads()</font> |
否 | 提前初始化所有核心线程,首个任务复用 |
<font style="color:rgb(0, 0, 0);">allowCoreThreadTimeOut(true)</font>+ 核心线程已销毁 |
是 | 核心线程超时销毁后,首个任务重新创建 |
线程池非核心线程与阻塞队列逻辑是否可交换?
线程池中非核心线程的创建逻辑和阻塞队列的任务存储逻辑无法交换 ,核心原因是二者遵循「先队列、后扩容非核心线程」的固定调度优先级,这是线程池设计的核心规则,交换后会完全颠覆线程池的资源管控目标。
核****心设计目标:优先用队列缓冲任务,避免频繁创建 / 销毁非核心线程(线程创建销毁有开销),保证资源使用的稳定性。
若交换逻辑(先创建非核心线程,队列仅在非核心线程满后使用),会导致:
| 维度 | 原生逻辑(队列优先) | 交换逻辑(非核心线程优先) |
|---|---|---|
| 资源开销 | 线程数稳定在核心线程数,仅队列满时扩容,开销低 | 任务一来就创建非核心线程,线程数快速飙升,CPU / 内存开销剧增 |
| 线程稳定性 | 核心线程常驻,避免频繁上下文切换 | 非核心线程超时销毁(默认 60s),任务波动时线程数反复增减,性能抖动 |
| 资源管控 | 队列缓冲 + 非核心线程兜底,可控性强 | 非核心线程快速占满上限,队列几乎无用,拒绝策略频繁触发 |
| 设计初衷背离 | 符合 "池化"(复用核心线程)思想 | 退化为 "任务来就创建线程",失去线程池 |
业务
T+1策略中限流是如何实现?
项目中的CAS是如何实现?
你有线上故障?
总部商品和门店商品的区别在哪?
RocketMQ技术选型?
如何来设计实现一个短链接系统?
长 URL→短字符串映射、低延迟跳转、高可用高并发、防刷防遍历。
plain
用户层 → 接入层(Nginx/网关) → 业务层(生成/跳转服务) → 存储层(Redis+MySQL)
- 接入层:路由转发、限流防刷、缓存高频短链映射;
- 业务层:拆分为生成 / 跳转服务,无状态设计支持水平扩容;
- 存储层:Redis 缓存短链映射(核心),MySQL 持久化全量数据。
AI
您怎么理解 Agent 这种东西?
AI 领域的 Agent 是具备感知、决策、执行能力的智能实体 ,能模拟人类思维完成复杂任务,核心是 "大模型 + 工具调用 + 记忆机制"。
- 核心架构
- 感知层:接收用户指令或环境输入(如文本、数据);
- 规划层:大模型拆解任务,生成执行步骤(如 "先查天气→再查航班→再推荐酒店");
- 工具层:调用外部工具(搜索引擎、API、计算器)完成具体操作;
- 记忆层:存储历史交互信息,支持上下文理解。
- 典型案例:AutoGPT(自主完成复杂任务)、LangChain Agent(大模型工具调用框架)、智能客服机器人。