[{"content":"概述 从 2022 年 ChatGPT 的横空出世，到 2024-2025 年 AI Agent 的全面爆发，大语言模型（LLM）领域经历了从\u0026quot;对话工具\u0026quot;到\u0026quot;智能体平台\u0026quot;的深刻演进。本文系统梳理这一发展脉络中的核心概念和名词，按 LLM 基础、RAG 与知识增强、推理与提示技术、Agent 核心、Agent 协议与标准、Agent 框架与工具、Agent 评估与可观测性、新兴概念八大维度，力求全面覆盖、简明解释，并附上可查证的参考链接。\n一、LLM 基础概念 Token（令牌）\n解释：模型处理文本的最小单位，一个 Token 可以是一个词、一个子词或一个字符，取决于分词策略。模型以 Token 序列为输入和输出。 相关文档：OpenAI: What are tokens? | Hugging Face Tokenizers Tokenization（分词）\n解释：将原始文本切分为 Token 序列的过程，常见算法包括 BPE（Byte-Pair Encoding）、WordPiece、SentencePiece 等。分词直接影响模型的词表大小和编解码效率。 相关文档：Hugging Face: Tokenizers Summary Prompt（提示词）\n解释：用户输入给模型的文本指令或上下文，引导模型生成期望的输出。Prompt 的设计质量直接影响模型响应的准确性和实用性。 相关文档：OpenAI: Prompt Engineering Guide Prompt Engineering（提示工程）\n解释：系统性地设计和优化 Prompt 以获得更好的模型输出的方法论与实践。包括角色设定、示例设计、思维链引导等技巧。 相关文档：Prompt Engineering Guide (DAIR.AI) System Prompt（系统提示词）\n解释：在对话开始时设定模型角色、行为边界和输出格式的隐藏指令，对用户不可见但影响模型的所有后续响应。 相关文档：OpenAI: System Messages Few-shot Learning（少样本学习）\n解释：在 Prompt 中提供少量（通常 1-5 个）输入-输出示例，让模型从示例中推断任务模式。介于 Zero-shot 和 Fine-tuning 之间。 相关文档：Brown et al., \u0026ldquo;Language Models are Few-Shot Learners\u0026rdquo; (GPT-3) Zero-shot Learning（零样本学习）\n解释：不给任何示例，仅靠任务描述指令让模型直接完成新任务。依赖模型在预训练中获得的能力泛化。 相关文档：GPT-3 Paper In-context Learning（上下文学习，ICL）\n解释：模型无需更新参数，仅通过在 Prompt 中提供示例和上下文就能临时学习新任务的能力。这是 LLM 涌现出的核心能力之一。 相关文档：Dong et al., \u0026ldquo;A Survey on In-context Learning\u0026rdquo; Context Window（上下文窗口）\n解释：模型在单次推理中能处理的输入 + 输出 Token 总长度的上限。窗口越大，模型能\u0026quot;看到\u0026quot;的信息越多。典型值从 4K 到 1M+ 不等。 相关文档：Google: Gemini 1.5 Pro (1M context) | Anthropic: Claude (200K context) Temperature（温度）\n解释：控制模型输出随机性的参数。值越高输出越多样随机，值越低越确定保守。0 表示近乎确定性输出。 相关文档：OpenAI: API Reference Top-p Sampling（核采样，Nucleus Sampling）\n解释：从概率累积达到 p 的最小 Token 集合中进行采样的解码策略，平衡多样性和质量。常与 Temperature 配合使用。 相关文档：Holtzman et al., \u0026ldquo;The Curious Case of Neural Text Degeneration\u0026rdquo; Top-k Sampling\n解释：只从概率最高的 k 个 Token 中进行采样的解码策略，截断长尾低概率选项，提升输出质量。 相关文档：Fan et al., \u0026ldquo;Hierarchical Neural Story Generation\u0026rdquo; Hallucination（幻觉）\n解释：模型生成看似合理但实际不正确或虚构的内容。是 LLM 的核心挑战之一，根源在于统计生成模型的不确定性。 相关文档：Ji et al., \u0026ldquo;Survey of Hallucination in NLG\u0026rdquo; Embedding（嵌入 / 向量表示）\n解释：将文本、图片等离散数据映射为高维连续向量，使语义相近的内容在向量空间中距离更近。是语义检索和 RAG 的基础。 相关文档：OpenAI: Embeddings Guide | Sentence-BERT Fine-tuning（微调）\n解释：在预训练模型基础上，使用特定领域数据进一步训练，使模型适配下游任务。包括全量微调和参数高效微调。 相关文档：Hugging Face: Fine-tuning Guide SFT（Supervised Fine-Tuning，监督微调）\n解释：使用人工标注的输入-输出对进行有监督训练，让模型学会按特定格式和风格响应。是后训练阶段的第一步。 相关文档：Ouyang et al., \u0026ldquo;InstructGPT\u0026rdquo; RLHF（Reinforcement Learning from Human Feedback，基于人类反馈的强化学习）\n解释：通过人类对模型输出的偏好标注训练奖励模型，再用 PPO 等强化学习算法优化模型策略。是 ChatGPT 成功的关键技术。 相关文档：Christiano et al., \u0026ldquo;Deep RL from Human Preferences\u0026rdquo; | OpenAI: ChatGPT DPO（Direct Preference Optimization，直接偏好优化）\n解释：不需要显式训练奖励模型，直接用偏好数据对模型进行优化的方法。比 RLHF 更简单稳定。 相关文档：Rafailov et al., \u0026ldquo;Direct Preference Optimization\u0026rdquo; LoRA（Low-Rank Adaptation，低秩适配）\n解释：冻结预训练模型权重，在旁边训练低秩矩阵来适配新任务的参数高效微调方法。大幅降低显存和计算需求。 相关文档：Hu et al., \u0026ldquo;LoRA\u0026rdquo; QLoRA（Quantized LoRA，量化低秩适配）\n解释：将基座模型量化到 4-bit，再使用 LoRA 进行微调的方法。可在单张 GPU 上微调大模型。 相关文档：Dettmers et al., \u0026ldquo;QLoRA\u0026rdquo; Quantization（量化）\n解释：将模型参数从高精度（如 FP16）压缩到低精度（如 INT8、INT4），减少显存占用和推理成本，同时尽量保持模型性能。 相关文档：GPTQ Paper | bitsandbytes MoE（Mixture of Experts，混合专家模型）\n解释：模型包含多个\u0026quot;专家\u0026quot;子网络，每次推理时通过门控机制动态选择少量专家参与计算。在增大参数量的同时控制推理成本。代表模型有 Mixtral、DeepSeek-MoE。 相关文档：Shazeer et al., \u0026ldquo;Sparsely-Gated MoE\u0026rdquo; | Mixtral 8x7B Multimodal（多模态）\n解释：模型能够同时处理文本、图像、音频、视频等多种模态输入和输出。是 LLM 向通用 AI 发展的重要方向。 相关文档：OpenAI: GPT-4V | Google: Gemini VLM（Vision-Language Model，视觉语言模型）\n解释：能够理解和推理图像与文本关系的模型，支持图像描述、视觉问答、图文匹配等任务。 相关文档：Liu et al., \u0026ldquo;LLaVA\u0026rdquo; | Qwen-VL Pre-training（预训练）\n解释：在海量无标注文本上进行自监督学习，学习语言的通用表示和世界知识。是 LLM \u0026ldquo;出厂\u0026quot;时的能力来源。 相关文档：Kaplan et al., \u0026ldquo;Scaling Laws for Neural Language Models\u0026rdquo; Post-training（后训练 / 对齐训练）\n解释：在预训练之后，通过 SFT、RLHF 等方法使模型输出符合人类偏好和任务需求的过程。决定了模型的\u0026quot;可用性\u0026rdquo;。 相关文档：OpenAI: GPT-4 Technical Report Constitutional AI（宪法 AI）\n解释：Anthropic 提出的 AI 对齐方法，通过一组\u0026quot;宪法\u0026quot;原则让模型自我评估和修正输出，减少对人类标注的依赖。 相关文档：Bai et al., \u0026ldquo;Constitutional AI\u0026rdquo; Guardrails（护栏）\n解释：为模型输入输出设置安全过滤和约束机制，防止生成有害、越狱或超出范围的回复。 相关文档：NVIDIA NeMo Guardrails Structured Output（结构化输出 / JSON Mode）\n解释：约束模型输出为特定格式（如 JSON Schema），确保输出可被程序解析。是实现 Tool Use 的基础。 相关文档：OpenAI: Structured Outputs | Anthropic: Tool Use 二、RAG 与知识增强 RAG（Retrieval-Augmented Generation，检索增强生成）\n解释：在生成回答前，先从外部知识库检索相关文档，将检索结果作为上下文提供给模型。有效缓解幻觉、提供最新知识。 相关文档：Lewis et al., \u0026ldquo;RAG for Knowledge-Intensive NLP Tasks\u0026rdquo; Vector Database / Vector Store（向量数据库 / 向量存储）\n解释：专门存储和检索高维向量的数据库，支持近似最近邻（ANN）搜索。常见方案有 FAISS、Milvus、Pinecone、Chroma 等。 相关文档：Milvus | Pinecone | Chroma Chunking（分块 / 文本分块）\n解释：将长文档切分为较小片段的过程，以便进行向量化和检索。分块策略直接影响检索质量和 RAG 效果。 相关文档：LangChain: Text Splitters Chunking Strategy（分块策略）\n解释：决定如何分块的方法论，包括固定大小分块、按语义分块（Semantic Chunking）、按段落分块等。好的策略平衡上下文完整性和检索精度。 相关文档：LlamaIndex: Chunking Semantic Search（语义搜索）\n解释：基于向量相似度进行搜索，而非关键词精确匹配。能理解查询的语义含义，找到概念相关但字面不同的结果。 相关文档：Pinecone: Semantic Search Hybrid Search（混合搜索）\n解释：结合向量语义搜索和传统关键词（BM25）搜索的方法，兼顾语义理解和精确匹配，提升召回率。 相关文档：Weaviate: Hybrid Search Reranking（重排序）\n解释：对初步检索结果使用更精细的模型（如 Cross-Encoder）重新打分排序，提升最终呈现给模型的 Top-K 结果的相关性。 相关文档：Cohere: Rerank | SBERT Cross-Encoders Grounding（事实锚定 / 接地）\n解释：将模型生成的内容锚定到可验证的外部知识源，确保回答有据可查，是 RAG 的核心目标之一。 相关文档：Google Cloud: Grounding with Gemini Knowledge Graph (in LLM context)（知识图谱）\n解释：在 LLM 场景中，知识图谱用于存储实体间的结构化关系，可以作为 RAG 的补充，提供更精确的事实推理能力。GraphRAG 是典型应用。 相关文档：Microsoft: GraphRAG | Neo4j + LLM 三、推理与提示技术 Chain-of-Thought（思维链，CoT）\n解释：在 Prompt 中引导模型\u0026quot;一步步思考\u0026quot;，展示推理过程后再给出答案。显著提升数学、逻辑等复杂任务的准确率。 相关文档：Wei et al., \u0026ldquo;Chain-of-Thought Prompting\u0026rdquo; ReAct（Reasoning + Acting，推理与行动）\n解释：让模型交替进行推理和行动（如调用工具），将思考过程和工具调用结果结合来解决问题。是 Agent 的基础范式之一。 相关文档：Yao et al., \u0026ldquo;ReAct\u0026rdquo; Tree of Thoughts（思维树，ToT）\n解释：将推理过程组织为树形结构，允许探索多条推理路径并通过评估回溯选择最优路径。适合搜索和规划类问题。 相关文档：Yao et al., \u0026ldquo;Tree of Thoughts\u0026rdquo; Graph of Thoughts（思维图，GoT）\n解释：在 ToT 基础上进一步泛化，允许推理节点形成任意有向图结构，支持路径合并和循环，增强复杂推理能力。 相关文档：Besta et al., \u0026ldquo;Graph of Thoughts\u0026rdquo; Self-Consistency（自洽性 / 自我一致性）\n解释：对同一问题多次采样不同的推理路径，通过多数投票选择最一致的答案。提升 CoT 的鲁棒性。 相关文档：Wang et al., \u0026ldquo;Self-Consistency Improves CoT\u0026rdquo; Reflection / Self-Reflection（反思 / 自我反思）\n解释：让模型在生成答案后回顾和评估自己的输出，发现错误并修正。是 Agent 自我改进的重要机制。 相关文档：Shinn et al., \u0026ldquo;Reflexion\u0026rdquo; | Madaan et al., \u0026ldquo;Self-Refine\u0026rdquo; Test-time Compute（测试时计算 / 推理时计算扩展）\n解释：在推理阶段增加计算量来提升输出质量，如多次采样、迭代修正等。这是推理模型（如 o1）的核心范式。 相关文档：OpenAI: Learning to Reason with LLMs | Snell et al., \u0026ldquo;Scaling LLM Test-Time Compute\u0026rdquo; Reasoning Models（推理模型）\n解释：通过强化学习训练模型在回答前进行长链推理，显著提升数学、编程等复杂任务能力。代表模型有 OpenAI o1/o3、DeepSeek-R1。 相关文档：OpenAI o1 System Card | DeepSeek-R1 Scratchpad（草稿本）\n解释：允许模型在输出中使用隐藏的中间步骤进行推理，最终只输出结论。类似人类打草稿的过程。 相关文档：Nye et al., \u0026ldquo;Show Your Work: Scratchpads\u0026rdquo; 四、Agent 核心概念 AI Agent（AI 智能体）\n解释：能够感知环境、自主规划、调用工具并执行行动以完成目标的 AI 系统。LLM 作为\u0026quot;大脑\u0026quot;，工具作为\u0026quot;手脚\u0026quot;，形成完整的代理能力。 相关文档：Lilian Weng: \u0026ldquo;LLM Powered Autonomous Agents\u0026rdquo; | Anthropic: Building Effective Agents Agentic Workflow（智能体工作流）\n解释：由多个 LLM 调用步骤组成的自动化流程，每个步骤可能涉及推理、工具调用或决策。是 Agent 落地的主要形态。 相关文档：Andrew Ng: \u0026ldquo;Agentic Design Patterns\u0026rdquo; Tool Use / Tool Calling / Function Calling（工具使用 / 工具调用 / 函数调用）\n解释：模型根据用户意图，选择并调用外部工具（如搜索、计算器、API），将工具返回结果融入回答。是 Agent 与外部世界交互的核心能力。 相关文档：OpenAI: Function Calling | Anthropic: Tool Use Planning（规划）\n解释：Agent 将复杂目标分解为可执行的子任务序列的能力。包括任务分解、步骤排序、依赖管理等。 相关文档：Lilian Weng: \u0026ldquo;LLM Powered Autonomous Agents\u0026rdquo; (Planning section) Memory (Short-term / Long-term / Working Memory)（记忆：短期 / 长期 / 工作记忆）\n解释：Agent 存储和检索信息的能力。短期记忆对应上下文窗口，长期记忆通常使用向量数据库，工作记忆是当前任务的中间状态。 相关文档：LangChain: Memory | MemGPT Observation（观察）\n解释：Agent 从环境（如工具返回值、用户反馈、系统状态）中获取信息的过程，是 Agent Loop 的感知环节。 相关文档：Yao et al., \u0026ldquo;ReAct\u0026rdquo; (Observation in the loop) Action Space（行动空间）\n解释：Agent 可以执行的所有可能动作的集合，包括可调用的工具、可发送的消息等。行动空间的设计决定了 Agent 的能力边界。 相关文档：OpenAI: Function Calling Guide Reward / Feedback Loop（奖励 / 反馈循环）\n解释：Agent 根据行动结果获得正向或负向信号，据此调整后续行为。可以来自人类、环境或模型自评。 相关文档：Sutton \u0026amp; Barto, \u0026ldquo;Reinforcement Learning: An Introduction\u0026rdquo; Autonomy Levels（自治等级）\n解释：衡量 Agent 自主决策程度的指标，从完全人工操作到完全自主。不同任务需要不同的自治等级。 相关文档：Microsoft: Autonomous AI Agent Levels Human-in-the-Loop（HITL，人在回路）\n解释：在 Agent 执行过程中引入人类审核和干预，确保关键决策的安全性和准确性。常用于高风险场景。 相关文档：LangGraph: Human-in-the-loop | Anthropic: Building Effective Agents Agent Loop（智能体循环：感知 → 规划 → 行动 → 观察）\n解释：Agent 的核心运行周期：感知环境状态 → 规划下一步行动 → 执行行动 → 观察结果，循环直至完成目标。 相关文档：ReAct Paper | Anthropic: Agent Loop Multi-Agent System（多智能体系统）\n解释：多个 Agent 协作完成复杂任务的系统架构，每个 Agent 扮演不同角色，通过通信和协调达成目标。 相关文档：AutoGen: Multi-Agent Framework | CAMEL: Communicative Agents Role-playing（角色扮演）\n解释：在多 Agent 系统中，为每个 Agent 分配特定角色（如 coder、reviewer、manager），通过角色间的交互协作完成任务。 相关文档：CAMEL: Role-Playing Language Agents Handoff（交接 / 转交）\n解释：Agent 将当前任务或子任务转交给另一个 Agent 或人类处理的机制，实现专业化分工。 相关文档：OpenAI: Agents SDK (Handoffs) SOP（Standard Operating Procedure，标准操作流程）\n解释：为 Agent 预定义的标准化操作流程，将复杂任务拆解为可复用的步骤模板，提升一致性和可靠性。 相关文档：MetaGPT: SOPs for Agents Durable Execution（持久化执行）\n解释：Agent 的执行状态可以被持久化保存，即使进程中断也能恢复继续执行。对长时间运行的任务至关重要。 相关文档：Temporal: Durable Execution | Restate: Durable Agents Sandboxing / Code Interpreter（沙箱 / 代码解释器）\n解释：为 Agent 提供隔离的代码执行环境（如容器、Jupyter），让 Agent 能运行代码、处理数据，同时确保安全性。 相关文档：OpenAI: Code Interpreter | E2B: Code Interpreter SDK Computer Use（计算机使用）\n解释：Agent 能够像人类一样操作计算机界面（点击、输入、滚动），直接与桌面应用和操作系统交互。 相关文档：Anthropic: Claude Computer Use Browser Automation（浏览器自动化）\n解释：Agent 通过程序化方式控制浏览器，实现网页浏览、表单填写、数据抓取等操作。是 Web Agent 的基础能力。 相关文档：Playwright | Browser Frameworks: Browser Use 五、Agent 协议与标准 MCP（Model Context Protocol，模型上下文协议）\n解释：Anthropic 提出的开放协议，标准化了 LLM 应用与外部数据源、工具之间的连接方式。类似 AI 领域的 USB-C 接口。 相关文档：Model Context Protocol 官网 | Anthropic: MCP blog ACP（Agent Communication Protocol，Agent 通信协议）\n解释：标准化 Agent 之间通信和协作的协议，定义消息格式、角色协调和信息共享规则。 相关文档：Agent Communication Protocol (Google ACP spec) A2A（Agent2Agent Protocol，Agent 间协议）\n解释：Google 提出的开放协议，允许不同框架构建的 Agent 互相发现、通信和协作，打破 Agent 生态孤岛。 相关文档：Google: A2A Protocol | Google Blog: A2A OpenAPI (in Agent context)（OpenAPI 规范）\n解释：Agent 通过 OpenAPI（原 Swagger）规范描述的 API 定义来理解和调用外部服务，实现工具的自动发现和集成。 相关文档：OpenAPI Initiative | OpenAI: Function Calling with OpenAPI Function Calling Schema（函数调用 Schema）\n解释：定义 Agent 可调用函数的名称、参数、返回值等规范，使模型能正确生成函数调用请求。通常以 JSON Schema 表达。 相关文档：OpenAI: Function Calling Guide Agent Client Protocol（Agent 客户端协议）\n解释：定义 Agent 与其客户端（前端界面、调用方）之间的交互协议，包括消息格式、流式输出、状态同步等。 相关文档：AGNTCY: Agent Client Protocol 六、Agent 框架与工具 LangChain（LangChain 框架）\n解释：最流行的 LLM 应用开发框架，提供 Prompt 管理、链式调用、工具集成、Agent 编排等完整能力。 相关文档：LangChain 官网 | GitHub LangGraph（LangGraph）\n解释：LangChain 团队推出的 Agent 编排框架，基于图（Graph）结构定义 Agent 工作流，支持循环、分支、人工干预等复杂流程。 相关文档：LangGraph 文档 | GitHub LangSmith（LangSmith）\n解释：LangChain 的可观测性平台，提供 LLM 应用的 Tracing、调试、评估和监控能力。 相关文档：LangSmith 官网 AutoGen（AutoGen 框架）\n解释：微软推出的多 Agent 对话框架，通过 Agent 间的多轮对话协作解决复杂任务，支持人类参与和代码执行。 相关文档：AutoGen GitHub | AutoGen Paper CrewAI（CrewAI 框架）\n解释：基于角色的多 Agent 协作框架，定义 Crew（团队）、Agent（成员）、Task（任务）等概念，简单直观地编排 Agent 团队。 相关文档：CrewAI 官网 | GitHub LlamaIndex（LlamaIndex 框架）\n解释：专注数据连接和 RAG 的 LLM 应用框架，提供数据摄入、索引、检索和生成的完整工具链。 相关文档：LlamaIndex 官网 | GitHub Semantic Kernel（Semantic Kernel）\n解释：微软推出的 LLM 编排 SDK，支持 C#、Python、Java，以\u0026quot;技能（Skills/Plugins）\u0026ldquo;为核心抽象，面向企业级应用。 相关文档：Semantic Kernel 文档 | GitHub AutoGPT（AutoGPT）\n解释：早期著名 autonomous agent 项目，让 GPT-4 自主设定子目标、执行任务、自我反思。开创了 AI Agent 的先河。 相关文档：AutoGPT GitHub MetaGPT（MetaGPT）\n解释：模拟软件开发团队的 Multi-Agent 框架，为不同 Agent 分配产品经理、架构师、工程师等角色，按 SOP 协作完成软件开发。 相关文档：MetaGPT GitHub | MetaGPT Paper 七、Agent 评估与可观测性 Benchmark（基准测试）\n解释：用于标准化评估 LLM 和 Agent 能力的测试集和排名体系，通过统一任务和指标对比不同模型/系统的表现。 相关文档：Hugging Face Open LLM Leaderboard Harness（测试工具 / 评估框架）\n解释：运行 Benchmark 的软件框架，管理模型推理、评分计算和结果输出。lm-evaluation-harness 是最广泛使用的实现。 相关文档：lm-evaluation-harness (EleutherAI) SWE-bench（软件工程基准测试）\n解释：评估 Agent 在真实软件工程任务中表现的基准测试，要求 Agent 修复 GitHub 上的真实 issue。 相关文档：SWE-bench 官网 | 论文 AgentBench（Agent 基准测试）\n解释：全面评估 LLM 作为 Agent 在多种环境（操作系统、数据库、知识图谱、游戏等）中表现能力的基准测试。 相关文档：AgentBench 论文 GAIA（General AI Assistants Benchmark）\n解释：评估通用 AI 助手的基准测试，包含需要多步推理、工具使用和真实世界知识的复杂任务。 相关文档：GAIA 论文 | GAIA Hugging Face Tracing / Observability（追踪 / 可观测性）\n解释：对 LLM 和 Agent 的执行过程进行监控和记录，包括调用链路、Token 消耗、延迟、错误等。是生产环境运维的核心能力。 相关文档：LangSmith | Langfuse | Arize Phoenix Eval / Evaluation（评估）\n解释：系统性地评估 LLM/Agent 输出质量和能力的过程，包括自动评估（如 LLM-as-judge）和人工评估。 相关文档：OpenAI: Evals | Ragas (RAG eval) Leaderboard（排行榜）\n解释：公开展示不同模型/Agent 在特定 Benchmark 上表现的排名，方便横向比较。知名的有 Hugging Face Leaderboard、LMSYS Chatbot Arena。 相关文档：LMSYS Chatbot Arena | Hugging Face Leaderboard 八、新兴概念 AGI（Artificial General Intelligence，通用人工智能）\n解释：能在任何智力任务上达到或超越人类水平的 AI。这是当前 AI 研究的终极目标，尚无明确定义的实现标准。 相关文档：OpenAI: AGI definition | DeepMind: Levels of AGI Scaling Laws（缩放定律）\n解释：描述模型性能与参数量、数据量、计算量之间幂律关系的规律。是当前 LLM 发展的基础理论支撑。 相关文档：Kaplan et al., \u0026ldquo;Scaling Laws for Neural Language Models\u0026rdquo; | Hoffmann et al., \u0026ldquo;Chinchilla Scaling Laws\u0026rdquo; Emergent Abilities（涌现能力）\n解释：模型规模达到一定阈值后突然出现的新能力，这些能力在小模型中不存在。如 In-context Learning、CoT 推理等。 相关文档：Wei et al., \u0026ldquo;Emergent Abilities of Large Language Models\u0026rdquo; Compound AI Systems（复合 AI 系统）\n解释：由多个 AI 组件（LLM、检索器、工具、评估器等）组合而成的系统，通过组件协作实现比单一模型更强的能力。Agent 是其典型形态。 相关文档：Berkeley AI: Compound AI Systems AI Operating System（AIOS，AI 操作系统）\n解释：将 Agent 作为一等公民的操作系统概念，为 Agent 提供资源调度、权限管理、Agent 间通信等基础设施。 相关文档：AIOS: AI Agent Operating System (paper) | AIOS GitHub Agent Marketplace（Agent 市场）\n解释：类似应用商店的 Agent 交易平台，开发者发布和分享可复用的 Agent，用户按需选择和使用。 相关文档：OpenAI: GPT Store | Coze GUI Agent（图形界面智能体）\n解释：能够通过理解图形用户界面（GUI）进行操作的 Agent，与 Computer Use 类似但更侧重移动端和 Web 端。 相关文档：Anthropic: Claude Computer Use | AppAgent Agentic Search（智能体搜索）\n解释：由 Agent 驱动的下一代搜索范式，不仅返回链接，还自动浏览、理解和综合信息，直接给出答案。 相关文档：Perplexity AI | OpenAI: Deep Research Deep Research（深度研究）\n解释：Agent 自主进行多轮搜索、阅读、分析、综合的长文本研究能力，能产出结构化的研究报告。OpenAI 和 Google 均已推出相关功能。 相关文档：OpenAI Deep Research | Google Gemini Deep Research 发展脉络图 时间 里程碑事件 代表性技术 / 产品 2017 Transformer 架构提出 Attention is All You Need 2018-2019 预训练模型兴起 BERT、GPT-2 2020 GPT-3 发布，提出 In-context Learning Few-shot Learning、Scaling Laws 2022.01 Chain-of-Thought 提示技术 CoT Prompting 2022.03 RLHF 在 InstructGPT 中应用 SFT + RLHF 对齐训练 2022.10 ReAct 范式提出 Reasoning + Acting 2022.11 ChatGPT 发布，LLM 大规模普及 GPT-3.5、对话式 AI 2023.02 多模态 LLM 出现 GPT-4、GPT-4V 2023.03 AutoGPT 引爆 autonomous agent 概念 AutoGPT、BabyAGI 2023.06 RAG 成为知识增强主流方案 LangChain、LlamaIndex 2023.10 Agent 框架百花齐放 AutoGen、MetaGPT、CrewAI 2024.05 多 Agent 协作标准酝酿 A2A Protocol、MCP 2024.09 OpenAI o1 发布，推理模型时代开启 Reasoning Models、Test-time Compute 2024.10 Anthropic 推出 Computer Use GUI Agent、Browser Automation 2024.11 Anthropic 发布 MCP 协议 Model Context Protocol 2025.01 DeepSeek-R1 开源推理模型 RL for Reasoning 2025.02 Google 推出 A2A 协议 Agent 互操作性 2025.06 OpenAI 推出 Deep Research Agentic Search 2025+ Agent 走向生产环境 AIOS、Agent Marketplace 🌍 The world is yours. — Tony Montana, AI Generated\n","permalink":"/writing/llm-to-agent-concepts/","summary":"从 LLM 基础、RAG 与知识增强、推理与提示技术，到 Agent 核心、协议标准、框架工具、评估可观测性与新兴概念——系统梳理大模型到 Agent 演进脉络中的核心名词。","title":"从大模型到 Agent：核心概念名词全梳理"},{"content":"ES介绍 ES是一个建立在Apache Lucene之上的分布式、Restful风格的搜索和数据分析引擎。\n基本概念 集群（Cluster）：由多台服务器组成，共同存储全部数据，对外提供统一入口。\n节点（Node）：集群中的一台服务器就是一个Node，一个Node对应一个ES实例。\n主节点（Master）：管理集群范围的操作，如创建、删除索引、分配分片。只做轻量级元数据管理。 数据节点（Data）：存储数据，执行数据相关的CRUD、搜索、聚合。 协调节点（Coordinating）：转发请求，合并结果。每个节点默认都是协调节点，但可专门分离出来。 预处理节点（Ingest）：在写入前对文档做简单处理，如重命名字段、删除字段等。 索引（Index）：一类文档的集合，类似关系型数据库里的数据库。\n文档（Document）：一条记录，用JSON表示，是最基本的信息单元。\n字段（Field）：记录里的key，比如：name、title。\n分片（Shard）：一个索引可以水平分割为多个分片，分布在不同的节点上。分片又分为主分片和从分片。\n副本（Replica）：每个分片可以有一个或多个副本，用于容灾和提高查询性能。\n倒排索引 基本思想：从文档找词 转变为 从词找文档。倒排索引建立的是 词项 到 文档ID 的映射。\n逻辑层面：ES发起搜索请求时，面对的是一个倒排索引。 物理层面：每个倒排索引实际存储在多个Segment中，每个Segment都是一个独立的、自包含的小型倒排索引，拥有自己的词典、文档列表和评分数据。 示例：\n正排索引： Doc1 → [ \u0026#34;elasticserch\u0026#34;, \u0026#34;is\u0026#34;, \u0026#34;fast\u0026#34; ] Doc2 → [ \u0026#34;fast\u0026#34;, \u0026#34;search\u0026#34;, \u0026#34;with\u0026#34;, \u0026#34;elasticserch\u0026#34; ] 倒排索引： \u0026#34;elasticserch\u0026#34; → { Doc1, Doc2 } \u0026#34;fast\u0026#34; → { Doc1, Doc2 } \u0026#34;search\u0026#34; → { Doc2 } \u0026#34;is\u0026#34; → { Doc1 } \u0026#34;with\u0026#34; → { Doc2 } 假设要搜索\u0026quot;fast\u0026quot;这个词项，倒排索引必须回答两个问题：\n\u0026ldquo;fast\u0026quot;这个词是否存在？ 如果存在，它在哪些文档的那些位置？ 这恰好对应Lucene倒排索引的三个组成：\nTerm Index：词项索引 Term Dictionary：词项词典 Posting List：倒排列表 组件 存储位置 数据结构 核心作用 特点 Term Index 堆内存 FST 快速定位到词典中的词项位置 极度压缩，支持前缀/模糊查询 Term Dictionary 磁盘 排序词项列表 + FST 全量词项的唯一集合，指向倒排列表 所有词项永久存储 Posting List 磁盘 差值压缩+位图 存储词项出现的文档、频率、位置 支持快速合并与评分计算 查询路径：从Term Index快速定位到Term Dictionary中的词项，再通过指针拿到Posting List。\nTerm Dictionary Term Dictionary，词项词典，是全量词项的有序集合。它将索引中的所有文档的所有词项，经排序后形成唯一词项列表。\n每个词项条目包含：\n词项文本 指向该词项Posting List的文件偏移指针 文档频率 Term Index Term Index，词项索引，是词典的目录与内存核心。词典存储在磁盘上，直接二分查找会产生大量随机IO。所以需要一种极小内存占用的结构，快速定位词典中某个前缀的位置。\n实现方式：FST（Finite State Transducer），本质是共享前缀+共享后缀的有向无环图，可将所有词项压缩成极小的内存结构。\nTerm Index常驻堆内存，是整个倒排索引查询速度的基石。它让Lucene在万亿级别词项规模下仍能极速定位。\nPosting List Posting List，倒排列表，是词到文档的映射，并承载评分信息。\n每个词项的Posting List包含：\n文档ID列表：有序，用差值编码，例如：[102, 105, 109, 150] → [102, 3, 4, 41] 词频TF：每个文档中该词出现的次数 位置信息Positions：词在文档内的位置 偏移量Offsets：词的起止字符位置 Payloads：用户自定义的负载信息 倒排索引检索流程 以搜索 \u0026ldquo;elasticsearch fast\u0026rdquo; 为例（分析后为两个词项）：\nTerm Index 查找 对每个词项，用 FST 在内存中查找，迅速返回其在 Term Dictionary 中的位置。 Term Dictionary 定位 根据 FST 返回的偏移，从磁盘的词典文件中读取词项条目，获取 Posting List 的文件指针。 读取 Posting List 从磁盘读取相应压缩块到内存，解码出文档 ID 列表、词频、位置等。 多词合并与评分 根据查询逻辑（AND/OR），对两个词项的文档列表求交集或并集，利用词频、文档长度等计算 BM25 得分。 返回结果 按得分排序，取 Top-N 文档 ID，再通过 Fetch Phase 加载完整文档返回。 详细流程：\n步骤 位置 操作 用到 Posting List 的哪些信息 1. 词项定位 内存 FST → 磁盘词典 找到词项的 Posting List 文档频率（DF）用于 IDF 2. 读取文档列表 磁盘 Posting List 解码出文档 ID 列表（差值编码） 有序文档 ID 列表、词频 TF 3. 计算评分 分片本地 对每个文档计算 BM25 TF、文档长度（dl）、平均长度（avgdl）、IDF 4. 排序取 Top-N 分片本地 选出本地前 N 个文档 ID+分数 仅需 ID 和分数 5. Query Phase 合并 协调节点 合并各分片结果，全局 Top-N 6. Fetch Phase 协调 → 数据节点 根据 ID 列表获取完整文档 不再需要 Posting List 数据写入流程 客户端向某个节点发送POST请求。 接收到请求的节点自动成为协调节点，根据路由公式计算目标分片：shard = hash(_routing) % num_primary_shards（_routing为文档id）。找到该分片的主分片所在节点，将请求转发过去。 主分片所在节点收到写请求后，执行以下步骤： 写入Translog预写日志，保证即使宕机也能重放恢复。 写入内存缓冲区（此时文档还不能搜索）。 主分片并发的转发给副本分片，确保多数副本分片写入成功。 协调节点返回客户端（此时文档还不能搜索）。 Refresh，默认每秒执行一次： 将内存缓冲区中的文档生成一个新的Segment（一个不可变、自包含的小型倒排索引） 打开Segment，此时文档可以被搜索。 Flush，数据持久化（触发条件为Translog超过512MB或每30分钟一次）： 将当前所有在文件系统缓存中的Segment强制fsync到磁盘。 创建一个提交点Commit Point，记录当前所有已持久化的Segment列表。 清空旧Translog，并开始记录新Translog。 删除/更新与段合并： 删除：不修改Segment，只在.del文件中标记文档ID为已删除，查询时会跳过。 更新：先标记旧文档删除，再写入新文档，产生新的Segment。 段合并：后台任务会自动将多个小Segment合并为大Segment，并物理清理已删除文档，释放空间。 数据检索流程 客户端发起请求：向某个节点发起GET请求。 Query Phase，查询阶段： 协调节点接收请求：解析查询DSL，确定目标索引和分片。 广播查询：将查询请求发送给目标索引的所有主分片或副本分片。 分片本地执行，每个分片独立查询。 返回中间结果：每个分片将Top-N的_id + _score 列表返回给协调节点。 协调节点合并排序：在内存中对所有分片的Top-N合并后进行重新排序，去除全局的Top-N文档ID。 Fetch Phase，获取阶段： 协调节点根据最终选出的文档ID列表，向对应分片发起Multi-Get请求，拉取这些文档的完整信息。 协调节点组装最终结果，返回客户端。 深分页问题 为什么深分页慢？\n网络开销大：每个分片都要传输10000条数据，分片越多，传输量成倍增长。 内存压力大：协调节点需要暂存，并对海量中间结果排序，查询的页越深，需要的内存越大。 重复计算：每次翻页都要从头查一遍，无法利用之前的计算结果。 硬性限制：ES默认max_result_window=10000，from+size 超过这个值直接报错。 方案一：Search After（推荐使用） 适用场景：无限翻页、瀑布流、实时数据。 核心思想：根据上一页最后一条的排序值作为游标起点，进行后续数据查询。search after支持查询多个字段。 局限性： 需要一个唯一的排序字段。 只能向后翻页，不能跳页。 // 第一页 GET /products/_search { \u0026#34;size\u0026#34;: 10, \u0026#34;sort\u0026#34;: [ { \u0026#34;price\u0026#34;: \u0026#34;desc\u0026#34; }, { \u0026#34;_id\u0026#34;: \u0026#34;asc\u0026#34; } // 必须包含唯一字段作为 tiebreaker ] } // 返回结果中最后一条的 sort 值： // \u0026#34;sort\u0026#34;: [999, \u0026#34;abc123\u0026#34;] // 下一页：用 search_after 传入上一页最后一条的 sort 值 GET /products/_search { \u0026#34;size\u0026#34;: 10, \u0026#34;sort\u0026#34;: [ { \u0026#34;price\u0026#34;: \u0026#34;desc\u0026#34; }, { \u0026#34;_id\u0026#34;: \u0026#34;asc\u0026#34; } ], \u0026#34;search_after\u0026#34;: [999, \u0026#34;abc123\u0026#34;] } 方案二：Scroll API（不推荐使用） 适用场景：一次性导出海量数据、批量处理。 核心思想：生成一个数据快照，然后像游标一样分批遍历。 局限性： 不实时：拿到的数据是快照数据。 官方不推荐。 方案三：Point In Time(PIT) + Search After （官方推荐） 适用场景：一致性视图+无限翻页 核心思想：PIT创建一个轻量级时间点视图，配合Search After实现一致性游标翻页。 // 1. 创建 PIT POST /products/_pit?keep_alive=1m // 返回 { \u0026#34;id\u0026#34;: \u0026#34;pit_id_xxx\u0026#34; } // 2. 用 PIT + search_after 查询 GET /_search { \u0026#34;size\u0026#34;: 10, \u0026#34;pit\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;pit_id_xxx\u0026#34;, \u0026#34;keep_alive\u0026#34;: \u0026#34;1m\u0026#34; }, \u0026#34;sort\u0026#34;: [ { \u0026#34;@timestamp\u0026#34;: \u0026#34;desc\u0026#34; }, { \u0026#34;_shard_doc\u0026#34;: \u0026#34;asc\u0026#34; } // PIT 下用 _shard_doc 做 tiebreaker ], \u0026#34;search_after\u0026#34;: [1620000000000, 12345] } // 3. 用完释放 PIT DELETE /_pit { \u0026#34;id\u0026#34;: \u0026#34;pit_id_xxx\u0026#34; } 方案四：限制深度分页（业务折衷） 适用场景：B端后台管理、搜索引擎前端。 ","permalink":"/knowledges/middleware/elastic-search/","summary":"\u003ch2 id=\"es介绍\"\u003eES介绍\u003c/h2\u003e\n\u003cp\u003eES是一个建立在Apache Lucene之上的分布式、Restful风格的搜索和数据分析引擎。\u003c/p\u003e\n\u003ch2 id=\"基本概念\"\u003e基本概念\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e集群（Cluster）\u003c/strong\u003e：由多台服务器组成，共同存储全部数据，对外提供统一入口。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e节点（Node）\u003c/strong\u003e：集群中的一台服务器就是一个Node，一个Node对应一个ES实例。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e主节点（Master）：管理集群范围的操作，如创建、删除索引、分配分片。只做轻量级元数据管理。\u003c/li\u003e\n\u003cli\u003e数据节点（Data）：存储数据，执行数据相关的CRUD、搜索、聚合。\u003c/li\u003e\n\u003cli\u003e协调节点（Coordinating）：转发请求，合并结果。每个节点默认都是协调节点，但可专门分离出来。\u003c/li\u003e\n\u003cli\u003e预处理节点（Ingest）：在写入前对文档做简单处理，如重命名字段、删除字段等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e索引（Index）\u003c/strong\u003e：一类文档的集合，类似关系型数据库里的数据库。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e文档（Document）\u003c/strong\u003e：一条记录，用JSON表示，是最基本的信息单元。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e字段（Field）\u003c/strong\u003e：记录里的key，比如：name、title。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e分片（Shard）\u003c/strong\u003e：一个索引可以水平分割为多个分片，分布在不同的节点上。分片又分为主分片和从分片。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e副本（Replica）\u003c/strong\u003e：每个分片可以有一个或多个副本，用于容灾和提高查询性能。\u003c/p\u003e\n\u003ch2 id=\"倒排索引\"\u003e倒排索引\u003c/h2\u003e\n\u003cp\u003e基本思想：从文档找词 转变为 从词找文档。倒排索引建立的是 词项 到 文档ID 的映射。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e逻辑层面：ES发起搜索请求时，面对的是一个倒排索引。\u003c/li\u003e\n\u003cli\u003e物理层面：每个倒排索引实际存储在多个Segment中，每个Segment都是一个独立的、自包含的小型倒排索引，拥有自己的词典、文档列表和评分数据。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e示例：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e正排索引：\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDoc1 → [ \u0026#34;elasticserch\u0026#34;, \u0026#34;is\u0026#34;, \u0026#34;fast\u0026#34; ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDoc2 → [ \u0026#34;fast\u0026#34;, \u0026#34;search\u0026#34;, \u0026#34;with\u0026#34;, \u0026#34;elasticserch\u0026#34; ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e倒排索引：\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026#34;elasticserch\u0026#34; → { Doc1, Doc2 }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026#34;fast\u0026#34;         → { Doc1, Doc2 }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026#34;search\u0026#34;       → { Doc2 }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026#34;is\u0026#34;           → { Doc1 }\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026#34;with\u0026#34;         → { Doc2 }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e假设要搜索\u0026quot;fast\u0026quot;这个词项，倒排索引必须回答两个问题：\u003c/p\u003e","title":"ElasticSearch"},{"content":"\n原子性：一个或多个操作，要么全部执行完且中间不被任何干扰，要么一个都不执行。 可见性：一个线程对共享变量的修改，其他线程能立刻看到。 有序性：程序执行的顺序，按照代码的书写顺序来。 Java内存模型 JMM 主要解决可见性、有序性，基本保证原子性操作。\nJMM是什么 Java内存模型（Java Memory Model），简称JMM，是一套抽象的规范。定义了多线程环境中共享变量的访问规则。 JMM将内存划分为主内存和工作内存。\n主内存：所有线程共享，存放变量的正式值。 工作内存：每个线程私有，存放该线程用到的主内存变量的副本。 JMM解决什么问题 JMM解决可见性、有序性两个问题：\n可见性问题：CPU多级缓存与缓冲区。 有序性问题：编译器和处理器为了性能会进行指令重排序。 解决可见性 建立\u0026quot;强制刷新/失效\u0026quot;协议。JMM规定，线程对变量的操作不能一直停留在工作内存里，必须在特定时刻同步到主内存。\nvolatile： 写操作：新值必须立即刷新到主内存。 读操作：每次读取前强制从主内存重新加载，并让其他线程的副本实效。 synchronized： 加锁后：必须清空工作内存中变量副本，强制从主内存重新加载。 解锁前：工作内存的修改必须全部刷新到主内存。 final： 只要构造期间没有让this引用逸出，构造完成后final字段的值对其他线程立刻可见，无需额外同步。 解决有序性 定义Happens-Before原则。规定哪些操作不可重排序。\n保证基本的原子性 JMM只保证基本读写操作的原子性，除了long、double外，其他变量的单次读写操作都是原子的。复合操作必须通过锁或其他方式保证原子性。\nJMM怎么实现 JMM的规范是抽象的， 需要靠JIT编译器插入内存屏障、处理器提供的硬件指令来落地。\n内存屏障 JIT在编译字节码时，会在关键位置插入四种内存屏障指令。\n屏障类型 作用 LoadLoad 禁止屏障前后的读操作重排 StoreStore 禁止屏障前后的写操作重排 LoadStore 禁止屏障前的读与屏障后的写重排 StoreLoad 禁止屏障前的写与屏障后的读重排（最重，同时具备其他三者效果） volatile： 写操作：写前插入 StoreStore，写后插入 StoreLoad。 读操作：读后插入 LoadLoad 和 LoadStore。 synchronized：使用字节码指令monitorenter 和 monitorexit触发内存屏障，保证临界区内的读写在锁释放后可见。 final：在构造方法末尾插入 StoreStore屏障，保证final字段赋值不会与对象引用赋值被重排序。 硬件指令 内存屏障主要通过CPU指令lock xxx实现。\n强制将当前CPU缓存刷新到主内存。 通过MESI缓存一致性协议，将其他CPU缓存中对应数据失效。 阻止处理器对lock前后的指令进行重排序。 volatile volatile 最轻量的同步机制，通过写前 StoreStore + 写后 StoreLoad、读后 LoadLoad + LoadStore 的内存屏障策略，保证多线程环境下的可见性和有序性，但不保证原子性。\n可见性： 写入：volatile写后插入 StoreLoad屏障，强制将当前线程工作内存刷新到主内存。 读取：强制从主内存加载。 有序性： 写入：写前插入 StoreStore，写后插入StoreLoad。 读取：读后插入 LoadLoad、LoadStore。 synchronized synchronized 较重的同步机制，保证原子性、可见性、有序性。\nsynchronized用法：\n修饰普通方法：锁当前实例对象this。 修饰静态方法：锁当前类对象。 修饰代码块：锁指定的对象。 如何保障的原子性、可见性、有序性：\n原子性：锁互斥。同一时刻只有一个线程能进入临界区执行。 可见性：加锁后，工作内存被清空，强制从主内存重新加载；解锁前，强制刷新到主内存。 有序性：保证临界区内的代码不会与临界区外的代码发生重排序（即代码不能跨出或跨入临界区），但临界区内部的指令是允许重排序的。 底层实现原理：\n字节码层：临界区代码由monitorenter和monitorexit包裹，方法修饰符中设置了 ACC_SYNCHRONIZED 标志。确保同一时刻，只有一个线程能获取对象关联的Monitor。 对象头与Monitor：每个对象的对象头都有一个Mark Word，记录了锁状态（无锁、偏向锁、轻量级锁、重量级锁）。重量级锁状态下，Mark Word指向一个ObjectMonitor。 硬件层：原子性由CAS 或 操作系统互斥量保障。可见性和有序性由内存屏障保障。 锁升级：\n无锁：没有加锁的情况下。 偏向锁：消除同一线程反复获取锁的同步开销。线程第一次获取到锁时，通过CAS在对象头的Mark Word记录下自己的线程ID。之后进入同步块，只需判断线程ID是否一致来确定是否是当前线程持有锁。 轻量级锁：并发情况下，通过CAS自旋来获取锁，获取成功后将对象头的Mark Word修改为指向当前线程在栈中创建的Lock Record。 重量级锁：自旋失败或竞争激烈时，膨胀为重量级锁。JVM创建ObjectMonitor，未获取到锁的线程进入等待队列，被操作系统挂起，直到锁释放后被唤醒。 final final 解决不可变对象的安全发布问题。\n核心保证是：当一个对象的构造函数执行完毕，且 this 引用没有在构造期间逸出，那么其他线程即使没有同步，也能立刻看到该对象中所有 final 字段的正确初始化值。\n实现原理：在构造函数执行完毕、即将返回时，JIT 编译器插入了 StoreStore 内存屏障。确保在构造方法返回前，final变量已安全发布。\nPS：只有包含final字段的构造方法，才会插入StoreStore屏障。\nAQS AQS，全称AbstractQueuedSynchronizer，是一个抽象的、基于FIFO等待队列的、用一个volatile ine表示同步状态的框架。\nAQS解决的问题是：把线程如何安全地排队、阻塞、唤醒等这些复杂且容易出错的操作封装起来，让开发者只需关心何时加锁、何时释放的上层逻辑。\nAQS使用模板方法模式定义：\n线程如何安全地入队、出队。 如何用LockSupport.park/unpark挂起和唤醒线程。 如何处理中断和超时。 AQS数据结构：\n状态变量state：表示同步状态，可以是重入锁或许可证。 FIFO等待队列：一个由Node节点构成的双向链表，头节点表示当前持有锁或正在获取锁的线程。 等待状态waitStatus：每个Node节点有一个waitStatus。 SIGNAL（-1）：表示后继节点需要被唤醒。当前节点释放锁时，必须检查并unpark后继节点。 CANCELLED（1）：节点因超时或中断被取消。 AQS的两种获取锁模式：\n独占模式： 核心特征：同一时刻只有一个线程能成功获取资源。 典型实现：ReentrantLock。 状态变量state含义：锁支持次数（0=空闲，1=持有，\u0026gt;1=重入）。 共享模式： 核心特征：同一时刻多个线程可以同时成功获取资源。 典型实现：Semaphore、CountDownLatch。 状态变量state含义：剩余许可证/资源数量。 ReentrantLock、Semaphore、CountDownLatch等都是基于AQS实现的。\nReentrantLock 锁模式：独占模式\nReentrantLock是一个可重入的互斥锁，内部有一个继承自AQS的Sync，并分为公平锁和非公平锁两种实现，默认为实现为非公平锁。\n加锁逻辑：\nlock()：调用acquire(1) tryAcquire(1)： 若state等于0，则CAS获取锁，成功则设为锁持有者。 若当前线程已是持有者，则state+1，表示重入。 若CAS失败，则进入等待队列。 解锁逻辑：\nunlock()：调用release(1) tryRelease(1)：state-1，若state清零，则释放锁，并唤醒后继节点。 两种锁实现：\n公平锁：先检查队列里有没有线程在等，有就乖乖排队。 非公平锁：一上来就CAS抢锁，不看队列里有没有线程排队。 Semaphore 锁模式：共享模式\nSemaphore是信号量，将state表示为剩余许可证数量，来控制多个线程访问资源。内部有一个继承自AQS的Sync，并分为公平锁和非公平锁两种实现，默认为实现为非公平锁。\n加锁逻辑：\nacquire()：调用acquireSharedInterruptibly(1) tryAcquireShared(1)： 若state大于0，CAS更新state-1，表示当前线程获取一个许可。 若state小于0，返回负数，进入等待队列。 解锁逻辑：\nrelease()：调用releaseShared(1) tryReleaseShared(1)：CAS更新state+1，表示归还一个许可。 若剩余许可证数量大于0，AQS会自动唤醒下一个等待的线程，直到许可证为0。\nCountDownLatch 锁模式：共享模式\nCountDownLatch是倒计时门闩，将state表示为还需等待的事件数量，是一个一次性的共享模式实现。\n加锁逻辑：\nawait()：调用acquireSharedInterruptibly(1) tryAcquireShared(1)： 若state等于0，直接放行。 若state大于0，则进入等待队列。 解锁逻辑：\ncountDown()：调用releaseShared(1) tryReleaseShared(1)：CAS更新state-1，当state等于0，释放锁成功，并触发唤醒。 Semaphore和CountDownLatch的关键区别：\nSemaphore：state可复用。 CountDownLatch：state是一次性的，当state等于0，所有等待线程被唤醒，门闩永久打开。 AQS实现比对 特性 ReentrantLock Semaphore CountDownLatch 模式 独占 共享 共享 state 含义 锁持有次数 剩余许可证数 还需等待的事件数 获取 lock() → state 0→1 acquire() → state 减 N await() → 等 state==0 释放 unlock() → state 减 1 release() → state 加 1 countDown() → state 减 1 可重入 ✅ ❌ ❌ 锁支持 公平锁/非公平锁 公平锁/非公平锁 ❌无此概念 复用性 可复用 可复用 一次性 唤醒传播 无（独占） 有（共享） 有（共享，到 0 时） ThreadLocal ThreadLocal是一个线程级别的隔离工具。每个线程有一份独立的存储，可以避免线程间的数据共享和竞争。\n核心原理：每个Thread对象有一个ThreadLocalMap。ThreadLocalMap是一个哈希表，key为ThreadLocal对象（弱引用），value为需要存储的值。\nkey为什么设计为弱引用？ 为了在ThreadLocal对象失去外部引用后，key可以被GC回收。这样ThreadLocalMap里的key为null，但value还被强引用。所以必须通过remove来清理，防止内存泄漏。\n线程池 线程池是一种池化资源技术，它预先创建一定数量的线程，放入\u0026quot;池子\u0026quot;中。当有任务需要执行时，从池子里取一个线程来执行，任务执行完成后线程不会销毁，而是返回池子等待下一个任务。\n为什么需要线程池？\n降低资源消耗：避免频繁创建、销毁线程的开销。 提高响应速度：任务到达时，无需等待线程创建就可以立即执行。 提高线程的可管理性：可以统一分配、调优和监控线程的数量和状态。 ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue\u0026lt;Runnable\u0026gt; workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) ThreadPoolExecutor是线程池的核心实现类，提供了以下参数：\ncorePoolSize：核心线程数，即使空闲也不会回收。 maximumPoolSize：线程池允许的最大线程数。 keepAliveTime：当线程池大于核心线程数时，多余空闲线程的存活时间。 unit：存活时间的单位。 workQueue：线程等待队列。 threadFactory：线程工厂，可以自定义线程名称。 handler：拒绝策略，当队列满且达到最大线程数时，拒绝线程的策略。 线程池执行流程 若 当前线程数 \u0026lt; 核心线程数，则创建核心线程执行。 若 当前线程数 = 核心线程数，且队列未满，则将线程放入等待队列。 若 队列已满，但当前线程数 \u0026lt; 最大线程数，则创建非核心线程执行。 若 队列已满，且当前线程数 \u0026gt;= 最大线程数，则执行拒绝策略。 核心线程数是常驻线程，最大线程数是临时的救急线程。\nExecutors Executors是JUC包中的一个线程池工厂类，用于快速创建ExecutorService实例。\nExecutors默认提供了几个工厂方法：\nnewFixedThreadPool： 线程池介绍：固定大小的线程池。 核心线程数：指定大小。 最大线程数：指定大小。 等待时间：0 等待队列：无界队列LinkedBlockingQueue。 newCachedThreadPool 线程池介绍：缓存线程池。 核心线程数：0。 最大线程数：无限大。 等待时间：60s。 等待队列：无容量队列SynchronousQueue。 newSingleThreadExecutor： 线程池介绍：单个线程的线程池。 核心线程数：1。 最大线程数：1。 等待时间：0。 等待队列：无界队列LinkedBlockingQueue。 newScheduledThreadPool： 线程池介绍：定时调度线程池。 核心线程数：指定大小。 最大线程数：无限大。 等待时间：10s。 等待队列：延迟队列DelayedWorkQueue。 《阿里巴巴 Java 开发手册》规定：不允许使用 Executors 创建线程池，必须通过 ThreadPoolExecutor 手动创建。 使用默认线程池工厂方法的风险：\nOOM：等待队列无限大，可能发生OOM。 资源耗尽：最大线程数无限大，可能导致资源耗尽。 等待队列 线程池使用的队列全部来自 java.util.concurrent.BlockingQueue接口的实现：\nArrayBlockingQueue：数组阻塞队列，有界，必须指定容量。 LinkedBlockingQueue：线性阻塞队列，默认无界，可指定有界。 SynchronousQueue：同步阻塞队列，无容量，直接提交。 PriorityBlockingQueue：优先级阻塞队列，无界，按优先级提交。 DelayedWorkQueue：延迟阻塞队列，无界，延迟提交。 ArrayBlockingQueue和LinkedBlockingQueue的区别：\n内存占用控制：ArrayBlockingQueue的内存占用更可控一些，可以指定大小，避免任务堆积。 吞吐量：ArrayBlockingQueue使用单锁，LinkedBlockingQueue：双锁，吞吐量高。 灵活性：对任务量变化较大的应用场景，LinkedBlockingQueue更灵活。 拒绝策略 当队列已满且达到最大线程数时，触发拒绝策略。\n拒绝策略：\nAbortPolicy：默认拒绝策略，直接抛出RejectedExecutionException异常，需要处理异常。 CallerRunsPolicy：由调用者执行任务。 DiscardPolicy：直接丢弃新任务，不会抛出异常。 DiscardOldestPolicy：丢弃队列中最老的任务。 线程池配置最佳实践 CPU密集型任务：以计算为主，线程数 = CPU核心数 + 1。 IO密集型任务：大量阻塞等待，线程数 = CPU核心数 * 2，或更精准的计算方式：CPU核心数 * （1+平均等待时间/平均计算时间）。 线程生命周期 NEW：线程刚创建，尚未启动。 RUNNABLE：可运行状态（包含等待调度和已抢占到时间片两种情况）。 BLOCKED：获取锁被阻塞，仅限synchronized锁。 WAITING：无限期等待直到被唤醒。常见如：ReentrantLock使用LockSupport.park()来阻塞线程、wait()、join()。 TIMED_WAITING：限时等待，超时自动返回。 TERMINATED：线程执行完毕或异常退出。 原子类 FAQ run方法和start方法的区别？ ","permalink":"/knowledges/java/java-concurrency/","summary":"\u003cp\u003e\u003cimg alt=\"Java并发总览\" loading=\"lazy\" src=\"/images/Java%E5%B9%B6%E5%8F%91%E6%80%BB%E8%A7%88.png\"\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e原子性：一个或多个操作，要么全部执行完且中间不被任何干扰，要么一个都不执行。\u003c/li\u003e\n\u003cli\u003e可见性：一个线程对共享变量的修改，其他线程能立刻看到。\u003c/li\u003e\n\u003cli\u003e有序性：程序执行的顺序，按照代码的书写顺序来。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"java内存模型\"\u003eJava内存模型\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eJMM\u003c/strong\u003e 主要解决可见性、有序性，基本保证原子性操作。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cimg alt=\"JMM总览\" loading=\"lazy\" src=\"/images/JMM%E6%80%BB%E8%A7%88.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"jmm是什么\"\u003eJMM是什么\u003c/h3\u003e\n\u003cp\u003eJava内存模型（Java Memory Model），简称JMM，是一套抽象的规范。定义了多线程环境中共享变量的访问规则。\nJMM将内存划分为主内存和工作内存。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e主内存：所有线程共享，存放变量的正式值。\u003c/li\u003e\n\u003cli\u003e工作内存：每个线程私有，存放该线程用到的\u003cstrong\u003e主内存变量的副本\u003c/strong\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"JMM内存模型\" loading=\"lazy\" src=\"/images/JMM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"jmm解决什么问题\"\u003eJMM解决什么问题\u003c/h3\u003e\n\u003cp\u003eJMM解决可见性、有序性两个问题：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e可见性问题：CPU多级缓存与缓冲区。\u003c/li\u003e\n\u003cli\u003e有序性问题：编译器和处理器为了性能会进行指令重排序。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"解决可见性\"\u003e解决可见性\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e建立\u0026quot;强制刷新/失效\u0026quot;协议\u003c/strong\u003e。JMM规定，线程对变量的操作不能一直停留在工作内存里，必须在特定时刻同步到主内存。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003evolatile：\n\u003cul\u003e\n\u003cli\u003e写操作：新值必须立即刷新到主内存。\u003c/li\u003e\n\u003cli\u003e读操作：每次读取前强制从主内存重新加载，并让其他线程的副本实效。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003esynchronized：\n\u003cul\u003e\n\u003cli\u003e加锁后：必须清空工作内存中变量副本，强制从主内存重新加载。\u003c/li\u003e\n\u003cli\u003e解锁前：工作内存的修改必须全部刷新到主内存。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003efinal：\n\u003cul\u003e\n\u003cli\u003e只要构造期间没有让this引用逸出，构造完成后final字段的值对其他线程立刻可见，无需额外同步。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"解决有序性\"\u003e解决有序性\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e定义Happens-Before原则\u003c/strong\u003e。规定哪些操作不可重排序。\u003c/p\u003e\n\u003ch4 id=\"保证基本的原子性\"\u003e保证基本的原子性\u003c/h4\u003e\n\u003cp\u003eJMM只保证基本读写操作的原子性，除了long、double外，其他变量的单次读写操作都是原子的。复合操作必须通过锁或其他方式保证原子性。\u003c/p\u003e\n\u003ch3 id=\"jmm怎么实现\"\u003eJMM怎么实现\u003c/h3\u003e\n\u003cp\u003eJMM的规范是抽象的， 需要靠\u003cstrong\u003eJIT编译器插入内存屏障\u003c/strong\u003e、\u003cstrong\u003e处理器提供的硬件指令\u003c/strong\u003e来落地。\u003c/p\u003e\n\u003ch4 id=\"内存屏障\"\u003e内存屏障\u003c/h4\u003e\n\u003cp\u003eJIT在编译字节码时，会在关键位置插入四种内存屏障指令。\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e屏障类型\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e作用\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eLoadLoad\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e禁止屏障前后的读操作重排\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eStoreStore\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e禁止屏障前后的写操作重排\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eLoadStore\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e禁止屏障前的读与屏障后的写重排\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eStoreLoad\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e禁止屏障前的写与屏障后的读重排（最重，同时具备其他三者效果）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cul\u003e\n\u003cli\u003evolatile：\n\u003cul\u003e\n\u003cli\u003e写操作：写前插入 StoreStore，写后插入 StoreLoad。\u003c/li\u003e\n\u003cli\u003e读操作：读后插入 LoadLoad 和 LoadStore。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003esynchronized：使用字节码指令\u003ccode\u003emonitorenter\u003c/code\u003e 和 \u003ccode\u003emonitorexit\u003c/code\u003e触发内存屏障，保证临界区内的读写在锁释放后可见。\u003c/li\u003e\n\u003cli\u003efinal：在构造方法末尾插入 StoreStore屏障，保证final字段赋值不会与对象引用赋值被重排序。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"硬件指令\"\u003e硬件指令\u003c/h4\u003e\n\u003cp\u003e内存屏障主要通过CPU指令lock xxx实现。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e强制将当前CPU缓存刷新到主内存。\u003c/li\u003e\n\u003cli\u003e通过MESI缓存一致性协议，将其他CPU缓存中对应数据失效。\u003c/li\u003e\n\u003cli\u003e阻止处理器对lock前后的指令进行重排序。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"volatile\"\u003evolatile\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003evolatile\u003c/strong\u003e 最轻量的同步机制，通过写前 StoreStore + 写后 StoreLoad、读后 LoadLoad + LoadStore 的内存屏障策略，保证多线程环境下的可见性和有序性，但不保证原子性。\u003c/p\u003e","title":"Java并发"},{"content":"Java集合总览 Collection 单列集合 Collection 是单值存储的根接口，继承自Iterable接口，表示一组元素的集合。\nList 特点：有序，可重复\nArrayList 数据结构：动态数组。 特点： 查询快：数组在内存中是连续的，可以通过索引下标计算出内存地址，所以查询快，时间复杂度为：O(1)。 写入慢： 在数组首部添加元素：需要移动其他元素，时间复杂度为：O(n)。 在数组尾部添加元素：直接加入到数组末尾，可能伴随扩容，时间复杂度均摊下来为：O(1)。 扩容机制：初始化时仅构造空数组，在第一次执行add操作时，将数组大小扩容为默认大小：10。当插入元素等于当前数组容量时，扩容为原有数组的1.5倍。 是否线程安全：否。 适用场景：适合查询多、写入少的场景。 LinkedList 数据结构：双向链表。 特点： 写入快：通过节点的前驱和后继指针关联节点，无需移动其他元素。时间复杂度：O(1)。 查询慢：需遍历链表逐个查询。 是否线程安全：否。 适用场景：适合操作元素多、查询少的场景。 Vector 数据结构：动态数组。 是否线程安全：是。对所有方法增加synchronized关键字，实现线程安全。 Set 特点：无序，不可重复。\nHashSet 数据结构：基于HashMap实现，使用HashMap的key作为数据存储，value为不可变的Object对象。 是否线程安全：否。 LinkedHashSet 数据结构：继承于HashSet，使用HashSet中的特殊构造方法，基于LinkedHashMap实现。 是否线程安全：否。 TreeSet 数据结构：基于TreeMap实现，底层实现为红黑树。 特点：有序，唯一。 是否线程安全：否。 Queue 数据结构：队列。 特点：通常遵循FIFO（先进先出）原则，但也有支持按优先级排序或双端操作的变体。 是否线程安全：大部分非线程安全，但也有线程安全的队列，如：BlockingQueue。 Map 双列集合 双列集合的根接口，用于存储键值对。每个键最多映射到一个值，键不允许重复。（通过 equals 和 hashcode 判断）。，用于存储键值对。每个键最多映射到一个值，键不允许重复。（通过equals和hashcode判断）。\nHashMap 数据结构： JDK1.7：数组+链表，时间复杂度：O(n)。 JDK1.8：数据+链表+红黑树，时间复杂度：O(logn)。 特点：存储键值对，允许一个null键，允许多个null值。 写入流程： 扩容判断：判断数组是否为空，为空则进行扩容。 哈希计算：通过hashcode+扰动函数： (key == null) ? 0 : (h = key.hashCode()) ^ (h \u0026gt;\u0026gt;\u0026gt; 16)，计算插入元素key的哈希值，在通过hash\u0026amp;(n-1)确定桶位置。 写入数据： 桶内没有元素：创建新节点，插入键值对。 桶内有元素（哈希冲突）：通过equals和hashcode方法判断桶内的首个节点是否与key相同。 相同：直接更新对应值。 不相同： 节点是树节点：在红黑树中插入键值对。 节点不是树节点：在链表插入键值对。 扩容判断：判断实际存储的键值对是否超过了当前容量，超过则扩容。 扩容机制：初始化时默认负载因子为：0.75，第一次执行put操作时，将容量扩充为默认大小：16。当插入元素超过 当前容量x负载因子 时，扩容为原有容量的2倍。 树化时机：参考泊松分布，当单个桶中链表的长度大于8，且数组容量大小大于64时，链表会转换为红黑树，查询时间复杂度由O(n)降至O(logn)。当红黑树节点数少于6时，退化为链表。 重新计算索引：扩容时会触发哈希重算，元素的位置为 原桶位置 或 原桶位置+原桶容量。因为HashMap容量值为2次幂，计算桶位置是通过 hash \u0026amp; oldCapacity。如果元素的hash值高位为1，则位置变化为原桶位置+原桶容量。如果元素的hash值高位为0，则位置不变化。 是否线程安全：否。 LinkedHashMap 数据结构：数组+双向链表+红黑树。基于HashMap实现，增加了双向链表。 是否线程安全：否。 TreeMap 数据结构：红黑树。 特点：有序。 是否线程安全：否。 ConcurrentHashMap 数据结构： JDK1.7：Segment数组 + HashEntry链表。 JDK1.8：Node数组+链表+红黑树。 特点：线程安全，高性能。 并发控制： JDK1.7：分段锁。将桶分为多个段（Segment），每个段是一个独立的可重入锁（ReentrantLock）。每个段包含一个HashEntry数组，HashEntry为链表结构。 JDK1.8：CAS+synchronized。 计算哈希值：对 key 进行哈希运算，以定位到数组中的相应桶（位置）。 空桶处理：若桶为空，通过CAS插入新Node节点。 非空桶处理：通过synchronized锁住第一个Node节点，表示有线程在操作这个桶。 插入数据： 存在相同key：直接更新对应值。 不存在相同key：在链表或红黑树中新建Node节点插入键值对。 释放第一个Node节点。 扩容机制： 多线程并发扩容：每个线程负责一段桶区间，迁移桶时锁住头节点，迁移过的桶标记为ForwardingNode节点。其他线程遇到该节点会协助或跳过。 无锁读取：遇到ForwardingNode节点则路由到新桶中。 ConcurrentHashMap数据结构 - JDK1.7：\nConcurrentHashMap │ ├── Segment[] segments (默认容量 16) │ │ │ ├── Segment[0] ────┐ │ │ │ (持有 ReentrantLock) │ │ ▼ │ │ ┌─────────────────┐ │ │ │ HashEntry[] │ (桶数组) │ │ │ [0] → null │ │ │ │ [1] → HashEntry(key1, val1) → HashEntry(key2, val2) → null │ │ │ [2] → null │ │ │ │ ... │ │ │ └─────────────────┘ │ │ │ ├── Segment[1] ────┐ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ HashEntry[] │ │ │ │ [0] → null │ │ │ │ ... │ │ │ └─────────────────┘ │ │ │ └── Segment[15] ──┐ │ ▼ │ ┌─────────────────┐ │ │ HashEntry[] │ │ │ ... │ │ └─────────────────┘ │ └── 并发控制：每个 Segment 独立锁，写操作锁住整个 Segment ConcurrentHashMap数据结构 - JDK1.8：\nConcurrentHashMap │ ├── Node[] table (长度初始 16，2 的幂次扩容) │ │ │ ├── [0] → null │ │ │ ├── [1] ──→ 链表结构（头节点被 synchronized 锁） │ │ ┌────────────┐ ┌────────────┐ │ │ │ Node(keyA) │ ──→ │ Node(keyB) │ ──→ null │ │ │ valA │ │ valB │ │ │ │ next ──────┘ │ next = null│ │ │ └────────────┘ └────────────┘ │ │ ↑ │ │ └── 当对该桶写操作时，锁住这个头节点 │ │ │ ├── [2] → null │ │ │ ├── [3] ──→ 红黑树结构（链表长度 ≥ 8 且数组长度 ≥ 64） │ │ ┌─────────────┐ │ │ │ TreeNode │ │ │ │ (root) │ │ │ │ left/right│ │ │ └─────────────┘ │ │ │ ├── [4] ──→ ForwardingNode (扩容时标记，指向新 table) │ │ │ └── ... │ ├── 辅助控制变量：sizeCtl (扩容阈值、初始化控制) │ └── 并发机制： - 读操作：无锁（Node 的 value/next 为 volatile） - 空桶写操作：CAS 放入新 Node - 非空桶写操作：synchronized 锁桶的头节点（细粒度） - 扩容：多线程并行迁移数据（每个线程处理一段桶区间） FAQ 普通数据和ArrayList的区别？ 数组容量： 普通数组：创建后数组长度固定。 ArrayList：长度可变。 元素类型： 普通数组：可存储基本类型或引用类型，但不能混用。 ArrayList：只支持存储引用类型，基本类型会自动装箱。 泛型支持： 普通数组：不支持。 ArrayList：支持。 ArrayList和LinkedList的区别？ 数据结构： ArrayList：动态数组。 LinkedList：双向链表。 访问效率： ArrayList：O(1)。 LinkedList：O(n)。 增删效率： ArrayList：操作尾部元素，约等于O(1)。其他情况需要移动元素，O(n)。 LinkedList：O(1)。 内存占用：LinkedList \u0026gt; ArrayList，LinkedList除了存储数据，还要存储前驱指针和后继指针。 ArrayList和Vector的区别？ 线程安全： ArrayList：线程不安全。 Vector：线程安全。 性能：ArrayList \u0026gt; Vector，Vector中使用synchronized保证线程安全。 扩容： ArrayList：1.5倍。 Vector：2倍。 HashSet和LinkedHashSet的区别？ 数据结构： HashSet：基于HashMap实现。 LinkedHashSet：继承自HashSet，使用HashSet中的构造方法，基于LinkedHashMap实现。 元素顺序： HashSet：无序。 LinkedHashSet：按插入顺序排序。 内存占用：LinkedHashSet \u0026gt; HashSet，LinkedHashSet需要额外维护前驱指针和后继指针。 是否允许null：两者都允许一个null键，一个null值（因不可重复，所以只允许一个null值）。 线程安全：两者都是线程不安全的。 JDK1.7和JDK1.8 中 HashMap 的底层实现有什么区别? 数据结构： JDK1.7：数组+链表。 JDK1.8：数组+链表+红黑树。 插入方式： JDK1.7：头插法。（多线程扩容容易形成环形链表） JDK1.8：尾插法。（解决了环形链表问题） 哈希重算： JDK1.7：需要重新计算每个元素的索引，用新数组长度取模。 JDK1.8：利用2次幂进行位运算，决定节点是去原位还是高位，避免了重复计算。 初始化时机： JDK1.7：构造方法时初始化。 JDK1.8：第一次put时初始化。 为什么用红黑树而不是平衡二叉树（AVL）？ AVL 树追求严格平衡，查询性能略优于红黑树，但插入和删除时旋转次数更多。HashMap 场景下插入和查找都频繁，红黑树牺牲少量查询理论性能，换来更少的旋转和更好的插入/删除效率，整体成本更低。此外红黑树的实现在大规模并发库中更通用，维护成本可控。\nHashMap和Hashtable的区别？ 线程安全： HashMap：线程不安全。 Hashtable：线程安全。使用synchronized保证线程安全。 是否允许null： HashMap：允许一个null键，多个null值。 Hashtable：不允许null键和null值。对null会抛出空指针异常。 扩容： HashMap：第一次put时初始化为16。每次扩容为原来的2倍。 Hashtable：使用构造方法初始化为11。每次扩容为原来的2n+1。 数据结构优化： HashMap：数组+链表+红黑树。 Hashtable：数组+链表。不会转红黑树。 HashMap和ConcurrentHashMap的区别？ 线程安全： HashMap：线程不安全。 ConcurrentHashMap：线程安全。 是否允许null值： HashMap：允许一个null键，多个null值。 ConcurrentHashMap：不允许有null键、null值。（在并发情况下，无法区分是没有对应key，还是对应key值为null）。 HashMap在JDK1.7为什么会发生死循环？ JDK1.7中，HashMap采用头插法插入元素，在扩容时会遍历每个桶中的链表，插入到新链表中，这个过程会导致链表反转。如果在并发情况下，可能会产生环形链表。 示例：\n扩容前：A-\u0026gt;B-\u0026gt;null 线程t1开始扩容：记录A.next=B；线程暂停。 线程t2开始扩容：B-\u0026gt;A-\u0026gt;null；线程执行完毕。 线程t1继续扩容：A-\u0026gt;B-\u0026gt;A-\u0026gt;null；形成环形链表。 ","permalink":"/knowledges/java/java-collections/","summary":"\u003ch2 id=\"java集合总览\"\u003eJava集合总览\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"Java集合总览\" loading=\"lazy\" src=\"/images/Java%E9%9B%86%E5%90%88%E6%80%BB%E8%A7%88.png\"\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"collection-单列集合\"\u003eCollection 单列集合\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eCollection\u003c/strong\u003e 是单值存储的根接口，继承自Iterable接口，表示一组元素的集合。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"list\"\u003eList\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e特点\u003c/strong\u003e：有序，可重复\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch4 id=\"arraylist\"\u003eArrayList\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：动态数组。\u003c/li\u003e\n\u003cli\u003e特点：\n\u003cul\u003e\n\u003cli\u003e查询快：数组在内存中是连续的，可以通过索引下标计算出内存地址，所以查询快，时间复杂度为：O(1)。\u003c/li\u003e\n\u003cli\u003e写入慢：\n\u003cul\u003e\n\u003cli\u003e在数组首部添加元素：需要移动其他元素，时间复杂度为：O(n)。\u003c/li\u003e\n\u003cli\u003e在数组尾部添加元素：直接加入到数组末尾，可能伴随扩容，时间复杂度均摊下来为：O(1)。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e扩容机制：初始化时仅构造空数组，在第一次执行add操作时，将数组大小扩容为默认大小：10。当插入元素等于当前数组容量时，扩容为原有数组的1.5倍。\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003cli\u003e适用场景：适合查询多、写入少的场景。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"linkedlist\"\u003eLinkedList\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：双向链表。\u003c/li\u003e\n\u003cli\u003e特点：\n\u003cul\u003e\n\u003cli\u003e写入快：通过节点的前驱和后继指针关联节点，无需移动其他元素。时间复杂度：O(1)。\u003c/li\u003e\n\u003cli\u003e查询慢：需遍历链表逐个查询。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003cli\u003e适用场景：适合操作元素多、查询少的场景。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"vector\"\u003eVector\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：动态数组。\u003c/li\u003e\n\u003cli\u003e是否线程安全：是。对所有方法增加synchronized关键字，实现线程安全。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"set\"\u003eSet\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e特点\u003c/strong\u003e：无序，不可重复。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch4 id=\"hashset\"\u003eHashSet\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：基于HashMap实现，使用HashMap的key作为数据存储，value为不可变的Object对象。\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"linkedhashset\"\u003eLinkedHashSet\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：继承于HashSet，使用HashSet中的特殊构造方法，基于LinkedHashMap实现。\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"treeset\"\u003eTreeSet\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：基于TreeMap实现，底层实现为红黑树。\u003c/li\u003e\n\u003cli\u003e特点：有序，唯一。\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"queue\"\u003eQueue\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：队列。\u003c/li\u003e\n\u003cli\u003e特点：通常遵循FIFO（先进先出）原则，但也有支持按优先级排序或双端操作的变体。\u003c/li\u003e\n\u003cli\u003e是否线程安全：大部分非线程安全，但也有线程安全的队列，如：BlockingQueue。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"map-双列集合\"\u003eMap 双列集合\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e双列集合\u003c/strong\u003e的根接口，用于存储键值对。每个键最多映射到一个值，键不允许重复。（通过 equals 和 hashcode 判断）。，用于存储键值对。每个键最多映射到一个值，键不允许重复。（通过equals和hashcode判断）。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"hashmap\"\u003eHashMap\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：\n\u003cul\u003e\n\u003cli\u003eJDK1.7：数组+链表，时间复杂度：O(n)。\u003c/li\u003e\n\u003cli\u003eJDK1.8：数据+链表+红黑树，时间复杂度：O(logn)。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e特点：存储键值对，允许一个null键，允许多个null值。\u003c/li\u003e\n\u003cli\u003e写入流程：\n\u003cul\u003e\n\u003cli\u003e扩容判断：判断数组是否为空，为空则进行扩容。\u003c/li\u003e\n\u003cli\u003e哈希计算：通过hashcode+扰动函数： \u003ccode\u003e(key == null) ? 0 : (h = key.hashCode()) ^ (h \u0026gt;\u0026gt;\u0026gt; 16)\u003c/code\u003e，计算插入元素key的哈希值，在通过\u003ccode\u003ehash\u0026amp;(n-1)\u003c/code\u003e确定桶位置。\u003c/li\u003e\n\u003cli\u003e写入数据：\n\u003cul\u003e\n\u003cli\u003e桶内没有元素：创建新节点，插入键值对。\u003c/li\u003e\n\u003cli\u003e桶内有元素（哈希冲突）：通过equals和hashcode方法判断桶内的首个节点是否与key相同。\n\u003cul\u003e\n\u003cli\u003e相同：直接更新对应值。\u003c/li\u003e\n\u003cli\u003e不相同：\n\u003cul\u003e\n\u003cli\u003e节点是树节点：在红黑树中插入键值对。\u003c/li\u003e\n\u003cli\u003e节点不是树节点：在链表插入键值对。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e扩容判断：判断实际存储的键值对是否超过了当前容量，超过则扩容。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e扩容机制：初始化时默认负载因子为：0.75，第一次执行put操作时，将容量扩充为默认大小：16。当插入元素超过 当前容量x负载因子 时，扩容为原有容量的2倍。\n\u003cul\u003e\n\u003cli\u003e树化时机：参考泊松分布，当单个桶中链表的长度大于8，且数组容量大小大于64时，链表会转换为红黑树，查询时间复杂度由O(n)降至O(logn)。当红黑树节点数少于6时，退化为链表。\u003c/li\u003e\n\u003cli\u003e重新计算索引：扩容时会触发哈希重算，元素的位置为 原桶位置 或 原桶位置+原桶容量。因为HashMap容量值为2次幂，计算桶位置是通过 hash \u0026amp; oldCapacity。如果元素的hash值高位为1，则位置变化为原桶位置+原桶容量。如果元素的hash值高位为0，则位置不变化。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"linkedhashmap\"\u003eLinkedHashMap\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：数组+双向链表+红黑树。基于HashMap实现，增加了双向链表。\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"treemap\"\u003eTreeMap\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：红黑树。\u003c/li\u003e\n\u003cli\u003e特点：有序。\u003c/li\u003e\n\u003cli\u003e是否线程安全：否。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"concurrenthashmap\"\u003eConcurrentHashMap\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：\n\u003cul\u003e\n\u003cli\u003eJDK1.7：Segment数组 + HashEntry链表。\u003c/li\u003e\n\u003cli\u003eJDK1.8：Node数组+链表+红黑树。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e特点：线程安全，高性能。\u003c/li\u003e\n\u003cli\u003e并发控制：\n\u003cul\u003e\n\u003cli\u003eJDK1.7：分段锁。将桶分为多个段（Segment），每个段是一个独立的可重入锁（ReentrantLock）。每个段包含一个HashEntry数组，HashEntry为链表结构。\u003c/li\u003e\n\u003cli\u003eJDK1.8：CAS+synchronized。\n\u003cul\u003e\n\u003cli\u003e计算哈希值：对 key 进行哈希运算，以定位到数组中的相应桶（位置）。\u003c/li\u003e\n\u003cli\u003e空桶处理：若桶为空，通过CAS插入新Node节点。\u003c/li\u003e\n\u003cli\u003e非空桶处理：通过synchronized锁住第一个Node节点，表示有线程在操作这个桶。\u003c/li\u003e\n\u003cli\u003e插入数据：\n\u003cul\u003e\n\u003cli\u003e存在相同key：直接更新对应值。\u003c/li\u003e\n\u003cli\u003e不存在相同key：在链表或红黑树中新建Node节点插入键值对。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e释放第一个Node节点。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e扩容机制：\n\u003cul\u003e\n\u003cli\u003e多线程并发扩容：每个线程负责一段桶区间，迁移桶时锁住头节点，迁移过的桶标记为ForwardingNode节点。其他线程遇到该节点会协助或跳过。\u003c/li\u003e\n\u003cli\u003e无锁读取：遇到ForwardingNode节点则路由到新桶中。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eConcurrentHashMap数据结构 - JDK1.7：\u003c/p\u003e","title":"Java集合"},{"content":"\nJVM是什么 JVM是Java虚拟机，解决的核心问题是一次编写，到处运行。JVM通过在操作系统之上建立一个抽象的计算机，让字节码文件可以跨平台执行。\nJVM组成结构 类加载器：加载字节码文件到内存，进行加载、链接、初始化操作，生成对应的Class对象。 运行时数据区：JVM的内存管理区域，包含共享区域（堆、方法区）、线程私有区域（虚拟机栈、本地方法栈、程序计数器）。 执行引擎：将字节码翻译为操作系统可以执行的机器指令，包含解释器、JIT编译器两种方式。 本地方法接口（JNI）：一套标准接口，允许Java代码调用c/c++等语言实现的本地方法。 本地方法库：用c/c++等编写并编译好的动态链接库，提供Java无法直接完成的系统级操作，通过JNI被JVM加载和调用。 类加载机制 一个类的生命周期：加载 -》 验证 -》准备 -》解析》初始化 -》使用 -》卸载\n双亲委派模型是类加载机制的核心，当一个类加载器收到加载请求时，自己不先尝试加载，而是逐级向上委派给父加载器，直到最顶层的Bootstrap ClassLoader。只有父加载器无法加载该类时，子加载器才尝试加载。\n启动类加载器：Bootstrap ClassLoader。 扩展类加载器：Extension ClassLoader。 应用类加载器：Application ClassLoader。 为什么需要双亲委派模型？\n安全：保证Java核心类库由启动类加载器加载，避免被篡改。 唯一：优先交给父加载器加载，避免重复加载。 如何打破双亲委派模型？ 通过自定义类加载器，打破\u0026quot;父加载器无法直接加载子加载器可见类\u0026quot;。 实现方式：每个线程都有一个contextClassLoader，默认是应用类加载器。核心库的代码通过Thread.currentThread().getContextClassLoader()拿到线程上下文加载器，然后用它来加载子类，从而打破\u0026quot;父加载器无法直接加载子加载器可见类\u0026quot;。\n类的生命周期：\n加载：根据全限定名找到字节码，在堆中生成Class对象。 验证：检查字节码是否安全合规，防止恶意代码。 准备：为静态变量分配内存并赋予默认值。普通静态变量设置为0、null或false，编译器静态变量（static final修饰的基本类型或String类型）设置为代码中指定值。 解析：把符号引用转成能直接定位的内存引用，如类引用、方法引用等。 初始化：执行类的构造器方法，为静态变量赋值、执行静态代码块。 使用：程序通过Class对象创建实例或调用方法。 卸载：该类的Class对象被回收，方法区数据清除。 运行时数据区 运行时数据区是JVM的内存管理区域，规定了程序在运行时的数据该放在哪里、由谁共享、何时创建销毁等。\n堆 线程安全：线程共享\nJVM中最大的一块内存，几乎所有对象实例都在此分配。从GC视角，堆采用分代设计，将内存分为：\n新生代：包含Eden区和两个Survivor区，比例为：8:1:1。绝大多数对象诞生在Eden区，熬过垃圾回收的对象会晋升到Survivor区，再从Survivor区晋升到老年代（每熬过一次Minor GC，则对象的GC年龄+1，达到默认值15时，晋升到老年代）。 TLAB：线程本地分配缓冲区，全称：Thread Local Allocation Buffer。TLAB是JVM为加速多线程下对象分配而设计的核心优化，在Eden区为每个线程划分一块独享空间，让线程在自己的独享空间内分配内存，与其他线程互不干扰。 老年代：存放长期存活对象，如缓存、数据库连接池等。 因为大部分对象都\u0026quot;朝生夕死\u0026quot;，所以在不同生命周期的对象采用不同的垃圾回收算法。新生代大部分对象生命周期较短，适合标记复制算法。老年代对象生命周期相对较长，适合标记整理或标记清除算法。\n对象分配流程：\n线程创建对象，先检查TLAB剩余空间是否足够，足够则直接在TLAB内指针碰撞，完成分配。 若TLAB剩余空间不够： 申请新TLAB分配：申请一块更大的TLAB，在新TLAB分配。 在Eden区分配：对象大小适中，但新申请TLAB不划算，则在Eden区的公共区域通过同步操作分配。 在老年区分配：对象较大，则跳过Eden区，直接在老年代分配，避免在新生代频繁复制。 方法区 线程安全：线程共享\n存储类的元数据、运行时常量池、静态变量、JIT编译后的代码缓存等。\n类的元数据：存放类的全限定名、字段描述、方法描述等。 运行时常量池：存放字面量和符号引用。（动态链接就靠它） 静态变量：static修饰的变量。从JDK1.7起，字符串常量池和静态变量从原来的方法区移动到了堆中，但逻辑上还是方法区。 JIT编译后的代码缓存：热点方法被JIT编译称本地机器码后，缓存在方法区。 演变历史：\nJDK1.7：在堆内实现为永久代，容易OOM。 JDK1.8：改为使用本地内存的元空间，可以直接使用本地系统内存，仅受物理内存限制。同时，原来在永久代的字符串常量池和静态变量转移到了堆中。 虚拟机栈 线程安全：线程私有\n每个线程创建时，JVM会为其分配一个私有的虚拟机栈，内部由一个个栈帧组成。每个方法被执行时，都会创建一个栈帧入栈，方法结束后出栈。\n栈帧的结构：\n局部变量表：存放方法参数和局部变量，以槽位Slot为最小单位。 操作数栈：一个后进先出的栈，用于计算的临时工作区。例如计算 int a = 1+2; 先压入1、2，执行iadd指令相加，结果放回栈顶，再存入局部变量表。 动态链接：指向方法区中的运行时常量池对该方法的符号引用，并替换为直接引用。（多态） 方法返回地址：记录方法调用后的下一条指令地址，正常退出或异常退出都要恢复调用者现场。 栈常见错误：\nStackOverFlowError：栈溢出，通常由于方法递归深度太大导致。 OutOfMemoryError：栈无法申请足够内存时。 程序计数器 线程安全：线程私有\n程序计数器是当前线程所执行的字节码行号指示器，CPU时间片在各线程间切换，每个线程都有自己的\u0026quot;进度条\u0026quot;。\n当线程正在执行一个Java方法，程序计数器记录的是当前字节码指令的地址。 当线程正在执行一个Native方法，程序计数器的值为空。 为什么需要程序计数器？\n流程控制：字节码指令执行时，解释器根据计数器取下一行指令。 线程恢复：多线程抢占CPU时，任何线程被挂起后再恢复，是程序计数器来告诉线程\u0026quot;上一次执行到哪一行\u0026quot;的。 异常处理：抛出异常时，JVM通过当前计数器位置查找异常表，确定该在哪一行catch。 本地方法栈 线程安全：线程私有\n本地方法栈是为Native方法调用服务的栈，它让c、c++代码能在JVM内部安全运行。通过JNI，Java可以调用c、c++实现的函数，而本地方法栈就为这些Native方法提供栈环境。\n执行引擎 执行引擎是JVM的心脏，它负责把字节码翻译成操作系统能执行的机器指令并运行。但为了既快又灵活，采用了\u0026quot;解释执行+编译执行\u0026quot;混合驱动的设计。\n执行引擎组成部分：\n解释器：逐条将字节码翻译为本地机器指令并执行。类比：同声传译。 JIT编译器：将热点代码一次性编译成本地机器码，后续直接运行。类比：翻译后的文字稿。 垃圾回收器：严格说垃圾回收器也属于执行引擎的一部分，负责自动内存回收。 为什么要用解释器+JIT编译器混合模式？ 为了找到一种性能平衡，所以采用了混合模式：\n解释器：负责\u0026quot;预热\u0026quot;、\u0026ldquo;兜底\u0026rdquo;。程序刚启动时，快速响应。当JIT编译失败时，退回解释执行保证正确性。 JIT编译器：负责\u0026quot;加速\u0026quot;。收集到足够热点信息后，对真正的性能瓶颈进行深度优化。 这也是说Java是编译和解释共存的原因。 如何识别热点代码？ HotSpot采用热点探测， 主要基于计数器：\n方法调用计数器：记录每个方法被调用的次数。 回边计数器：记录循环体执行的次数。热点循环会触发OSR（栈上替换），将正在解释执行的循环替换为编译好的机器码。 需注意，为避免临时热点代码造成的资源浪费，计数器会随时间进行半衰减的\u0026quot;降温\u0026quot;，只保留高频使用的热点代码。 JIT编译器采用分层编译，C1快速预热+收集数据 -》C2精准优化。\nC1编译器：编译速度快，生成代码质量中等，适合需要快速处理的短任务。 C2编译器：编译耗时长，但会进行大量基于运行时信息的激进优化（如逃逸分析、方法内联）等，生成代码执行效率极高，适合长时间运行的服务器应用。 JVM垃圾回收 垃圾回收（Garbage Collection），简称GC，是JVM自动管理内存的机制。它会自动识别不再使用对象，并释放它们占用的空间。\nGC解决的核心问题 提高系统健壮性：把内存管理的负担从人交给机器，让程序不会因忘记释放内存而慢慢撑爆。 支撑\u0026quot;一次编写，到处运行\u0026quot;：内存管理交给JVM，是跨平台保障的一部分。 核心矛盾：GC时必须暂停用户线程，即STW。因为不能在一个正在变化的对象关系图上找垃圾。GC的进化史，都是在让STW时间更短、更可控。\n寻找垃圾的两种方式 引用计数法（Java不用）：每个对象有一个计数器，有引用则+1，引用失效则减1，引用为0则表示可以回收。但存在循环引用问题时会导致永远无法回收。 可达性分析（Java采用）：从一组GC Roots根对象出发，沿着引用链搜索。不在引用链上的对象，就是不可达对象，表示可以回收。 GC Roots对象：\n虚拟机栈（栈帧中局部变量表）引用的对象 方法区中静态变量引用的对象 方法区中常量引用的对象 本地方法栈中JNI引用的对象 所有被同步锁持有的对象 四种引用类型 不是所有引用都希望永远绑着对象不放，有时我们希望对象在内存紧张时可以被回收，有时可能只想跟踪对象是否被回收了。 这四种引用不是改变可达性分析的根，而是定义了引用强度的等级。\n引用类型 GC回收策略 何时回收 典型用途 强引用 永不回收 —— 最普通的对象引用 软引用 内存不足时回收 即将OOM 内存敏感的高速缓存 弱引用 GC时必定回收 下一次GC WeakHashMap、ThreadLocal 虚引用 任何时候，仅收通知 对象被回收时通知 管理堆外内存（DirectByteBuffer） 强引用： 常见的对象引用都是强引用，例如Object o = new Object。\n软引用：SoftReference 用SoftReference包裹对象，当系统内存充足时，GC不会回收。当内存不足即将OOM时，JVM会把软引用对象全部回收。\n弱引用：WeakReference 用WeakReference包裹对象，一旦GC发生，不管内存是否充足，弱引用指向的对象必定被回收。它的生命周期只存活到下一次GC前。\n典型用途：ThreadLocal中的ThreadLocalMap的key是ThreadLocal对象，是弱引用。 虚引用：PhantomReference 用PhantomReference包裹对象，get永远返回null。它的唯一作用是：当指向的对象被GC回收时，虚引用会被放入关联的ReferenceQueue中，起到通知作用。\n常见垃圾回收算法 标记-清除：先标记，再统一清除。缺点是会产生内存碎片。 标记-复制：把内存分两块，用一块，存活的对象复制到另一块。整体清空当前块。解决了内存碎片问题，但造成空间浪费。 标记-整理：标记后，让存活对象往一端移动，直接清理边界外的内存。 常见垃圾回收器 收集器 作用区域 算法 线程模型 核心目标 Serial 新生代 标记-复制 单线程 简单高效，Client模式首选 ParNew 新生代 标记-复制 多线程 Serial的多线程版，配合CMS Parallel Scavenge 新生代 标记-复制 多线程 吞吐量优先 Serial Old 老年代 标记-整理 单线程 配合Serial，CMS后备 Parallel Old 老年代 标记-整理 多线程 配合Parallel Scavenge CMS 老年代 标记-清除 多线程 最短停顿时间 G1 全堆 混合（复制+整理） 多线程 可预测的停顿时间 CMS CMS 是一款老年代垃圾收集器，核心目标是收集老年代的垃圾。在JDK9被废弃、JDK14彻底移除。\n核心原理：基于标记-清除算法，把最耗时的标记过程交给GC线程和用户线程并发执行，只在无法并发的关键节点短暂STW。\n执行阶段：\n阶段 STW？ 做什么 耗时 初始标记 是 只标记 GC Roots 直接关联的对象 极短 并发标记 否 从直接关联对象出发，遍历整个对象图，并通过卡表标记脏页。 长 并发预清理 否 提前扫描卡表脏页并尽量尽量等待一次Minor GC，为重新标记阶段减负。 长 重新标记 是 修正并发标记期间因用户线程运行而变动的标记 较短 并发清除 否 清理垃圾对象 长 CMS的三色标记法：\n白色：未被扫描过的对象，标记结束后，白色为可回收对象。 灰色：对象本身已被访问，但它引用的子对象还没扫描。 黑色：对象及其所有子引用全部扫描完毕，是确定存活的对象。 标记过程：\n在初始标记阶段，STW。从GC Roots出发，把直接关联的对象标记为灰色。 在并发标记阶段，从标记为灰色的对象出发，把自身标记为黑色。然后扫描它引用的对象，标记为灰色。如此，层层扫描。 因用户线程也在执行，CMS会通过写屏障+卡表记录哪些对象的引用发生了变化，在赋值指令后立即执行：找到卡页，并标记为脏页。写屏障是记录动作，卡表是记录结果。 并发预清理：主动扫描卡表中的脏页，把其中涉及的对象重新扫描和标记，尽可能的修复漏标。 可终止的并发预清理：尽量等待一次Minor GC，减少重新标记阶段需要扫描的新生代对象。 在重新标记阶段，STW。重新扫描 新生代中所有存活对象、GC Roots、卡表中所有仍为脏页的。 卡表介绍： 卡表是一个位图（bitmap），将整个堆内存按固定大小（通常为512字节）划分为卡页。每个卡页对应卡表中的一个位。当这个位置被标记为1，表示这个卡页脏了。\nG1 G1，Garbage-First，优先回收价值最高的垃圾，在JDK9成为默认垃圾回收器。追求目标是：在可控的停顿时间内，获得尽可能高的吞吐量，特别适合大堆。\nG1把整个堆分成大小相等的Region，默认约2048个，每块1-32MB，取2的幂次。 每个Region可以在不同时间扮演不同的角色：\nEden：新生代 Survivor：S0、S1 Old：老年代 Humongous：巨型对象，当一个对象超过Region的一半大小时，被视为巨型对象。 核心目标：G1会根据历史数据，推测回收一个Region需要的时间，然后在此次停顿时间内，只回收预估可以完成的价值最高的Region。可以通过-XX:MaxGCPauseMillis设定停顿时间。\n标记阶段：\n初始标记：伴随Minor GC，标记GC Roots直接关联对象，因为Minor GC会将新生代的活对象复制到Survivor区。所以就以Survivor Region作为根区间。 根区间：扫描所有Survivor Region，找出所有Survivor -》老年代的引用，为后续并发标记提供老年代入口。 并发标记：使用STAB快照，通过写前屏障将被删除的引用记录到STAB队列。全程并发，会产生浮动垃圾。 最终标记：处理STAB队列中剩余的引用变更，完成最终标记修正。STW极短。 STAB：开始标记前，先拍个快照。并发标记期间只要有引用断开，就把断开前指向的那个对象记下来并标记为灰色。宁可多留一些浮动垃圾，也绝不错回收。\nRSet：Remembered Set，是每个Region的备忘录，专门记录其他Region中的哪些卡页引用了本Region内的对象。把这些跨Region的引用维护好，在回收一个Region时，只扫描与他相关的其他Region即可，而不是整个堆。\n回收类型：\nMinor GC：当Eden区满后，把活对象复制到新的urvivor或晋升到老年代Region。 Mixed GC：当老年代堆占用达45%后，G1会启动一个并发标记周期，计算出每个老年代Region的垃圾比例，然后选取垃圾最多的老年代Region和所有年轻代Region一起回收。Mixed GC分多次进行，每次回收一小批。 Full GC：当对象分配过快，或巨型对象无法找到空间分配，则会退化为Serial Old单线程收集，会导致较长时间的STW。 GC调优 FAQ 为什么说Java是解释和编译并存？ Java同时采用解释执行和编译执行（JIT即时编译），是一种性能优化平衡策略，保证启动速度和执行效率。\n解释器：逐行翻译字节码为机器码，并立即执行，不会保存结果。类比：同声传译。 编译器：针对热点代码进行一次性的完整翻译，并保存结果，后续直接复用。类比：翻译后的文字稿。 Java是如何识别热点代码的？ ","permalink":"/knowledges/java/jvm/","summary":"\u003cp\u003e\u003cimg alt=\"JVM组成部分\" loading=\"lazy\" src=\"/images/JVM%E7%BB%84%E6%88%90%E9%83%A8%E5%88%86.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"jvm是什么\"\u003eJVM是什么\u003c/h2\u003e\n\u003cp\u003eJVM是Java虚拟机，解决的核心问题是\u003cstrong\u003e一次编写，到处运行\u003c/strong\u003e。JVM通过在操作系统之上建立一个抽象的计算机，让字节码文件可以跨平台执行。\u003c/p\u003e\n\u003ch2 id=\"jvm组成结构\"\u003eJVM组成结构\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e类加载器：加载字节码文件到内存，进行加载、链接、初始化操作，生成对应的Class对象。\u003c/li\u003e\n\u003cli\u003e运行时数据区：JVM的内存管理区域，包含共享区域（堆、方法区）、线程私有区域（虚拟机栈、本地方法栈、程序计数器）。\u003c/li\u003e\n\u003cli\u003e执行引擎：将字节码翻译为操作系统可以执行的机器指令，包含解释器、JIT编译器两种方式。\u003c/li\u003e\n\u003cli\u003e本地方法接口（JNI）：一套标准接口，允许Java代码调用c/c++等语言实现的本地方法。\u003c/li\u003e\n\u003cli\u003e本地方法库：用c/c++等编写并编译好的动态链接库，提供Java无法直接完成的系统级操作，通过JNI被JVM加载和调用。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"类加载机制\"\u003e类加载机制\u003c/h3\u003e\n\u003cp\u003e一个类的生命周期：加载 -》 验证 -》准备 -》解析》初始化 -》使用 -》卸载\u003c/p\u003e\n\u003cp\u003e双亲委派模型是类加载机制的核心，当一个类加载器收到加载请求时，自己不先尝试加载，而是逐级向上委派给父加载器，直到最顶层的Bootstrap ClassLoader。只有父加载器无法加载该类时，子加载器才尝试加载。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e启动类加载器：Bootstrap ClassLoader。\u003c/li\u003e\n\u003cli\u003e扩展类加载器：Extension ClassLoader。\u003c/li\u003e\n\u003cli\u003e应用类加载器：Application ClassLoader。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e为什么需要双亲委派模型？\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e安全：保证Java核心类库由启动类加载器加载，避免被篡改。\u003c/li\u003e\n\u003cli\u003e唯一：优先交给父加载器加载，避免重复加载。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e如何打破双亲委派模型？\n通过自定义类加载器，打破\u0026quot;父加载器无法直接加载子加载器可见类\u0026quot;。\n实现方式：每个线程都有一个contextClassLoader，默认是应用类加载器。核心库的代码通过\u003ccode\u003eThread.currentThread().getContextClassLoader()\u003c/code\u003e拿到线程上下文加载器，然后用它来加载子类，从而打破\u0026quot;父加载器无法直接加载子加载器可见类\u0026quot;。\u003c/p\u003e\n\u003cp\u003e类的生命周期：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e加载\u003c/strong\u003e：根据全限定名找到字节码，在堆中生成\u003ccode\u003eClass\u003c/code\u003e对象。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e验证\u003c/strong\u003e：检查字节码是否安全合规，防止恶意代码。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e准备\u003c/strong\u003e：为静态变量分配内存并赋予默认值。普通静态变量设置为0、null或false，编译器静态变量（static final修饰的基本类型或String类型）设置为代码中指定值。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e解析\u003c/strong\u003e：把符号引用转成能直接定位的内存引用，如类引用、方法引用等。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e初始化\u003c/strong\u003e：执行类的构造器方法，为静态变量赋值、执行静态代码块。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e使用\u003c/strong\u003e：程序通过\u003ccode\u003eClass\u003c/code\u003e对象创建实例或调用方法。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e卸载\u003c/strong\u003e：该类的\u003ccode\u003eClass\u003c/code\u003e对象被回收，方法区数据清除。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"运行时数据区\"\u003e运行时数据区\u003c/h3\u003e\n\u003cp\u003e运行时数据区是JVM的内存管理区域，规定了程序在运行时的数据该放在哪里、由谁共享、何时创建销毁等。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"JVM运行时数据区\" loading=\"lazy\" src=\"/images/JVM-%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA.png\"\u003e\u003c/p\u003e\n\u003ch4 id=\"堆\"\u003e堆\u003c/h4\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e线程安全\u003c/strong\u003e：线程共享\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eJVM中最大的一块内存，几乎所有对象实例都在此分配。从GC视角，堆采用分代设计，将内存分为：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e新生代\u003c/strong\u003e：包含Eden区和两个Survivor区，比例为：8:1:1。绝大多数对象诞生在Eden区，熬过垃圾回收的对象会晋升到Survivor区，再从Survivor区晋升到老年代（每熬过一次Minor GC，则对象的GC年龄+1，达到默认值15时，晋升到老年代）。\n\u003cul\u003e\n\u003cli\u003eTLAB：线程本地分配缓冲区，全称：Thread Local Allocation Buffer。TLAB是JVM为加速多线程下对象分配而设计的核心优化，在Eden区为每个线程划分一块独享空间，让线程在自己的独享空间内分配内存，与其他线程互不干扰。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e老年代\u003c/strong\u003e：存放长期存活对象，如缓存、数据库连接池等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e因为大部分对象都\u0026quot;朝生夕死\u0026quot;，所以在不同生命周期的对象采用不同的垃圾回收算法。新生代大部分对象生命周期较短，适合标记复制算法。老年代对象生命周期相对较长，适合标记整理或标记清除算法。\u003c/p\u003e\n\u003cp\u003e对象分配流程：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e线程创建对象，先检查TLAB剩余空间是否足够，足够则直接在TLAB内指针碰撞，完成分配。\u003c/li\u003e\n\u003cli\u003e若TLAB剩余空间不够：\n\u003cul\u003e\n\u003cli\u003e申请新TLAB分配：申请一块更大的TLAB，在新TLAB分配。\u003c/li\u003e\n\u003cli\u003e在Eden区分配：对象大小适中，但新申请TLAB不划算，则在Eden区的公共区域通过同步操作分配。\u003c/li\u003e\n\u003cli\u003e在老年区分配：对象较大，则跳过Eden区，直接在老年代分配，避免在新生代频繁复制。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"方法区\"\u003e方法区\u003c/h4\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e线程安全\u003c/strong\u003e：线程共享\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e存储类的元数据、运行时常量池、静态变量、JIT编译后的代码缓存等。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e类的元数据\u003c/strong\u003e：存放类的全限定名、字段描述、方法描述等。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e运行时常量池\u003c/strong\u003e：存放字面量和符号引用。（动态链接就靠它）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e静态变量\u003c/strong\u003e：static修饰的变量。从JDK1.7起，字符串常量池和静态变量从原来的方法区移动到了堆中，但逻辑上还是方法区。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eJIT编译后的代码缓存\u003c/strong\u003e：热点方法被JIT编译称本地机器码后，缓存在方法区。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e演变历史：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eJDK1.7：在堆内实现为\u003cstrong\u003e永久代\u003c/strong\u003e，容易OOM。\u003c/li\u003e\n\u003cli\u003eJDK1.8：改为使用\u003cstrong\u003e本地内存的元空间\u003c/strong\u003e，可以直接使用本地系统内存，仅受物理内存限制。同时，原来在永久代的字符串常量池和静态变量转移到了堆中。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"虚拟机栈\"\u003e虚拟机栈\u003c/h4\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e线程安全\u003c/strong\u003e：线程私有\u003c/p\u003e","title":"JVM"},{"content":"MySQL是开源关系型数据库管理系统，使用客户端-服务端模型，支持可插拔存储引擎。\n整体架构 整体分为两层：Server层 和 存储引擎层。\nServer层：\n连接器：负责与客户端建立TCP连接，验证身份、权限，维持会话状态等。 分析器：词法分析、语法分析，验证SQL合法性。 优化器：优化SQL、索引选择等，选择最优执行计划。 执行器：调用执行引擎读写接口进行数据交互。 存储引擎层：\nInnoDB：默认引擎，支持ACID、行锁、MVCC、外键等。 MyISAM：早期默认引擎，不支持事务、表级锁等。 InnoDB引擎 Buffer Pool：数据与索引的缓存池。 Log Buffer：redo log的缓存。 Buffer Pool Buffer Pool 是 InnoDB 最大的内存区域，以 页（Page，默认 16KB） 为单位缓存数据。所有对数据的读写操作，都会优先经过它。\n它缓存什么？\n数据页与索引页：无论是聚簇索引还是二级索引，它们的 B+Tree 节点都会被加载到这里。 Change Buffer：当修改非唯一二级索引，而目标页不在 Buffer Pool 时，更改会先写到这里，等页被读入时再合并，减少随机 I/O。它物理上就占用 Buffer Pool 空间。 自适应哈希索引 (AHI)：InnoDB 自动对高频访问的 B+Tree 页构建哈希索引，加速等值查询。同样存在 Buffer Pool 中。 锁信息：行锁、表锁等内存数据结构也在此维护。 数据字典：表的元数据信息。 三大链表管理页的生命周期\nBuffer Pool 内部通过三张链表，精密管理所有缓存页的状态：\nFree 链表：存储\u0026quot;空闲页\u0026quot;，需要加载新页时，从这里取。 LRU 链表：存储\u0026quot;已被使用的页\u0026quot;，并按最近最少使用排序。InnoDB 将 LRU 链表分为 Young 区（热数据） 和 Old 区（冷数据）。新读入的页不会直接插入 Young 区头部，而是插入 Old 区头部。只有在 Old 区存活足够时间并被再次访问时，才会晋升到 Young 区。这有效防止了全表扫描把真正的热数据冲走。 Flush 链表：存储\u0026quot;脏页\u0026quot;（内存中被修改过，但还没刷入磁盘的页），按第一次变脏的时间排序。后台线程会按此链表顺序将脏页写入磁盘，并更新 LSN（日志序列号）。 Log Buffer Log Buffer 是一块独立于 Buffer Pool 的内存区域，专门缓存redo log 条目。\n为什么要独立？ redo log 是顺序写入的环形日志，和数据页的随机访问模式完全不同。独立出来可以避免跟数据页争抢 Buffer Pool 空间，也方便实现顺序写磁盘的高吞吐。 写盘时机：Log Buffer 中的数据在下面三种情况下刷到磁盘 redo 文件： 事务提交：innodb_flush_log_at_trx_commit = 1 时，每次提交都刷。 Log Buffer 半满：达到容量的 50%。 后台线程每秒刷盘。 大小：默认 16MB，对于写入密集的场景可适当调大，减少刷盘频率。 内存与磁盘的协同工作流 读请求：\n查询 Buffer Pool，命中则直接返回。 未命中，从磁盘读取数据页到 Buffer Pool 的空闲页（从 Free 链表取），然后返回。 写请求（以 UPDATE 为例）：\n先写 Log Buffer 记录一条 redo 条目。 修改 Buffer Pool 中的对应数据页，使其变成脏页，并加入 Flush 链表。 修改对应的 undo 页，同样产生 redo 条目，undo 页也变成脏页。 事务提交时，Log Buffer 中的 redo 刷盘，事务完成。脏页则留待后台线程择机刷盘，实现WAL（Write-Ahead Log）。 索引 InnoDB索引的数据结构为B+树，包含聚簇索引、非聚簇索引。\n聚簇索引：也叫主键索引，叶子节点存完整行数据。 非聚簇索引：也叫二级索引，叶子节点存储 索引列 + 主键值，通过双向链表连接不同叶子节点。当索引列+主键无法覆盖查询字段时，需要回到聚簇索引进行二次查询，称为回表查询。 核心概念：\n最左前缀：查询字段按照联合索引字段顺序查询。 覆盖索引：索引包含全部查询字段，无需回表查询。 索引下推：在存储引擎层提前过滤数据，减少回表查询次数。MySQL5.6+版本引入的索引下推。 前缀索引：对长字符串取前缀构建索引，节省空间。 为什么使用B+树，而不是B树？\n高度低、磁盘IO少、叶子双向链表支持范围查询和排序。 事务 事务是保证一组数据库操作，要么全部成功、要么全部失败。在MySQL中，事务是在存储引擎层中实现的。MyISAM引擎不支持事务，InnoDB支持事务。\n事务特性 ACID：\n原子性：undo log保障。 一致性：约束、锁、undo log 共同保障。 隔离性：锁、MVCC保障。 持久性：redo log保障。 隔离级别 读未提交：事务还未提交，就可能被其他事务看到。（直接返回记录上的最新值，不创建Read View） 读已提交：事务必须提交后，才能被其他事务看到。（每次执行SQL时创建Read View） 可重复读：事务执行过程中，看到的数据与事务启动时一致。（事务启动时创建Read View） 串行化：写加写锁，读加读锁，当出现读写锁冲突时，需要等上一个事务执行完成。（不创建Read View） 隔离级别 脏读 不可重复读 幻读 读未提交 可能 可能 可能 读已提交(RC) 避免 可能 可能 可重复读(RR)(默认) 避免 避免 部分避免 串行化 避免 避免 避免 脏读：一个事务读取到了另一个事务尚未提交的数据。 不可重复读：同一个事务内，两次读取同一行数据，值不一样。 幻读：同一个事务内，两次查询同一个范围的数据，行数不一样。 MVCC 实现原理：\n隐藏字段：每行记录有最新修改的事务ID：DB_TRX_ID 和 指向undo log的版本链：DB_ROLL_PTR。 undo log 版本链：更新时先写入undo log，记录旧版本，形成版本链。 Read View快照：包含当前活跃的事务ID列表，用来判断版本可见性。RR级别在事务第一次快照读时生成，之后复用。RC级别每次快照读都生成新的。 实际事务执行时，从根据当前活跃事务ID列表 及 当前数据行的最新事务ID，来判断数据可见性。\n当前读 update、delete、select xxx for update这类更新操作会读取最新的数据版本，称为当前读。 在执行更新操作时，会根据具体SQL判断是否需要加行锁、间隙锁、临键锁，防止其他事务更改当前行或在数据间隙插入数据。 在执行insert 操作时，会产生插入意向锁。插入意向锁与间隙锁互斥，需要等待间隙锁释放后再执行。\n行锁：\n间隙锁：\n临键锁：\n锁 层级 锁类型 本质 表级锁 1. 表锁 (Table Lock) 手动强制整表串行 2. 元数据锁 (MDL) 自动保护表结构不冲突 3. 意向锁 (IS/IX) 行锁前的声明，表级检查 4. 自增锁 (AUTO-INC) 保护自增主键不重复 行级锁 (InnoDB) 5. 行锁 (S/X) + 三种算法 并发控制的核心 6. 插入意向锁 INSERT 的排队信号 死锁：不是锁，是一种并发事务互相等待锁资源造成的循环等待现象。\nredo log \u0026amp; undo log \u0026amp; binlog redo log 和 undo log 是InnoDB存储引擎层独有的，物理记录数据页变更或旧数据。 binlog是Server层产生的，与引擎无关，逻辑记录SQL或行变化。 redo log redo log，叫做重做日志，是物理逻辑日志，用于记录\u0026quot;在某页偏移量做了某修改\u0026quot;，它记录了所有的数据页（包括undo log）的物理变更。\nWAL机制：先写日志，再修改buffer pool，脏页后台刷盘。循环写。\nundo log undo log，叫做回滚日志，是逻辑日志，记录行的旧版本。用于事务回滚和构建MVCC版本链，保证原子性和一致性。\nbinlog binlog，叫作归档日志，是Server层逻辑日志，记录SQL或行变化，用于主从复制和基于时间点的恢复。\n两阶段提交 两阶段提交，用于保证redo log 和binlog在事务提交时的一致性：\nprepare阶段：写入redo log，并标记prepare状态。 写binlog阶段：将本次变更写入binlog。 commit阶段：redo log标记为commit，事务完成。 崩溃恢复时，若redo log处于prepare阶段，且binlog有完整数据，则提交事务，否则回滚。\n","permalink":"/knowledges/database/mysql/","summary":"\u003cp\u003eMySQL是开源关系型数据库管理系统，使用客户端-服务端模型，支持可插拔存储引擎。\u003c/p\u003e\n\u003ch2 id=\"整体架构\"\u003e整体架构\u003c/h2\u003e\n\u003cp\u003e整体分为两层：Server层 和 存储引擎层。\u003c/p\u003e\n\u003cp\u003eServer层：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e连接器：负责与客户端建立TCP连接，验证身份、权限，维持会话状态等。\u003c/li\u003e\n\u003cli\u003e分析器：词法分析、语法分析，验证SQL合法性。\u003c/li\u003e\n\u003cli\u003e优化器：优化SQL、索引选择等，选择最优执行计划。\u003c/li\u003e\n\u003cli\u003e执行器：调用执行引擎读写接口进行数据交互。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e存储引擎层：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eInnoDB：默认引擎，支持ACID、行锁、MVCC、外键等。\u003c/li\u003e\n\u003cli\u003eMyISAM：早期默认引擎，不支持事务、表级锁等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"innodb引擎\"\u003eInnoDB引擎\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eBuffer Pool：数据与索引的缓存池。\u003c/li\u003e\n\u003cli\u003eLog Buffer：redo log的缓存。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"InnoDB内存结构\" loading=\"lazy\" src=\"/images/InnoDB%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84.png\"\u003e\u003c/p\u003e\n\u003ch4 id=\"buffer-pool\"\u003eBuffer Pool\u003c/h4\u003e\n\u003cp\u003eBuffer Pool 是 InnoDB 最大的内存区域，以 \u003cstrong\u003e页（Page，默认 16KB）\u003c/strong\u003e 为单位缓存数据。所有对数据的读写操作，都会优先经过它。\u003c/p\u003e\n\u003cp\u003e它缓存什么？\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e数据页与索引页\u003c/strong\u003e：无论是聚簇索引还是二级索引，它们的 B+Tree 节点都会被加载到这里。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eChange Buffer\u003c/strong\u003e：当修改非唯一二级索引，而目标页不在 Buffer Pool 时，更改会先写到这里，等页被读入时再合并，减少随机 I/O。它物理上就占用 Buffer Pool 空间。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e自适应哈希索引 (AHI)\u003c/strong\u003e：InnoDB 自动对高频访问的 B+Tree 页构建哈希索引，加速等值查询。同样存在 Buffer Pool 中。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e锁信息\u003c/strong\u003e：行锁、表锁等内存数据结构也在此维护。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e数据字典\u003c/strong\u003e：表的元数据信息。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e三大链表管理页的生命周期\u003c/p\u003e\n\u003cp\u003eBuffer Pool 内部通过三张链表，精密管理所有缓存页的状态：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eFree 链表\u003c/strong\u003e：存储\u0026quot;空闲页\u0026quot;，需要加载新页时，从这里取。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLRU 链表\u003c/strong\u003e：存储\u0026quot;已被使用的页\u0026quot;，并按最近最少使用排序。InnoDB 将 LRU 链表分为 \u003cstrong\u003eYoung 区（热数据）\u003c/strong\u003e 和 \u003cstrong\u003eOld 区（冷数据）\u003c/strong\u003e。新读入的页不会直接插入 Young 区头部，而是插入 Old 区头部。只有在 Old 区存活足够时间并被再次访问时，才会晋升到 Young 区。这有效防止了全表扫描把真正的热数据冲走。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFlush 链表\u003c/strong\u003e：存储\u0026quot;脏页\u0026quot;（内存中被修改过，但还没刷入磁盘的页），按第一次变脏的时间排序。后台线程会按此链表顺序将脏页写入磁盘，并更新 LSN（日志序列号）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"log-buffer\"\u003eLog Buffer\u003c/h3\u003e\n\u003cp\u003eLog Buffer 是一块独立于 Buffer Pool 的内存区域，专门缓存\u003cstrong\u003eredo log 条目\u003c/strong\u003e。\u003c/p\u003e","title":"MySQL"},{"content":"Redis是什么？ Redis是一个基于内存的、单线程驱动的、可持久化的数据结构服务器。\nRedis在微服务中的常见使用场景：\n缓存：加速数据查询，扛住读流量。 分布式锁：用Redission 解决超卖、重复执行。 排行榜/计数器：Sorted Set做实时排名，String做原子计数。 轻量消息队列：List当简单队列，Stream当可靠消费队列。 特征存储：AI场景下存向量、用户画像标签。 Redis为什么快？ 完全基于内存：没有磁盘IO瓶颈，读写都在纳秒级。 单线程执行命令： 在Redis 6.0前，所有命令都在主线程串行执行，没有上下文切换、锁竞争等问题。瓶颈本身不在CPU，而在网络IO。 在Redis 6.0后，引入多线程优化读写网络IO，但还是用单线程来执行命令。 IO多路复用：一种同步非阻塞的IO模型，允许单线程通过一个系统调用，同时监控多个文件描述符，当其中某些描述符就绪，内核会返回就绪列表，应用程序再逐个处理，而不会阻塞在某个未就绪的连接上。（好比一个柜员同时监控多位客户的状态，哪位客户准备好了，柜员就去处理。虽然是一个柜员，但柜员一直在工作） Redis的数据结构 String 数据结构：SDS（Simple Dynamic String） SDS优点：二进制安全，O(1)获取长度、预分配空间减少内存重分配、惰性释放、杜绝缓冲区溢出。 使用场景：缓存对象、计数器、分布式锁等。 List 数据结构：双向链表，有序、可重复。支持左右压入弹出。 使用场景：消息队列、最新列表、时间线。 Hash 数据结构：键值对集合 使用场景：存储用户属性、购物车。 Set 数据结构：无序、不可重复的集合。 使用场景：标签系统、共同好友、抽奖去重。 Sorted Set 数据结构：有序集合，每个成员关联一个score，按score排序，成员唯一。 使用场景：排行榜、延迟队列、带权重的实时队列。 Bitmaps 数据结构：操作位数组。 使用场景：签到、用户画像。 HyperLogLog 数据结构：概率算法 使用场景：基数统计。 Redis的数据过期策略 惰性删除：数据被访问时，检查是否需要清理。 定期删除：定时执行，随机抽取部分key检查是否需要清理。 Redis的数据淘汰策略 当内存满时，通过淘汰策略释放内存：\nnoeviction：拒绝写入。 allkeys-lru：所有key中，淘汰最近最少使用的。 volatile-lru：设置了过期的key中，淘汰最近最少使用的。 allkeys-lfu：所有key中，淘汰最不经常使用的。 volatile-lfu：设置了过期的key中，淘汰最不经常使用的。 volatile-ttl：设置了过期的key中，淘汰最接近过期时间的。 allkeys-random：所有key中，随机淘汰。 volatile-random：设置了过期的key中，随机淘汰。 Redis的数据持久化方式 RDB（Redis Database Backup）：定时快照，二进制紧凑，恢复快，但可能丢失最后一次快照之后的数据。 刷盘方式：fork子线程，将内存快照写入操作系统的页缓存，然后显式调用fsync()进行刷盘，同步到磁盘上。 AOF（Append Only File）：顺序追加记录每个写命令，可通过重放AOF命令来恢复数据。 always：每条命令立即刷盘，由主线程执行。 everysec：每秒刷一次，由后台线程执行。 no：不主动刷盘，由OS决定。 混合持久化：只在AOF重写时发生，子线程将当前内存数据用RDB形式写入AOF的文件开头，后续文件采用AOF追加写。 ┌────────────────────┐ │ RDB 二进制数据 │ ← 全量快照（重启时快速加载） ├────────────────────┤ │ 增量 AOF 文本 │ ← 快照时刻之后的写命令 └────────────────────┘ Redis\u0026amp;数据库常见问题 缓存雪崩 定义：某一时刻，大量缓存同时失效或不可用，导致大量请求直接打到数据库，使数据库压力骤增甚至崩溃。 原因：大量缓存设置了相同的过期时间、缓存服务器宕机 解决方式： 缓存过期时间增加随机值：对设置的缓存过期时间增加一定的随机值，避免大量缓存同时失效。 多级缓存：增加本地缓存，本地缓存没有时再查询分布式缓存。 限流降级：在缓存失效时，限制对后端系统的请求速率，并提供可降级能力。 缓存穿透 定义：查询一个缓存中不存在的数据，导致每次请求都查询数据库，使数据库压力变大。 原因：用户请求的数据在缓存和数据库都不存在。 解决方式： 缓存空结果：对于查询结果为空的数据，也进行缓存，设置一个较短的过期时间。 布隆过滤器：在缓存前增加布隆过滤器，快速判断请求是否可能存在，从而减少对数据库的压力。 参数校验：对请求参数进行校验，过滤无效请求。 缓存击穿 定义：某个热点数据在缓存过期的瞬间，大量请求访问该数据，导致请求直接打到数据库。 原因：热点数据过期时，大量请求并发访问。 解决方式： 热点数据永不过期：对于热点数据设置为永不过期，通过后台异步更新缓存。 提前更新缓存：在缓存即将过期时，提前触发缓存更新，确保缓存中的数据始终有效。 互斥锁：在缓存失效时，通过加分布式锁的方式，只允许一个请求区加载数据并更新缓存。 Redis集群 主从复制：\n特点：读写分离 数据分片：无 故障转移：无 适用场景：少量读扩展 哨兵模式：\n特点：监控节点状态、故障通知、自动故障转移。 数据分片：无 故障转移：有，当主节点故障时，哨兵会从 从节点 中选举新的主节点。 适用场景：中小规模、高可用 集群模式：\n特点：水平扩展、自动分片、无中心架构。 数据分片：有，默认16384个Slot，每个主节点负责一部分Slot。 故障转移：有，当主节点故障时，自动从 从节点 选举新的主节点。 适用场景：大规模分布式应用 Redis事务 Redis 事务提供了一种一次性、顺序性、排他性地执行多个命令的机制。它通过三个核心命令实现：\nMULTI：开启事务。 EXEC：执行事务队列中的所有命令。 DISCARD：取消事务。 WATCH：乐观锁，监视一个或多个键，如果这些键在事务执行前被改动，则事务中断。 本质：命令的批处理 + 执行期间的隔离保证，但不会回滚。基本上被lua脚本替代。\n","permalink":"/knowledges/database/redis/","summary":"\u003ch2 id=\"redis是什么\"\u003eRedis是什么？\u003c/h2\u003e\n\u003cp\u003eRedis是一个基于内存的、单线程驱动的、可持久化的数据结构服务器。\u003c/p\u003e\n\u003cp\u003eRedis在微服务中的常见使用场景：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e缓存：加速数据查询，扛住读流量。\u003c/li\u003e\n\u003cli\u003e分布式锁：用Redission 解决超卖、重复执行。\u003c/li\u003e\n\u003cli\u003e排行榜/计数器：Sorted Set做实时排名，String做原子计数。\u003c/li\u003e\n\u003cli\u003e轻量消息队列：List当简单队列，Stream当可靠消费队列。\u003c/li\u003e\n\u003cli\u003e特征存储：AI场景下存向量、用户画像标签。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"redis为什么快\"\u003eRedis为什么快？\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e完全基于内存\u003c/strong\u003e：没有磁盘IO瓶颈，读写都在纳秒级。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e单线程执行命令\u003c/strong\u003e：\n\u003cul\u003e\n\u003cli\u003e在Redis 6.0前，所有命令都在主线程串行执行，没有上下文切换、锁竞争等问题。瓶颈本身不在CPU，而在网络IO。\u003c/li\u003e\n\u003cli\u003e在Redis 6.0后，引入多线程优化读写网络IO，但还是用单线程来执行命令。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eIO多路复用\u003c/strong\u003e：一种同步非阻塞的IO模型，允许单线程通过一个系统调用，同时监控多个文件描述符，当其中某些描述符就绪，内核会返回就绪列表，应用程序再逐个处理，而不会阻塞在某个未就绪的连接上。（好比一个柜员同时监控多位客户的状态，哪位客户准备好了，柜员就去处理。虽然是一个柜员，但柜员一直在工作）\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"redis的数据结构\"\u003eRedis的数据结构\u003c/h2\u003e\n\u003ch3 id=\"string\"\u003eString\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：SDS（Simple Dynamic String）\n\u003cul\u003e\n\u003cli\u003eSDS优点：二进制安全，O(1)获取长度、预分配空间减少内存重分配、惰性释放、杜绝缓冲区溢出。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e使用场景：缓存对象、计数器、分布式锁等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"list\"\u003eList\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：双向链表，有序、可重复。支持左右压入弹出。\u003c/li\u003e\n\u003cli\u003e使用场景：消息队列、最新列表、时间线。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"hash\"\u003eHash\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：键值对集合\u003c/li\u003e\n\u003cli\u003e使用场景：存储用户属性、购物车。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"set\"\u003eSet\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：无序、不可重复的集合。\u003c/li\u003e\n\u003cli\u003e使用场景：标签系统、共同好友、抽奖去重。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"sorted-set\"\u003eSorted Set\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：有序集合，每个成员关联一个score，按score排序，成员唯一。\u003c/li\u003e\n\u003cli\u003e使用场景：排行榜、延迟队列、带权重的实时队列。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"bitmaps\"\u003eBitmaps\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：操作位数组。\u003c/li\u003e\n\u003cli\u003e使用场景：签到、用户画像。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"hyperloglog\"\u003eHyperLogLog\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e数据结构：概率算法\u003c/li\u003e\n\u003cli\u003e使用场景：基数统计。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"redis的数据过期策略\"\u003eRedis的数据过期策略\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e惰性删除：数据被访问时，检查是否需要清理。\u003c/li\u003e\n\u003cli\u003e定期删除：定时执行，随机抽取部分key检查是否需要清理。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"redis的数据淘汰策略\"\u003eRedis的数据淘汰策略\u003c/h2\u003e\n\u003cp\u003e当内存满时，通过淘汰策略释放内存：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003enoeviction：拒绝写入。\u003c/li\u003e\n\u003cli\u003eallkeys-lru：所有key中，淘汰最近最少使用的。\u003c/li\u003e\n\u003cli\u003evolatile-lru：设置了过期的key中，淘汰最近最少使用的。\u003c/li\u003e\n\u003cli\u003eallkeys-lfu：所有key中，淘汰最不经常使用的。\u003c/li\u003e\n\u003cli\u003evolatile-lfu：设置了过期的key中，淘汰最不经常使用的。\u003c/li\u003e\n\u003cli\u003evolatile-ttl：设置了过期的key中，淘汰最接近过期时间的。\u003c/li\u003e\n\u003cli\u003eallkeys-random：所有key中，随机淘汰。\u003c/li\u003e\n\u003cli\u003evolatile-random：设置了过期的key中，随机淘汰。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"redis的数据持久化方式\"\u003eRedis的数据持久化方式\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eRDB\u003c/strong\u003e（Redis Database Backup）：定时快照，二进制紧凑，恢复快，但可能丢失最后一次快照之后的数据。\n\u003cul\u003e\n\u003cli\u003e刷盘方式：fork子线程，将内存快照写入操作系统的页缓存，然后显式调用fsync()进行刷盘，同步到磁盘上。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAOF\u003c/strong\u003e（Append Only File）：顺序追加记录每个写命令，可通过重放AOF命令来恢复数据。\n\u003cul\u003e\n\u003cli\u003ealways：每条命令立即刷盘，由主线程执行。\u003c/li\u003e\n\u003cli\u003eeverysec：每秒刷一次，由后台线程执行。\u003c/li\u003e\n\u003cli\u003eno：不主动刷盘，由OS决定。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e混合持久化\u003c/strong\u003e：只在AOF重写时发生，子线程将当前内存数据用RDB形式写入AOF的文件开头，后续文件采用AOF追加写。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e┌────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e │   RDB 二进制数据    │  ← 全量快照（重启时快速加载）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├────────────────────┤\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   增量 AOF 文本     │  ← 快照时刻之后的写命令\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e└────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"redis数据库常见问题\"\u003eRedis\u0026amp;数据库常见问题\u003c/h2\u003e\n\u003ch3 id=\"缓存雪崩\"\u003e缓存雪崩\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e定义：某一时刻，大量缓存同时失效或不可用，导致大量请求直接打到数据库，使数据库压力骤增甚至崩溃。\u003c/li\u003e\n\u003cli\u003e原因：大量缓存设置了相同的过期时间、缓存服务器宕机\u003c/li\u003e\n\u003cli\u003e解决方式：\n\u003cul\u003e\n\u003cli\u003e缓存过期时间增加随机值：对设置的缓存过期时间增加一定的随机值，避免大量缓存同时失效。\u003c/li\u003e\n\u003cli\u003e多级缓存：增加本地缓存，本地缓存没有时再查询分布式缓存。\u003c/li\u003e\n\u003cli\u003e限流降级：在缓存失效时，限制对后端系统的请求速率，并提供可降级能力。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"缓存穿透\"\u003e缓存穿透\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e定义：查询一个缓存中不存在的数据，导致每次请求都查询数据库，使数据库压力变大。\u003c/li\u003e\n\u003cli\u003e原因：用户请求的数据在缓存和数据库都不存在。\u003c/li\u003e\n\u003cli\u003e解决方式：\n\u003cul\u003e\n\u003cli\u003e缓存空结果：对于查询结果为空的数据，也进行缓存，设置一个较短的过期时间。\u003c/li\u003e\n\u003cli\u003e布隆过滤器：在缓存前增加布隆过滤器，快速判断请求是否可能存在，从而减少对数据库的压力。\u003c/li\u003e\n\u003cli\u003e参数校验：对请求参数进行校验，过滤无效请求。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"缓存击穿\"\u003e缓存击穿\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e定义：\u003cstrong\u003e某个热点数据\u003c/strong\u003e在缓存过期的瞬间，大量请求访问该数据，导致请求直接打到数据库。\u003c/li\u003e\n\u003cli\u003e原因：热点数据过期时，大量请求并发访问。\u003c/li\u003e\n\u003cli\u003e解决方式：\n\u003cul\u003e\n\u003cli\u003e热点数据永不过期：对于热点数据设置为永不过期，通过后台异步更新缓存。\u003c/li\u003e\n\u003cli\u003e提前更新缓存：在缓存即将过期时，提前触发缓存更新，确保缓存中的数据始终有效。\u003c/li\u003e\n\u003cli\u003e互斥锁：在缓存失效时，通过加分布式锁的方式，只允许一个请求区加载数据并更新缓存。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"redis集群\"\u003eRedis集群\u003c/h2\u003e\n\u003cp\u003e主从复制：\u003c/p\u003e","title":"Redis"},{"content":"Spring是一个轻量级的Java开发框架，目标是让Java开发者能专注于开发逻辑。底层有两个核心特性：依赖注入（IOC）、面向切面编程（AOP）。\n依赖注入 IOC，全称 Inversion of Control，控制反转。不是 Spring 发明的，是一种设计原则。\n常见实现IOC的方式：\nService Locator：组件主动从容器中查找依赖，如：context.getBean(\u0026ldquo;xxx\u0026rdquo;); 这种方式对业务代码侵入性较高。 Dependency Injection（DI）：容器被动地将依赖注入给组件，组件完全不感知容器。这种方式对好处是：代码干净、依赖关系清晰、易于测试。 Spring选择了DI作为主要的IOC实现机制，但也保留了Service Locator的能力（ApplicationContextAware）。\nDI的三种注入方式及理解 构造器注入：\n@Service public class OrderService { private final OrderRepository repository; public OrderService(OrderRepository repository) { this.repository = repository; } } 优点：\n依赖不可变，声明为final，保证线程安全。 确保依赖不为null，编译时保证注入。 易于单元测试，直接通过构造期传入Mock。 缺点： 无法解决循环依赖。 依赖过多时，代码显得很臃肿。 Setter注入：\n@Service public class OrderService { private OrderRepository repository; @Autowired public void setRepository(OrderRepository repository) { this.repository = repository; } } 优点：\n可以解决循环依赖 保留有一定的封装性 缺点： 依赖可变，可能造成对象状态不稳定 不能保证注入不为null 字段注入：\n@Service public class OrderService { @Autowired private OrderRepository repository; } 优点：\n代码最简洁 缺点： 违背了不可变性原则，依赖不能设置为final 难以单元测试，通常需要反射或其他框架配合mock注入 Spring官方推荐使用构造器注入必须依赖，而Setter注入可选依赖。\nDI原理 注入过程：BeanDefinition注册 → 注入点元数据收集 → 依赖查找与解析 → 依赖写入\n在Spring中，每一个被管理的对象在诞生之前，都会被解析成一个BeanDefinition对象，它相当于Bean的图纸，包含：\n类的全限定名 作用域（单例、多例） 是否懒加载 依赖关系 构造器参数与属性值 自动装配模式（byType、byName） BeanDefinition的三种来源：\nXML中的bean配置，被XmlBeanDefinitionReader解析成BeanDefinition @Component、@Service等，被ClassPathBeanDefinitionScanner扫描并解析成BeanDefinition @Bean注解，被ConfigurationClassPostProcessor解析成BeanDefinition 这些解析器最终都会调用DefaultListableBeanFactory#registerBeanDefinition，将BeanDefinition存入beanDefinitionMap中，成为后续DI查找依赖的唯一注册表。 private final Map\u0026lt;String, BeanDefinition\u0026gt; beanDefinitionMap = new ConcurrentHashMap\u0026lt;\u0026gt;(256);\n后续getBean时，会获取或创建Bean。\n在实例化之后，属性填充之前，AutowiredAnnotationBeanPostProcessor将可能存在继承关系的BeanDefinition合并为完整定义，该后处理器一次性扫描所有注入点并缓存下来，封装为InjectionMetadata，形成后续属性填充时直接可用的\u0026quot;注入地图\u0026quot;。\n现在有了BeanDefinition蓝图、也有了Bean的所有注入点地图。下一步会进行属性填充，也就是执行真正的Bean注入。但在注入之前，还会做一件事：将能生成该Bean早期引用的ObjectFactory放入三级缓存singletonFactories中。这是解决循环依赖的核心。\n接下来是属性填充，真正进行依赖查找，此时若发生循环依赖，就能从三级缓存中拿到Bean的早期引用。 依赖查找有以下几个路径：\n处理特殊依赖，如BeanFactory、ApplicationContext等，直接返回容器自身，无需查找。 处理延迟注入，如ObjectFactory等，返回一个代理对象，真正获取Bean时调用其getObject方法才执行后续查找。 处理集合和数组，查找容器中类型匹配的所有Bean全部注入。 普通字段注入，优先按类型查找、@Qulifier指定、@Primary等，兜底用beanName查找，否则报错找不到bean。 最后是依赖注入，其中构造器注入在实例化阶段完成，Setter注入和字段注入在属性填充阶段完成。 DI注入完整流程图：\nflowchart TD A[容器启动 refresh] --\u0026gt; B[解析配置] B --\u0026gt; C[生成 BeanDefinition] C --\u0026gt; D[存入 beanDefinitionMap] D --\u0026gt; E[getBean 触发实例化] E --\u0026gt; F{是否构造器注入?} F --\u0026gt;|是| G[解析构造器参数] G --\u0026gt; H[调用 resolveDependency] H --\u0026gt; I{循环依赖?} I --\u0026gt;|是| J[抛出异常] I --\u0026gt;|否| K[Constructor.newInstance] K --\u0026gt; L[完成] F --\u0026gt;|否| M[无参构造 / 工厂方法] M --\u0026gt; N[实例化原始对象] N --\u0026gt; O[ObjectFactory 放入三级缓存] O --\u0026gt; P[收集注入点元数据] P --\u0026gt; Q[缓存 InjectionMetadata] Q --\u0026gt; R[populateBean 属性填充] R --\u0026gt; S[postProcessProperties] S --\u0026gt; T[遍历注入点] T --\u0026gt; U[调用 resolveDependency] U --\u0026gt; V{依赖类型} V --\u0026gt;|容器内建类型| V1[直接返回容器实例] V --\u0026gt;|延迟注入| V2[返回 ObjectFactory 代理] V --\u0026gt;|集合或数组| V3[查找全部匹配 Bean] V --\u0026gt;|单值依赖| W[findAutowireCandidates] W --\u0026gt; X[扫描 beanDefinitionMap 并 getBean] X --\u0026gt; Y{候选数量} Y --\u0026gt;|0| Y1[返回 null 或抛出异常] Y --\u0026gt;|1| Y2[返回唯一候选] Y --\u0026gt;|多个| Z[歧义消除] Z --\u0026gt; Z1{有 @Primary?} Z1 --\u0026gt;|是| Y2 Z1 --\u0026gt;|否| Z2{有 @Priority?} Z2 --\u0026gt;|是| Y2 Z2 --\u0026gt;|否| Z3{字段名匹配?} Z3 --\u0026gt;|是| Y2 Z3 --\u0026gt;|否| Z4[抛出 NoUniqueBeanDefinitionException] Y2 --\u0026gt; AA{注入方式} AA --\u0026gt;|字段注入| AA1[Field.set 反射赋值] AA --\u0026gt;|Setter 注入| AA2[Method.invoke 反射调用] AA1 --\u0026gt; AB[属性填充完成] AA2 --\u0026gt; AB AB --\u0026gt; AC[initializeBean 初始化] AC --\u0026gt; AD[放入 singletonObjects 一级缓存] N -.-\u0026gt;|提前暴露| O O -.-\u0026gt; AE[其他 Bean 获取早期引用] AE --\u0026gt; AF[存入 earlySingletonObjects] AF -.-\u0026gt; X Bean的生命周期 加载BeanDefinition —— 扫描Bean，注册为BeanDefinition 实例化 —— 反射/工厂方法创建原始对象（构造器注入在这里完成） 属性填充 —— 给 @Autowired、@Value 字段赋值（三级缓存解决循环依赖） Aware 回调 —— 让 Bean 感知自己的名字、容器等 前置处理 —— @PostConstruct 在这里执行 初始化 —— InitializingBean 和 init-method 后置处理 —— AOP 代理在这里生成（事务、切面都在这时生效） 放入单例池 —— Bean 可以被正常使用 销毁 —— @PreDestroy 和 destroy-method 收尾 面向切面编程 在OOP中，通常按业务划分出不同的类，但总有一些公共逻辑散落在各个类中。比如：日志记录、事务管理等。 这些逻辑叫做横切关注点。横切逻辑与业务代码紧密耦合，会导致代码重复、难以维护等问题。AOP通过将这些横切逻辑模块化为独立的切面，在不修改业务代码的情况下，将横切逻辑动态织入到指定的连接点，保持业务代码的纯粹。\nAOP核心术语：\n连接点：程序执行过程中可以插入额外逻辑的点。每个类的每个方法都是一个连接点。 切点：匹配需要操作的连接点的条件，决定在哪些位置做额外逻辑。 通知：在匹配的连接点上要执行的额外逻辑，做什么、何时做。 前置通知（Before）：方法执行之前做。 后置通知（After）：方法执行之后做。无论成功还是异常都会执行。 返回通知（AfterReturning）：仅在方法正常返回时执行，可以拿到返回值。 异常通知（AfterThrowing）：仅在方法抛出异常时执行，可以拿到异常对象。 环绕通知（Around）：包裹整个方法调用，可控制是否执行目标方法、以及修改返回值。 切面：通知+切点的组合，是横切逻辑的完整模块。 目标对象：被通知的真正业务对象。 织入：将切面应用到目标对象，并创建代理对象的过程。 Spring AOP原理 原理：动态代理\nSpring AOP基于动态代理实现，常见动态代理方式：\nJDK动态代理：要求目标类至少实现一个接口，通过反射在运行时动态生成代理类的字节码，这个代理类实现了目标类的所有接口，但并非目标类的子类。所有方法调用都会被转发到一个InvocationHandler上。 CGLIB代理：对目标类无要求，CGLIB使用ASM字节码框架，生成目标类的子类，重写父类中允许重写的方法，在子类中插入拦截逻辑，以实现代理目的。 激活Spring AOP的入口是注解 @EnableAspectJAutoProxy，该注解通过@Import(AspectJAutoProxyRegistrar.class)向容器注册了一个后处理器：AnnotationAwareAspectJAutoProxyCreator，是Spring AOP的核心引擎，做了两件事：\n收集切面：找到容器中所有@AspeceJ标注的类，并解析为一组Advisor。 创建代理：在Bean初始化完成后，检查是否需要代理，需要时动态生成代理对象。 核心设计思路：\n用@AspectJ声明切面，用@Before、@After等声明通知，用切点表达式定义拦截位置。 Spring容器自动发现这些切面，并将它们解析为一组Advisor。 在Bean创建过程中，Spring会检查每个Bean是否需要被某个Advisor匹配，如果需要则创建代理对象。 代理对象在方法调用时，将根据匹配的Advisor生成拦截器链（MethodInterceptor），通过递归调用的责任链模式执行通知逻辑。 正常返回：Around前部-\u0026gt;Before-\u0026gt;目标方法-\u0026gt;AfterReturning-\u0026gt;After-\u0026gt;Around后部 异常情况：Around前部-\u0026gt;Before-\u0026gt;目标方法-\u0026gt;AfterThrowing-\u0026gt;After-\u0026gt;Around后部 在Spring AOP中，任何横切逻辑最终都会变成一个MethodInterceptor，在具体执行时，通过递归+责任链模式执行。\n事务管理 事务的四大特性（ACID）：\n原子性（Atomicity）：要么全做，要么全不做。 一致性（Consistency）：事务前后数据完整性不变。 隔离性（Isolation）：并发事务互不干扰。 持久性（Durability）：提交后数据永久保存。 Spring不直接管理数据库事务，而是提供统一的抽象层。\n核心接口关系：\nPlatformTransactionManager：事务管理器，不同数据库有不同的实现，如DataSourceTransactionManager、JpaTransactionManager等。 TransactionDefinition：事务定义，包括隔离级别、传播行为、超时等。 TransactionStatus：事务状态 事务管理的两种方式：\n声明式事务：@Transactional + AOP 编程式事务：使用TransactionTemplate、PlatformTransactionManager调用 声明式事务原理 通过注解@EnableTransactionManagement开启事务，该注解包含两个核心组件：\nAutoProxyRegistrar：注册InfrastructureAdvisorAutoProxyCreator，用于遍历Advisor。 ProxyTransactionManagementConfiguration：生成事务Advisor。 TransactionInterceptor：事务逻辑的真正执行者（实现了MethodInterceptor接口），它持有负责解析事务注解的TransactionAttributeSource和事务管理器PlatformTransactionManager。 BeanFactoryTransactionAttributeSourceAdvisor：事务Advisor。 当Bean初始化完成，属性填充之后，InfrastructureAdvisorAutoProxyCreator会遍历所有Advisor，匹配到使用@Transactional注解的方法，并创建代理完成织入。\n当代理对象被调用时，TransactionInterceptor被触发，大致处理流程如下：\n获取事务属性 获取事务管理器 开启事务 调用目标方法 提交事务，异常时回滚 清理事务 事务传播行为 想象你是一个项目经理（调用方），你分配给下属一个任务（调用事务方法）。现在有两种情况：\n你手里已经有一个正在进行的项目（当前有事务） 你手里没有项目（当前无事务） 传播行为就是：下属接到任务后，是加入你的项目一起干，还是自己单开一个新项目，还是直接拒绝？ 七种传播行为可以分为四组：一起干、单干、拒绝、特殊。\n第一组：一起干（加入或新建）\n传播行为 当前有事务 当前无事务 类比 REQUIRED（默认） 加入你的项目 自己开项目 好员工，有活就跟着干，没活自己牵头干 SUPPORTS 加入你的项目 不干（无事务） 支持型员工，有项目就参与，没项目就裸奔 MANDATORY 加入你的项目 直接摔门走人 必须型员工，没项目？这活没法干 记忆口诀：\nREQUIRED：必须有（自己或别人有都行） SUPPORTS：可以有（有就参与，没有拉倒） MANDATORY：必须有你的（必须是别人开的） 第二组：单干（自己开新项目）\n传播行为 当前有事务 当前无事务 类比 REQUIRES_NEW 挂起你的项目，自己开新项目 自己开项目 独立员工，不管你在干嘛，我都要另起炉灶 NOT_SUPPORTED 挂起你的项目，自己裸跑 自己裸跑 佛系员工，我讨厌事务，有也当没有 NEVER 直接摔门走人 自己裸跑 事务洁癖，有事务就炸 记忆口诀：\nREQUIRES_NEW：必须新的（每次都新建，旧的挂起） NOT_SUPPORTED：不支持（不管你支不支持，我不支持） NEVER：绝对不要（有事务就报错） 第三组：特殊模式\n传播行为 当前有事务 当前无事务 类比 NESTED 嵌套子事务（保存点） 自己开项目 子任务，父项目失败全完蛋，自己失败父项目可继续 记忆要点：NESTED 和 REQUIRES_NEW 最容易混淆：\nREQUIRES_NEW：两个项目完全独立，互相不影响。 NESTED：父子关系，父亲回滚儿子必回滚，儿子回滚父亲不受影响。 一幅记忆逻辑图：\n接到任务，当前有事务吗？ ├── 有事务 │ ├── 加入你：REQUIRED / SUPPORTS / MANDATORY │ │ └── MANDATORY：必须加入，否则不让干 │ ├── 单开：REQUIRES_NEW（挂起你的） │ ├── 裸奔：NOT_SUPPORTED（挂起你的） │ ├── 报错：NEVER（事务洁癖） │ └── 嵌套：NESTED（父子关系） └── 无事务 ├── 新建：REQUIRED / REQUIRES_NEW / NESTED ├── 裸奔：SUPPORTS / NOT_SUPPORTED / NEVER └── 报错：MANDATORY 事务失效场景 所有失效场景的根源都在于AOP代理机制。\n失效场景1：类内部调用\n失效原因：this是目标对象，不是代理对象，所以不会走AOP。 @Service public class UserService { @Transactional public void outer() { this.inner(); // 不经过代理！ } @Transactional public void inner() { ... } } 失效场景2：非public方法\n失效原因：CGLIB代理生成子类，可重写public、protected方法，private方法无法重写，代理无法拦截。基于接口的JDK代理也只能拦截public方法。 失效场景3：异常被catch\n失效原因：只有在目标方法抛出异常时事务才会回滚，如果方法内部吞掉了异常，拦截器则感知不到任何异常，事务会正常提交。 失效场景4：异常类型不匹配\n失效原因：@Transactional默认只回滚RuntimeException和Error，如果异常不一致，则不会回滚。 失效场景5：数据库引擎不支持事务\n失效原因：如MyISAM引擎不支持事务 失效场景6：多线程\n失效原因：跨线程导致事务丢失 失效场景7：事务传播行为错误\n失效原因：错误使用事务传播行为，导致事务丢失。 Spring MVC Spring Boot Spring Boot将Spring的可配置性固化为约定，通过自动装配、起步依赖、内嵌容器和配置文件等内容，让开发者更快速的搭建应用。\n自动装配原理 @SpringBootApplication注解是Spring Boot的入口，该注解是一个组合注解，包含三个注解：\n@SpringBootConfiguration：内部实际为@Configuration，表明当前类为配置类。 @ComponentScan：组件扫描，默认扫描当前包及子包中的所有Bean。 @EnableAutoConfiguration：自动装配的核心。 @EnableAutoConfiguration中通过@Import注解，导入了AutoConfigurationImportSelector类，该类实现了ImportSelector接口，通过selectImports方法扫描要装配的所有类。\n自动装配流程如下：\n加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的类名（Spring Boot3.0之后扫描xxx.imports，3.0之前扫描spring.factories） 过滤重复类名 根据对应类中的条件注解来判定，若条件满足则装载对应Bean 需注意，默认只会加载AutoConfiguration注解开头的imports，如有其他xxx.imports，由其他包自行实现ImportSelector扫描并装配。 启动流程 步骤 做什么 你学过的知识 new SpringApplication() 推断是 Web 还是普通应用，加载初始化器和监听器 从spring.factories扫描需要加载的类 准备环境 加载 application.yml、环境变量、命令行参数 Environment 抽象 创建上下文 根据 Web 类型创建对应容器 ApplicationContext refresh() 核心！ 执行 Spring 容器的全部启动逻辑 BeanDefinition、BeanPostProcessor、Bean 生命周期 refresh() 中自动配置生效 AutoConfigurationImportSelector 读取 .imports 文件，条件过滤，注册 Bean 自动装配 refresh() 中启动内嵌容器 onRefresh() 启动 Tomcat，注册 DispatcherServlet 内嵌容器原理 启动完成 执行 ApplicationRunner，应用就绪 — 自定义Starter Starter本质上是一个Maven/Gradle依赖，它里面没有业务代码，只做两件事：\n聚合依赖：把需要的jar包一次性引入。 触发自动装配：通过AutoConfiguration.imports文件告诉Spring Boot哪些自动配置类需要加载。 通常拆分为两个模块：\nxxx-spring-boot-starter：starter模块，只包含pom.xml。 xxx-spring-boot-autoconfigure：自动配置模块。 示例 1、创建 greeter-spring-boot-autoconfigure 模块 ① 编写属性类，用 @ConfigurationProperties 绑定配置\n@ConfigurationProperties(prefix = \u0026#34;greeter\u0026#34;) public class GreeterProperties { private String prefix = \u0026#34;Hello\u0026#34;; // 默认值 private String suffix = \u0026#34;!\u0026#34;; // getters and setters } ② 编写核心服务类\npublic class GreeterService { private final GreeterProperties properties; public GreeterService(GreeterProperties properties) { this.properties = properties; } public String greet(String name) { return properties.getPrefix() + \u0026#34;, \u0026#34; + name + properties.getSuffix(); } } ③ 编写自动配置类\n@AutoConfiguration // 等价于 @Configuration，但专门用于自动配置 @EnableConfigurationProperties(GreeterProperties.class) @ConditionalOnClass(GreeterService.class) public class GreeterAutoConfiguration { @Bean @ConditionalOnMissingBean public GreeterService greeterService(GreeterProperties properties) { return new GreeterService(properties); } } ④ 创建 AutoConfiguration.imports 文件 位置：src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 内容：\ncom.example.greeter.autoconfigure.GreeterAutoConfiguration 2、创建 Starter 模块 在 Starter 的 pom.xml 中引入对应依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.example\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;greeter-spring-boot-autoconfigure\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 3、用户引入Starter 当用户引入 greeter-spring-boot-starter 时：\nStarter 带入 autoconfigure 模块。 Spring Boot 启动时扫描到 AutoConfiguration.imports 中的配置类。 条件满足（classpath 有 GreeterService，且用户未手动创建 GreeterService Bean）则自动创建。 用户可以在 application.yml 中自定义 greeter.prefix 和 greeter.suffix。 Spring Cloud 单体应用发展到一定规模后，会面临：单点故障、扩展困难、协同开发难等问题，Spring Cloud提供了一套微服务架构的工具集，是分布式微服务架构的一站式解决方案，解决分布式中如下常见问题：\n服务注册与发现：Nacos 远程调用：OpenFeign 服务容错：Sentinel 配置中心：Nacos 网关：Gateway 链路追踪：Sleuth + Zipkin Spring Cloud建立在Spring Boot之上，每个组件都是以Starter形式提供。Spring Boot让一个服务快速启动，Spring Cloud让一堆服务协同工作。\n国内大部分企业使用Spring Cloud Alibaba，后续以Spring Cloud Alibaba展开。\nNacos Nacos，全称：Dynamic Naming and Configuration Service，是一个面向云原生和 AI 应用的动态服务发现、配置管理和 AI 管理中心平台。最早围绕两个核心问题构建：应用如何找到服务、应用如何安全地读取和更新配置。进入 3.x 后，Nacos 在这些能力之上扩展了 AI 管理中心，用来管理 Skill、A2A Agent、MCP server、Prompt 等 AI 资源。官网：https://nacos.io/\n服务注册与发现流程：\n微服务应用在启动过程中将自身包含的服务名称、主机IP地址、端口号等信息发送至注册中心。 上游微服务在处理请求时，根据服务名称到注册中心查询待调用的服务信息，包括IP及端口号等。 发起服务调用。 FAQ 为什么构造器注入无法解决循环依赖，Setter注入和注解注入却可以？ ","permalink":"/knowledges/java/spring/","summary":"\u003cp\u003eSpring是一个轻量级的Java开发框架，目标是让Java开发者能专注于开发逻辑。底层有两个核心特性：依赖注入（IOC）、面向切面编程（AOP）。\u003c/p\u003e\n\u003ch2 id=\"依赖注入\"\u003e依赖注入\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eIOC\u003c/strong\u003e，全称 Inversion of Control，控制反转。不是 Spring 发明的，是一种设计原则。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e常见实现IOC的方式：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eService Locator：组件主动从容器中查找依赖，如：context.getBean(\u0026ldquo;xxx\u0026rdquo;); 这种方式对业务代码侵入性较高。\u003c/li\u003e\n\u003cli\u003eDependency Injection（DI）：容器被动地将依赖\u003cstrong\u003e注入\u003c/strong\u003e给组件，组件完全不感知容器。这种方式对好处是：代码干净、依赖关系清晰、易于测试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSpring选择了DI作为主要的IOC实现机制，但也保留了Service Locator的能力（ApplicationContextAware）。\u003c/p\u003e\n\u003ch3 id=\"di的三种注入方式及理解\"\u003eDI的三种注入方式及理解\u003c/h3\u003e\n\u003cp\u003e构造器注入：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Service\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eOrderService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003efinal\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eOrderRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erepository\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eOrderService\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eOrderRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erepository\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003erepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erepository\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e优点：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e依赖不可变，声明为final，保证线程安全。\u003c/li\u003e\n\u003cli\u003e确保依赖不为null，编译时保证注入。\u003c/li\u003e\n\u003cli\u003e易于单元测试，直接通过构造期传入Mock。\n缺点：\u003c/li\u003e\n\u003cli\u003e无法解决循环依赖。\u003c/li\u003e\n\u003cli\u003e依赖过多时，代码显得很臃肿。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSetter注入：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Service\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eOrderService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eOrderRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erepository\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Autowired\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esetRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eOrderRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erepository\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003erepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erepository\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e优点：\u003c/p\u003e","title":"Spring"}]