<?xml version='1.0' encoding='utf-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jeffrey</title>
  <subtitle>一个独立开发者的博客</subtitle>
  <link href="https://aspirant02.github.io/atom.xml" rel="self" />
  <link href="https://aspirant02.github.io/" />
  <updated>2026-08-08T13:01:24Z</updated>
  <id>https://aspirant02.github.io/</id>
  <author>
    <name>符政辉</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <entry>
    <title>AI开发-Hermes：会让 Skill 迭代进化的 Agent</title>
    <link href="https://aspirant02.github.io/2026/08/08/AI%E5%BC%80%E5%8F%91-Hermes%EF%BC%9A%E4%B8%80%E4%B8%AA%E5%81%8F%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E5%92%8C%E7%BB%93%E6%9E%84%E5%8C%96%E8%BE%93%E5%87%BA%E7%9A%84%E6%A8%A1%E5%9E%8B%E7%B3%BB%E5%88%97/" />
    <id>https://aspirant02.github.io/2026/08/08/AI%E5%BC%80%E5%8F%91-Hermes%EF%BC%9A%E4%B8%80%E4%B8%AA%E5%81%8F%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E5%92%8C%E7%BB%93%E6%9E%84%E5%8C%96%E8%BE%93%E5%87%BA%E7%9A%84%E6%A8%A1%E5%9E%8B%E7%B3%BB%E5%88%97/</id>
    <published>2026-08-08T13:01:24Z</published>
    <updated>2026-08-08T13:01:24Z</updated>
    <content type="html">&lt;h1 id="前言"&gt;&lt;a class="headerlink" href="#前言" title="前言"&gt;&lt;/a&gt;前言&lt;/h1&gt;
&lt;p&gt;这篇文章重新说一下 Hermes。这里的 Hermes 不是某个偏函数调用的模型系列，也不是单纯的格式化能力。更准确地说，我想讨论的是一种 Agent：它不是只完成一次任务，而是会把一次任务中的有效做法沉淀成 Skill，再在后续任务里复用、校验、修正，最终让自己的工作方法慢慢变好。&lt;/p&gt;
&lt;p&gt;普通 Agent 的循环通常是计划、执行、观察、继续执行。Hermes 更进一步，它会关心“这次任务里有没有可复用的方法”。例如它修好了一个前端布局问题，不只是把这次代码改完，还会总结出一个 Skill：如何定位页面重叠、如何判断是 sticky 布局问题还是 z-index 问题、如何做截图验证、哪些改动容易影响其它页面。下一次遇到相似问题时，它不需要从零开始摸索，而是先调用这个 Skill。&lt;/p&gt;
&lt;p&gt;所以我理解的 Hermes 有两个关键词：一个是 Agent，另一个是 Skill Evolution。Agent 负责在环境里行动，Skill Evolution 负责把行动经验转成可复用的能力单元，并且允许这个能力单元继续被测试、被拆分、被合并、被淘汰。&lt;/p&gt;
&lt;h1 id="Hermes 的定位"&gt;&lt;a class="headerlink" href="#Hermes 的定位" title="Hermes 的定位"&gt;&lt;/a&gt;Hermes 的定位&lt;/h1&gt;
&lt;p&gt;如果把 Agent 看成一个执行者，Hermes 更像一个会写工作手册、会更新工作手册、还会检查工作手册是否过期的执行者。它不是把所有知识都塞进 prompt，而是把稳定的经验变成一个个 Skill。Skill 可以是很小的，也可以是组合型的。&lt;/p&gt;
&lt;p&gt;一个小 Skill 可能只是“读取一个仓库时先找 package.json、README、配置文件，再判断启动方式”。一个组合型 Skill 可能是“修复静态博客页面错位”，里面包含定位页面、识别主题生成结构、修改 CSS 或 HTML、验证线上缓存等多个步骤。&lt;/p&gt;
&lt;p&gt;这和普通 memory 不太一样。memory 经常只是记录“用户喜欢什么”或“之前发生过什么”，而 Skill 更强调可执行。一个 Skill 应该能指导 Agent 下一次怎么做，最好还能被单独测试。它不是一句经验，而是一段可调用的方法。&lt;/p&gt;
&lt;h1 id="Skill 是 Hermes 的能力单位"&gt;&lt;a class="headerlink" href="#Skill 是 Hermes 的能力单位" title="Skill 是 Hermes 的能力单位"&gt;&lt;/a&gt;Skill 是 Hermes 的能力单位&lt;/h1&gt;
&lt;p&gt;要让 Hermes 真正进化，Skill 不能只是自然语言笔记。比较实用的 Skill 至少应该包含这些字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name：Skill 的名字，要求短、清楚、可检索。&lt;/li&gt;
&lt;li&gt;intent：这个 Skill 解决什么问题，不解决什么问题。&lt;/li&gt;
&lt;li&gt;trigger：什么样的任务或上下文适合调用它。&lt;/li&gt;
&lt;li&gt;inputs：调用时需要哪些信息，例如仓库路径、错误日志、截图、用户目标。&lt;/li&gt;
&lt;li&gt;procedure：具体步骤，可以是自然语言，也可以是工具调用模板。&lt;/li&gt;
&lt;li&gt;checks：完成后怎么验证。&lt;/li&gt;
&lt;li&gt;failure_cases：曾经失败过的场景，用来提醒 Agent 不要复读错误。&lt;/li&gt;
&lt;li&gt;version：Skill 的版本，方便回滚和对比。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个最小 Skill 可以长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  "name": "fix_static_blog_sidebar_overlap",
  "intent": "修复静态博客中侧栏卡片随滚动覆盖其它卡片的问题",
  "trigger": ["sidebar overlap", "sticky layout", "archive card covers comments"],
  "inputs": ["page_url", "screenshot", "repo_path"],
  "procedure": [
    "定位页面中 aside-content 和 sticky_layout 的结构",
    "判断 sticky 是否在错误容器中生效",
    "优先用局部 CSS 修正 position/top，不改全局主题布局",
    "对归档、分类、标签等单栏页面检查是否需要移除侧栏 HTML",
    "用本地和线上页面分别验证"
  ],
  "checks": [
    "页面主内容没有 aside-content 残留",
    "滚动后最新评论、归档、标签卡片不重叠",
    "移动端菜单仍可正常打开"
  ],
  "failure_cases": [
    "只用 display:none 隐藏侧栏，但 HTML 仍残留，用户会认为没有真正删除"
  ],
  "version": "1.2.0"
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里面最重要的不是 JSON，而是 Skill 的边界。很多 Agent 失败，是因为它把“经验”写得太空。比如“检查页面布局”这种 Skill 基本没用，因为它既不知道什么时候用，也不知道怎么验证。Hermes 要进化，Skill 必须可触发、可执行、可验证。&lt;/p&gt;
&lt;h1 id="从一次任务提炼 Skill"&gt;&lt;a class="headerlink" href="#从一次任务提炼 Skill" title="从一次任务提炼 Skill"&gt;&lt;/a&gt;从一次任务提炼 Skill&lt;/h1&gt;
&lt;p&gt;Hermes 的关键动作不是“执行任务”，而是“任务结束后复盘”。一次任务完成后，它可以把轨迹拆成三类内容：有效步骤、无效步骤、环境约束。有效步骤可能进入 Skill，失败步骤可能进入 failure_cases，环境约束则变成触发条件或注意事项。&lt;/p&gt;
&lt;p&gt;下面是一段简化的伪代码，展示如何从任务轨迹中生成候选 Skill。真实系统里，这一步可以由模型完成，但最好再经过规则和测试过滤。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from dataclasses import dataclass, field

@dataclass
class TraceStep:
    thought: str
    action: str
    observation: str
    success: bool

@dataclass
class SkillDraft:
    name: str
    intent: str
    trigger: list[str]
    procedure: list[str]
    checks: list[str]
    failure_cases: list[str] = field(default_factory=list)


def propose_skill(task_goal: str, trace: list[TraceStep]) -&amp;gt; SkillDraft:
    useful_steps = [s for s in trace if s.success]
    failed_steps = [s for s in trace if not s.success]

    return SkillDraft(
        name="draft_from_task",
        intent=f"复用解决任务的流程: {task_goal}",
        trigger=[task_goal[:40]],
        procedure=[f"执行 {s.action}，观察 {s.observation[:80]}" for s in useful_steps],
        checks=["重新运行验证命令", "确认用户报告的问题已经消失"],
        failure_cases=[f"避免重复: {s.action}" for s in failed_steps]
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个版本当然很粗糙，但它说明了 Hermes 的核心：任务不是一次性消耗品，任务轨迹是训练 Skill 的原料。Agent 做得越多，如果不沉淀，下一次还是从零开始；如果会沉淀，就会慢慢形成自己的工具箱。&lt;/p&gt;
&lt;h1 id="Skill 的迭代闭环"&gt;&lt;a class="headerlink" href="#Skill 的迭代闭环" title="Skill 的迭代闭环"&gt;&lt;/a&gt;Skill 的迭代闭环&lt;/h1&gt;
&lt;p&gt;Skill 不能一写完就永久有效。代码库会变，工具会变，用户偏好会变，模型能力也会变。Hermes 需要一个迭代闭环：生成候选 Skill，选择是否启用，在任务中调用，记录成功率，失败后修订，长期无效就降权或删除。&lt;/p&gt;
&lt;p&gt;我更喜欢把 Skill 分成三个状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;draft：刚从任务中提炼出来，还没有被充分验证。&lt;/li&gt;
&lt;li&gt;active：多次任务验证有效，可以被自动检索和调用。&lt;/li&gt;
&lt;li&gt;deprecated：已经过期或效果不稳定，只保留历史记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;状态切换最好依赖数据，而不是只靠模型自信。比如同一个 Skill 最近 10 次调用中有 8 次成功，它可以保持 active；如果连续 3 次引导 Agent 走错方向，就应该降权。下面是一个很小的评分器：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;def update_skill_score(skill, run_result):
    if run_result.passed_checks:
        skill.score += 2
    if run_result.required_human_fix:
        skill.score -= 3
    if run_result.caused_regression:
        skill.score -= 6

    if skill.score &amp;gt;= 10:
        skill.state = "active"
    elif skill.score &amp;lt;= -5:
        skill.state = "deprecated"
    else:
        skill.state = "draft"

    return skill&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的分数不需要复杂，关键是让 Hermes 有“经验是否真的有效”的反馈。没有反馈的 Skill 进化，很容易变成越写越长的提示词垃圾堆。&lt;/p&gt;
&lt;h1 id="Skill 检索与调用"&gt;&lt;a class="headerlink" href="#Skill 检索与调用" title="Skill 检索与调用"&gt;&lt;/a&gt;Skill 检索与调用&lt;/h1&gt;
&lt;p&gt;当用户给 Hermes 一个任务时，系统可以先做 Skill 检索。检索不只是看关键词，还要看触发条件、输入是否满足、风险等级是否匹配。例如“修复博客分类页显示异常”可以召回静态博客布局 Skill；但如果用户只是问“分类页是什么意思”，就不应该调用修复 Skill。&lt;/p&gt;
&lt;p&gt;一个可控的调用流程可以是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;理解任务目标，抽取关键词和约束。&lt;/li&gt;
&lt;li&gt;从 Skill Registry 中召回候选 Skill。&lt;/li&gt;
&lt;li&gt;让模型判断 Skill 是否适用，并给出理由。&lt;/li&gt;
&lt;li&gt;只把最相关的 1 到 3 个 Skill 注入上下文。&lt;/li&gt;
&lt;li&gt;执行后记录 Skill 是否真的帮上忙。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里有一个细节：不要把所有 Skill 一次性塞给模型。Skill 太多会污染上下文，模型可能为了使用 Skill 而使用 Skill。Hermes 的 skill system 应该像一个工具箱，而不是一本永远展开的百科全书。&lt;/p&gt;
&lt;h1 id="和普通 Agent 的区别"&gt;&lt;a class="headerlink" href="#和普通 Agent 的区别" title="和普通 Agent 的区别"&gt;&lt;/a&gt;和普通 Agent 的区别&lt;/h1&gt;
&lt;p&gt;普通 Agent 更关注“这次任务能不能完成”。Hermes 更关注“这次任务完成后，系统有没有变得更会做这类事”。这两者的工程重心不同。&lt;/p&gt;
&lt;p&gt;普通 Agent 需要计划器、工具调用、记忆和验证。Hermes 还需要 Skill Registry、Skill 版本管理、Skill 评分、回滚机制和去重机制。没有这些东西，所谓进化就只是把聊天记录存起来，下一次再翻出来看看。&lt;/p&gt;
&lt;p&gt;我觉得 Hermes 最适合那些重复但不完全相同的任务场景，例如代码库维护、自动化测试修复、文档整理、数据清洗、运营报表生成、静态站点发布。每一次任务都有差异，但底层套路可以复用，这正是 Skill 可以发挥价值的地方。&lt;/p&gt;
&lt;h1 id="风险与边界"&gt;&lt;a class="headerlink" href="#风险与边界" title="风险与边界"&gt;&lt;/a&gt;风险与边界&lt;/h1&gt;
&lt;p&gt;让 Agent 自己进化听起来很诱人，但也有风险。第一，坏 Skill 会被反复调用，导致错误固化。第二，Skill 如果权限太大，可能把危险操作包装成“标准流程”。第三，Skill 太细会检索困难，太粗又会失去指导意义。&lt;/p&gt;
&lt;p&gt;比较稳的做法是把 Skill 进化分层：低风险 Skill 可以自动晋级，例如阅读项目结构、生成检查清单；高风险 Skill 必须人工确认，例如删除文件、修改数据库、部署生产环境。Skill 本身也应该能被 diff、review 和回滚。&lt;/p&gt;
&lt;p&gt;Hermes 不应该追求“完全自治”。更好的目标是让 Agent 在可追溯、可验证、可回滚的前提下，逐步沉淀自己的工作方法。这样的进化比较慢，但工程上更靠谱。&lt;/p&gt;
&lt;h1 id="小结"&gt;&lt;a class="headerlink" href="#小结" title="小结"&gt;&lt;/a&gt;小结&lt;/h1&gt;
&lt;p&gt;Hermes 的核心不是某个模型名字，而是一种会成长的 Agent 形态。它把任务轨迹提炼成 Skill，把 Skill 放进 Registry，在后续任务中检索和调用，再根据验证结果持续修订。这个过程让 Agent 不只是“完成一次任务”，而是逐渐形成自己的方法库。&lt;/p&gt;
&lt;p&gt;如果要实现一个最小 Hermes，我建议先做三件事：记录完整任务轨迹，允许任务结束后生成候选 Skill，为每个 Skill 设计可执行检查。等这三件事稳定，再考虑自动召回、自动评分和多 Skill 组合。Skill 进化不是魔法，它是工程化复盘、验证和版本管理的组合。&lt;/p&gt;</content>
    <summary type="html">Hermes 是一种把任务经验沉淀为 Skill、再通过验证和反馈持续迭代 Skill 的 Agent 设计思路。 ...</summary>
    <category term="AI开发" scheme="https://aspirant02.github.io/categories/AI%E5%BC%80%E5%8F%91/" />
    <category term="AI开发" scheme="https://aspirant02.github.io/tags/AI%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>八股文系列-并发编程</title>
    <link href="https://aspirant02.github.io/2026/08/02/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" />
    <id>https://aspirant02.github.io/2026/08/02/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/</id>
    <published>2026-08-02T15:11:35Z</published>
    <updated>2026-08-02T15:11:35Z</updated>
    <content type="html">&lt;p&gt;&lt;a name="MIJch"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="基础"&gt;&lt;a class="headerlink" href="#基础" title="基础"&gt;&lt;/a&gt;基础&lt;/h2&gt;&lt;p&gt;&lt;a name="WPmsd"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="1-并行跟并发有什么区别？"&gt;&lt;a class="headerlink" href="#1-并行跟并发有什么区别？" title="1.并行跟并发有什么区别？"&gt;&lt;/a&gt;1.并行跟并发有什么区别？&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;并行是指多个处理器同时执行多个任务，每个核心实际上可以在同一时间独立地执行不同的任务。&lt;/li&gt;
&lt;li&gt;并发是指系统有处理多个任务的能力，但是任意时刻只有一个任务在执行。在单核处理器上，多个任务是通过时间片轮转的方式实现的。但这种切换非常快，给人感觉是在同时执行。&lt;br/&gt;&lt;a name="r4gnW"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="你对线程安全的理解是什么？"&gt;&lt;a class="headerlink" href="#你对线程安全的理解是什么？" title="你对线程安全的理解是什么？"&gt;&lt;/a&gt;你对线程安全的理解是什么？&lt;/h4&gt;&lt;p&gt;线程安全是并发编程中一个重要的概念，如果一段代码块或者一个方法在多线程环境中被多个线程同时执行时能够正确地处理共享数据，那么这段代码块或者方法就是线程安全的。&lt;br/&gt;可以从三个要素来确保线程安全：&lt;br/&gt;&lt;strong&gt;①、原子性&lt;/strong&gt;：确保当某个线程修改共享变量时，没有其他线程可以同时修改这个变量，即这个操作是不可分割的。&lt;br/&gt;原子性可以通过互斥锁（如 synchronized）或原子操作（如 AtomicInteger 类中的方法）来保证。&lt;br/&gt;&lt;strong&gt;②、可见性&lt;/strong&gt;：确保一个线程对共享变量的修改可以立即被其他线程看到。&lt;br/&gt;volatile 关键字可以保证了变量的修改对所有线程立即可见，并防止编译器优化导致的可见性问题。&lt;br/&gt;&lt;strong&gt;③、活跃性问题&lt;/strong&gt;：要确保线程不会因为死锁、饥饿、活锁等问题导致无法继续执行。&lt;br/&gt;&lt;a name="VNztG"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="2-说说什么是进程和线程？"&gt;&lt;a class="headerlink" href="#2-说说什么是进程和线程？" title="2.说说什么是进程和线程？"&gt;&lt;/a&gt;2.说说什么是进程和线程？&lt;/h3&gt;&lt;p&gt;进程说简单点就是我们在电脑上启动的一个个应用，比如我们启动一个浏览器，就会启动了一个浏览器进程。进程是操作系统资源分配的最小单位，它包括了程序、数据和进程控制块等。&lt;br/&gt;线程说简单点就是我们在 Java 程序中启动的一个 main 线程，一个进程至少会有一个线程。当然了，我们也可以启动多个线程，比如说一个线程进行 IO 读写，一个线程进行加减乘除计算，这样就可以充分发挥多核 CPU 的优势，因为 IO 读写相对 CPU 计算来说慢得多。线程是 CPU 分配资源的基本单位。&lt;br/&gt;一个进程中可以有多个线程，多个线程共用进程的堆和方法区（Java 虚拟机规范中的一个定义，JDK 8 以后的实现为元空间）资源，但是每个线程都会有自己的程序计数器和栈。&lt;br/&gt;&lt;a name="qkrVq"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="如何理解协程？"&gt;&lt;a class="headerlink" href="#如何理解协程？" title="如何理解协程？"&gt;&lt;/a&gt;如何理解协程？&lt;/h4&gt;&lt;p&gt;协程通常被视为比线程更轻量级的并发单元，它们主要在一些支持异步编程模型的语言中得到了原生支持，如 Kotlin、Go 等。&lt;br/&gt;不过，我们可以使用 CompletableFuture 来模拟协程式的异步执行任务。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CompletableFutureExample&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ExecutionException, InterruptedException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 异步执行任务1&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        CompletableFuture&amp;lt;Integer&amp;gt; future1 = CompletableFuture.supplyAsync(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.sleep(&lt;span class="number"&gt;1000&lt;/span&gt;); &lt;span class="comment"&gt;// 模拟耗时操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="number"&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 异步执行任务2&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        CompletableFuture&amp;lt;Integer&amp;gt; future2 = CompletableFuture.supplyAsync(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.sleep(&lt;span class="number"&gt;1000&lt;/span&gt;); &lt;span class="comment"&gt;// 模拟耗时操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="number"&gt;20&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 合并两个任务的结果并计算&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        CompletableFuture&amp;lt;Integer&amp;gt; resultFuture = future1.thenCombine(future2, Integer::sum);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 等待最终结果并打印&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"结果: "&lt;/span&gt; + resultFuture.get());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在这个示例中，我们创建了两个 CompletableFuture 对象来异步执行两个简单的数值返回任务。这两个任务都会休眠 1 秒钟来模拟耗时计算。&lt;br/&gt;然后我们使用 thenCombine 方法来合并这两个任务的结果。最后，我们通过 get 方法等待最终结果的完成，并打印出来。&lt;br/&gt;&lt;a name="mKued"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说说线程的共享内存？"&gt;&lt;a class="headerlink" href="#说说线程的共享内存？" title="说说线程的共享内存？"&gt;&lt;/a&gt;说说线程的共享内存？&lt;/h4&gt;&lt;p&gt;线程之间想要进行通信，可以通过消息传递和共享内存两种方法来完成。那 Java 采用的是共享内存的并发模型。&lt;br/&gt;这个模型被称为 Java 内存模型，也就是 JMM，JMM 决定了一个线程对共享变量的写入何时对另外一个线程可见。&lt;br/&gt;线程之间的共享变量存储在主内存（main memory）中，每个线程都有一个私有的本地内存（local memory），本地内存中存储了共享变量的副本。当然了，本地内存是 JMM 的一个抽象概念，并不真实存在。线程 A 与线程 B 之间如要通信的话，必须要经历下面 2 个步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程 A 把本地内存 A 中的共享变量副本刷新到主内存中。&lt;/li&gt;
&lt;li&gt;线程 B 到主内存中读取线程 A 刷新过的共享变量，再同步到自己的共享变量副本中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="H5LUe"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="3-说说线程有几种创建方式？"&gt;&lt;a class="headerlink" href="#3-说说线程有几种创建方式？" title="3.说说线程有几种创建方式？"&gt;&lt;/a&gt;3.说说线程有几种创建方式？&lt;/h3&gt;&lt;p&gt;Java 中创建线程主要有三种方式，分别为继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。第一种，继承 Thread 类，重写 &lt;code&gt;run()&lt;/code&gt;方法，调用 &lt;code&gt;start()&lt;/code&gt;方法启动线程。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;ThreadTask&lt;/span&gt; &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"看完二哥的 Java 进阶之路，上岸了!"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;ThreadTask&lt;/span&gt; &lt;span class="variable"&gt;task&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadTask&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        task.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这种方法的缺点是，由于 Java 不支持多重继承，所以如果类已经继承了另一个类，就不能使用这种方法了。&lt;br/&gt;第二种，实现 Runnable 接口，重写 &lt;code&gt;run()&lt;/code&gt; 方法，然后创建 Thread 对象，将 Runnable 对象作为参数传递给 Thread 对象，调用 &lt;code&gt;start()&lt;/code&gt; 方法启动线程。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;RunnableTask&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Runnable&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"看完二哥的 Java 进阶之路，上岸了!"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;RunnableTask&lt;/span&gt; &lt;span class="variable"&gt;task&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;RunnableTask&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;thread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(task);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        thread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这种方法的优点是可以避免 Java 的单继承限制，并且更符合面向对象的编程思想，因为 Runnable 接口将任务代码和线程控制的代码解耦了。&lt;br/&gt;第三种，实现 Callable 接口，重写 &lt;code&gt;call()&lt;/code&gt; 方法，然后创建 FutureTask 对象，参数为 Callable 对象；紧接着创建 Thread 对象，参数为 FutureTask 对象，调用 &lt;code&gt;start()&lt;/code&gt; 方法启动线程。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CallableTask&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Callable&lt;/span&gt;&amp;lt;String&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; String &lt;span class="title function_"&gt;call&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="string"&gt;"看完二哥的 Java 进阶之路，上岸了!"&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ExecutionException, InterruptedException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;CallableTask&lt;/span&gt; &lt;span class="variable"&gt;task&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CallableTask&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        FutureTask&amp;lt;String&amp;gt; futureTask = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;FutureTask&lt;/span&gt;&amp;lt;&amp;gt;(task);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;thread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(futureTask);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        thread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(futureTask.get());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这种方法的优点是可以获取线程的执行结果。&lt;br/&gt;&lt;a name="V2Y0u"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="一个-8G-内存的系统最多能创建多少线程"&gt;&lt;a class="headerlink" href="#一个-8G-内存的系统最多能创建多少线程" title="一个 8G 内存的系统最多能创建多少线程?"&gt;&lt;/a&gt;一个 8G 内存的系统最多能创建多少线程?&lt;/h4&gt;&lt;p&gt;推荐阅读：&lt;a href="https://javabetter.cn/jvm/neicun-jiegou.html"&gt;深入理解 JVM 的运行时数据区&lt;/a&gt;&lt;br/&gt;在确定一个系统最多可以创建多个线程时，除了需要考虑系统的内存大小外，Java 虚拟机栈的大小也是值得考虑的因素。&lt;br/&gt;线程在创建的时候会被分配一个虚拟机栈，在 64 位操作系统中，默认大小为 1M。&lt;br/&gt;通过 &lt;code&gt;java -XX:+PrintFlagsFinal -version | grep ThreadStackSize&lt;/code&gt; 这个命令可以查看 JVM 栈的默认大小。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/neicun-jiegou-20231225145929.png"/&gt;&lt;br/&gt;其中 ThreadStackSize 的单位是字节，也就是说默认的 JVM 栈大小是 1024 KB，也就是 1M。&lt;br/&gt;换句话说，8GB = 8 _ 1024 MB = 8 _ 1024 _ 1024 KB，所以一个 8G 内存的系统可以创建的线程数为 8 _ 1024 = 8192 个。&lt;br/&gt;但操作系统本身的运行也需要消耗一定的内存，所以实际上可以创建的线程数肯定会比 8192 少一些。&lt;br/&gt;可以通过下面这段代码来验证一下：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;StackOverflowErrorTest1&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;AtomicInteger&lt;/span&gt; &lt;span class="variable"&gt;count&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;AtomicInteger&lt;/span&gt;(&lt;span class="number"&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (&lt;span class="literal"&gt;true&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            testStackOverflowError();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;testStackOverflowError&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(count.incrementAndGet());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        testStackOverflowError();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="H0vsw"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="启动一个-Java-程序，你能说说里面有哪些线程吗？"&gt;&lt;a class="headerlink" href="#启动一个-Java-程序，你能说说里面有哪些线程吗？" title="启动一个 Java 程序，你能说说里面有哪些线程吗？"&gt;&lt;/a&gt;启动一个 Java 程序，你能说说里面有哪些线程吗？&lt;/h4&gt;&lt;p&gt;首先是 main 线程，这是程序开始执行的入口。&lt;br/&gt;然后是垃圾回收线程，它是一个后台线程，负责回收不再使用的对象。&lt;br/&gt;还有编译器线程，在及时编译中（JIT），负责把一部分热点代码编译后放到 codeCache 中，以提升程序的执行效率。&lt;br/&gt;可以通过下面这段代码进行检测：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;ThreadLister&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 获取所有线程的堆栈跟踪&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Map&amp;lt;Thread, StackTraceElement[]&amp;gt; threads = Thread.getAllStackTraces();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (Thread thread : threads.keySet()) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(&lt;span class="string"&gt;"Thread: "&lt;/span&gt; + thread.getName() + &lt;span class="string"&gt;" (ID="&lt;/span&gt; + thread.getId() + &lt;span class="string"&gt;")"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;结果如下所示：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;Thread: Monitor Ctrl-Break (ID=5)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread: Reference Handler (ID=2)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread: main (ID=1)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread: Signal Dispatcher (ID=4)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread: Finalizer (ID=3)&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;简单解释下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Thread: main (ID=1)&lt;/code&gt; - 主线程，Java 程序启动时由 JVM 创建。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread: Reference Handler (ID=2)&lt;/code&gt; - 这个线程是用来处理引用对象的，如软引用（SoftReference）、弱引用（WeakReference）和虚引用（PhantomReference）。负责清理被 JVM 回收的对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread: Finalizer (ID=3)&lt;/code&gt; - 终结器线程，负责调用对象的 finalize 方法。对象在垃圾回收器标记为可回收之前，由该线程执行其 finalize 方法，用于执行特定的资源释放操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread: Signal Dispatcher (ID=4)&lt;/code&gt; - 信号调度线程，处理来自操作系统的信号，将它们转发给 JVM 进行进一步处理，例如响应中断、停止等信号。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread: Monitor Ctrl-Break (ID=5)&lt;/code&gt; - 监视器线程，通常由一些特定的 IDE 创建，用于在开发过程中监控和管理程序执行或者处理中断。&lt;br/&gt;&lt;a name="qKC4K"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-调用-start-方法时会执行-run-方法，那怎么不直接调用-run-方法？"&gt;&lt;a class="headerlink" href="#4-调用-start-方法时会执行-run-方法，那怎么不直接调用-run-方法？" title="4.调用 start()方法时会执行 run()方法，那怎么不直接调用 run()方法？"&gt;&lt;/a&gt;4.调用 start()方法时会执行 run()方法，那怎么不直接调用 run()方法？&lt;/h3&gt;&lt;p&gt;在 Java 中，启动一个新的线程应该调用其&lt;code&gt;start()&lt;/code&gt;方法，而不是直接调用&lt;code&gt;run()&lt;/code&gt;方法。&lt;br/&gt;当调用&lt;code&gt;start()&lt;/code&gt;方法时，会启动一个新的线程，并让这个新线程调用&lt;code&gt;run()&lt;/code&gt;方法。这样，&lt;code&gt;run()&lt;/code&gt;方法就在新的线程中运行，从而实现多线程并发。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyThread&lt;/span&gt; &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(Thread.currentThread().getName());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;MyThread&lt;/span&gt; &lt;span class="variable"&gt;t1&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;MyThread&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        t1.start(); &lt;span class="comment"&gt;// 正确的方式，创建一个新线程，并在新线程中执行 run()&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        t1.run(); &lt;span class="comment"&gt;// 仅在主线程中执行 run()，没有创建新线程&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;如果直接调用&lt;code&gt;run()&lt;/code&gt;方法，那么&lt;code&gt;run()&lt;/code&gt;方法就在当前线程中运行，没有新的线程被创建，也就没有实现多线程的效果。&lt;br/&gt;来看输出结果：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;main&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread-0&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;也就是说，&lt;code&gt;start()&lt;/code&gt; 方法的调用会告诉 JVM 准备好所有必要的新线程结构，分配其所需资源，并调用线程的 &lt;code&gt;run()&lt;/code&gt; 方法在这个新线程中执行。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-5.png"/&gt;&lt;br/&gt;&lt;a name="cn0nD"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="5-线程有哪些常用的调度方法？"&gt;&lt;a class="headerlink" href="#5-线程有哪些常用的调度方法？" title="5.线程有哪些常用的调度方法？"&gt;&lt;/a&gt;5.线程有哪些常用的调度方法？&lt;/h3&gt;&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-6.png"/&gt;&lt;br/&gt;&lt;a name="J9xI2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说说线程等待与通知？"&gt;&lt;a class="headerlink" href="#说说线程等待与通知？" title="说说线程等待与通知？"&gt;&lt;/a&gt;说说线程等待与通知？&lt;/h4&gt;&lt;p&gt;在 Object 类中有一些方法可以用于线程的等待与通知。&lt;br/&gt;①、&lt;code&gt;wait()&lt;/code&gt;：当一个线程 A 调用一个共享变量的 &lt;code&gt;wait()&lt;/code&gt; 方法时，线程 A 会被阻塞挂起，直到发生下面几种情况才会返回 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程 B 调用了共享对象 &lt;code&gt;notify()&lt;/code&gt;或者 &lt;code&gt;notifyAll()&lt;/code&gt; 方法；&lt;/li&gt;
&lt;li&gt;其他线程调用了线程 A 的 &lt;code&gt;interrupt()&lt;/code&gt; 方法，线程 A 抛出 InterruptedException 异常返回。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;②、&lt;code&gt;wait(long timeout)&lt;/code&gt; ：这个方法相比 &lt;code&gt;wait()&lt;/code&gt; 方法多了一个超时参数，它的不同之处在于，如果线程 A 调用共享对象的 &lt;code&gt;wait(long timeout)&lt;/code&gt;方法后，没有在指定的 timeout 时间内被其它线程唤醒，那么这个方法还是会因为超时而返回。&lt;br/&gt;③、&lt;code&gt;wait(long timeout, int nanos)&lt;/code&gt;，其内部调用的是 &lt;code&gt;wait(long timout)&lt;/code&gt; 方法。&lt;br/&gt;唤醒线程主要有下面两个方法：&lt;br/&gt;①、&lt;code&gt;notify()&lt;/code&gt;：一个线程 A 调用共享对象的 &lt;code&gt;notify()&lt;/code&gt; 方法后，会唤醒一个在这个共享变量上调用 wait 系列方法后被挂起的线程。&lt;br/&gt;一个共享变量上可能会有多个线程在等待，具体唤醒哪个等待的线程是随机的。&lt;br/&gt;②、&lt;code&gt;notifyAll()&lt;/code&gt;：不同于在共享变量上调用 &lt;code&gt;notify()&lt;/code&gt; 方法会唤醒被阻塞到该共享变量上的一个线程，notifyAll 方法会唤醒所有在该共享变量上调用 wait 系列方法而被挂起的线程。&lt;br/&gt;Thread 类还提供了一个 &lt;code&gt;join()&lt;/code&gt; 方法，意思是如果一个线程 A 执行了 &lt;code&gt;thread.join()&lt;/code&gt;，当前线程 A 会等待 thread 线程终止之后才从 &lt;code&gt;thread.join()&lt;/code&gt; 返回。&lt;br/&gt;&lt;a name="To7O2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说说线程休眠"&gt;&lt;a class="headerlink" href="#说说线程休眠" title="说说线程休眠"&gt;&lt;/a&gt;说说线程休眠&lt;/h4&gt;&lt;p&gt;&lt;code&gt;sleep(long millis)&lt;/code&gt;：Thread 类中的静态方法，当一个执行中的线程 A 调用了 Thread 的 sleep 方法后，线程 A 会暂时让出指定时间的执行权。&lt;br/&gt;但是线程 A 所拥有的监视器资源，比如锁，还是持有不让出的。指定的睡眠时间到了后该方法会正常返回，接着参与 CPU 的调度，获取到 CPU 资源后就可以继续运行。&lt;br/&gt;&lt;a name="wzAa4"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说说让出优先权"&gt;&lt;a class="headerlink" href="#说说让出优先权" title="说说让出优先权"&gt;&lt;/a&gt;说说让出优先权&lt;/h4&gt;&lt;p&gt;&lt;code&gt;yield()&lt;/code&gt;：Thread 类中的静态方法，当一个线程调用 yield 方法时，实际是在暗示线程调度器，当前线程请求让出自己的 CPU，但是线程调度器可能会“装看不见”忽略这个暗示。&lt;br/&gt;&lt;a name="LxDGO"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说说线程中断"&gt;&lt;a class="headerlink" href="#说说线程中断" title="说说线程中断"&gt;&lt;/a&gt;说说线程中断&lt;/h4&gt;&lt;p&gt;Java 中的线程中断是一种线程间的协作模式，通过设置线程的中断标志并不能直接终止该线程的执行。被中断的线程会根据中断状态自行处理。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;void interrupt()&lt;/code&gt; 方法：中断线程，例如，当线程 A 运行时，线程 B 可以调用线程 &lt;code&gt;interrupt()&lt;/code&gt; 方法来设置线程的中断标志为 true 并立即返回。设置标志仅仅是设置标志, 线程 B 实际并没有被中断，会继续往下执行。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boolean isInterrupted()&lt;/code&gt; 方法： 检测当前线程是否被中断。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boolean interrupted()&lt;/code&gt; 方法： 检测当前线程是否被中断，与 isInterrupted 不同的是，该方法如果发现当前线程被中断，则会清除中断标志。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了响应中断，线程的执行代码应该这样编写：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (!Thread.currentThread().isInterrupted()) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// 执行任务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 线程被中断时的清理代码&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 线程结束前的清理代码&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;stop 方法用来强制线程停止执行，目前已经处于废弃状态，因为 stop 方法会导致线程立即停止，可能会在不一致的状态下释放锁，破坏对象的一致性，导致难以发现的错误和资源泄漏。&lt;br/&gt;&lt;a name="CayJ0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="6-线程有几种状态？"&gt;&lt;a class="headerlink" href="#6-线程有几种状态？" title="6.线程有几种状态？"&gt;&lt;/a&gt;6.线程有几种状态？&lt;/h3&gt;&lt;p&gt;在 Java 中，线程共有 6 种状态：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;状态&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;NEW&lt;/td&gt;
&lt;td&gt;当线程被创建后，如通过&lt;code&gt;new Thread()&lt;/code&gt;，它处于新建状态。此时，线程已经被分配了必要的资源，但还没有开始执行。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RUNNABLE&lt;/td&gt;
&lt;td&gt;当调用线程的&lt;code&gt;start()&lt;/code&gt;方法后，线程进入可运行状态。在这个状态下，线程可能正在运行也可能正在等待获取 CPU 时间片，具体取决于线程调度器的调度策略。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOCKED&lt;/td&gt;
&lt;td&gt;线程在试图获取一个锁以进入同步块/方法时，如果锁被其他线程持有，线程将进入阻塞状态，直到它获取到锁。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WAITING&lt;/td&gt;
&lt;td&gt;线程进入等待状态是因为调用了如下方法之一：&lt;code&gt;Object.wait()&lt;/code&gt;或&lt;code&gt;LockSupport.park()&lt;/code&gt;。在等待状态下，线程需要其他线程显式地唤醒，否则不会自动执行。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIME_WAITING&lt;/td&gt;
&lt;td&gt;当线程调用带有超时参数的方法时，如&lt;code&gt;Thread.sleep(long millis)&lt;/code&gt;、&lt;code&gt;Object.wait(long timeout)&lt;/code&gt; 或&lt;code&gt;LockSupport.parkNanos()&lt;/code&gt;，它将进入超时等待状态。线程在指定的等待时间过后会自动返回可运行状态。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TERMINATED&lt;/td&gt;
&lt;td&gt;当线程的&lt;code&gt;run()&lt;/code&gt;方法执行完毕后，或者因为一个未捕获的异常终止了执行，线程进入终止状态。一旦线程终止，它的生命周期结束，不能再被重新启动。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;线程在自身的生命周期中，并不是固定地处于某个状态，而是在不同的状态之间进行切换：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-7.png"/&gt;&lt;br/&gt;&lt;a name="AUFEw"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="7-什么是线程上下文切换？"&gt;&lt;a class="headerlink" href="#7-什么是线程上下文切换？" title="7.什么是线程上下文切换？"&gt;&lt;/a&gt;7.什么是线程上下文切换？&lt;/h3&gt;&lt;p&gt;使用多线程的目的是为了充分利用 CPU，但是我们知道，并发其实是一个 CPU 来应付多个线程。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-8.png"/&gt;&lt;br/&gt;为了让用户感觉多个线程是在同时执行的， CPU 资源的分配采用了时间片轮转也就是给每个线程分配一个时间片，线程在时间片内占用 CPU 执行任务。当线程使用完时间片后，就会处于就绪状态并让出 CPU 让其他线程占用，这就是上下文切换。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-9.png"/&gt;&lt;br/&gt;&lt;a name="Wt7qj"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="线程可以被多核调度吗？"&gt;&lt;a class="headerlink" href="#线程可以被多核调度吗？" title="线程可以被多核调度吗？"&gt;&lt;/a&gt;线程可以被多核调度吗？&lt;/h4&gt;&lt;p&gt;当然可以，在现代操作系统和多核处理器的环境中，线程的调度和管理是操作系统内核的重要职责之一。&lt;br/&gt;操作系统的调度器负责将线程分配给可用的 CPU 核心，从而实现并行处理。&lt;br/&gt;多核处理器提供了并行执行多个线程的能力。每个核心可以独立执行一个或多个线程，操作系统的任务调度器会根据策略和算法，如优先级调度、轮转调度等，决定哪个线程何时在哪个核心上运行。&lt;br/&gt;&lt;a name="jVrJm"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="8-守护线程了解吗？"&gt;&lt;a class="headerlink" href="#8-守护线程了解吗？" title="8.守护线程了解吗？"&gt;&lt;/a&gt;8.守护线程了解吗？&lt;/h3&gt;&lt;p&gt;Java 中的线程分为两类，分别为 daemon 线程（守护线程）和 user 线程（用户线程）。&lt;br/&gt;在 JVM 启动时会调用 main 方法，main 方法所在的线程就是一个用户线程。其实在 JVM 内部同时还启动了很多守护线程， 比如垃圾回收线程。&lt;br/&gt;那么守护线程和用户线程有什么区别呢？区别之一是当最后一个非守护线程束时， JVM 会正常退出，而不管当前是否存在守护线程，也就是说守护线程是否结束并不影响 JVM 退出。换而言之，只要有一个用户线程还没结束，正常情况下 JVM 就不会退出。&lt;br/&gt;&lt;a name="bGlth"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="9-线程间有哪些通信方式？"&gt;&lt;a class="headerlink" href="#9-线程间有哪些通信方式？" title="9.线程间有哪些通信方式？"&gt;&lt;/a&gt;9.线程间有哪些通信方式？&lt;/h3&gt;&lt;p&gt;线程之间传递信息有多种方式，每种方式适用于不同的场景。比如说使用共享对象、&lt;code&gt;wait()&lt;/code&gt; 和 &lt;code&gt;notify()&lt;/code&gt;、Exchanger 和 CompletableFuture。&lt;br/&gt;①、&lt;strong&gt;使用共享对象&lt;/strong&gt;，多个线程可以访问和修改同一个对象，从而实现信息的传递，比如说 volatile 和 synchronized 关键字。&lt;br/&gt;&lt;a href="https://javabetter.cn/thread/volatile.html"&gt;关键字 volatile&lt;/a&gt; 用来修饰成员变量，告知程序任何对该变量的访问均需要从共享内存中获取，而对它的改变必须同步刷新回共享内存，保证所有线程对变量访问的可见性。&lt;br/&gt;&lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;关键字 synchronized&lt;/a&gt; 可以修饰方法，或者以同步代码块的形式来使用，确保多个线程在同一个时刻，只能有一个线程在执行某个方法或某个代码块。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;SharedObject&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String message;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="variable"&gt;hasMessage&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;writeMessage&lt;/span&gt;&lt;span class="params"&gt;(String message)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (hasMessage) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                wait();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.currentThread().interrupt();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.message = message;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        hasMessage = &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        notifyAll();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; String &lt;span class="title function_"&gt;readMessage&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (!hasMessage) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                wait();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.currentThread().interrupt();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        hasMessage = &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        notifyAll();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; message;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Main&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;SharedObject&lt;/span&gt; &lt;span class="variable"&gt;sharedObject&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;SharedObject&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;writer&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            sharedObject.writeMessage(&lt;span class="string"&gt;"Hello from Writer!"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;reader&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;message&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; sharedObject.readMessage();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(&lt;span class="string"&gt;"Reader received: "&lt;/span&gt; + message);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        writer.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        reader.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、**使用 wait() 和 notify()**，例如，生产者-消费者模式中，生产者生产数据，消费者消费数据，通过 &lt;code&gt;wait()&lt;/code&gt; 和 &lt;code&gt;notify()&lt;/code&gt; 方法可以实现生产和消费的协调。&lt;br/&gt;一个线程调用共享对象的 &lt;code&gt;wait()&lt;/code&gt; 方法时，它会进入该对象的等待池，并释放已经持有的该对象的锁，进入等待状态，直到其他线程调用相同对象的 &lt;code&gt;notify()&lt;/code&gt; 或 &lt;code&gt;notifyAll()&lt;/code&gt; 方法。&lt;br/&gt;一个线程调用共享对象的 &lt;code&gt;notify()&lt;/code&gt; 方法时，它会唤醒在该对象等待池中等待的一个线程，使其进入锁池，等待获取锁。&lt;br/&gt;&lt;a href="https://javabetter.cn/thread/condition.html"&gt;Condition&lt;/a&gt; 也提供了类似的方法，&lt;code&gt;await()&lt;/code&gt; 负责等待、&lt;code&gt;signal()&lt;/code&gt; 和 &lt;code&gt;signalAll()&lt;/code&gt; 负责通知。&lt;br/&gt;通常与锁（特别是 &lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;ReentrantLock&lt;/a&gt;）一起使用，为线程提供了一种等待某个条件成真的机制，并允许其他线程在该条件变化时通知等待线程。更灵活、更强大。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MessageBox&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String message;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="variable"&gt;empty&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;produce&lt;/span&gt;&lt;span class="params"&gt;(String message)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (!empty) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                wait();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.currentThread().interrupt();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empty = &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.message = message;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        notifyAll();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; String &lt;span class="title function_"&gt;consume&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (empty) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                wait();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.currentThread().interrupt();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empty = &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        notifyAll();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; message;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Main&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;MessageBox&lt;/span&gt; &lt;span class="variable"&gt;box&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;MessageBox&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;producer&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            box.produce(&lt;span class="string"&gt;"Message from producer"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;consumer&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;message&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; box.consume();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(&lt;span class="string"&gt;"Consumer received: "&lt;/span&gt; + message);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        producer.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        consumer.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;③、&lt;strong&gt;使用 Exchanger&lt;/strong&gt;，Exchanger 是一个同步点，可以在两个线程之间交换数据。一个线程调用 exchange() 方法，将数据传递给另一个线程，同时接收另一个线程的数据。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; java.util.concurrent.Exchanger;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Main&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Exchanger&amp;lt;String&amp;gt; exchanger = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Exchanger&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;thread1&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;message&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="string"&gt;"Message from thread1"&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;response&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; exchanger.exchange(message);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread1 received: "&lt;/span&gt; + response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.currentThread().interrupt();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;thread2&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;message&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="string"&gt;"Message from thread2"&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;response&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; exchanger.exchange(message);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread2 received: "&lt;/span&gt; + response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.currentThread().interrupt();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        thread1.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        thread2.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;④、&lt;strong&gt;使用 CompletableFuture&lt;/strong&gt;，CompletableFuture 是 Java 8 引入的一个类，支持异步编程，允许线程在完成计算后将结果传递给其他线程。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Main&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        CompletableFuture&amp;lt;String&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// 模拟长时间计算&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="string"&gt;"Message from CompletableFuture"&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        future.thenAccept(message -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(&lt;span class="string"&gt;"Received: "&lt;/span&gt; + message);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://javabetter.cn/zhishixingqiu/mianshi.html"&gt;Java 面试指南（付费）&lt;/a&gt;收录的华为 OD 的面试中出现过该原题。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://javabetter.cn/zhishixingqiu/mianshi.html"&gt;Java 面试指南（付费）&lt;/a&gt;收录的阿里面经同学 1 闲鱼后端一面的原题：线程之间传递信息?&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a name="W5DUp"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="10-请说说-sleep-和-wait-的区别？（补充）"&gt;&lt;a class="headerlink" href="#10-请说说-sleep-和-wait-的区别？（补充）" title="10.请说说 sleep 和 wait 的区别？（补充）"&gt;&lt;/a&gt;10.请说说 sleep 和 wait 的区别？（补充）&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;2026 年 03 月 21 日增补&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;答：&lt;code&gt;sleep()&lt;/code&gt; 和 &lt;code&gt;wait()&lt;/code&gt; 是 Java 中用于暂停当前线程的两个重要方法，sleep 是让当前线程休眠，不涉及对象类，也不需要获取对象的锁，属于 Thread 类的方法；wait 是让获得对象锁的线程实现等待，前提要获得对象的锁，属于 Object 类的方法。&lt;br/&gt;它们之间的区别主要有以下几点：&lt;br/&gt;①、所属类不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sleep()&lt;/code&gt; 方法专属于 &lt;code&gt;Thread&lt;/code&gt; 类。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait()&lt;/code&gt; 方法专属于 &lt;code&gt;Object&lt;/code&gt; 类。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;②、锁行为不同&lt;br/&gt;当线程执行 sleep 方法时，它不会释放任何锁。也就是说，如果一个线程在持有某个对象的锁时调用了 sleep，它在睡眠期间仍然会持有这个锁。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;SleepDoesNotReleaseLock&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Object&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;sleepingThread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread 1 会继续持有锁，并且进入睡眠状态"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    Thread.sleep(&lt;span class="number"&gt;5000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread 1 醒来了，并且释放了锁"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;waitingThread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread 2 进入同步代码块"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        sleepingThread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Thread.sleep(&lt;span class="number"&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        waitingThread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输出结果：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;Thread 1 会继续持有锁，并且进入睡眠状态&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread 1 醒来了，并且释放了锁&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread 2 进入同步代码块&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;从输出中我们可以看到，waitingThread 必须等待 sleepingThread 完成睡眠后才能进入同步代码块。&lt;br/&gt;而当线程执行 wait 方法时，它会释放它持有的那个对象的锁，这使得其他线程可以有机会获取该对象的锁。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WaitReleasesLock&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Object&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;waitingThread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"Thread 1 持有锁，准备等待 5 秒"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    lock.wait(&lt;span class="number"&gt;5000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"Thread 1 醒来了，并且退出同步代码块"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;notifyingThread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread 2 尝试唤醒等待中的线程"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                lock.notify();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Thread 2 执行完了 notify"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        waitingThread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Thread.sleep(&lt;span class="number"&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        notifyingThread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输出结果：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;Thread 1 持有锁，准备等待 5 秒&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread 2 尝试唤醒等待中的线程&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread 2 执行完了 notify&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Thread 1 醒来了，并且退出同步代码块&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这表明 waitingThread 在调用 wait 后确实释放了锁。&lt;br/&gt;③、使用条件不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sleep()&lt;/code&gt; 方法可以在任何地方被调用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait()&lt;/code&gt; 方法必须在同步代码块或同步方法中被调用，这是因为调用 &lt;code&gt;wait()&lt;/code&gt; 方法的前提是当前线程必须持有对象的锁。否则会抛出 &lt;code&gt;IllegalMonitorStateException&lt;/code&gt; 异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;④、唤醒方式不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用 sleep 方法后，线程会进入 TIMED_WAITING 状态（定时等待状态），即在指定的时间内暂停执行。当指定的时间结束后，线程会自动恢复到 RUNNABLE 状态（就绪状态），等待 CPU 调度再次执行。&lt;/li&gt;
&lt;li&gt;调用 wait 方法后，线程会进入 WAITING 状态（无限期等待状态），直到有其他线程在同一对象上调用 notify 或 notifyAll，线程才会从 WAITING 状态转变为 RUNNABLE 状态，准备再次获得 CPU 的执行权。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⑤、抛出异常不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sleep()&lt;/code&gt; 方法在等待期间，如果线程被中断，会抛出 &lt;code&gt;InterruptedException&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果线程被中断或等待时间到期时，&lt;code&gt;wait()&lt;/code&gt; 方法同样会在等待期间抛出 &lt;code&gt;InterruptedException&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们来通过代码再感受一下 &lt;code&gt;sleep()&lt;/code&gt; 和 &lt;code&gt;wait()&lt;/code&gt; 在用法上的区别，先看 &lt;code&gt;sleep()&lt;/code&gt; 的用法：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;SleepExample&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;thread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(&lt;span class="string"&gt;"线程准备休眠 2 秒"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Thread.sleep(&lt;span class="number"&gt;2000&lt;/span&gt;); &lt;span class="comment"&gt;// 线程将睡眠2秒&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(&lt;span class="string"&gt;"线程醒来了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        thread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;再来看 &lt;code&gt;wait()&lt;/code&gt; 的用法：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WaitExample&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Object&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;thread&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"线程准备等待 2 秒"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    lock.wait(&lt;span class="number"&gt;2000&lt;/span&gt;); &lt;span class="comment"&gt;// 线程会等待2秒，或者直到其他线程调用 lock.notify()/notifyAll()&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"线程结束等待"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        thread.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="RK0s7"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="11-线程安全，说一个使用场景？（补充）"&gt;&lt;a class="headerlink" href="#11-线程安全，说一个使用场景？（补充）" title="11.线程安全，说一个使用场景？（补充）"&gt;&lt;/a&gt;11.线程安全，说一个使用场景？（补充）&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;2026 年 05 月 01 日增补&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;线程安全是 Java 并发编程中一个非常重要的概念，它指的是多线程环境下，多个线程对共享资源的访问不会导致数据的不一致性。&lt;br/&gt;一个常见的使用场景是在实现单例模式时确保线程安全。&lt;br/&gt;单例模式确保一个类只有一个实例，并提供一个全局访问点。在多线程环境下，如果多个线程同时尝试创建实例，单例类必须确保只创建一个实例。&lt;br/&gt;饿汉式是一种比较直接的实现方式，它通过在类加载时就立即初始化单例对象来保证线程安全。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Singleton&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Singleton&lt;/span&gt; &lt;span class="variable"&gt;instance&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Singleton&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="title function_"&gt;Singleton&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; Singleton &lt;span class="title function_"&gt;getInstance&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; instance;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;懒汉式单例则在第一次使用时初始化，这种方式需要使用双重检查锁定来确保线程安全，volatile 用来保证可见性，syncronized 用来保证同步。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LazySingleton&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;volatile&lt;/span&gt; LazySingleton instance;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="title function_"&gt;LazySingleton&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; LazySingleton &lt;span class="title function_"&gt;getInstance&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (instance == &lt;span class="literal"&gt;null&lt;/span&gt;) { &lt;span class="comment"&gt;// 第一次检查&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (LazySingleton.class) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;if&lt;/span&gt; (instance == &lt;span class="literal"&gt;null&lt;/span&gt;) { &lt;span class="comment"&gt;// 第二次检查&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    instance = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;LazySingleton&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; instance;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="IBOtH"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="ThreadLocal"&gt;&lt;a class="headerlink" href="#ThreadLocal" title="ThreadLocal"&gt;&lt;/a&gt;ThreadLocal&lt;/h2&gt;&lt;p&gt;&lt;a name="FrtYg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="12-ThreadLocal-是什么？"&gt;&lt;a class="headerlink" href="#12-ThreadLocal-是什么？" title="12.ThreadLocal 是什么？"&gt;&lt;/a&gt;12.ThreadLocal 是什么？&lt;/h3&gt;&lt;p&gt;&lt;a href="https://javabetter.cn/thread/ThreadLocal.html"&gt;ThreadLocal&lt;/a&gt; 是 Java 中提供的一种用于实现线程局部变量的工具类。它允许每个线程都拥有自己的独立副本，从而实现线程隔离，用于解决多线程中共享对象的线程安全问题。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-11.png"/&gt;&lt;br/&gt;在 Web 应用中，可以使用 ThreadLocal 存储用户会话信息，这样每个线程在处理用户请求时都能方便地访问当前用户的会话信息。&lt;br/&gt;在数据库操作中，可以使用 ThreadLocal 存储数据库连接对象，每个线程有自己独立的数据库连接，从而避免了多线程竞争同一数据库连接的问题。&lt;br/&gt;在格式化操作中，例如日期格式化，可以使用 ThreadLocal 存储 SimpleDateFormat 实例，避免多线程共享同一实例导致的线程安全问题。&lt;br/&gt;使用 ThreadLocal 通常分为四步：&lt;br/&gt;①、创建 ThreadLocal&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//创建一个ThreadLocal变量&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; ThreadLocal&amp;lt;String&amp;gt; localVariable = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadLocal&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、设置 ThreadLocal 的值&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//设置ThreadLocal变量的值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;localVariable.set(&lt;span class="string"&gt;"沉默王二是沙雕"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;③、获取 ThreadLocal 的值&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//获取ThreadLocal变量的值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;value&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; localVariable.get();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;④、删除 ThreadLocal 的值&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//删除ThreadLocal变量的值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;localVariable.remove();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="KpXJD"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="除了-ThreadLocal，还有什么解决线程安全问题的方法？"&gt;&lt;a class="headerlink" href="#除了-ThreadLocal，还有什么解决线程安全问题的方法？" title="除了 ThreadLocal，还有什么解决线程安全问题的方法？"&gt;&lt;/a&gt;除了 ThreadLocal，还有什么解决线程安全问题的方法？&lt;/h4&gt;&lt;p&gt;①、Java 中的 synchronized 关键字可以用于方法和代码块，确保同一时间只有一个线程可以执行特定的代码段。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;method&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 线程安全的操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、Java 并发包（java.util.concurrent.locks）中提供了 Lock 接口和一些实现类，如 ReentrantLock。相比于 synchronized，ReentrantLock 提供了公平锁和非公平锁。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ReentrantLock&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;method&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    lock.lock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 线程安全的操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        lock.unlock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;③、Java 并发包还提供了一组原子变量类（如 AtomicInteger，AtomicLong 等），它们利用 CAS（比较并交换），实现了无锁的原子操作，适用于简单的计数器场景。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;AtomicInteger&lt;/span&gt; &lt;span class="variable"&gt;atomicInteger&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;AtomicInteger&lt;/span&gt;(&lt;span class="number"&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;increment&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    atomicInteger.incrementAndGet();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;④、Java 并发包提供了一些线程安全的集合类，如 ConcurrentHashMap，CopyOnWriteArrayList 等。这些集合类内部实现了必要的同步策略，提供了更高效的并发访问。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;ConcurrentHashMap&amp;lt;String, String&amp;gt; map = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ConcurrentHashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;⑤、volatile 变量保证了变量的可见性，修改操作是立即同步到主存的，读操作从主存中读取。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;volatile&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="variable"&gt;flag&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="UNHik"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="13-你在工作中用到过-ThreadLocal-吗？"&gt;&lt;a class="headerlink" href="#13-你在工作中用到过-ThreadLocal-吗？" title="13.你在工作中用到过 ThreadLocal 吗？"&gt;&lt;/a&gt;13.你在工作中用到过 ThreadLocal 吗？&lt;/h3&gt;&lt;p&gt;有用到过，用来存储用户信息。&lt;br/&gt;&lt;a href="https://javabetter.cn/zhishixingqiu/paicoding.html"&gt;技术派实战项目&lt;/a&gt;是典型的 MVC 架构，登录后的用户每次访问接口，都会在请求头中携带一个 token，在控制层可以根据这个 token，解析出用户的基本信息。&lt;br/&gt;假如在服务层和持久层也要用到用户信息，就可以在控制层拦截请求把用户信息存入 ThreadLocal。&lt;br/&gt;这样我们在任何一个地方，都可以取出 ThreadLocal 中存的用户信息。&lt;br/&gt;很多其它场景的 cookie、session 等等数据隔离都可以通过 ThreadLocal 去实现。&lt;br/&gt;数据库连接池也可以用 ThreadLocal，将数据库连接池的连接交给 ThreadLocal 进行管理，能够保证当前线程的操作都是同一个 Connnection。&lt;br/&gt;&lt;a name="UBzMw"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="14-ThreadLocal-怎么实现的呢？"&gt;&lt;a class="headerlink" href="#14-ThreadLocal-怎么实现的呢？" title="14.ThreadLocal 怎么实现的呢？"&gt;&lt;/a&gt;14.ThreadLocal 怎么实现的呢？&lt;/h3&gt;&lt;p&gt;ThreadLocal 本身并不存储任何值，它只是作为一个映射，来映射线程的局部变量。当一个线程调用 ThreadLocal 的 set 或 get 方法时，实际上是访问线程自己的 ThreadLocal.ThreadLocalMap。&lt;br/&gt;ThreadLocalMap 是 ThreadLocal 的静态内部类，它内部维护了一个 Entry 数组，key 是 ThreadLocal 对象，value 是线程的局部变量本身。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-13.png"/&gt;&lt;br/&gt;早期的 ThreadLocal 不是这样的，它的 ThreadLocalMap 中使用 Thread 作为 key，这也是最简单的实现方式。优化后的方案有两个好处，一个是 Map 中存储的键值对变少了；另一个是 ThreadLocalMap 的生命周期和线程一样长，线程销毁的时候，ThreadLocalMap 也会被销毁。&lt;br/&gt;Entry 继承了 WeakReference，它限定了 key 是一个弱引用，弱引用的好处是当内存不足时，JVM 会回收 ThreadLocal 对象，并且将其对应的 Entry 的 value 设置为 null，这样在很大程度上可以避免内存泄漏。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Entry&lt;/span&gt; &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;WeakReference&lt;/span&gt;&amp;lt;ThreadLocal&amp;lt;?&amp;gt;&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/** The value associated with this ThreadLocal. */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Object value;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//节点类&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Entry(ThreadLocal&amp;lt;?&amp;gt; k, Object v) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//key赋值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;super&lt;/span&gt;(k);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//value赋值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        value = v;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;ThreadLocal 的实现原理就是，每个线程维护一个 Map，key 为 ThreadLocal 对象，value 为想要实现线程隔离的对象。&lt;br/&gt;1、当需要存线程隔离的对象时，通过 ThreadLocal 的 set 方法将对象存入 Map 中。&lt;br/&gt;2、当需要取线程隔离的对象时，通过 ThreadLocal 的 get 方法从 Map 中取出对象。&lt;br/&gt;3、Map 的大小由 ThreadLocal 对象的多少决定。&lt;a name="zklfM"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="什么是弱引用，什么是强引用？"&gt;&lt;a class="headerlink" href="#什么是弱引用，什么是强引用？" title="什么是弱引用，什么是强引用？"&gt;&lt;/a&gt;什么是弱引用，什么是强引用？&lt;/h4&gt;&lt;p&gt;强引用，比如说 &lt;code&gt;User user = new User("沉默王二")&lt;/code&gt; 中，user 就是一个强引用，&lt;code&gt;new User("沉默王二")&lt;/code&gt; 就是一个强引用对象。&lt;br/&gt;当 user 被置为 null 时（&lt;code&gt;user = null&lt;/code&gt;），&lt;code&gt;new User("沉默王二")&lt;/code&gt; 将会被垃圾回收；如果 user 不被置为 null，即便是内存空间不足，JVM 也不会回收 &lt;code&gt;new User("沉默王二")&lt;/code&gt; 这个强引用对象，宁愿抛出 OutOfMemoryError。&lt;br/&gt;弱引用，比如说下面这段代码：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;ThreadLocal&amp;lt;User&amp;gt; userThreadLocal = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadLocal&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;userThreadLocal.set(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;User&lt;/span&gt;(&lt;span class="string"&gt;"沉默王二"&lt;/span&gt;));&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;①、userThreadLocal 是一个强引用，&lt;code&gt;new ThreadLocal&amp;lt;&amp;gt;()&lt;/code&gt; 是一个强引用对象；&lt;br/&gt;②、&lt;code&gt;new User("沉默王二")&lt;/code&gt; 是一个强引用对象。&lt;br/&gt;③、在 ThreadLocalMap 中，&lt;code&gt;key = new ThreadLocal&amp;lt;&amp;gt;()&lt;/code&gt; 是一个弱引用对象。当 JVM 进行垃圾回收时，如果发现了弱引用对象，就会将其回收。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-14.png"/&gt;&lt;br/&gt;其关系链就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ThreadLocal 强引用 -&amp;gt; ThreadLocal 对象。&lt;/li&gt;
&lt;li&gt;Thread 强引用 -&amp;gt; ThreadLocalMap。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ThreadLocalMap[i]&lt;/code&gt; 强引用了 -&amp;gt; Entry。&lt;/li&gt;
&lt;li&gt;Entry.key 弱引用 -&amp;gt; ThreadLocal 对象。&lt;/li&gt;
&lt;li&gt;Entry.value 强引用 -&amp;gt; 线程的局部变量对象。&lt;br/&gt;&lt;a name="QBSyz"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="15-ThreadLocal-内存泄露是怎么回事？"&gt;&lt;a class="headerlink" href="#15-ThreadLocal-内存泄露是怎么回事？" title="15.ThreadLocal 内存泄露是怎么回事？"&gt;&lt;/a&gt;15.ThreadLocal 内存泄露是怎么回事？&lt;/h3&gt;&lt;p&gt;通常情况下，随着线程 Thread 的结束，其内部的 ThreadLocalMap 也会被回收，从而避免了内存泄漏。&lt;br/&gt;但如果一个线程一直在运行，并且其 &lt;code&gt;ThreadLocalMap&lt;/code&gt; 中的 Entry.value 一直指向某个强引用对象，那么这个对象就不会被回收，从而导致内存泄漏。当 Entry 非常多时，可能就会引发更严重的内存溢出问题。&lt;a name="q8zaj"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="那怎么解决内存泄漏问题呢？"&gt;&lt;a class="headerlink" href="#那怎么解决内存泄漏问题呢？" title="那怎么解决内存泄漏问题呢？"&gt;&lt;/a&gt;那怎么解决内存泄漏问题呢？&lt;/h4&gt;&lt;p&gt;很简单，使用完 ThreadLocal 后，及时调用 &lt;code&gt;remove()&lt;/code&gt; 方法释放内存空间。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    threadLocal.set(value);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 执行业务操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;} &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    threadLocal.remove(); &lt;span class="comment"&gt;// 确保能够执行清理&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;remove()&lt;/code&gt; 方法会将当前线程的 ThreadLocalMap 中的所有 key 为 null 的 Entry 全部清除，这样就能避免内存泄漏问题。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;remove&lt;/span&gt;&lt;span class="params"&gt;(ThreadLocal&amp;lt;?&amp;gt; key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Entry[] tab = table;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;len&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; tab.length;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; key.threadLocalHashCode &amp;amp; (len-&lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;Entry&lt;/span&gt; &lt;span class="variable"&gt;e&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; tab[i];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            e != &lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            e = tab[i = nextIndex(i, len)]) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (e.get() == key) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            e.clear();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            expungeStaleEntry(i);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;clear&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;this&lt;/span&gt;.referent = &lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="eJezc"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="那为什么-key-要设计成弱引用？"&gt;&lt;a class="headerlink" href="#那为什么-key-要设计成弱引用？" title="那为什么 key 要设计成弱引用？"&gt;&lt;/a&gt;那为什么 key 要设计成弱引用？&lt;/h4&gt;&lt;p&gt;弱引用的好处是，当内存不足的时候，JVM 会主动回收掉弱引用的对象。&lt;br/&gt;比如说：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;WeakReference&lt;/span&gt; &lt;span class="variable"&gt;key&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;WeakReference&lt;/span&gt;(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadLocal&lt;/span&gt;());&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;key 是弱引用，&lt;code&gt;new WeakReference(new ThreadLocal())&lt;/code&gt; 是弱引用对象，当 JVM 进行垃圾回收时，如果发现了弱引用对象，就会将其回收。&lt;br/&gt;一旦 key 被回收，ThreadLocalMap 在进行 set、get 的时候就会对 key 为 null 的 Entry 进行清理。总结一下，在 ThreadLocal 被垃圾收集后，下一次访问 ThreadLocalMap 时，Java 会自动清理那些键为 null 的条目（参照源码中的 replaceStaleEntry 方法），这个过程会在执行 ThreadLocalMap 相关操作（如 &lt;code&gt;get()&lt;/code&gt;, &lt;code&gt;set()&lt;/code&gt;, &lt;code&gt;remove()&lt;/code&gt;）时触发。&lt;br/&gt;&lt;a name="M5eXD"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="你了解哪些-ThreadLocal-的改进方案？"&gt;&lt;a class="headerlink" href="#你了解哪些-ThreadLocal-的改进方案？" title="你了解哪些 ThreadLocal 的改进方案？"&gt;&lt;/a&gt;你了解哪些 ThreadLocal 的改进方案？&lt;/h4&gt;&lt;p&gt;在 JDK 20 Early-Access Build 28 版本中，出现了 ThreadLocal 的改进方案，即 &lt;code&gt;ScopedValue&lt;/code&gt;。&lt;br/&gt;还有 Netty 中的 FastThreadLocal，它是 Netty 对 ThreadLocal 的优化，它内部维护了一个索引常量 index，每次创建 FastThreadLocal 中都会自动+1，用来取代 hash 冲突带来的损耗，用空间换时间。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; index;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;FastThreadLocal&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    index = InternalThreadLocalMap.nextVariableIndex();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;nextVariableIndex&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;index&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; nextIndex.getAndIncrement();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (index &amp;lt; &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        nextIndex.decrementAndGet();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; index;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="u5Yfg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="16-ThreadLocalMap-的源码看过吗？"&gt;&lt;a class="headerlink" href="#16-ThreadLocalMap-的源码看过吗？" title="16.ThreadLocalMap 的源码看过吗？"&gt;&lt;/a&gt;16.ThreadLocalMap 的源码看过吗？&lt;/h3&gt;&lt;p&gt;ThreadLocalMap 虽然被叫做 Map，其实它是没有实现 Map 接口的，但是结构还是和 HashMap 比较类似的，主要关注的是两个要素：&lt;code&gt;元素数组&lt;/code&gt;和&lt;code&gt;散列方法&lt;/code&gt;。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-15.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;元素数组一个 table 数组，存储 Entry 类型的元素，Entry 是 ThreaLocal 弱引用作为 key，Object 作为 value 的结构。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; Entry[] table;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;散列方法散列方法就是怎么把对应的 key 映射到 table 数组的相应下标，ThreadLocalMap 用的是哈希取余法，取出 key 的 threadLocalHashCode，然后和 table 数组长度减一&amp;amp;运算（相当于取余）。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; key.threadLocalHashCode &amp;amp; (table.length - &lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这里的 threadLocalHashCode 计算有点东西，每创建一个 ThreadLocal 对象，它就会新增&lt;code&gt;0x61c88647&lt;/code&gt;，这个值很特殊，它是&lt;strong&gt;斐波那契数&lt;/strong&gt; 也叫 &lt;strong&gt;黄金分割数&lt;/strong&gt;。&lt;code&gt;hash&lt;/code&gt;增量为 这个数字，带来的好处就是 &lt;code&gt;hash&lt;/code&gt; &lt;strong&gt;分布非常均匀&lt;/strong&gt;。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;HASH_INCREMENT&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0x61c88647&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;nextHashCode&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; nextHashCode.getAndAdd(HASH_INCREMENT);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="l4mVp"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="17-ThreadLocalMap-怎么解决-Hash-冲突的？"&gt;&lt;a class="headerlink" href="#17-ThreadLocalMap-怎么解决-Hash-冲突的？" title="17.ThreadLocalMap 怎么解决 Hash 冲突的？"&gt;&lt;/a&gt;17.ThreadLocalMap 怎么解决 Hash 冲突的？&lt;/h3&gt;&lt;p&gt;我们可能都知道 HashMap 使用了链表来解决冲突，也就是所谓的链地址法。&lt;br/&gt;ThreadLocalMap 没有使用链表，自然也不是用链地址法来解决冲突了，它用的是另外一种方式——&lt;strong&gt;开放定址法&lt;/strong&gt;。开放定址法是什么意思呢？简单来说，就是这个坑被人占了，那就接着去找空着的坑。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-16.png"/&gt;&lt;br/&gt;如上图所示，如果我们插入一个 value=27 的数据，通过 hash 计算后应该落入第 4 个槽位中，而槽位 4 已经有了 Entry 数据，而且 Entry 数据的 key 和当前不相等。此时就会线性向后查找，一直找到 Entry 为 null 的槽位才会停止查找，把元素放到空的槽中。&lt;br/&gt;在 get 的时候，也会根据 ThreadLocal 对象的 hash 值，定位到 table 中的位置，然后判断该槽位 Entry 对象中的 key 是否和 get 的 key 一致，如果不一致，就判断下一个位置。&lt;br/&gt;&lt;a name="xDx9g"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="18-ThreadLocalMap-扩容机制了解吗？"&gt;&lt;a class="headerlink" href="#18-ThreadLocalMap-扩容机制了解吗？" title="18.ThreadLocalMap 扩容机制了解吗？"&gt;&lt;/a&gt;18.ThreadLocalMap 扩容机制了解吗？&lt;/h3&gt;&lt;p&gt;在 ThreadLocalMap.set()方法的最后，如果执行完启发式清理工作后，未清理到任何数据，且当前散列数组中&lt;code&gt;Entry&lt;/code&gt;的数量已经达到了列表的扩容阈值&lt;code&gt;(len*2/3)&lt;/code&gt;，就开始执行&lt;code&gt;rehash()&lt;/code&gt;逻辑：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; (!cleanSomeSlots(i, sz) &amp;amp;&amp;amp; sz &amp;gt;= threshold)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    rehash();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;再着看 rehash()具体实现：这里会先去清理过期的 Entry，然后还要根据条件判断&lt;code&gt;size &amp;gt;= threshold - threshold / 4&lt;/code&gt; 也就是&lt;code&gt;size &amp;gt;= threshold* 3/4&lt;/code&gt;来决定是否需要扩容。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;rehash&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//清理过期Entry&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    expungeStaleEntries();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//扩容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (size &amp;gt;= threshold - threshold / &lt;span class="number"&gt;4&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        resize();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//清理过期Entry&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;expungeStaleEntries&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Entry[] tab = table;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;len&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; tab.length;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;j&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;; j &amp;lt; len; j++) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Entry&lt;/span&gt; &lt;span class="variable"&gt;e&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; tab[j];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (e != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; e.get() == &lt;span class="literal"&gt;null&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            expungeStaleEntry(j);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;接着看看具体的&lt;code&gt;resize()&lt;/code&gt;方法，扩容后的&lt;code&gt;newTab&lt;/code&gt;的大小为老数组的两倍，然后遍历老的 table 数组，散列方法重新计算位置，开放地址解决冲突，然后放到新的&lt;code&gt;newTab&lt;/code&gt;，遍历完成之后，&lt;code&gt;oldTab&lt;/code&gt;中所有的&lt;code&gt;entry&lt;/code&gt;数据都已经放入到&lt;code&gt;newTab&lt;/code&gt;中了，然后 table 引用指向&lt;code&gt;newTab&lt;/code&gt;&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-17.png"/&gt;&lt;br/&gt;&lt;a name="Ccihf"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="19-父子线程怎么共享数据？"&gt;&lt;a class="headerlink" href="#19-父子线程怎么共享数据？" title="19.父子线程怎么共享数据？"&gt;&lt;/a&gt;19.父子线程怎么共享数据？&lt;/h3&gt;&lt;p&gt;父线程能用 ThreadLocal 来给子线程传值吗？毫无疑问，不能。那该怎么办？&lt;br/&gt;这时候可以用到另外一个类——&lt;code&gt;InheritableThreadLocal &lt;/code&gt;。&lt;br/&gt;使用起来很简单，在主线程的 InheritableThreadLocal 实例设置值，在子线程中就可以拿到了。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;InheritableThreadLocalTest&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;ThreadLocal&lt;/span&gt; &lt;span class="variable"&gt;threadLocal&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;InheritableThreadLocal&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 主线程&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        threadLocal.set(&lt;span class="string"&gt;"不擅技术"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//子线程&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Thread&lt;/span&gt; &lt;span class="variable"&gt;t&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;() {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="built_in"&gt;super&lt;/span&gt;.run();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"鄙人三某 ，"&lt;/span&gt; + threadLocal.get());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        t.start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;那原理是什么呢？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原理很简单，在 Thread 类里还有另外一个变量：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;ThreadLocal.&lt;span class="type"&gt;ThreadLocalMap&lt;/span&gt; &lt;span class="variable"&gt;inheritableThreadLocals&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在 Thread.init 的时候，如果父线程的&lt;code&gt;inheritableThreadLocals&lt;/code&gt;不为空，就把它赋给当前线程（子线程）的&lt;code&gt;inheritableThreadLocals &lt;/code&gt;。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; (inheritThreadLocals &amp;amp;&amp;amp; parent.inheritableThreadLocals != &lt;span class="literal"&gt;null&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;this&lt;/span&gt;.inheritableThreadLocals =&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="N6dCa"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="Java-内存模型"&gt;&lt;a class="headerlink" href="#Java-内存模型" title="Java 内存模型"&gt;&lt;/a&gt;Java 内存模型&lt;/h2&gt;&lt;p&gt;&lt;a name="Ykt8F"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="20-说一下你对-Java-内存模型的理解？"&gt;&lt;a class="headerlink" href="#20-说一下你对-Java-内存模型的理解？" title="20.说一下你对 Java 内存模型的理解？"&gt;&lt;/a&gt;20.说一下你对 Java 内存模型的理解？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://javabetter.cn/thread/jmm.html"&gt;说说 Java 的内存模型&lt;/a&gt;&lt;br/&gt;Java 内存模型（Java Memory Model）是一种抽象的模型，简称 JMM，主要用来定义多线程中变量的访问规则，用来解决变量的可见性、有序性和原子性问题，确保在并发环境中安全地访问共享变量。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/jmm-f02219aa-e762-4df0-ac08-6f4cceb535c2.jpeg"/&gt;&lt;br/&gt;JMM 定义了线程内存和主内存之间的抽象关系：线程之间的共享变量存储在&lt;code&gt;主内存&lt;/code&gt;（Main Memory）中，每个线程都有一个私有的&lt;code&gt;本地内存&lt;/code&gt;（Local Memory），本地内存中存储了共享变量的副本，用来进行线程内部的读写操作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当一个线程更改了本地内存中共享变量的副本后，它需要将这些更改刷新到主内存中，以确保其他线程可以看到这些更改。&lt;/li&gt;
&lt;li&gt;当一个线程需要读取共享变量时，它可能首先从本地内存中读取。如果本地内存中的副本是过时的，线程将从主内存中重新加载共享变量的最新值到本地内存中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本地内存是 JMM 中的一个抽象概念，并不真实存在。实际上，本地内存可能对应于 CPU 缓存、寄存器或者其他硬件和编译器优化。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-20.png"/&gt;&lt;br/&gt;对于一个双核 CPU 的系统架构，每个核都有自己的控制器和运算器，其中控制器包含一组寄存器和操作控制器，运算器执行算术逻辅运算。&lt;br/&gt;每个核都有自己的一级缓存，在有些架构里面还有一个所有 CPU 共享的二级缓存。&lt;br/&gt;Java 内存模型里面的本地内存，可能对应的是 L1 缓存或者 L2 缓存或者 CPU 寄存器。&lt;br/&gt;&lt;a name="NFqcs"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="为什么线程要用自己的内存？"&gt;&lt;a class="headerlink" href="#为什么线程要用自己的内存？" title="为什么线程要用自己的内存？"&gt;&lt;/a&gt;为什么线程要用自己的内存？&lt;/h4&gt;&lt;p&gt;第一，在多线程环境中，如果所有线程都直接操作主内存中的共享变量，会引发更多的内存访问竞争，这不仅影响性能，还增加了线程安全问题的复杂度。通过让每个线程使用本地内存，可以减少对主内存的直接访问和竞争，从而提高程序的并发性能。&lt;br/&gt;第二，现代 CPU 为了优化执行效率，可能会对指令进行乱序执行（指令重排序）。使用本地内存（CPU 缓存和寄存器）可以在不影响最终执行结果的前提下，使得 CPU 有更大的自由度来乱序执行指令，从而提高执行效率。&lt;br/&gt;&lt;a name="IqiCC"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="21-说说你对原子性、可见性、有序性的理解？"&gt;&lt;a class="headerlink" href="#21-说说你对原子性、可见性、有序性的理解？" title="21.说说你对原子性、可见性、有序性的理解？"&gt;&lt;/a&gt;21.说说你对原子性、可见性、有序性的理解？&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原子性&lt;/strong&gt;：指的是一个操作是不可分割的，要么全部执行成功，要么完全不执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可见性&lt;/strong&gt;：指的是一个线程对共享变量的修改，能够被其他线程及时看见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有序性&lt;/strong&gt;：指的是程序代码的执行顺序与代码中的顺序一致。在没有同步机制的情况下，编译器可能会对指令进行重排序，以优化性能。这种重排序可能会导致多线程的执行结果与预期不符。&lt;br/&gt;&lt;a name="yjNaR"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="原子性、可见性、有序性都应该怎么保证呢？"&gt;&lt;a class="headerlink" href="#原子性、可见性、有序性都应该怎么保证呢？" title="原子性、可见性、有序性都应该怎么保证呢？"&gt;&lt;/a&gt;原子性、可见性、有序性都应该怎么保证呢？&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;原子性：JMM 只能保证基本的原子性，如果要保证一个代码块的原子性，需要使用&lt;code&gt;synchronized &lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;可见性：Java 是利用&lt;code&gt;volatile&lt;/code&gt;关键字来保证可见性的，除此之外，&lt;code&gt;final&lt;/code&gt;和&lt;code&gt;synchronized&lt;/code&gt;也能保证可见性。&lt;/li&gt;
&lt;li&gt;有序性：&lt;code&gt;synchronized&lt;/code&gt;或者&lt;code&gt;volatile&lt;/code&gt;都可以保证多线程之间操作的有序性。&lt;br/&gt;&lt;a name="f8mxL"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="i-是原子操作吗？"&gt;&lt;a class="headerlink" href="#i-是原子操作吗？" title="i++是原子操作吗？"&gt;&lt;/a&gt;i++是原子操作吗？&lt;/h4&gt;&lt;p&gt;i++ 不是一个原子操作，它包括三个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从内存中读取 i 的值。&lt;/li&gt;
&lt;li&gt;对 i 进行加 1 操作。&lt;/li&gt;
&lt;li&gt;将新的值写入内存。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;假如两个线程同时对 i 进行 i++ 操作时，可能会发生以下情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;线程 A 读取 i 的值（假设 i 的初始值为 1）。&lt;/li&gt;
&lt;li&gt;线程 B 也读取 i 的值（值仍然是 1）。&lt;/li&gt;
&lt;li&gt;线程 A 将 i 增加到 2，并将其写回内存。&lt;/li&gt;
&lt;li&gt;线程 B 也将 i 增加到 2，并将其写回内存。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;尽管进行了两次递增操作，i 的值只增加了 1 而不是 2。可以使用 synchronized 或 AtomicInteger 确保操作的原子性。&lt;br/&gt;&lt;a name="GdE4p"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="22-那说说什么是指令重排？"&gt;&lt;a class="headerlink" href="#22-那说说什么是指令重排？" title="22.那说说什么是指令重排？"&gt;&lt;/a&gt;22.那说说什么是指令重排？&lt;/h3&gt;&lt;p&gt;在执行程序时，为了提高性能，编译器和处理器常常会对指令做重排序。重排序分 3 种类型。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;编译器优化的重排序。编译器在不改变单线程程序语义的前提下，可以重新安排语句的执行顺序。&lt;/li&gt;
&lt;li&gt;指令级并行的重排序。现代处理器采用了指令级并行技术（Instruction-Level Parallelism，ILP）来将多条指令重叠执行。如果不存在数据依赖性，处理器可以改变语句对应 机器指令的执行顺序。&lt;/li&gt;
&lt;li&gt;内存系统的重排序。由于处理器使用缓存和读/写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从 Java 源代码到最终实际执行的指令序列，会分别经历下面 3 种重排序，如图：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-21.png"/&gt;&lt;br/&gt;我们比较熟悉的双重校验单例模式就是一个经典的指令重排的例子，&lt;code&gt;Singleton instance=new Singleton()；&lt;/code&gt;对应的 JVM 指令分为三步：分配内存空间–&amp;gt;初始化对象—&amp;gt;对象指向分配的内存空间，但是经过了编译器的指令重排序，第二步和第三步就可能会重排序。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-22.png"/&gt;&lt;br/&gt;JMM 属于语言级的内存模型，它确保在不同的编译器和不同的处理器平台之上，通过禁止特定类型的编译器重排序和处理器重排序，为程序员提供一致的内存可见性保证。&lt;br/&gt;&lt;a name="LxXOf"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="23-指令重排有限制吗？happens-before-了解吗？"&gt;&lt;a class="headerlink" href="#23-指令重排有限制吗？happens-before-了解吗？" title="23.指令重排有限制吗？happens-before 了解吗？"&gt;&lt;/a&gt;23.指令重排有限制吗？happens-before 了解吗？&lt;/h3&gt;&lt;p&gt;指令重排也是有一些限制的，有两个规则&lt;code&gt;happens-before&lt;/code&gt;和&lt;code&gt;as-if-serial&lt;/code&gt;来约束。&lt;br/&gt;happens-before 的定义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果一个操作 happens-before 另一个操作，那么第一个操作的执行结果将对第二个操作可见，而且第一个操作的执行顺序排在第二个操作之前。&lt;/li&gt;
&lt;li&gt;两个操作之间存在 happens-before 关系，并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果，与按 happens-before 关系来执行的结果一致，那么这种重排序并不非法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;happens-before 和我们息息相关的有六大规则：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-23.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;程序顺序规则&lt;/strong&gt;：一个线程中的每个操作，happens-before 于该线程中的任意后续操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监视器锁规则&lt;/strong&gt;：对一个锁的解锁，happens-before 于随后对这个锁的加锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;volatile 变量规则&lt;/strong&gt;：对一个 volatile 域的写，happens-before 于任意后续对这个 volatile 域的读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递性&lt;/strong&gt;：如果 A happens-before B，且 B happens-before C，那么 A happens-before C。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;start()规则&lt;/strong&gt;：如果线程 A 执行操作 ThreadB.start()（启动线程 B），那么 A 线程的 ThreadB.start()操作 happens-before 于线程 B 中的任意操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;join()规则&lt;/strong&gt;：如果线程 A 执行操作 ThreadB.join()并成功返回，那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回。&lt;br/&gt;&lt;a name="pBa34"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="24-as-if-serial-又是什么？单线程的程序一定是顺序的吗？"&gt;&lt;a class="headerlink" href="#24-as-if-serial-又是什么？单线程的程序一定是顺序的吗？" title="24.as-if-serial 又是什么？单线程的程序一定是顺序的吗？"&gt;&lt;/a&gt;24.as-if-serial 又是什么？单线程的程序一定是顺序的吗？&lt;/h3&gt;&lt;p&gt;as-if-serial 语义的意思是：不管怎么重排序（编译器和处理器为了提高并行度），&lt;strong&gt;单线程程序的执行结果不能被改变&lt;/strong&gt;。编译器、runtime 和处理器都必须遵守 as-if-serial 语义。&lt;br/&gt;为了遵守 as-if-serial 语义，编译器和处理器不会对存在数据依赖关系的操作做重排序，因为这种重排序会改变执行结果。但是，如果操作之间不存在数据依赖关系，这些操作就可能被编译器和处理器重排序。为了具体说明，请看下面计算圆面积的代码示例。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;double&lt;/span&gt; &lt;span class="variable"&gt;pi&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;3.14&lt;/span&gt;;   &lt;span class="comment"&gt;// A&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;double&lt;/span&gt; &lt;span class="variable"&gt;r&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1.0&lt;/span&gt;;   &lt;span class="comment"&gt;// B&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;double&lt;/span&gt; &lt;span class="variable"&gt;area&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; pi * r * r;   &lt;span class="comment"&gt;// C&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;上面 3 个操作的数据依赖关系：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-24.png"/&gt;&lt;br/&gt;A 和 C 之间存在数据依赖关系，同时 B 和 C 之间也存在数据依赖关系。因此在最终执行的指令序列中，C 不能被重排序到 A 和 B 的前面（C 排到 A 和 B 的前面，程序的结果将会被改变）。但 A 和 B 之间没有数据依赖关系，编译器和处理器可以重排序 A 和 B 之间的执行顺序。&lt;br/&gt;所以最终，程序可能会有两种执行顺序：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-25.png"/&gt;&lt;br/&gt;as-if-serial 语义把单线程程序保护了起来，遵守 as-if-serial 语义的编译器、runtime 和处理器共同编织了这么一个“楚门的世界”：单线程程序是按程序的“顺序”来执行的。as- if-serial 语义使单线程情况下，我们不需要担心重排序的问题，可见性的问题。&lt;br/&gt;&lt;a name="J62kQ"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="25-volatile-实现原理了解吗？"&gt;&lt;a class="headerlink" href="#25-volatile-实现原理了解吗？" title="25.volatile 实现原理了解吗？"&gt;&lt;/a&gt;25.volatile 实现原理了解吗？&lt;/h3&gt;&lt;p&gt;volatile 关键字主要有两个作用，一个是保证变量的内存可见性，一个是禁止指令重排序。&lt;br/&gt;&lt;a name="ZKPT7"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="volatile-怎么保证可见性的呢？"&gt;&lt;a class="headerlink" href="#volatile-怎么保证可见性的呢？" title="volatile 怎么保证可见性的呢？"&gt;&lt;/a&gt;volatile 怎么保证可见性的呢？&lt;/h4&gt;&lt;p&gt;当一个变量被声明为 volatile 时，Java 内存模型会确保所有线程看到该变量时的值是一致的。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/jmm-f02219aa-e762-4df0-ac08-6f4cceb535c2.jpeg"/&gt;&lt;br/&gt;也就是说，当线程对 volatile 变量进行写操作时，JMM 会在写入这个变量之后插入一个 Store-Barrier（写屏障）指令，这个指令会强制将本地内存中的变量值刷新到主内存中。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-28.png"/&gt;&lt;br/&gt;当线程对 volatile 变量进行读操作时，JMM 会插入一个 Load-Barrier（读屏障）指令，这个指令会强制让本地内存中的变量值失效，从而重新从主内存中读取最新的值。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-29.png"/&gt;&lt;br/&gt;例如，我们声明一个 volatile 变量 x：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;volatile&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;x&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;线程 A 对 x 写入后会将其最新的值刷新到主内存中，线程 B 读取 x 时由于本地内存中的 x 失效了，就会从主内存中读取最新的值，内存可见性达成！&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-26.png"/&gt;&lt;br/&gt;&lt;a name="QqUeL"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="volatile-怎么保证有序性的呢？"&gt;&lt;a class="headerlink" href="#volatile-怎么保证有序性的呢？" title="volatile 怎么保证有序性的呢？"&gt;&lt;/a&gt;volatile 怎么保证有序性的呢？&lt;/h4&gt;&lt;p&gt;在程序执行期间，为了提高性能，编译器和处理器会对指令进行重排序。但涉及到 volatile 变量时，它们必须遵循一定的规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写 volatile 变量的操作之前的操作不会被编译器重排序到写操作之后。&lt;/li&gt;
&lt;li&gt;读 volatile 变量的操作之后的操作不会被编译器重排序到读操作之前。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着 volatile 变量的写操作总是发生在任何后续读操作之前。&lt;br/&gt;&lt;a name="Y1iyq"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="volatile-和-synchronized-的区别"&gt;&lt;a class="headerlink" href="#volatile-和-synchronized-的区别" title="volatile 和 synchronized 的区别"&gt;&lt;/a&gt;volatile 和 synchronized 的区别&lt;/h4&gt;&lt;p&gt;volatile 关键字用于修饰变量，确保该变量的更新操作对所有线程是可见的，即一旦某个线程修改了 volatile 变量，其他线程会立即看到最新的值。&lt;br/&gt;synchronized 关键字用于修饰方法或代码块，确保同一时刻只有一个线程能够执行该方法或代码块，从而实现互斥访问。&lt;br/&gt;&lt;a name="xmrsW"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="volatile-加在基本类型和对象上的区别？"&gt;&lt;a class="headerlink" href="#volatile-加在基本类型和对象上的区别？" title="volatile 加在基本类型和对象上的区别？"&gt;&lt;/a&gt;volatile 加在基本类型和对象上的区别？&lt;/h4&gt;&lt;p&gt;当 &lt;code&gt;volatile&lt;/code&gt; 用于基本数据类型时，能确保该变量的读写操作是直接从主内存中读取或写入的。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;volatile&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;count&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;当 &lt;code&gt;volatile&lt;/code&gt; 用于引用类型时，它确保引用本身的可见性，即确保引用指向的对象地址是最新的。&lt;br/&gt;但是，&lt;code&gt;volatile&lt;/code&gt; 并不能保证引用对象内部状态的线程安全性。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;volatile&lt;/span&gt; &lt;span class="type"&gt;SomeObject&lt;/span&gt; &lt;span class="variable"&gt;obj&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;SomeObject&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;虽然 &lt;code&gt;volatile&lt;/code&gt; 确保了 &lt;code&gt;obj&lt;/code&gt; 引用的可见性，但对 &lt;code&gt;obj&lt;/code&gt; 引用的具体对象的操作并不受 &lt;code&gt;volatile&lt;/code&gt; 保护。如果需要保证引用对象内部状态的线程安全，需要使用其他同步机制（如 &lt;code&gt;synchronized&lt;/code&gt; 或 &lt;code&gt;ReentrantLock&lt;/code&gt;）。&lt;br/&gt;&lt;a name="BiWhR"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="锁"&gt;&lt;a class="headerlink" href="#锁" title="锁"&gt;&lt;/a&gt;锁&lt;/h2&gt;&lt;p&gt;&lt;a name="p0TpD"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="26-synchronized-用过吗？怎么使用？"&gt;&lt;a class="headerlink" href="#26-synchronized-用过吗？怎么使用？" title="26.synchronized 用过吗？怎么使用？"&gt;&lt;/a&gt;26.synchronized 用过吗？怎么使用？&lt;/h3&gt;&lt;p&gt;在 Java 中，synchronized 是最常用的锁，它使用简单，并且可以保证线程安全，避免多线程并发访问时出现数据不一致的情况。&lt;br/&gt;随着 JDK 版本的进化，synchronized 的性能也得到了进一步的提升，不再像以前样重量级了。&lt;br/&gt;synchronized 可以用在方法和代码块中。&lt;br/&gt;①、修饰方法&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;increment&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;this&lt;/span&gt;.count++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;当在方法声明中使用了 synchronized 关键字，就表示该方法是同步的，也就是说，线程在执行这个方法的时候，其他线程不能同时执行，需要等待锁释放。&lt;br/&gt;如果是静态方法的话，锁的是这个类的 Class 对象，因为静态方法是属于类级别的。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;increment&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    count++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、修饰代码块&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;increment&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (&lt;span class="built_in"&gt;this&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.count++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;同步代码块可以减少需要同步的代码量，颗粒度更低，更灵活。synchronized 后面的括号中指定了要锁定的对象，可以是 this，也可以是其他对象。&lt;br/&gt;&lt;a name="raVrI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="27-synchronized-的实现原理？"&gt;&lt;a class="headerlink" href="#27-synchronized-的实现原理？" title="27.synchronized 的实现原理？"&gt;&lt;/a&gt;27.synchronized 的实现原理？&lt;/h3&gt;&lt;p&gt;&lt;a name="yEPd2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="synchronized-是怎么加锁的呢？"&gt;&lt;a class="headerlink" href="#synchronized-是怎么加锁的呢？" title="synchronized 是怎么加锁的呢？"&gt;&lt;/a&gt;synchronized 是怎么加锁的呢？&lt;/h4&gt;&lt;p&gt;synchronized 是 JVM 帮我们实现的，因此在使用的时候不用手动去 lock 和 unlock，JVM 会帮我们自动加锁和解锁。&lt;br/&gt;①、synchronized 修饰代码块时，JVM 会通过 &lt;code&gt;monitorenter&lt;/code&gt;、&lt;code&gt;monitorexit&lt;/code&gt; 两个指令来实现同步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;monitorenter&lt;/code&gt; 指向同步代码块的开始位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;monitorexit&lt;/code&gt; 指向同步代码块的结束位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 &lt;code&gt;javap -c -s -v -l SynchronizedDemo.class&lt;/code&gt; 反编译一段 synchronized 代码块时，可以看到 monitorenter 和 monitorexit 指令。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-30.png"/&gt;&lt;br/&gt;②、synchronized 修饰方法时，JVM 会通过 &lt;code&gt;ACC_SYNCHRONIZED&lt;/code&gt; 标记符来实现同步。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-31.png"/&gt;&lt;br/&gt;&lt;a name="zd0u4"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="synchronized-锁住的是什么呢？"&gt;&lt;a class="headerlink" href="#synchronized-锁住的是什么呢？" title="synchronized 锁住的是什么呢？"&gt;&lt;/a&gt;synchronized 锁住的是什么呢？&lt;/h4&gt;&lt;p&gt;monitorenter、monitorexit 或者 ACC_SYNCHRONIZED 都是&lt;strong&gt;基于 Monitor 实现&lt;/strong&gt;的。&lt;br/&gt;实例对象结构里有对象头，对象头里面有一块结构叫 Mark Word，Mark Word 指针指向了&lt;strong&gt;monitor&lt;/strong&gt;。&lt;br/&gt;所谓的 Monitor 其实是一种&lt;strong&gt;同步工具&lt;/strong&gt;，也可以说是一种&lt;strong&gt;同步机制&lt;/strong&gt;。在 Java 虚拟机（HotSpot）中，Monitor 是由&lt;strong&gt;ObjectMonitor 实现&lt;/strong&gt;的，可以叫做内部锁，或者 Monitor 锁。&lt;br/&gt;ObjectMonitor 的工作原理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ObjectMonitor 有两个队列：_WaitSet、_EntryList，用来保存 ObjectWaiter 对象列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;_owner，获取 Monitor 对象的线程进入 _owner 区时， _count + 1。如果线程调用了 wait() 方法，此时会释放 Monitor 对象， _owner 恢复为空， _count - 1。同时该等待线程进入 _WaitSet 中，等待被唤醒。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;ObjectMonitor() {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _header       = NULL;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _count        = &lt;span class="number"&gt;0&lt;/span&gt;; &lt;span class="comment"&gt;// 记录线程获取锁的次数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _waiters      = &lt;span class="number"&gt;0&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _recursions   = &lt;span class="number"&gt;0&lt;/span&gt;;  &lt;span class="comment"&gt;//锁的重入次数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _object       = NULL;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _owner        = NULL;  &lt;span class="comment"&gt;// 指向持有ObjectMonitor对象的线程&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _WaitSet      = NULL;  &lt;span class="comment"&gt;// 处于wait状态的线程，会被加入到_WaitSet&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _WaitSetLock  = &lt;span class="number"&gt;0&lt;/span&gt; ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _Responsible  = NULL ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _succ         = NULL ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _cxq          = NULL ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    FreeNext      = NULL ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _EntryList    = NULL ;  &lt;span class="comment"&gt;// 处于等待锁block状态的线程，会被加入到该列表&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _SpinFreq     = &lt;span class="number"&gt;0&lt;/span&gt; ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    _SpinClock    = &lt;span class="number"&gt;0&lt;/span&gt; ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    OwnerIsThread = &lt;span class="number"&gt;0&lt;/span&gt; ;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;所以我们就知道了，同步是锁住的什么东西：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;monitorenter，在判断拥有同步标识 ACC_SYNCHRONIZED 抢先进入此方法的线程会优先拥有 Monitor 的 owner ，此时计数器 +1。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;monitorexit，当执行完退出后，计数器 -1，归 0 后被其他进入的线程获得。&lt;br/&gt;&lt;a name="ca0Dz"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="会不会牵扯到-os-层面呢？"&gt;&lt;a class="headerlink" href="#会不会牵扯到-os-层面呢？" title="会不会牵扯到 os 层面呢？"&gt;&lt;/a&gt;会不会牵扯到 os 层面呢？&lt;/h4&gt;&lt;p&gt;会，synchronized 升级为重量级锁时，依赖于操作系统的互斥量（mutex）来实现，mutex 用于保证任何给定时间内，只有一个线程可以执行某一段特定的代码段。&lt;br/&gt;&lt;a name="Btdtg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="28-除了原子性，synchronized-可见性，有序性，可重入性怎么实现？"&gt;&lt;a class="headerlink" href="#28-除了原子性，synchronized-可见性，有序性，可重入性怎么实现？" title="28.除了原子性，synchronized 可见性，有序性，可重入性怎么实现？"&gt;&lt;/a&gt;28.除了原子性，synchronized 可见性，有序性，可重入性怎么实现？&lt;/h3&gt;&lt;p&gt;&lt;a name="FDYvU"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="synchronized-怎么保证可见性？"&gt;&lt;a class="headerlink" href="#synchronized-怎么保证可见性？" title="synchronized 怎么保证可见性？"&gt;&lt;/a&gt;synchronized 怎么保证可见性？&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;线程加锁前，将清空工作内存中共享变量的值，从而使用共享变量时需要从主内存中重新读取最新的值。&lt;/li&gt;
&lt;li&gt;线程加锁后，其它线程无法获取主内存中的共享变量。&lt;/li&gt;
&lt;li&gt;线程解锁前，必须把共享变量的最新值刷新到主内存中。&lt;br/&gt;&lt;a name="YMmYX"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="synchronized-怎么保证有序性？"&gt;&lt;a class="headerlink" href="#synchronized-怎么保证有序性？" title="synchronized 怎么保证有序性？"&gt;&lt;/a&gt;synchronized 怎么保证有序性？&lt;/h4&gt;&lt;p&gt;synchronized 同步的代码块，具有排他性，一次只能被一个线程拥有，所以 synchronized 保证同一时刻，代码是单线程执行的。&lt;br/&gt;因为 as-if-serial 语义的存在，单线程的程序能保证最终结果是有序的，但是不保证不会指令重排。&lt;br/&gt;所以 synchronized 保证的有序是执行结果的有序性，而不是防止指令重排的有序性。&lt;br/&gt;&lt;a name="UepPI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="synchronized-怎么实现可重入的呢？"&gt;&lt;a class="headerlink" href="#synchronized-怎么实现可重入的呢？" title="synchronized 怎么实现可重入的呢？"&gt;&lt;/a&gt;synchronized 怎么实现可重入的呢？&lt;/h4&gt;&lt;p&gt;可重入意味着同一个线程可以多次获得同一个锁，而不会被阻塞。具体来说，如果一个线程已经持有某个锁，那么它可以再次进入该锁保护的代码块或方法，而不会被阻塞。&lt;br/&gt;synchronized 之所以支持可重入，是因为 Java 的对象头包含了一个 Mark Word，用于存储对象的状态，包括锁信息。&lt;br/&gt;当一个线程获取对象锁时，JVM 会将该线程的 ID 写入 Mark Word，并将锁计数器设为 1。&lt;br/&gt;如果一个线程尝试再次获取已经持有的锁，JVM 会检查 Mark Word 中的线程 ID。如果 ID 匹配，表示的是同一个线程，锁计数器递增。&lt;br/&gt;当线程退出同步块时，锁计数器递减。如果计数器值为零，JVM 将锁标记为未持有状态，并清除线程 ID 信息。&lt;br/&gt;&lt;a name="lopyl"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="29-锁升级？synchronized-优化了解吗？"&gt;&lt;a class="headerlink" href="#29-锁升级？synchronized-优化了解吗？" title="29.锁升级？synchronized 优化了解吗？"&gt;&lt;/a&gt;29.锁升级？synchronized 优化了解吗？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://javabetter.cn/thread/synchronized.html"&gt;偏向锁、轻量级锁、重量级锁到底是什么？&lt;/a&gt;&lt;br/&gt;&lt;a name="m5LOP"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="什么是锁升级？"&gt;&lt;a class="headerlink" href="#什么是锁升级？" title="什么是锁升级？"&gt;&lt;/a&gt;什么是锁升级？&lt;/h4&gt;&lt;p&gt;锁升级是 Java 虚拟机中的一个优化机制，用于提高多线程环境下 synchronized 的并发性能。锁升级涉及从较轻的锁状态（如无锁或偏向锁）逐步升级到较重的锁状态（如轻量级锁和重量级锁），以适应不同程度的竞争情况。&lt;br/&gt;Java 对象头里的 &lt;code&gt;Mark Word&lt;/code&gt; 会记录锁的状态，一共有四种状态：&lt;br/&gt;①、无锁状态，在这个状态下，没有线程试图获取锁。&lt;br/&gt;②、偏向锁，当第一个线程访问同步块时，锁会进入偏向模式。Mark Word 会被设置为偏向模式，并且存储了获取它的线程 ID。&lt;br/&gt;偏向锁的目的是消除同一线程的后续锁获取和释放的开销。如果同一线程再次请求锁，就无需再次同步。&lt;br/&gt;③、当有多个线程竞争锁，但没有锁竞争的强烈迹象（即线程交替执行同步块）时，偏向锁会升级为轻量级锁。&lt;br/&gt;线程尝试通过&lt;a href="https://javabetter.cn/thread/cas.html"&gt;CAS 操作&lt;/a&gt;（Compare-And-Swap）将对象头的 Mark Word 替换为指向锁记录的指针。如果成功，当前线程获取轻量级锁；如果失败，说明有竞争。&lt;br/&gt;④、重量级锁，当锁竞争激烈时，轻量级锁会膨胀为重量级锁。&lt;br/&gt;重量级锁通过将对象头的 Mark Word 指向监视器（Monitor）对象来实现，该对象包含了锁的持有者、锁的等待队列等信息。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-34.png"/&gt;&lt;br/&gt;&lt;a name="J0dgX"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="synchronized-做了哪些优化？"&gt;&lt;a class="headerlink" href="#synchronized-做了哪些优化？" title="synchronized 做了哪些优化？"&gt;&lt;/a&gt;synchronized 做了哪些优化？&lt;/h4&gt;&lt;p&gt;在 JDK1.6 之前，synchronized 是直接调用 ObjectMonitor 的 enter 和 exit 实现的，这种锁也被称为&lt;strong&gt;重量级锁&lt;/strong&gt;。这也是为什么很多声音说不要用 synchronized 的原因，有点“谈虎色变”的感觉。&lt;br/&gt;从 JDK 1.6 开始，HotSpot 对 Java 中的锁进行优化，如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略，极大提升了 synchronized 的性能。&lt;br/&gt;&lt;strong&gt;①、偏向锁&lt;/strong&gt;：当一个线程首次获得锁时，JVM 会将锁标记为偏向这个线程，将锁的标志位设置为偏向模式，并且在对象头中记录下该线程的 ID。&lt;br/&gt;之后，当相同的线程再次请求这个锁时，就无需进行额外的同步。如果另一个线程尝试获取这个锁，偏向模式会被撤销，并且锁会升级为轻量级锁。&lt;br/&gt;&lt;strong&gt;②、轻量级锁&lt;/strong&gt;：多个线程在不同时段获取同一把锁，即不存在锁竞争的情况，也就没有线程阻塞。针对这种情况，JVM 采用轻量级锁来避免线程的阻塞与唤醒。&lt;br/&gt;当一个线程尝试获取轻量级锁时，它会在自己的栈帧中创建一个锁记录（Lock Record），然后尝试使用 CAS 操作将对象头的 Mark Word 替换为指向锁记录的指针。&lt;br/&gt;如果成功，该线程持有锁；如果失败，表示有其他线程竞争，锁会升级为重量级锁。&lt;br/&gt;&lt;strong&gt;③、自旋锁&lt;/strong&gt;：当线程尝试获取轻量级锁失败时，它会进行自旋，即循环检查锁是否可用，以避免立即进入阻塞状态。&lt;br/&gt;自旋的次数不是固定的，而是根据之前在同一个锁上的自旋时间和锁的状态动态调整的。&lt;br/&gt;&lt;strong&gt;④、锁粗化&lt;/strong&gt;：如果 JVM 检测到一系列连续的锁操作实际上是在单一线程中完成的，则会将多个锁操作合并为一个更大范围的锁操作，这可以减少锁请求的次数。&lt;br/&gt;锁粗化主要针对循环内连续加锁解锁的情况进行优化。&lt;br/&gt;&lt;strong&gt;⑤、锁消除&lt;/strong&gt;：JVM 的即时编译器（&lt;a href="https://javabetter.cn/jvm/jit.html"&gt;JIT&lt;/a&gt;）可以在运行时进行代码分析，如果发现某些锁操作不可能被多个线程同时访问，那么这些锁操作就会被完全消除。锁消除可以减少不必要的同步开销。&lt;br/&gt;&lt;a name="QDTKM"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="锁升级的过程是什么样的？"&gt;&lt;a class="headerlink" href="#锁升级的过程是什么样的？" title="锁升级的过程是什么样的？"&gt;&lt;/a&gt;锁升级的过程是什么样的？&lt;/h4&gt;&lt;p&gt;无锁–&amp;gt;偏向锁—&amp;gt; 轻量级锁—-&amp;gt;重量级锁。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-35.png"/&gt;&lt;br/&gt;大体上省简的升级过程是这样的：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-36.png"/&gt;&lt;br/&gt;完整的升级过程是这样的：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-37.png"/&gt;&lt;br/&gt;&lt;strong&gt;①、从无锁到偏向锁：&lt;/strong&gt;&lt;br/&gt;当一个线程首次访问同步块时，如果此对象无锁状态且偏向锁未被禁用，JVM 会将该对象头的锁标记改为偏向锁状态，并记录下当前线程的 ID。此时，对象头中的 Mark Word 中存储了持有偏向锁的线程 ID。&lt;br/&gt;如果另一个线程尝试获取这个已被偏向的锁，JVM 会检查当前持有偏向锁的线程是否活跃。如果持有偏向锁的线程不活跃，则可以将锁重偏向至新的线程；如果持有偏向锁的线程还活跃，则需要撤销偏向锁，升级为轻量级锁。&lt;br/&gt;&lt;strong&gt;②、偏向锁的轻量级锁：&lt;/strong&gt;&lt;br/&gt;进行偏向锁撤销时，会遍历堆栈的所有锁记录，暂停拥有偏向锁的线程，并检查锁对象。如果这个过程中发现有其他线程试图获取这个锁，JVM 会撤销偏向锁，并将锁升级为轻量级锁。&lt;br/&gt;当有两个或以上线程竞争同一个偏向锁时，偏向锁模式不再有效，此时偏向锁会被撤销，对象的锁状态会升级为轻量级锁。&lt;br/&gt;&lt;strong&gt;③、轻量级锁到重量级锁：&lt;/strong&gt;&lt;br/&gt;轻量级锁通过线程自旋来等待锁释放。如果自旋超过预定次数（自旋次数是可调的，并且自适应的），表明锁竞争激烈，轻量级锁的自旋已经不再高效。&lt;br/&gt;当自旋等待失败，或者有线程在等待队列中等待相同的轻量级锁时，轻量级锁会升级为重量级锁。在这种情况下，JVM 会在操作系统层面创建一个互斥锁（Mutex），所有进一步尝试获取该锁的线程将会被阻塞，直到锁被释放。&lt;br/&gt;&lt;a name="Jf4rR"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="30-说说-synchronized-和-ReentrantLock-的区别？"&gt;&lt;a class="headerlink" href="#30-说说-synchronized-和-ReentrantLock-的区别？" title="30.说说 synchronized 和 ReentrantLock 的区别？"&gt;&lt;/a&gt;30.说说 synchronized 和 ReentrantLock 的区别？&lt;/h3&gt;&lt;p&gt;&lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized&lt;/a&gt; 是一个关键字，而 Lock 属于一个接口，其实现类主要有 &lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;ReentrantLock&lt;/a&gt;、&lt;a href="https://javabetter.cn/thread/ReentrantReadWriteLock.html"&gt;ReentrantReadWriteLock&lt;/a&gt;。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-38.png"/&gt;&lt;br/&gt;&lt;a name="ade9b"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="使用方式有什么不同？"&gt;&lt;a class="headerlink" href="#使用方式有什么不同？" title="使用方式有什么不同？"&gt;&lt;/a&gt;使用方式有什么不同？&lt;/h4&gt;&lt;p&gt;synchronized 可以直接在方法上加锁，也可以在代码块上加锁（无需手动释放锁，锁会自动释放），而 ReentrantLock 必须手动声明来加锁和释放锁。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// synchronized 修饰方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;method&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 业务代码&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// synchronized 修饰代码块&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;synchronized&lt;/span&gt; (&lt;span class="built_in"&gt;this&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 业务代码&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// ReentrantLock 加锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ReentrantLock&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;lock.lock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 业务代码&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;} &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    lock.unlock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;随着 JDK 版本的升级，synchronized 的性能已经可以媲美 ReentrantLock 了，加入了偏向锁、轻量级锁和重量级锁的自适应优化等，所以可以大胆地用。&lt;br/&gt;&lt;a name="YlZgd"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="功能特点有什么不同？"&gt;&lt;a class="headerlink" href="#功能特点有什么不同？" title="功能特点有什么不同？"&gt;&lt;/a&gt;功能特点有什么不同？&lt;/h4&gt;&lt;p&gt;如果需要更细粒度的控制（如可中断的锁操作、尝试非阻塞获取锁、超时获取锁或者使用公平锁等），可以使用 Lock。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ReentrantLock 提供了一种能够中断等待锁的线程的机制，通过 &lt;code&gt;lock.lockInterruptibly()&lt;/code&gt;来实现这个机制。&lt;/li&gt;
&lt;li&gt;ReentrantLock 可以指定是公平锁还是非公平锁。&lt;/li&gt;
&lt;li&gt;ReentrantReadWriteLock 读写锁，读锁是共享锁，写锁是独占锁，读锁可以同时被多个线程持有，写锁只能被一个线程持有。这种锁的设计可以提高性能，特别是在读操作的数量远远超过写操作的情况下。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lock 还提供了&lt;code&gt;newCondition()&lt;/code&gt;方法来创建等待通知条件&lt;a href="https://javabetter.cn/thread/condition.html"&gt;Condition&lt;/a&gt;，比 synchronized 与 &lt;code&gt;wait()&lt;/code&gt;、 &lt;code&gt;notify()/notifyAll()&lt;/code&gt;方法的组合更强大。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ReentrantLock&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;Condition&lt;/span&gt; &lt;span class="variable"&gt;condition&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; lock.newCondition();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="yDrU3"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="并发量大的情况下，使用-synchronized-还是-ReentrantLock？"&gt;&lt;a class="headerlink" href="#并发量大的情况下，使用-synchronized-还是-ReentrantLock？" title="并发量大的情况下，使用 synchronized 还是 ReentrantLock？"&gt;&lt;/a&gt;并发量大的情况下，使用 synchronized 还是 ReentrantLock？&lt;/h4&gt;&lt;p&gt;在并发量特别高的情况下，ReentrantLock 的性能可能会优于 synchronized，原因包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ReentrantLock 提供了超时和公平锁等特性，可以更好地应对复杂的并发场景 。&lt;/li&gt;
&lt;li&gt;ReentrantLock 允许更细粒度的锁控制，可以有效减少锁竞争。&lt;/li&gt;
&lt;li&gt;ReentrantLock 支持条件变量 Condition，可以实现比 synchronized 更复杂的线程间通信机制。&lt;br/&gt;&lt;a name="KV0pZ"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="31-AQS-了解多少？"&gt;&lt;a class="headerlink" href="#31-AQS-了解多少？" title="31.AQS 了解多少？"&gt;&lt;/a&gt;31.AQS 了解多少？&lt;/h3&gt;&lt;p&gt;AQS，全称是 AbstractQueuedSynchronizer，中文意思是抽象队列同步器，由 Doug Lea 设计，是 Java 并发包&lt;code&gt;java.util.concurrent&lt;/code&gt;的核心框架类，许多同步类的实现都依赖于它，如 ReentrantLock、Semaphore、CountDownLatch 等。&lt;br/&gt;AQS 的思想是，如果被请求的共享资源空闲，则当前线程能够成功获取资源；否则，它将进入一个等待队列，当有其他线程释放资源时，系统会挑选等待队列中的一个线程，赋予其资源。&lt;br/&gt;整个过程通过维护一个 int 类型的状态和一个先进先出（FIFO）的队列，来实现对共享资源的管理。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-39.png"/&gt;&lt;br/&gt;①、同步状态 state 由 volatile 修饰，保证了多线程之间的可见性；&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;volatile&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; state;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、同步队列是通过内部定义的 Node 类来实现的，每个 Node 包含了等待状态、前后节点、线程的引用等。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Node&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;CANCELLED&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt;  &lt;span class="number"&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;SIGNAL&lt;/span&gt;    &lt;span class="operator"&gt;=&lt;/span&gt; -&lt;span class="number"&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;CONDITION&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; -&lt;span class="number"&gt;2&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;PROPAGATE&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; -&lt;span class="number"&gt;3&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;volatile&lt;/span&gt; Node prev;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;volatile&lt;/span&gt; Node next;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;volatile&lt;/span&gt; Thread thread;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;AQS 支持两种同步方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;独占模式：这种方式下，每次只能有一个线程持有锁，例如 ReentrantLock。&lt;/li&gt;
&lt;li&gt;共享模式：这种方式下，多个线程可以同时获取锁，例如 Semaphore 和 CountDownLatch。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;子类可以通过继承 AQS 并实现它的方法来管理同步状态，这些方法包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tryAcquire&lt;/code&gt;：独占方式尝试获取资源，成功则返回 true，失败则返回 false；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tryRelease&lt;/code&gt;：独占方式尝试释放资源；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tryAcquireShared(int arg)&lt;/code&gt;：共享方式尝试获取资源；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tryReleaseShared(int arg)&lt;/code&gt;：共享方式尝试释放资源；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isHeldExclusively()&lt;/code&gt;：该线程是否正在独占资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果共享资源被占用，需要一种特定的阻塞等待唤醒机制来保证锁的分配，AQS 会将竞争共享资源失败的线程添加到一个 CLH 队列中。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-40.png"/&gt;&lt;br/&gt;在 CLH 锁中，当一个线程尝试获取锁并失败时，它会将自己添加到队列的尾部并自旋，等待前一个节点的线程释放锁。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-41.png"/&gt;&lt;br/&gt;&lt;a name="PHRJv"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="32-ReentrantLock-实现原理？"&gt;&lt;a class="headerlink" href="#32-ReentrantLock-实现原理？" title="32.ReentrantLock 实现原理？"&gt;&lt;/a&gt;32.ReentrantLock 实现原理？&lt;/h3&gt;&lt;p&gt;&lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;ReentrantLock&lt;/a&gt; 是可重入的独占锁，只能有一个线程可以获取该锁，其它获取该锁的线程会被阻塞。&lt;br/&gt;可重入表示当前线程获取该锁后再次获取不会被阻塞，也就意味着同一个线程可以多次获得同一个锁而不会发生死锁。&lt;br/&gt;ReentrantLock 的加锁和解锁：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 创建非公平锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ReentrantLock&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 获取锁操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;lock.lock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 执行代码逻辑&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;} &lt;span class="keyword"&gt;catch&lt;/span&gt; (Exception ex) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;} &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 解锁操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    lock.unlock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;new ReentrantLock() &lt;/code&gt;默认创建的是非公平锁 NonfairSync。在非公平锁模式下，锁可能会授予刚刚请求它的线程，而不考虑等待时间。&lt;br/&gt;ReentrantLock 也支持公平锁，该模式下，锁会授予等待时间最长的线程。&lt;br/&gt;ReentrantLock 内部通过一个计数器来跟踪锁的持有次数。&lt;br/&gt;当线程调用&lt;code&gt;lock()&lt;/code&gt;方法获取锁时，ReentrantLock 会检查当前状态，判断锁是否已经被其他线程持有。如果没有被持有，则当前线程将获得锁；如果锁已被其他线程持有，则当前线程将根据锁的公平性策略，可能会被加入到等待队列中。&lt;br/&gt;线程首次获取锁时，计数器值变为 1；如果同一线程再次获取锁，计数器增加；每释放一次锁，计数器减 1。&lt;br/&gt;当线程调用&lt;code&gt;unlock()&lt;/code&gt;方法时，ReentrantLock 会将持有锁的计数减 1，如果计数到达 0，则释放锁，并唤醒等待队列中的线程来竞争锁。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-42.png"/&gt;&lt;br/&gt;&lt;a name="ZLHSR"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="33-ReentrantLock-怎么实现公平锁的？"&gt;&lt;a class="headerlink" href="#33-ReentrantLock-怎么实现公平锁的？" title="33.ReentrantLock 怎么实现公平锁的？"&gt;&lt;/a&gt;33.ReentrantLock 怎么实现公平锁的？&lt;/h3&gt;&lt;p&gt;ReentrantLock 的默认构造方法创建的是非公平锁 NonfairSync。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;ReentrantLock&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    sync = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;NonfairSync&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;可以通过有参构造方法传递 true 参数来创建公平锁 FairSync。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ReentrantLock&lt;/span&gt;(&lt;span class="literal"&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;--- ReentrantLock&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// true 代表公平锁，false 代表非公平锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;ReentrantLock&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;boolean&lt;/span&gt; fair)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    sync = fair ? &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;FairSync&lt;/span&gt;() : &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;NonfairSync&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;FairSync、NonfairSync 都是 ReentrantLock 的内部类，分别实现了公平锁和非公平锁的逻辑。&lt;br/&gt;&lt;a name="EG6E8"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="非公平锁和公平锁有什么不同？"&gt;&lt;a class="headerlink" href="#非公平锁和公平锁有什么不同？" title="非公平锁和公平锁有什么不同？"&gt;&lt;/a&gt;非公平锁和公平锁有什么不同？&lt;/h4&gt;&lt;p&gt;①、公平锁意味着在多个线程竞争锁时，获取锁的顺序与线程请求锁的顺序相同，即先来先服务（FIFO）。&lt;br/&gt;虽然能保证锁的顺序，但实现起来比较复杂，因为需要额外维护一个有序队列。&lt;br/&gt;②、非公平锁不保证线程获取锁的顺序，当锁被释放时，任何请求锁的线程都有机会获取锁，而不是按照请求的顺序。&lt;br/&gt;&lt;a name="fToDV"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="怎么实现一个非公平锁呢？"&gt;&lt;a class="headerlink" href="#怎么实现一个非公平锁呢？" title="怎么实现一个非公平锁呢？"&gt;&lt;/a&gt;怎么实现一个非公平锁呢？&lt;/h4&gt;&lt;p&gt;要实现一个非公平锁，只需要在创建 ReentrantLock 实例时，不传递任何参数或者传递 false 给它的构造方法就好了。&lt;br/&gt;&lt;a name="dpEsX"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="34-CAS-了解多少？"&gt;&lt;a class="headerlink" href="#34-CAS-了解多少？" title="34.CAS 了解多少？"&gt;&lt;/a&gt;34.CAS 了解多少？&lt;/h3&gt;&lt;p&gt;CAS（Compare-and-Swap）是一种乐观锁的实现方式，全称为“比较并交换”，是一种无锁的原子操作。&lt;br/&gt;在 Java 中，我们可以使用 &lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized&lt;/a&gt;关键字和 &lt;code&gt;CAS&lt;/code&gt; 来实现加锁效果。&lt;br/&gt;synchronized 是悲观锁，尽管随着 JDK 版本的升级，synchronized 关键字已经“轻量级”了很多，但依然是悲观锁，线程开始执行第一步就要获取锁，一旦获得锁，其他的线程进入后就会阻塞并等待锁。&lt;br/&gt;CAS 是乐观锁，线程执行的时候不会加锁，它会假设此时没有冲突，然后完成某项操作；如果因为冲突失败了就重试，直到成功为止。&lt;br/&gt;在 CAS 中，有这样三个值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;V：要更新的变量(var)&lt;/li&gt;
&lt;li&gt;E：预期值(expected)&lt;/li&gt;
&lt;li&gt;N：新值(new)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比较并交换的过程如下：&lt;br/&gt;判断 V 是否等于 E，如果等于，将 V 的值设置为 N；如果不等，说明已经有其它线程更新了 V，于是当前线程放弃更新，什么都不做。&lt;br/&gt;这里的预期值 E 本质上指的是“旧值”。&lt;br/&gt;这个比较和替换的操作是原子的，即不可中断，确保了数据的一致性。&lt;br/&gt;举个例子，变量当前的值为 0，需要将其更新为 1，可以借助 AtomicInteger 类的 compareAndSet 方法来实现。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;AtomicInteger&lt;/span&gt; &lt;span class="variable"&gt;atomicInteger&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;AtomicInteger&lt;/span&gt;(&lt;span class="number"&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;expect&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;update&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;atomicInteger.compareAndSet(expect, update);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;compareAndSet 就是一个 CAS 方法，它调用的是 Unsafe 的 compareAndSwapInt。&lt;br/&gt;Unsafe 对 CAS 的实现是通过 C++ 实现的，它的具体实现和操作系统、CPU 都有关系。&lt;br/&gt;Linux 的 X86 下主要是通过 cmpxchgl 这个指令在 CPU 上完成 CAS 操作的，但在多处理器情况下，必须使用 lock 指令加锁来完成。当然，不同的操作系统和处理器在实现方式上肯定会有所不同。&lt;br/&gt;&lt;a name="nxbBe"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="35-CAS-有什么问题？如何解决？"&gt;&lt;a class="headerlink" href="#35-CAS-有什么问题？如何解决？" title="35.CAS 有什么问题？如何解决？"&gt;&lt;/a&gt;35.CAS 有什么问题？如何解决？&lt;/h3&gt;&lt;p&gt;CAS 存在三个经典问题。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-44.png"/&gt;&lt;br/&gt;&lt;a name="ESPBt"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="什么是-ABA-问题？如何解决？"&gt;&lt;a class="headerlink" href="#什么是-ABA-问题？如何解决？" title="什么是 ABA 问题？如何解决？"&gt;&lt;/a&gt;什么是 ABA 问题？如何解决？&lt;/h4&gt;&lt;p&gt;如果一个位置的值原来是 A，后来被改为 B，再后来又被改回 A，那么进行 CAS 操作的线程将无法知晓该位置的值在此期间已经被修改过。&lt;br/&gt;可以使用版本号/时间戳的方式来解决 ABA 问题。&lt;br/&gt;比如说，每次变量更新时，不仅更新变量的值，还更新一个版本号。CAS 操作时不仅要求值匹配，还要求版本号匹配。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;OptimisticLockExample&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; version;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; value;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;updateValue&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; newValue, &lt;span class="type"&gt;int&lt;/span&gt; currentVersion)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="built_in"&gt;this&lt;/span&gt;.version == currentVersion) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="built_in"&gt;this&lt;/span&gt;.value = newValue;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="built_in"&gt;this&lt;/span&gt;.version++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Java 的 AtomicStampedReference 类就实现了这种机制，它会同时检查引用值和 stamp 是否都相等。&lt;br/&gt;&lt;a name="ivaiE"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="循环性能开销"&gt;&lt;a class="headerlink" href="#循环性能开销" title="循环性能开销"&gt;&lt;/a&gt;循环性能开销&lt;/h4&gt;&lt;p&gt;自旋 CAS，如果一直循环执行，一直不成功，会给 CPU 带来非常大的执行开销。&lt;br/&gt;在 Java 中，很多使用自旋 CAS 的地方，会有一个自旋次数的限制，超过一定次数，就停止自旋。&lt;br/&gt;&lt;a name="AaqZm"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="只能保证一个变量的原子操作"&gt;&lt;a class="headerlink" href="#只能保证一个变量的原子操作" title="只能保证一个变量的原子操作"&gt;&lt;/a&gt;只能保证一个变量的原子操作&lt;/h4&gt;&lt;p&gt;CAS 保证的是对一个变量执行操作的原子性，如果对多个变量操作时，CAS 目前无法直接保证操作的原子性的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以考虑改用锁来保证操作的原子性&lt;/li&gt;
&lt;li&gt;可以考虑合并多个变量，将多个变量封装成一个对象，通过 AtomicReference 来保证原子性。&lt;br/&gt;&lt;a name="v09Cs"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="36-Java-有哪些保证原子性的方法？如何保证多线程下-i-结果正确？"&gt;&lt;a class="headerlink" href="#36-Java-有哪些保证原子性的方法？如何保证多线程下-i-结果正确？" title="36.Java 有哪些保证原子性的方法？如何保证多线程下 i++ 结果正确？"&gt;&lt;/a&gt;36.Java 有哪些保证原子性的方法？如何保证多线程下 i++ 结果正确？&lt;/h3&gt;&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-45.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用循环原子类，例如 AtomicInteger，实现 i++原子操作&lt;/li&gt;
&lt;li&gt;使用 juc 包下的锁，如 ReentrantLock ，对 i++操作加锁 lock.lock()来实现原子性&lt;/li&gt;
&lt;li&gt;使用 synchronized，对 i++操作加锁&lt;br/&gt;&lt;a name="kN7OP"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="37-原子操作类了解多少？"&gt;&lt;a class="headerlink" href="#37-原子操作类了解多少？" title="37.原子操作类了解多少？"&gt;&lt;/a&gt;37.原子操作类了解多少？&lt;/h3&gt;&lt;p&gt;当程序更新一个变量时，如果多线程同时更新这个变量，可能得到期望之外的值，比如变量 i=1，A 线程更新 i+1，B 线程也更新 i+1，经过两个线程操作之后可能 i 不等于 3，而是等于 2。因为 A 和 B 线程在更新变量 i 的时候拿到的 i 都是 1，这就是线程不安全的更新操作，一般我们会使用 synchronized 来解决这个问题，synchronized 会保证多线程不会同时更新变量 i。&lt;br/&gt;其实除此之外，还有更轻量级的选择，Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包，这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。&lt;br/&gt;因为变量的类型有很多种，所以在 Atomic 包里一共提供了 13 个类，属于 4 种类型的原子更新方式，分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性（字段）。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-46.png"/&gt;&lt;br/&gt;Atomic 包里的类基本都是使用 Unsafe 实现的包装类。&lt;br/&gt;使用原子的方式更新基本类型，Atomic 包提供了以下 3 个类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AtomicBoolean：原子更新布尔类型。&lt;/li&gt;
&lt;li&gt;AtomicInteger：原子更新整型。&lt;/li&gt;
&lt;li&gt;AtomicLong：原子更新长整型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过原子的方式更新数组里的某个元素，Atomic 包提供了以下 4 个类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AtomicIntegerArray：原子更新整型数组里的元素。&lt;/li&gt;
&lt;li&gt;AtomicLongArray：原子更新长整型数组里的元素。&lt;/li&gt;
&lt;li&gt;AtomicReferenceArray：原子更新引用类型数组里的元素。&lt;/li&gt;
&lt;li&gt;AtomicIntegerArray 类主要是提供原子的方式更新数组里的整型&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原子更新基本类型的 AtomicInteger，只能更新一个变量，如果要原子更新多个变量，就需要使用这个原子更新引用类型提供的类。Atomic 包提供了以下 3 个类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AtomicReference：原子更新引用类型。&lt;/li&gt;
&lt;li&gt;AtomicReferenceFieldUpdater：原子更新引用类型里的字段。&lt;/li&gt;
&lt;li&gt;AtomicMarkableReference：原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是 AtomicMarkableReference（V initialRef，boolean initialMark）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果需原子地更新某个类里的某个字段时，就需要使用原子更新字段类，Atomic 包提供了以下 3 个类进行原子字段更新：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AtomicIntegerFieldUpdater：原子更新整型的字段的更新器。&lt;/li&gt;
&lt;li&gt;AtomicLongFieldUpdater：原子更新长整型字段的更新器。&lt;/li&gt;
&lt;li&gt;AtomicStampedReference：原子更新带有版本号的引用类型。该类将整数值与引用关联起来，可用于原子的更新数据和数据的版本号，可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。&lt;br/&gt;&lt;a name="hkgi0"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="38-AtomicInteger-的原理？"&gt;&lt;a class="headerlink" href="#38-AtomicInteger-的原理？" title="38.AtomicInteger 的原理？"&gt;&lt;/a&gt;38.AtomicInteger 的原理？&lt;/h3&gt;&lt;p&gt;一句话概括：&lt;strong&gt;使用 CAS 实现&lt;/strong&gt;。&lt;br/&gt;以 AtomicInteger 的添加方法为例：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;getAndIncrement&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; unsafe.getAndAddInt(&lt;span class="built_in"&gt;this&lt;/span&gt;, valueOffset, &lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;通过&lt;code&gt;Unsafe&lt;/code&gt;类的实例来进行添加操作，来看看具体的 CAS 操作：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;getAndAddInt&lt;/span&gt;&lt;span class="params"&gt;(Object var1, &lt;span class="type"&gt;long&lt;/span&gt; var2, &lt;span class="type"&gt;int&lt;/span&gt; var4)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; var5;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;do&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        var5 = &lt;span class="built_in"&gt;this&lt;/span&gt;.getIntVolatile(var1, var2);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } &lt;span class="keyword"&gt;while&lt;/span&gt;(!&lt;span class="built_in"&gt;this&lt;/span&gt;.compareAndSwapInt(var1, var2, var5, var5 + var4));&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; var5;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;compareAndSwapInt 是一个 native 方法，基于 CAS 来操作 int 类型变量。其它的原子操作类基本都是大同小异。&lt;br/&gt;&lt;a name="cT2E6"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="39-线程死锁了解吗？该如何避免？"&gt;&lt;a class="headerlink" href="#39-线程死锁了解吗？该如何避免？" title="39.线程死锁了解吗？该如何避免？"&gt;&lt;/a&gt;39.线程死锁了解吗？该如何避免？&lt;/h3&gt;&lt;p&gt;死锁发生在多个线程相互等待对方释放锁资源，导致所有线程都无法继续执行。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-47.png"/&gt;&lt;br/&gt;&lt;a name="aA6oQ"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="那么为什么会产生死锁呢？"&gt;&lt;a class="headerlink" href="#那么为什么会产生死锁呢？" title="那么为什么会产生死锁呢？"&gt;&lt;/a&gt;那么为什么会产生死锁呢？&lt;/h4&gt;&lt;p&gt;讲个笑话，死锁的产生也不是你想产生就产生的，它是有条件的：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-48.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;互斥条件&lt;/strong&gt;：资源不能被多个线程共享，一次只能由一个线程使用。如果一个线程已经占用了一个资源，其他请求该资源的线程必须等待，直到资源被释放。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持有并等待条件&lt;/strong&gt;：一个线程至少已经持有至少一个资源，且正在等待获取额外的资源，这些额外的资源被其他线程占有。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可剥夺条件&lt;/strong&gt;：资源不能被强制从一个线程中抢占过来，只能由持有资源的线程主动释放。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环等待条件&lt;/strong&gt;：存在一种线程资源的循环链，每个线程至少持有一个其他线程所需要的资源，然后又等待下一个线程所占有的资源。这形成了一个循环等待的环路。&lt;br/&gt;&lt;a name="zvGHG"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="该如何避免死锁呢？"&gt;&lt;a class="headerlink" href="#该如何避免死锁呢？" title="该如何避免死锁呢？"&gt;&lt;/a&gt;该如何避免死锁呢？&lt;/h4&gt;&lt;p&gt;理解产生死锁的这四个必要条件后，就可以采取相应的措施来避免死锁，换句话说，就是&lt;strong&gt;至少破坏死锁发生的一个条件&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;破坏互斥条件&lt;/strong&gt;：这通常不可行，因为加锁就是为了互斥。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;破坏持有并等待条件&lt;/strong&gt;：一种方法是要求线程在开始执行前一次性地申请所有需要的资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;破坏非抢占条件&lt;/strong&gt;：占用部分资源的线程进一步申请其他资源时，如果申请不到，可以主动释放它占有的资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;破坏循环等待条件&lt;/strong&gt;：对所有资源类型进行排序，强制每个线程按顺序申请资源，这样可以避免循环等待的发生。&lt;br/&gt;&lt;a name="chJ0U"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="40-那死锁问题怎么排查呢？"&gt;&lt;a class="headerlink" href="#40-那死锁问题怎么排查呢？" title="40.那死锁问题怎么排查呢？"&gt;&lt;/a&gt;40.那死锁问题怎么排查呢？&lt;/h3&gt;&lt;p&gt;首先从系统级别上排查，比如说在 Linux 生产环境中，可以先使用 top ps 等命令查看进程状态，看看是否有进程占用了过多的资源。&lt;br/&gt;接着，使用 JDK 自带的一些性能监控工具进行排查，比如说 jps、jstat、jinfo、jmap、jstack、jcmd 等等。&lt;br/&gt;比如说，使用 &lt;code&gt;jps -l&lt;/code&gt; 查看当前 Java 进程，然后使用 &lt;code&gt;jstack 进程号&lt;/code&gt; 查看当前 Java 进程的线程堆栈信息，看看是否有线程在等待锁资源。&lt;br/&gt;来编写一个死锁程序：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeadLockDemo&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;lock1&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Object&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;lock2&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Object&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock1) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"线程1获取到了锁1"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    Thread.sleep(&lt;span class="number"&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock2) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"线程1获取到了锁2"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }).start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock2) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"线程2获取到了锁2"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    Thread.sleep(&lt;span class="number"&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (lock1) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"线程2获取到了锁1"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }).start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;创建了两个线程，每个线程都试图按照不同的顺序获取两个&lt;a href="https://javabetter.cn/thread/thread-bring-some-problem.html#%E6%B4%BB%E8%B7%83%E6%80%A7%E9%97%AE%E9%A2%98"&gt;锁（lock1 和 lock2）&lt;/a&gt;。这种锁的获取顺序不一致很容易导致死锁。&lt;br/&gt;运行这段代码，果然卡住了。运行 &lt;code&gt;jstack pid&lt;/code&gt; 命令，可以看到死锁的线程信息。诚不欺我！也可以使用一些可视化的性能监控工具，比如说 JConsole、VisualVM 等。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-49.png"/&gt;&lt;br/&gt;&lt;a name="tyGAM"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="41-聊聊如何进行线程同步？（补充）"&gt;&lt;a class="headerlink" href="#41-聊聊如何进行线程同步？（补充）" title="41.聊聊如何进行线程同步？（补充）"&gt;&lt;/a&gt;41.聊聊如何进行线程同步？（补充）&lt;/h3&gt;&lt;p&gt;所谓同步，即协同步调，按预定的先后次序访问共享资源，以免造成混乱。&lt;br/&gt;线程同步是多线程编程中的一个核心概念，它涉及到在多线程环境下如何安全地访问和修改共享资源的问题。&lt;br/&gt;当有一个线程在对内存进行操作时，其他线程都不可以对这个内存地址进行操作，直到该线程完成操作， 其他线程才能对该内存地址进行操作。&lt;br/&gt;如果多个线程同时读写某个共享资源（如变量、文件等），而没有适当的同步机制，就可能导致数据不一致、数据损坏等问题的出现。&lt;br/&gt;线程同步的实现方式有 6 种：互斥量、读写锁、条件变量、自旋锁、屏障、信号量。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;互斥量&lt;/strong&gt;：互斥量（mutex）是一种最基本的同步手段，本质上是一把锁，在访问共享资源前先对互斥量进行加锁，访问完后再解锁。对互斥量加锁后，任何其他试图再次对互斥量加锁的线程都会被阻塞，直到当前线程解锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读写锁&lt;/strong&gt;：&lt;a href="https://javabetter.cn/thread/ReentrantReadWriteLock.html"&gt;读写锁&lt;/a&gt;有三种状态，读模式加锁、写模式加锁和不加锁；一次只有一个线程可以占有写模式的读写锁，但是可以有多个线程同时占有读模式的读写锁。非常适合读多写少的场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条件变量&lt;/strong&gt;：&lt;a href="https://javabetter.cn/thread/condition.html"&gt;条件变量&lt;/a&gt;是一种同步手段，它允许线程在满足特定条件时才继续执行，否则进入等待状态。条件变量通常与互斥量一起使用，以防止竞争条件的发生。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自旋锁&lt;/strong&gt;：自旋锁是一种锁的实现方式，它不会让线程进入睡眠状态，而是一直循环检测锁是否被释放。自旋锁适用于锁的持有时间非常短的情况。&lt;/li&gt;
&lt;li&gt;信号量：信号量（&lt;a href="https://javabetter.cn/thread/CountDownLatch.html"&gt;Semaphore&lt;/a&gt;）本质上是一个计数器，用于为多个进程提供共享数据对象的访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 Java 中，&lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized 关键字&lt;/a&gt;和 Lock 接口是用来实现线程同步的常用方式，我就以它俩来举例说明。&lt;br/&gt;&lt;a name="J88oE"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="简单说说-synchronized-关键字"&gt;&lt;a class="headerlink" href="#简单说说-synchronized-关键字" title="简单说说 synchronized 关键字"&gt;&lt;/a&gt;简单说说 synchronized 关键字&lt;/h4&gt;&lt;p&gt;当一个线程访问某对象的 synchronized 方法或代码块时，其他线程对该对象的所有 synchronized 方法或代码块的访问将被阻塞，直到第一个线程完成操作。&lt;br/&gt;synchronized 关键字就属于典型的互斥量，它保证了同一时间只有一个线程可以访问共享资源。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Counter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;count&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 使用synchronized方法保证线程安全&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;increment&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        count++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;synchronized&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;getCount&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; count;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在这个例子中，increment 方法和 getCount 方法都被标记为 synchronized。这意味着同一时间内只有一个线程可以执行这两个方法中的任意一个。&lt;br/&gt;在 JVM 的早期版本中，synchronized 是重量级的，因为线程阻塞和唤醒需要操作系统的介入。但在 JVM 的后续版本中，对 synchronized 进行了大量优化，如偏向锁、轻量级锁和适应性自旋等，所以现在的 synchronized 并不一定是重量级的，其性能在许多情况下都很好，可以大胆地用。&lt;br/&gt;&lt;a name="rdslP"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="简单说说-Lock-接口？"&gt;&lt;a class="headerlink" href="#简单说说-Lock-接口？" title="简单说说 Lock 接口？"&gt;&lt;/a&gt;简单说说 Lock 接口？&lt;/h4&gt;&lt;p&gt;Lock 接口提供了比 synchronized 关键字更灵活的锁操作。比如说我们可以用重入锁 &lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;ReentrantLock&lt;/a&gt; 来实现同样的功能。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CounterWithLock&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;count&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Lock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ReentrantLock&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;increment&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        lock.lock();  &lt;span class="comment"&gt;// 获取锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            count++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            lock.unlock();  &lt;span class="comment"&gt;// 释放锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;getCount&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; count;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;increment 方法先上锁，然后尝试增加 count 的值，在完成操作后释放锁。这样就可以保证 count 的操作是线程安全的。&lt;br/&gt;ReentrantLock 和 synchronized 都可以用来实现同步，但它们之间也存在一些区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ReentrantLock 是一个类，而 synchronized 是 Java 中的关键字&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;**ReentrantLock 可以实现多路选择通知（可以绑定多个 **&lt;a href="https://javabetter.cn/thread/condition.html"&gt;Condition&lt;/a&gt;&lt;strong&gt;），而 synchronized 只能通过 wait 和 notify/notifyAll 方法唤醒一个线程或者唤醒全部线程（单路通知）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;ReentrantLock 必须手动释放锁。通常需要在 finally 块中调用 unlock 方法以确保锁被正确释放。&lt;/li&gt;
&lt;li&gt;synchronized 会自动释放锁，当同步块执行完毕时，由 JVM 自动释放，不需要手动操作。&lt;/li&gt;
&lt;li&gt;ReentrantLock: 通常提供更好的性能，特别是在高竞争环境下。&lt;/li&gt;
&lt;li&gt;synchronized: 在某些情况下，性能可能稍差一些，但随着 JDK 版本的升级，性能差距已经不大了。&lt;br/&gt;&lt;a name="nUCM6"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="42-聊聊悲观锁和乐观锁？（补充）"&gt;&lt;a class="headerlink" href="#42-聊聊悲观锁和乐观锁？（补充）" title="42.聊聊悲观锁和乐观锁？（补充）"&gt;&lt;/a&gt;42.聊聊悲观锁和乐观锁？（补充）&lt;/h3&gt;&lt;p&gt;对于悲观锁来说，它总是认为每次访问共享资源时会发生冲突，所以必须对每次数据操作加上锁，以保证临界区的程序同一时间只能有一个线程在执行。&lt;br/&gt;悲观锁的代表有 &lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized 关键字&lt;/a&gt;和 &lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;Lock 接口&lt;/a&gt;。&lt;br/&gt;乐观锁，顾名思义，它是乐观派。乐观锁总是假设对共享资源的访问没有冲突，线程可以不停地执行，无需加锁也无需等待。一旦多个线程发生冲突，乐观锁通常使用一种称为 &lt;a href="https://javabetter.cn/thread/cas.html"&gt;CAS&lt;/a&gt; 的技术来保证线程执行的安全性。&lt;br/&gt;由于乐观锁假想操作中没有锁的存在，因此不太可能出现死锁的情况，换句话说，乐观锁天生免疫死锁。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;乐观锁多用于“读多写少“的环境，避免频繁加锁影响性能；&lt;/li&gt;
&lt;li&gt;悲观锁多用于”写多读少“的环境，避免频繁失败和重试影响性能。&lt;br/&gt;&lt;a name="IY53t"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="并发工具类"&gt;&lt;a class="headerlink" href="#并发工具类" title="并发工具类"&gt;&lt;/a&gt;并发工具类&lt;/h2&gt;&lt;p&gt;&lt;a name="zYluk"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="43-CountDownLatch（倒计数器）了解吗？"&gt;&lt;a class="headerlink" href="#43-CountDownLatch（倒计数器）了解吗？" title="43.CountDownLatch（倒计数器）了解吗？"&gt;&lt;/a&gt;43.CountDownLatch（倒计数器）了解吗？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://javabetter.cn/thread/CountDownLatch.html"&gt;Java 并发编程通信工具类 Semaphore、Exchanger、CountDownLatch、CyclicBarrier、Phaser 等一网打尽&lt;/a&gt;&lt;br/&gt;CountDownLatch 是 JUC 包中的一个同步工具类，用于协调多个线程之间的同步。它允许一个或多个线程等待，直到其他线程中执行的一组操作完成。它通过一个计数器来实现，该计数器由线程递减，直到到达零。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化：创建 CountDownLatch 对象时，指定计数器的初始值。&lt;/li&gt;
&lt;li&gt;等待（await）：一个或多个线程调用 await 方法，进入等待状态，直到计数器的值变为零。&lt;/li&gt;
&lt;li&gt;倒计数（countDown）：其他线程在完成各自任务后调用 countDown 方法，将计数器的值减一。当计数器的值减到零时，所有在 await 上等待的线程会被唤醒，继续执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当等待多个线程完成各自的启动任务后再启动主线程的任务，就可以使用 CountDownLatch。&lt;br/&gt;CountDownLatch 的&lt;strong&gt;核心方法&lt;/strong&gt;也不多：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CountDownLatch(int count)&lt;/code&gt;：创建一个带有给定计数器的 CountDownLatch。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void await()&lt;/code&gt;：阻塞当前线程，直到计数器为零。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void countDown()&lt;/code&gt;：递减计数器的值，如果计数器值变为零，则释放所有等待的线程。&lt;br/&gt;&lt;a name="Eaenv"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="44-CyclicBarrier（同步屏障）了解吗？"&gt;&lt;a class="headerlink" href="#44-CyclicBarrier（同步屏障）了解吗？" title="44.CyclicBarrier（同步屏障）了解吗？"&gt;&lt;/a&gt;44.CyclicBarrier（同步屏障）了解吗？&lt;/h3&gt;&lt;p&gt;CyclicBarrier 的字面意思是可循环使用（Cyclic）的屏障（Barrier）。它要做的事情是，让一 组线程到达一个屏障（也可以叫同步点）时被阻塞，直到最后一个线程到达屏障时，屏障才会开门，所有被屏障拦截的线程才会继续运行。&lt;br/&gt;它和 CountDownLatch 类似，都可以协调多线程的结束动作，在它们结束后都可以执行特定动作，但是为什么要有 CyclicBarrier，自然是它有和 CountDownLatch 不同的地方。&lt;br/&gt;我们拿代码模拟这一场景，发现 CountDownLatch 无能为力了，因为 CountDownLatch 的使用是一次性的，无法重复利用，而这里等待了两次。此时，我们用 CyclicBarrier 就可以实现，因为它可以重复利用。&lt;br/&gt;CyclicBarrier 最最核心的方法，仍然是 await()：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果当前线程不是第一个到达屏障的话，它将会进入等待，直到其他线程都到达，除非发生&lt;strong&gt;被中断&lt;/strong&gt;、&lt;strong&gt;屏障被拆除&lt;/strong&gt;、&lt;strong&gt;屏障被重设&lt;/strong&gt;等情况；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面的例子抽象一下，本质上它的流程就是这样就是这样：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-55.png"/&gt;&lt;br/&gt;&lt;a name="GpDQR"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="45-CyclicBarrier-和-CountDownLatch-有什么区别？"&gt;&lt;a class="headerlink" href="#45-CyclicBarrier-和-CountDownLatch-有什么区别？" title="45.CyclicBarrier 和 CountDownLatch 有什么区别？"&gt;&lt;/a&gt;45.CyclicBarrier 和 CountDownLatch 有什么区别？&lt;/h3&gt;&lt;p&gt;两者最核心的区别[18]：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CountDownLatch 是一次性的，而 CyclicBarrier 则可以多次设置屏障，实现重复利用；&lt;/li&gt;
&lt;li&gt;CountDownLatch 中的各个子线程不可以等待其他线程，只能完成自己的任务；而 CyclicBarrier 中的各个线程可以等待其他线程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们区别用一个表格整理：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CyclicBarrier&lt;/th&gt;
&lt;th&gt;CountDownLatch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;CyclicBarrier 是可重用的，其中的线程会等待所有的线程完成任务。届时，屏障将被拆除，并可以选择性地做一些特定的动作。&lt;/td&gt;
&lt;td&gt;CountDownLatch 是一次性的，不同的线程在同一个计数器上工作，直到计数器为 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CyclicBarrier 面向的是线程数&lt;/td&gt;
&lt;td&gt;CountDownLatch 面向的是任务数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;在使用 CyclicBarrier 时，你必须在构造中指定参与协作的线程数，这些线程必须调用 await()方法&lt;/td&gt;
&lt;td&gt;使用 CountDownLatch 时，则必须要指定任务数，至于这些任务由哪些线程完成无关紧要&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CyclicBarrier 可以在所有的线程释放后重新使用&lt;/td&gt;
&lt;td&gt;CountDownLatch 在计数器为 0 时不能再使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;在 CyclicBarrier 中，如果某个线程遇到了中断、超时等问题时，则处于 await 的线程都会出现问题&lt;/td&gt;
&lt;td&gt;在 CountDownLatch 中，如果某个线程出现问题，其他线程不受影响&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;a name="gyS1n"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="46-Semaphore（信号量）了解吗？"&gt;&lt;a class="headerlink" href="#46-Semaphore（信号量）了解吗？" title="46.Semaphore（信号量）了解吗？"&gt;&lt;/a&gt;46.Semaphore（信号量）了解吗？&lt;/h3&gt;&lt;p&gt;Semaphore（信号量）是用来控制同时访问特定资源的线程数量，它通过协调各个线程，以保证合理的使用公共资源。&lt;br/&gt;我们把这个例子类比一下，车辆就是线程，进入停车场就是线程在执行，离开停车场就是线程执行完毕，看见红灯就表示线程被阻塞，不能执行，Semaphore 的本质就是&lt;strong&gt;协调多个线程对共享资源的获取&lt;/strong&gt;。&lt;br/&gt;我们再来看一个 Semaphore 的用途：它可以用于做流量控制，特别是公用资源有限的应用场景，比如数据库连接。&lt;br/&gt;在代码中，虽然有 30 个线程在执行，但是只允许 10 个并发执行。Semaphore 的构造方法&lt;code&gt; Semaphore（int permits&lt;/code&gt;）接受一个整型的数字，表示可用的许可证数量。&lt;code&gt;Semaphore（10）&lt;/code&gt;表示允许 10 个线程获取许可证，也就是最大并发数是 10。Semaphore 的用法也很简单，首先线程使用 Semaphore 的 acquire()方法获取一个许可证，使用完之后调用 release()方法归还许可证。还可以用 tryAcquire()方法尝试获取许可证。&lt;br/&gt;&lt;a name="D5Sj9"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="47-Exchanger-了解吗？"&gt;&lt;a class="headerlink" href="#47-Exchanger-了解吗？" title="47.Exchanger 了解吗？"&gt;&lt;/a&gt;47.Exchanger 了解吗？&lt;/h3&gt;&lt;p&gt;Exchanger（交换者）是一个用于线程间协作的工具类。Exchanger 用于进行线程间的数据交换。它提供一个同步点，在这个同步点，两个线程可以交换彼此的数据。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-58.jpeg"/&gt;&lt;br/&gt;这两个线程通过 exchange 方法交换数据，如果第一个线程先执行 exchange()方法，它会一直等待第二个线程也执行 exchange 方法，当两个线程都到达同步点时，这两个线程就可以交换数据，将本线程生产出来的数据传递给对方。&lt;br/&gt;Exchanger 可以用于遗传算法，遗传算法里需要选出两个人作为交配对象，这时候会交换两人的数据，并使用交叉规则得出 2 个交配结果。Exchanger 也可以用于校对工作，比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水，为了避免错误，采用 AB 岗两人进行录入，录入到 Excel 之后，系统需要加载这两个 Excel，并对两个 Excel 数据进行校对，看看是否录入一致。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;ExchangerTest&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; Exchanger&amp;lt;String&amp;gt; exgr = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Exchanger&lt;/span&gt;&amp;lt;String&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;ExecutorService&lt;/span&gt; &lt;span class="variable"&gt;threadPool&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Executors.newFixedThreadPool(&lt;span class="number"&gt;2&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        threadPool.execute(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Runnable&lt;/span&gt;() {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;A&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="string"&gt;"银行流水A"&lt;/span&gt;; &lt;span class="comment"&gt;// A录入银行流水数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    exgr.exchange(A);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        threadPool.execute(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Runnable&lt;/span&gt;() {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;B&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="string"&gt;"银行流水B"&lt;/span&gt;; &lt;span class="comment"&gt;// B录入银行流水数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;A&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; exgr.exchange(&lt;span class="string"&gt;"B"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(&lt;span class="string"&gt;"A和B数据是否一致："&lt;/span&gt; + A.equals(B) + &lt;span class="string"&gt;"，A录入的是："&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                            + A + &lt;span class="string"&gt;"，B录入是："&lt;/span&gt; + B);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        threadPool.shutdown();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;假如两个线程有一个没有执行 exchange()方法，则会一直等待，如果担心有特殊情况发生，避免一直等待，可以使用&lt;code&gt;exchange(V x, long timeOut, TimeUnit unit) &lt;/code&gt;设置最大等待时长。&lt;br/&gt;&lt;a name="WdZ1E"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="48-能说一下-ConcurrentHashMap-的实现吗？（补充）"&gt;&lt;a class="headerlink" href="#48-能说一下-ConcurrentHashMap-的实现吗？（补充）" title="48.能说一下 ConcurrentHashMap 的实现吗？（补充）"&gt;&lt;/a&gt;48.能说一下 ConcurrentHashMap 的实现吗？（补充）&lt;/h3&gt;&lt;p&gt;&lt;a href="https://javabetter.cn/thread/ConcurrentHashMap.html"&gt;ConcurrentHashMap&lt;/a&gt; 在 JDK 7 时采用的是分段锁机制（Segment Locking），整个 Map 被分为若干段，每个段都可以独立地加锁。因此，不同的线程可以同时操作不同的段，从而实现并发访问。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/map-20230816155810.png"/&gt;&lt;br/&gt;在 JDK 8 及以上版本中，ConcurrentHashMap 的实现进行了优化，不再使用分段锁，而是使用了一种更加精细化的锁——桶锁，以及 CAS 无锁算法。每个桶（Node 数组的每个元素）都可以独立地加锁，从而实现更高级别的并发访问。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/map-20230816155924.png"/&gt;&lt;br/&gt;同时，对于读操作，通常不需要加锁，可以直接读取，因为 ConcurrentHashMap 内部使用了 volatile 变量来保证内存可见性。&lt;br/&gt;对于写操作，ConcurrentHashMap 使用 CAS 操作来实现无锁的更新，这是一种乐观锁的实现，因为它假设没有冲突发生，在实际更新数据时才检查是否有其他线程在尝试修改数据，如果有，采用悲观的锁策略，如 synchronized 代码块来保证数据的一致性。&lt;br/&gt;&lt;a name="LFA40"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说一下-JDK-7-中的-ConcurrentHashMap-的实现原理？"&gt;&lt;a class="headerlink" href="#说一下-JDK-7-中的-ConcurrentHashMap-的实现原理？" title="说一下 JDK 7 中的 ConcurrentHashMap 的实现原理？"&gt;&lt;/a&gt;说一下 JDK 7 中的 ConcurrentHashMap 的实现原理？&lt;/h4&gt;&lt;p&gt;JDK 7 的 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组构成的。Segment 是一种可重入的锁 &lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;ReentrantLock&lt;/a&gt;，HashEntry 则用于存储键值对数据。&lt;br/&gt;一个 ConcurrentHashMap 里包含一个 Segment 数组，Segment 的结构和 HashMap 类似，是一种数组和链表结构，一个 Segment 里包含一个 HashEntry 数组，每个 HashEntry 是一个链表结构的元素，每个 Segment 守护着一个 HashEntry 数组里的元素，当对 HashEntry 数组的数据进行修改时，必须首先获得它对应的 Segment 锁。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/collection-31.png"/&gt;&lt;br/&gt;&lt;strong&gt;①、put 流程&lt;/strong&gt;&lt;br/&gt;ConcurrentHashMap 的 put 流程和 HashMap 非常类似，只不过是先定位到具体的 Segment，然后通过 ReentrantLock 去操作而已。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算 hash，定位到 segment，segment 如果是空就先初始化；&lt;/li&gt;
&lt;li&gt;使用 ReentrantLock 加锁，如果获取锁失败则尝试自旋，自旋超过次数就阻塞获取，保证一定能获取到锁；&lt;/li&gt;
&lt;li&gt;遍历 HashEntry，key 相同就直接替换，不存在就插入。&lt;/li&gt;
&lt;li&gt;释放锁。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;②、get 流程&lt;/strong&gt;&lt;br/&gt;get 也很简单，通过 &lt;code&gt;hash(key)&lt;/code&gt; 定位到 segment，再遍历链表定位到具体的元素上，需要注意的是 value 是 &lt;a href="https://javabetter.cn/thread/volatile.html"&gt;volatile 的&lt;/a&gt;，所以 get 是不需要加锁的。&lt;br/&gt;&lt;a name="wHDJr"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="说一下-JDK-8-中的-ConcurrentHashMap-的实现原理？"&gt;&lt;a class="headerlink" href="#说一下-JDK-8-中的-ConcurrentHashMap-的实现原理？" title="说一下 JDK 8 中的 ConcurrentHashMap 的实现原理？"&gt;&lt;/a&gt;说一下 JDK 8 中的 ConcurrentHashMap 的实现原理？&lt;/h4&gt;&lt;p&gt;JDK 8 中的 ConcurrentHashMap 取消了 Segment 分段锁，采用 CAS + synchronized 来保证并发安全性，整个容器只分为一个 Segment，即 table 数组。&lt;br/&gt;Node 和 JDK 7 一样，使用 volatile 关键字，保证多线程操作时，变量的可见性。&lt;br/&gt;ConcurrentHashMap 实现线程安全的关键点在于 put 流程。&lt;br/&gt;&lt;strong&gt;①、put 流程&lt;/strong&gt;&lt;br/&gt;第一步，计算 hash，遍历 node 数组，如果 node 是空的话，就通过 CAS+自旋的方式初始化。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 准备初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;tab = initTable();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 具体实现&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; Node&amp;lt;K,V&amp;gt;[] initTable() {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Node&amp;lt;K,V&amp;gt;[] tab; &lt;span class="type"&gt;int&lt;/span&gt; sc;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;while&lt;/span&gt; ((tab = table) == &lt;span class="literal"&gt;null&lt;/span&gt; || tab.length == &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//如果正在初始化或者扩容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; ((sc = sizeCtl) &amp;lt; &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//等待&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            Thread.&lt;span class="keyword"&gt;yield&lt;/span&gt;(); &lt;span class="comment"&gt;// lost initialization race; just spin&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;else&lt;/span&gt; &lt;span class="keyword"&gt;if&lt;/span&gt; (U.compareAndSwapInt(&lt;span class="built_in"&gt;this&lt;/span&gt;, SIZECTL, sc, -&lt;span class="number"&gt;1&lt;/span&gt;)) {   &lt;span class="comment"&gt;//CAS操作&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;if&lt;/span&gt; ((tab = table) == &lt;span class="literal"&gt;null&lt;/span&gt; || tab.length == &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;n&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (sc &amp;gt; &lt;span class="number"&gt;0&lt;/span&gt;) ? sc : DEFAULT_CAPACITY;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="meta"&gt;@SuppressWarnings("unchecked")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    Node&amp;lt;K,V&amp;gt;[] nt = (Node&amp;lt;K,V&amp;gt;[])&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Node&lt;/span&gt;&amp;lt;?,?&amp;gt;[n];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    table = tab = nt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    sc = n - (n &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;2&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                sizeCtl = sc;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; tab;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第二步，如果当前数组位置是空，直接通过 CAS 自旋写入数据。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &amp;lt;K,V&amp;gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;casTabAt&lt;/span&gt;&lt;span class="params"&gt;(Node&amp;lt;K,V&amp;gt;[] tab, &lt;span class="type"&gt;int&lt;/span&gt; i,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;                                    Node&amp;lt;K,V&amp;gt; c, Node&amp;lt;K,V&amp;gt; v)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; U.compareAndSwapObject(tab, ((&lt;span class="type"&gt;long&lt;/span&gt;)i &amp;lt;&amp;lt; ASHIFT) + ABASE, c, v);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第三步，如果 &lt;code&gt;hash==MOVED&lt;/code&gt;，说明需要扩容。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;else&lt;/span&gt; &lt;span class="keyword"&gt;if&lt;/span&gt; ((fh = f.hash) == MOVED)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    tab = helpTransfer(tab, f);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;扩容的具体实现：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;final&lt;/span&gt; Node&amp;lt;K,V&amp;gt;[] helpTransfer(Node&amp;lt;K,V&amp;gt;[] tab, Node&amp;lt;K,V&amp;gt; f) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Node&amp;lt;K,V&amp;gt;[] nextTab; &lt;span class="comment"&gt;// 下一个表的引用，即新的扩容后的数组&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; sc; &lt;span class="comment"&gt;// 用于缓存sizeCtl的值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 检查条件：传入的表不为空，节点f是ForwardingNode类型，且f中的nextTable不为空&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (tab != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; (f &lt;span class="keyword"&gt;instanceof&lt;/span&gt; ForwardingNode) &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        (nextTab = ((ForwardingNode&amp;lt;K,V&amp;gt;)f).nextTable) != &lt;span class="literal"&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;rs&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; resizeStamp(tab.length); &lt;span class="comment"&gt;// 根据当前表长度计算resize stamp&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 检查循环条件：nextTab等于nextTable，table等于传入的tab，且sizeCtl为负数（表示正在进行或准备进行扩容）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (nextTab == nextTable &amp;amp;&amp;amp; table == tab &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;               (sc = sizeCtl) &amp;lt; &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// 检查是否应该停止扩容（比如：resize stamp不匹配，或者已达到最大并发扩容线程数，或者transferIndex已经不大于0）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; ((sc &amp;gt;&amp;gt;&amp;gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + &lt;span class="number"&gt;1&lt;/span&gt; ||&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                sc == rs + MAX_RESIZERS || transferIndex &amp;lt;= &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// 尝试通过CAS增加sizeCtl的值，以表示有更多线程参与扩容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; (U.compareAndSwapInt(&lt;span class="built_in"&gt;this&lt;/span&gt;, SIZECTL, sc, sc + &lt;span class="number"&gt;1&lt;/span&gt;)) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                transfer(tab, nextTab); &lt;span class="comment"&gt;// 调用transfer方法，实际进行数据迁移&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; nextTab; &lt;span class="comment"&gt;// 返回新的表引用&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; table; &lt;span class="comment"&gt;// 如果不符合扩容协助条件，返回当前表引用&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第四步，如果都不满足，就使用 synchronized 写入数据，和 HashMap 一样，key 的 hash 一样就覆盖，反之使用拉链法解决哈希冲突，当链表长度超过 8 就转换成红黑树。&lt;br/&gt;&lt;strong&gt;②、get 查询&lt;/strong&gt;&lt;br/&gt;get 查询的时候，也是通过 key 的 hash 进行定位，需要注意的是 ConcurrentHashMap 会判断 hash 值是否小于 0。&lt;br/&gt;如果小于 0，说明是个特殊节点，会调用节点的 find 方法进行查找，比如说 ForwardingNode 的 find 方法或者 TreeNode 的 find 方法。&lt;br/&gt;&lt;a name="GLWbK"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="总结一下-HashMap-和-ConcurrentHashMap-的区别？"&gt;&lt;a class="headerlink" href="#总结一下-HashMap-和-ConcurrentHashMap-的区别？" title="总结一下 HashMap 和 ConcurrentHashMap 的区别？"&gt;&lt;/a&gt;总结一下 HashMap 和 ConcurrentHashMap 的区别？&lt;/h4&gt;&lt;p&gt;①、HashMap 是非线程安全的，多线程环境下应该使用 ConcurrentHashMap。&lt;br/&gt;②、由于 HashMap 仅在单线程环境下使用，所以不需要考虑同步问题，因此效率高于 ConcurrentHashMap。&lt;br/&gt;&lt;a name="lajbm"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="你项目中怎么使用-ConcurrentHashMap-的？"&gt;&lt;a class="headerlink" href="#你项目中怎么使用-ConcurrentHashMap-的？" title="你项目中怎么使用 ConcurrentHashMap 的？"&gt;&lt;/a&gt;你项目中怎么使用 ConcurrentHashMap 的？&lt;/h4&gt;&lt;p&gt;在&lt;a href="https://javabetter.cn/zhishixingqiu/paicoding.html"&gt;技术派实战项目&lt;/a&gt;中，很多地方都用到了 ConcurrentHashMap，比如说在异步工具类 AsyncUtil 中，使用 ConcurrentHashMap 来存储任务的名称和它们的运行时间，以便观察和分析任务的执行情况。&lt;br/&gt;&lt;a name="sYdoB"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="ConcurrentHashMap-对-HashMap-的优化？"&gt;&lt;a class="headerlink" href="#ConcurrentHashMap-对-HashMap-的优化？" title="ConcurrentHashMap 对 HashMap 的优化？"&gt;&lt;/a&gt;ConcurrentHashMap 对 HashMap 的优化？&lt;/h4&gt;&lt;p&gt;ConcurrentHashMap 是 HashMap 的线程安全版本，使用了 CAS、synchronized、volatile 来确保线程安全。&lt;br/&gt;首先是 hash 的计算方法上，ConcurrentHashMap 的 spread 方法接收一个已经计算好的 hashCode，然后将这个哈希码的高 16 位与自身进行异或运算，这里的 HASH_BITS 是一个常数，值为 0x7fffffff，它确保结果是一个非负整数。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;spread&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; h)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (h ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;)) &amp;amp; HASH_BITS;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;比 HashMap 的 hash 计算多了一个 &lt;code&gt;&amp;amp; HASH_BITS&lt;/code&gt; 的操作。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; h;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (key == &lt;span class="literal"&gt;null&lt;/span&gt;) ? &lt;span class="number"&gt;0&lt;/span&gt; : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;另外，ConcurrentHashMap 对节点 Node 做了进一步的封装，比如说用 Forwarding Node 来表示正在进行扩容的节点。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;ForwardingNode&lt;/span&gt;&amp;lt;K,V&amp;gt; &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;Node&lt;/span&gt;&amp;lt;K,V&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;final&lt;/span&gt; Node&amp;lt;K,V&amp;gt;[] nextTable;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    ForwardingNode(Node&amp;lt;K,V&amp;gt;[] tab) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;super&lt;/span&gt;(MOVED, &lt;span class="literal"&gt;null&lt;/span&gt;, &lt;span class="literal"&gt;null&lt;/span&gt;, &lt;span class="literal"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.nextTable = tab;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;最后就是 put 方法，通过 CAS + synchronized 来保证线程安全。&lt;br/&gt;&lt;a name="ayK6x"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="为什么-ConcurrentHashMap-在-JDK-1-7-中要用-ReentrantLock，而在-JDK-1-8-要用-synchronized"&gt;&lt;a class="headerlink" href="#为什么-ConcurrentHashMap-在-JDK-1-7-中要用-ReentrantLock，而在-JDK-1-8-要用-synchronized" title="为什么 ConcurrentHashMap 在 JDK 1.7 中要用 ReentrantLock，而在 JDK 1.8 要用 synchronized"&gt;&lt;/a&gt;为什么 ConcurrentHashMap 在 JDK 1.7 中要用 ReentrantLock，而在 JDK 1.8 要用 synchronized&lt;/h4&gt;&lt;p&gt;ConcurrentHashMap 在 JDK 1.7 和 JDK 1.8 中的实现机制不同，主要体现在锁的机制上。&lt;br/&gt;JDK 1.7 中的 ConcurrentHashMap 使用了分段锁机制，即 Segment 锁，每个 Segment 都是一个 ReentrantLock，这样可以保证每个 Segment 都可以独立地加锁，从而实现更高级别的并发访问。&lt;br/&gt;而在 JDK 1.8 中，ConcurrentHashMap 取消了 Segment 分段锁，采用了更加精细化的锁——桶锁，以及 CAS 无锁算法，每个桶（Node 数组的每个元素）都可以独立地加锁，从而实现更高级别的并发访问。&lt;br/&gt;再加上 JVM 对 synchronized 做了大量优化，如锁消除、锁粗化、自旋锁和偏向锁等，在低中等的竞争情况下，synchronized 的性能并不比 ReentrantLock 差，并且使用 synchronized 可以简化代码实现。&lt;br/&gt;&lt;a name="MQXvB"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="49-ConcurrentHashMap-怎么保证可见性？"&gt;&lt;a class="headerlink" href="#49-ConcurrentHashMap-怎么保证可见性？" title="49.ConcurrentHashMap 怎么保证可见性？"&gt;&lt;/a&gt;49.ConcurrentHashMap 怎么保证可见性？&lt;/h3&gt;&lt;p&gt;ConcurrentHashMap 保证可见性主要通过使用 volatile 关键字和 synchronized 同步块。&lt;br/&gt;在 Java 中，volatile 关键字保证了变量的可见性，即一个线程修改了一个 volatile 变量后，其他线程可以立即看到这个修改。在 ConcurrentHashMap 的内部实现中，有些关键的变量被声明为 volatile，比如 Segment 数组和 Node 数组等。&lt;br/&gt;此外，ConcurrentHashMap 还使用了 synchronized 同步块来保证复合操作的原子性。当一个线程进入 synchronized 同步块时，它会获得锁，然后执行同步块内的代码。当它退出 synchronized 同步块时，它会释放锁，并将在同步块内对共享变量的所有修改立即刷新到主内存，这样其他线程就可以看到这些修改了。&lt;br/&gt;通过这两种机制，ConcurrentHashMap 保证了在并发环境下的可见性，从而确保了线程安全。&lt;br/&gt;&lt;a name="WmRXr"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="50-为什么-ConcurrentHashMap-比-Hashtable-效率高"&gt;&lt;a class="headerlink" href="#50-为什么-ConcurrentHashMap-比-Hashtable-效率高" title="50.为什么 ConcurrentHashMap 比 Hashtable 效率高"&gt;&lt;/a&gt;50.为什么 ConcurrentHashMap 比 Hashtable 效率高&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;2026 年 03 月 26 日增补，从集合框架移动到并发编程这里&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hashtable 在任何时刻只允许一个线程访问整个 Map，通过对整个 Map 加锁来实现线程安全。&lt;br/&gt;而 ConcurrentHashMap（尤其是在 JDK 8 及之后版本）通过锁分离和 CAS 操作实现更细粒度的锁定策略，允许更高的并发。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &amp;lt;K,V&amp;gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;casTabAt&lt;/span&gt;&lt;span class="params"&gt;(Node&amp;lt;K,V&amp;gt;[] tab, &lt;span class="type"&gt;int&lt;/span&gt; i,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;                                    Node&amp;lt;K,V&amp;gt; c, Node&amp;lt;K,V&amp;gt; v)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; U.compareAndSwapObject(tab, ((&lt;span class="type"&gt;long&lt;/span&gt;)i &amp;lt;&amp;lt; ASHIFT) + ABASE, c, v);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;CAS 操作是一种乐观锁，它不会阻塞线程，而是在更新时检查是否有其他线程已经修改了数据，如果没有就更新，如果有就重试。&lt;br/&gt;ConcurrentHashMap 允许多个读操作并发进行而不加锁，因为它通过 &lt;a href="https://javabetter.cn/thread/volatile.html"&gt;volatile 变量&lt;/a&gt;来保证读取操作的内存可见性。相比之下，Hashtable 对读操作也加锁，增加了开销。&lt;br/&gt;&lt;a name="jzwGI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="51-能说一下-CopyOnWriteArrayList-的实现原理吗？"&gt;&lt;a class="headerlink" href="#51-能说一下-CopyOnWriteArrayList-的实现原理吗？" title="51.能说一下 CopyOnWriteArrayList 的实现原理吗？"&gt;&lt;/a&gt;51.能说一下 CopyOnWriteArrayList 的实现原理吗？&lt;/h3&gt;&lt;p&gt;CopyOnWriteArrayList 是一个线程安全的 ArrayList，它遵循写时复制（Copy-On-Write）的原则，即在写操作时，会先复制一个新的数组，然后在新的数组上进行写操作，写完之后再将原数组引用指向新数组。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/CopyOnWriteArrayList-01.png"/&gt;&lt;br/&gt;这样，读操作总是在一个不变的数组版本上进行的，就不需要同步了。&lt;br/&gt;&lt;a name="VrLsY"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="52-能说一下-BlockingQueue-吗？"&gt;&lt;a class="headerlink" href="#52-能说一下-BlockingQueue-吗？" title="52. 能说一下 BlockingQueue 吗？"&gt;&lt;/a&gt;52. 能说一下 BlockingQueue 吗？&lt;/h3&gt;&lt;p&gt;&lt;a href="https://javabetter.cn/thread/BlockingQueue.html"&gt;BlockingQueue&lt;/a&gt; 代表的是线程安全的队列，不仅可以由多个线程并发访问，还添加了等待/通知机制，以便在队列为空时阻塞获取元素的线程，直到队列变得可用，或者在队列满时阻塞插入元素的线程，直到队列变得可用。&lt;br/&gt;阻塞队列（BlockingQueue）被广泛用于“生产者-消费者”问题中，其原因是 BlockingQueue 提供了可阻塞的插入和移除方法。当队列容器已满，生产者线程会被阻塞，直到队列未满；当队列容器为空时，消费者线程会被阻塞，直至队列非空时为止。&lt;br/&gt;BlockingQueue 接口的实现类有 ArrayBlockingQueue、DelayQueue、LinkedBlockingDeque、LinkedBlockingQueue、LinkedTransferQueue、PriorityBlockingQueue、SynchronousQueue 等。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/BlockingQueue-20230818153420.png"/&gt;&lt;br/&gt;阻塞指的是一种程序执行状态，其中某个线程在等待某个条件满足时暂停其执行（即阻塞），直到条件满足时恢复其执行。&lt;br/&gt;&lt;a name="h9Tkq"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="阻塞队列是如何实现的？"&gt;&lt;a class="headerlink" href="#阻塞队列是如何实现的？" title="阻塞队列是如何实现的？"&gt;&lt;/a&gt;阻塞队列是如何实现的？&lt;/h4&gt;&lt;p&gt;就拿 ArrayBlockingQueue 来说，它是一个基于数组的有界阻塞队列，采用 &lt;a href="https://javabetter.cn/thread/reentrantLock.html"&gt;ReentrantLock&lt;/a&gt; 锁来实现线程的互斥，而 ReentrantLock 底层采用的是 AQS 实现的队列同步，线程的阻塞调用 &lt;a href="https://javabetter.cn/thread/LockSupport.html"&gt;LockSupport.park&lt;/a&gt; 实现，唤醒调用 LockSupport.unpark 实现。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;53&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;54&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;55&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;56&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;57&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;58&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;59&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;60&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;61&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;62&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;63&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;64&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;65&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;66&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;67&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;put&lt;/span&gt;&lt;span class="params"&gt;(E e)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    checkNotNull(e);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 使用ReentrantLock锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="built_in"&gt;this&lt;/span&gt;.lock;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 获取锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    lock.lockInterruptibly();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 如果队列已满，阻塞&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (count == items.length)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            notFull.await();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 插入元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        enqueue(e);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 释放锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        lock.unlock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; * 插入元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;enqueue&lt;/span&gt;&lt;span class="params"&gt;(E x)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;final&lt;/span&gt; Object[] items = &lt;span class="built_in"&gt;this&lt;/span&gt;.items;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    items[putIndex] = x;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (++putIndex == items.length)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        putIndex = &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    count++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 插入元素后，通知消费者线程可以继续取元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    notEmpty.signal();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; * 获取元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; E &lt;span class="title function_"&gt;take&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;ReentrantLock&lt;/span&gt; &lt;span class="variable"&gt;lock&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="built_in"&gt;this&lt;/span&gt;.lock;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 获取锁&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    lock.lockInterruptibly();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 如果队列为空，阻塞，等待生产者线程放入元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;while&lt;/span&gt; (count == &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            notEmpty.await();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 移除元素并返回&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; dequeue();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        lock.unlock();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; * 移除元素并返回&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; E &lt;span class="title function_"&gt;dequeue&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;final&lt;/span&gt; Object[] items = &lt;span class="built_in"&gt;this&lt;/span&gt;.items;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@SuppressWarnings("unchecked")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;E&lt;/span&gt; &lt;span class="variable"&gt;x&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (E) items[takeIndex];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    items[takeIndex] = &lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 数组是循环队列，如果到达数组末尾，从头开始&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (++takeIndex == items.length)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        takeIndex = &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    count--;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (itrs != &lt;span class="literal"&gt;null&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        itrs.elementDequeued();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 移除元素后，通知生产者线程可以继续放入元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    notFull.signal();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; x;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="n0oU7"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="线程池"&gt;&lt;a class="headerlink" href="#线程池" title="线程池"&gt;&lt;/a&gt;线程池&lt;/h2&gt;&lt;p&gt;&lt;a name="KFS0h"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="53-什么是线程池？"&gt;&lt;a class="headerlink" href="#53-什么是线程池？" title="53.什么是线程池？"&gt;&lt;/a&gt;53.什么是线程池？&lt;/h3&gt;&lt;p&gt;线程池，简单来说，就是一个管理线程的池子。&lt;br/&gt;①、频繁地创建和销毁线程会消耗系统资源，线程池能够复用已创建的线程。&lt;br/&gt;②、提高响应速度，当任务到达时，任务可以不需要等待线程创建就立即执行。&lt;br/&gt;③、线程池支持定时执行、周期性执行、单线程执行和并发数控制等功能。&lt;br/&gt;&lt;a name="fmkCH"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="54-能说说工作中线程池的应用吗？"&gt;&lt;a class="headerlink" href="#54-能说说工作中线程池的应用吗？" title="54.能说说工作中线程池的应用吗？"&gt;&lt;/a&gt;54.能说说工作中线程池的应用吗？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html"&gt;线程池在美团业务中的应用&lt;/a&gt;&lt;br/&gt;为了最大程度利用 CPU 的多核性能，并行运算的能力是不可获取的，通过线程池来管理线程是一个非常基础的操作。&lt;br/&gt;&lt;strong&gt;①、快速响应用户请求&lt;/strong&gt;&lt;br/&gt;当用户发起一个实时请求，服务器需要快速响应，此时如果每次请求都直接创建一个线程，那么线程的创建和销毁会消耗大量的系统资源。&lt;br/&gt;使用线程池，可以预先创建一定数量的线程，当用户请求到来时，直接从线程池中获取一个空闲线程，执行用户请求，执行完毕后，线程不销毁，而是继续保留在线程池中，等待下一个请求。&lt;br/&gt;注意：这种场景下需要调高 corePoolSize 和 maxPoolSize，尽可能多创建线程，避免使用队列去缓存任务。&lt;br/&gt;比如说，在项目中，当用户请求首页时，就使用了线程池去加载首页的热门文章、置顶文章、侧边栏、用户登录信息等。&lt;br/&gt;我们封装了一个异步类 AsyncUtil，内部的静态类 CompletableFutureBridge 是通过 &lt;a href="https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html"&gt;CompletableFuture&lt;/a&gt; 实现的，其中的 &lt;code&gt;runAsyncWithTimeRecord()&lt;/code&gt; 方法就是使用线程池去执行任务的。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; CompletableFutureBridge &lt;span class="title function_"&gt;runAsyncWithTimeRecord&lt;/span&gt;&lt;span class="params"&gt;(Runnable run, String name)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; runAsyncWithTimeRecord(run, name, executorService);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中线程池的初始化中，corePoolSize 为 CPU 核心数的两倍，因为技术派中的大多数任务都是 IO 密集型的，maxPoolSize 设置为 50，是一个比较理想的值，尤其是在本地环境中；阻塞队列为 SynchronousQueue，这意味着任务被创建后直接提交给等待的线程处理，而不是放入队列中。&lt;br/&gt;&lt;strong&gt;②、快速处理批量任务&lt;/strong&gt;&lt;br/&gt;这种场景也需要处理大量的任务，但可能不需要立即响应，这时候就应该设置队列去缓冲任务，corePoolSize 不需要设置得太高，避免线程上下文切换引起的频繁切换问题。&lt;br/&gt;&lt;a name="Qas0s"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="55-能简单说一下线程池的工作流程吗？"&gt;&lt;a class="headerlink" href="#55-能简单说一下线程池的工作流程吗？" title="55.能简单说一下线程池的工作流程吗？"&gt;&lt;/a&gt;55.能简单说一下线程池的工作流程吗？&lt;/h3&gt;&lt;p&gt;当应用程序提交一个任务时，线程池会根据当前线程的状态和参数决定如何处理这个任务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果线程池中的核心线程都在忙，并且线程池未达到最大线程数，新提交的任务会被放入队列中进行等待。&lt;/li&gt;
&lt;li&gt;如果任务队列已满，且当前线程数量小于最大线程数，线程池会创建新的线程来处理任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;空闲的线程会从任务队列中取出任务来执行，当任务执行完毕后，线程并不会立即销毁，而是继续保持在池中等待下一个任务。&lt;br/&gt;当线程空闲时间超出指定时间，且当前线程数量大于核心线程数时，线程会被回收。&lt;br/&gt;举例：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;corePoolSize 对应营业窗口数 3&lt;/li&gt;
&lt;li&gt;maximumPoolSize 对应最大窗口数 6&lt;/li&gt;
&lt;li&gt;workQueue 对应排队区&lt;/li&gt;
&lt;li&gt;handler 对应接待员小美&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolDemo&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 创建一个线程池&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;ExecutorService&lt;/span&gt; &lt;span class="variable"&gt;threadPool&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;(&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="number"&gt;3&lt;/span&gt;, &lt;span class="comment"&gt;// 核心线程数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="number"&gt;6&lt;/span&gt;, &lt;span class="comment"&gt;// 最大线程数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="number"&gt;0&lt;/span&gt;, &lt;span class="comment"&gt;// 线程空闲时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                TimeUnit.SECONDS, &lt;span class="comment"&gt;// 时间单位&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;LinkedBlockingQueue&lt;/span&gt;&amp;lt;&amp;gt;(&lt;span class="number"&gt;10&lt;/span&gt;), &lt;span class="comment"&gt;// 等待队列&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                Executors.defaultThreadFactory(), &lt;span class="comment"&gt;// 线程工厂&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;.AbortPolicy() &lt;span class="comment"&gt;// 拒绝策略&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        );&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 模拟 10 个顾客来银行办理业务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;; i &amp;lt;= &lt;span class="number"&gt;10&lt;/span&gt;; i++) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;tempInt&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; i;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                threadPool.execute(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    System.out.println(Thread.currentThread().getName() + &lt;span class="string"&gt;"\t"&lt;/span&gt; + &lt;span class="string"&gt;"办理业务"&lt;/span&gt; + tempInt);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        } &lt;span class="keyword"&gt;catch&lt;/span&gt; (Exception e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            threadPool.shutdown();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
好，我再来梳理一下线程池的整个工作流程。&lt;br/&gt;第一步，创建线程池。&lt;br/&gt;第二步，调用线程池的 &lt;code&gt;execute()&lt;/code&gt;方法，提交任务。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果正在运行的线程数量小于 corePoolSize，那么线程池会创建一个新的线程来执行这个任务；&lt;/li&gt;
&lt;li&gt;如果正在运行的线程数量大于或等于 corePoolSize，那么线程池会将这个任务放入等待队列；&lt;/li&gt;
&lt;li&gt;如果等待队列满了，而且正在运行的线程数量小于 maximumPoolSize，那么线程池会创建新的线程来执行这个任务；&lt;/li&gt;
&lt;li&gt;如果等待队列满了，而且正在运行的线程数量大于或等于 maximumPoolSize，那么线程池会执行拒绝策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-66.png"/&gt;&lt;br/&gt;第三步，线程执行完毕后，线程并不会立即销毁，而是继续保持在池中等待下一个任务。&lt;br/&gt;第四步，当线程空闲时间超出指定时间，且当前线程数量大于核心线程数时，线程会被回收。&lt;br/&gt;&lt;a name="hFeW2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="56-线程池主要参数有哪些？"&gt;&lt;a class="headerlink" href="#56-线程池主要参数有哪些？" title="56.线程池主要参数有哪些？"&gt;&lt;/a&gt;56.线程池主要参数有哪些？&lt;/h3&gt;&lt;p&gt;线程池有 7 个参数，需要重点关注&lt;code&gt;corePoolSize&lt;/code&gt;、&lt;code&gt;maximumPoolSize&lt;/code&gt;、&lt;code&gt;workQueue&lt;/code&gt;、&lt;code&gt;handler&lt;/code&gt; 这四个。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-67.png"/&gt;&lt;br/&gt;我一一说一下：&lt;br/&gt;&lt;strong&gt;①、corePoolSize&lt;/strong&gt;&lt;br/&gt;定义了线程池中的核心线程数量。即使这些线程处于空闲状态，它们也不会被回收。这是线程池保持在等待状态下的线程数。&lt;br/&gt;&lt;strong&gt;②、maximumPoolSize&lt;/strong&gt;&lt;br/&gt;线程池允许的最大线程数量。当工作队列满了之后，线程池会创建新线程来处理任务，直到线程数达到这个最大值。&lt;br/&gt;&lt;strong&gt;③、workQueue&lt;/strong&gt;&lt;br/&gt;用于存放待处理任务的阻塞队列。当所有核心线程都忙时，新任务会被放在这个队列里等待执行。&lt;br/&gt;&lt;strong&gt;④、handler&lt;/strong&gt;&lt;br/&gt;拒绝策略 RejectedExecutionHandler，定义了当线程池和工作队列都满了之后对新提交的任务的处理策略。常见的拒绝策略包括抛出异常、直接丢弃、丢弃队列中最老的任务、由提交任务的线程来直接执行任务等。&lt;br/&gt;&lt;strong&gt;⑤、threadFactory&lt;/strong&gt;&lt;br/&gt;一个创建新线程的工厂。它用于创建线程池中的线程。可以通过自定义 ThreadFactory 来给线程池中的线程设置有意义的名字，或设置优先级等。&lt;br/&gt;&lt;strong&gt;⑥、keepAliveTime&lt;/strong&gt;&lt;br/&gt;非核心线程的空闲存活时间。如果线程池中的线程数量超过了 corePoolSize，那么这些多余的线程在空闲时间超过 keepAliveTime 时会被终止。&lt;br/&gt;&lt;strong&gt;⑦、unit&lt;/strong&gt;&lt;br/&gt;keepAliveTime 参数的时间单位：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TimeUnit.DAYS; 天&lt;/li&gt;
&lt;li&gt;TimeUnit.HOURS; 小时&lt;/li&gt;
&lt;li&gt;TimeUnit.MINUTES; 分钟&lt;/li&gt;
&lt;li&gt;TimeUnit.SECONDS; 秒&lt;/li&gt;
&lt;li&gt;TimeUnit.MILLISECONDS; 毫秒&lt;/li&gt;
&lt;li&gt;TimeUnit.MICROSECONDS; 微秒&lt;/li&gt;
&lt;li&gt;TimeUnit.NANOSECONDS; 纳秒&lt;br/&gt;&lt;a name="diMLJ"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="能简单说一下参数之间的关系吗？"&gt;&lt;a class="headerlink" href="#能简单说一下参数之间的关系吗？" title="能简单说一下参数之间的关系吗？"&gt;&lt;/a&gt;能简单说一下参数之间的关系吗？&lt;/h4&gt;&lt;p&gt;①、corePoolSize 和 maximumPoolSize 共同定义了线程池的规模。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当提交的任务数不足以填满核心线程时，线程池只会创建足够的线程来处理任务。&lt;/li&gt;
&lt;li&gt;当任务数增多，超过核心线程的处理能力时，任务会被加入 workQueue。&lt;/li&gt;
&lt;li&gt;如果 workQueue 已满，而当前线程数又小于 maximumPoolSize，线程池会尝试创建新的线程来处理任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;②、keepAliveTime 和 unit 决定了非核心线程可以空闲存活多久。这会影响了线程池的资源回收策略。&lt;br/&gt;③、workQueue 的选择对线程池的行为有重大影响。不同类型的队列（如无界队列、有界队列）会导致线程池在任务增多时的反应不同。&lt;br/&gt;④、handler 定义了线程池的饱和策略，即当线程池无法接受新任务时的行为。决定了系统在极限情况下的表现。&lt;br/&gt;&lt;a name="LSwoj"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="核心线程数不够会怎么进行处理？"&gt;&lt;a class="headerlink" href="#核心线程数不够会怎么进行处理？" title="核心线程数不够会怎么进行处理？"&gt;&lt;/a&gt;核心线程数不够会怎么进行处理？&lt;/h4&gt;&lt;p&gt;当提交的任务数超过了 corePoolSize，但是小于 maximumPoolSize 时，线程池会创建新的线程来处理任务。&lt;br/&gt;当提交的任务数超过了 maximumPoolSize 时，线程池会根据拒绝策略来处理任务。&lt;br/&gt;&lt;a name="JPh1y"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="举个例子说一下这些参数的变化"&gt;&lt;a class="headerlink" href="#举个例子说一下这些参数的变化" title="举个例子说一下这些参数的变化"&gt;&lt;/a&gt;举个例子说一下这些参数的变化&lt;/h4&gt;&lt;p&gt;假设一个场景，线程池的配置如下：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;corePoolSize = &lt;span class="number"&gt;5&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;maximumPoolSize = &lt;span class="number"&gt;10&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;keepAliveTime = &lt;span class="number"&gt;60&lt;/span&gt;秒&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;workQueue = LinkedBlockingQueue（容量为&lt;span class="number"&gt;100&lt;/span&gt;）&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;默认的&lt;span class="type"&gt;threadFactory&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="variable"&gt;handler&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; ThreadPoolExecutor.AbortPolicy()&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;场景一&lt;/strong&gt;：当系统启动后，逐渐有 10 个任务提交到线程池。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前 5 个任务会立即执行，因为它们会占用所有的核心线程。&lt;/li&gt;
&lt;li&gt;随后的 5 个任务会被放入工作队列中等待执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;场景二&lt;/strong&gt;：如果此时再有 100 个任务提交到线程池。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工作队列已满，线程池会创建额外的线程来执行这些任务，直到线程总数达到 maximumPoolSize（10 个线程）。&lt;/li&gt;
&lt;li&gt;如果任务继续增加，超过了工作队列和最大线程数的限制，新来的任务将会根据拒绝策略（AbortPolicy）被拒绝，抛出 RejectedExecutionException 异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;场景三&lt;/strong&gt;：如果任务突然减少，只有少量的任务需要执行：&lt;br/&gt;核心线程会一直运行，而超出核心线程数的线程，如果空闲时间超过 keepAliveTime，将会被终止，直到线程池的线程数减少到 corePoolSize。&lt;br/&gt;&lt;a name="pS3Qq"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="57-线程池的拒绝策略有哪些？"&gt;&lt;a class="headerlink" href="#57-线程池的拒绝策略有哪些？" title="57.线程池的拒绝策略有哪些？"&gt;&lt;/a&gt;57.线程池的拒绝策略有哪些？&lt;/h3&gt;&lt;p&gt;主要有四种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AbortPolicy：这是默认的拒绝策略。该策略会抛出一个 RejectedExecutionException 异常。也就对应着“我们系统瘫痪了”。&lt;/li&gt;
&lt;li&gt;CallerRunsPolicy：该策略不会抛出异常，而是会让提交任务的线程（即调用 execute 方法的线程）自己来执行这个任务。也就对应着“谁叫你来办的你找谁去”。&lt;/li&gt;
&lt;li&gt;DiscardOldestPolicy：策略会丢弃队列中最老的一个任务（即队列中等待最久的任务），然后尝试重新提交被拒绝的任务。也就对应着“看你比较急，去队里加个塞”。&lt;/li&gt;
&lt;li&gt;DiscardPolicy：策略会默默地丢弃被拒绝的任务，不做任何处理也不抛出异常。也就对应着“今天没办法，不行你看改一天”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果想实现自己的拒绝策略，实现 RejectedExecutionHandler 接口即可。&lt;br/&gt;&lt;a name="KDkx5"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="58-线程池有哪几种阻塞队列？"&gt;&lt;a class="headerlink" href="#58-线程池有哪几种阻塞队列？" title="58.线程池有哪几种阻塞队列？"&gt;&lt;/a&gt;58.线程池有哪几种阻塞队列？&lt;/h3&gt;&lt;p&gt;在 Java 中，线程池（ThreadPoolExecutor）使用阻塞队列（BlockingQueue）来存储待处理的任务。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-69.png"/&gt;&lt;br/&gt;①、ArrayBlockingQueue：一个有界的先进先出的阻塞队列，底层是一个数组，适合固定大小的线程池。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;ArrayBlockingQueue&amp;lt;Integer&amp;gt; blockingQueue = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ArrayBlockingQueue&lt;/span&gt;&amp;lt;Integer&amp;gt;(&lt;span class="number"&gt;10&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、LinkedBlockingQueue：底层数据结构是链表，如果不指定大小，默认大小是 Integer.MAX_VALUE，相当于一个无界队列。项目中，就使用了 LinkedBlockingQueue 来配置 RabbitMQ 的消息队列。&lt;br/&gt;③、PriorityBlockingQueue：一个支持优先级排序的无界阻塞队列。任务按照其自然顺序或通过构造器给定的 Comparator 来排序。&lt;br/&gt;适用于需要按照给定优先级处理任务的场景，比如优先处理紧急任务。&lt;br/&gt;④、DelayQueue：类似于 PriorityBlockingQueue，由二叉堆实现的无界优先级阻塞队列。&lt;br/&gt;Executors 中的 &lt;code&gt;newScheduledThreadPool()&lt;/code&gt; 就使用了 DelayQueue 来实现延迟执行。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;ScheduledThreadPoolExecutor&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; corePoolSize)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;super&lt;/span&gt;(corePoolSize, Integer.MAX_VALUE, &lt;span class="number"&gt;0&lt;/span&gt;, NANOSECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;DelayedWorkQueue&lt;/span&gt;());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;⑤、SynchronousQueue：实际上它不是一个真正的队列，因为没有容量。每个插入操作必须等待另一个线程的移除操作，同样任何一个移除操作都必须等待另一个线程的插入操作。&lt;br/&gt;&lt;code&gt;Executors.newCachedThreadPool()&lt;/code&gt; 就使用了 SynchronousQueue，这个线程池会根据需要创建新线程，如果有空闲线程则会重复使用，线程空闲 60 秒后会被回收。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; ExecutorService &lt;span class="title function_"&gt;newCachedThreadPool&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;(&lt;span class="number"&gt;0&lt;/span&gt;, Integer.MAX_VALUE,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                    &lt;span class="number"&gt;60L&lt;/span&gt;, TimeUnit.SECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                    &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;SynchronousQueue&lt;/span&gt;&amp;lt;Runnable&amp;gt;());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="LBG9F"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="59-线程池提交-execute-和-submit-有什么区别？"&gt;&lt;a class="headerlink" href="#59-线程池提交-execute-和-submit-有什么区别？" title="59.线程池提交 execute 和 submit 有什么区别？"&gt;&lt;/a&gt;59.线程池提交 execute 和 submit 有什么区别？&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;execute 用于提交不需要返回值的任务&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;threadsPool.execute(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Runnable&lt;/span&gt;() {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// TODO Auto-generated method stub }&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;submit()方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象，通过这个 future 对象可以判断任务是否执行成功，并且可以通过 future 的 get()方法来获取返回值。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;Future&amp;lt;Object&amp;gt; future = executor.submit(haveReturnValuetask);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;try&lt;/span&gt; { &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;s&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; future.get(); } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 处理中断异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;} &lt;span class="keyword"&gt;catch&lt;/span&gt; (ExecutionException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 处理无法执行任务异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;} &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// 关闭线程池 executor.shutdown();&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="NWi54"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="60-线程池怎么关闭知道吗？"&gt;&lt;a class="headerlink" href="#60-线程池怎么关闭知道吗？" title="60.线程池怎么关闭知道吗？"&gt;&lt;/a&gt;60.线程池怎么关闭知道吗？&lt;/h3&gt;&lt;p&gt;可以通过调用线程池的&lt;code&gt;shutdown&lt;/code&gt;或&lt;code&gt;shutdownNow&lt;/code&gt;方法来关闭线程池。它们的原理是遍历线程池中的工作线程，然后逐个调用线程的 interrupt 方法来中断线程，所以无法响应中断的任务可能永远无法终止。&lt;br/&gt;&lt;strong&gt;shutdown() 将线程池状态置为 shutdown,并不会立即停止&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;停止接收外部 submit 的任务&lt;/li&gt;
&lt;li&gt;内部正在跑的任务和队列里等待的任务，会执行完&lt;/li&gt;
&lt;li&gt;等到第二步完成后，才真正停止&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;shutdownNow() 将线程池状态置为 stop。一般会立即停止，事实上不一定&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;和 shutdown()一样，先停止接收外部提交的任务&lt;/li&gt;
&lt;li&gt;忽略队列里等待的任务&lt;/li&gt;
&lt;li&gt;尝试将正在跑的任务 interrupt 中断&lt;/li&gt;
&lt;li&gt;返回未执行的任务列表&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;shutdown 和 shutdownnow 简单来说区别如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shutdownNow()能立即停止线程池，正在跑的和正在等待的任务都停下了。这样做立即生效，但是风险也比较大。&lt;/li&gt;
&lt;li&gt;shutdown()只是关闭了提交通道，用 submit()是无效的；而内部的任务该怎么跑还是怎么跑，跑完再彻底停止线程池。&lt;br/&gt;&lt;a name="ve21h"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="61-线程池的线程数应该怎么配置？"&gt;&lt;a class="headerlink" href="#61-线程池的线程数应该怎么配置？" title="61.线程池的线程数应该怎么配置？"&gt;&lt;/a&gt;61.线程池的线程数应该怎么配置？&lt;/h3&gt;&lt;p&gt;首先，我会分析线程池中执行的任务类型是 CPU 密集型还是 IO 密集型？&lt;br/&gt;①、对于 CPU 密集型任务，我的目标是尽量减少线程上下文切换，以优化 CPU 使用率。一般来说，核心线程数设置为处理器的核心数或核心数加一（以备不时之需，如某些线程因等待系统资源而阻塞时）是较理想的选择。&lt;br/&gt;②、对于 IO 密集型任务，由于线程经常处于等待状态（等待 IO 操作完成），可以设置更多的线程来提高并发性（比如说 2 倍），从而增加 CPU 利用率。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-70.png"/&gt;&lt;br/&gt;核心数可以通过 Java 的&lt;code&gt;Runtime.getRuntime().availableProcessors()&lt;/code&gt;方法获取。&lt;br/&gt;此外，每个线程都会占用一定的内存，因此我需要确保线程池的规模不会耗尽 JVM 内存，避免频繁的垃圾回收或内存溢出。&lt;br/&gt;最后，我会根据业务需求和系统资源来调整线程池的参数，比如核心线程数、最大线程数、非核心线程的空闲存活时间、任务队列容量等。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;ThreadPoolExecutor&lt;/span&gt; &lt;span class="variable"&gt;executor&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;(&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    cores, &lt;span class="comment"&gt;// 核心线程数设置为CPU核心数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    cores * &lt;span class="number"&gt;2&lt;/span&gt;, &lt;span class="comment"&gt;// 最大线程数为核心数的两倍&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="number"&gt;60L&lt;/span&gt;, TimeUnit.SECONDS, &lt;span class="comment"&gt;// 非核心线程的空闲存活时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;LinkedBlockingQueue&lt;/span&gt;&amp;lt;&amp;gt;(&lt;span class="number"&gt;100&lt;/span&gt;) &lt;span class="comment"&gt;// 任务队列容量&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a name="feMke"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="62-有哪几种常见的线程池？"&gt;&lt;a class="headerlink" href="#62-有哪几种常见的线程池？" title="62.有哪几种常见的线程池？"&gt;&lt;/a&gt;62.有哪几种常见的线程池？&lt;/h3&gt;&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-71.png"/&gt;&lt;br/&gt;可以通过 Executors 工厂类来创建四种常见的线程池：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;newFixedThreadPool (固定线程数目的线程池)&lt;/li&gt;
&lt;li&gt;newCachedThreadPool (可缓存线程的线程池)&lt;/li&gt;
&lt;li&gt;newSingleThreadExecutor (单线程的线程池)&lt;/li&gt;
&lt;li&gt;newScheduledThreadPool (定时及周期执行的线程池)&lt;br/&gt;&lt;a name="ULwjL"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="63-能说一下四种常见线程池的原理吗？"&gt;&lt;a class="headerlink" href="#63-能说一下四种常见线程池的原理吗？" title="63.能说一下四种常见线程池的原理吗？"&gt;&lt;/a&gt;63.能说一下四种常见线程池的原理吗？&lt;/h3&gt;&lt;p&gt;前三种线程池的构造直接调用 ThreadPoolExecutor 的构造方法。&lt;br/&gt;&lt;a name="hrYdn"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="newSingleThreadExecutor"&gt;&lt;a class="headerlink" href="#newSingleThreadExecutor" title="newSingleThreadExecutor"&gt;&lt;/a&gt;newSingleThreadExecutor&lt;/h4&gt;&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; ExecutorService &lt;span class="title function_"&gt;newSingleThreadExecutor&lt;/span&gt;&lt;span class="params"&gt;(ThreadFactory threadFactory)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;FinalizableDelegatedExecutorService&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        (&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;(&lt;span class="number"&gt;1&lt;/span&gt;, &lt;span class="number"&gt;1&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                &lt;span class="number"&gt;0L&lt;/span&gt;, TimeUnit.MILLISECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;LinkedBlockingQueue&lt;/span&gt;&amp;lt;Runnable&amp;gt;(),&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                threadFactory));&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;线程池特点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心线程数为 1&lt;/li&gt;
&lt;li&gt;最大线程数也为 1&lt;/li&gt;
&lt;li&gt;阻塞队列是无界队列 LinkedBlockingQueue，可能会导致 OOM&lt;/li&gt;
&lt;li&gt;keepAliveTime 为 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-72.png"/&gt;&lt;br/&gt;工作流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提交任务&lt;/li&gt;
&lt;li&gt;线程池是否有一条线程在，如果没有，新建线程执行任务&lt;/li&gt;
&lt;li&gt;如果有，将任务加到阻塞队列&lt;/li&gt;
&lt;li&gt;当前的唯一线程，从队列取任务，执行完一个，再继续取，一个线程执行任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;br/&gt;适用于串行执行任务的场景，一个任务一个任务地执行。&lt;br/&gt;&lt;a name="aXPIN"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="newFixedThreadPool"&gt;&lt;a class="headerlink" href="#newFixedThreadPool" title="newFixedThreadPool"&gt;&lt;/a&gt;newFixedThreadPool&lt;/h4&gt;&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; ExecutorService &lt;span class="title function_"&gt;newFixedThreadPool&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; nThreads, ThreadFactory threadFactory)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;(nThreads, nThreads,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                  &lt;span class="number"&gt;0L&lt;/span&gt;, TimeUnit.MILLISECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                  &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;LinkedBlockingQueue&lt;/span&gt;&amp;lt;Runnable&amp;gt;(),&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                  threadFactory);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;线程池特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心线程数和最大线程数大小一样&lt;/li&gt;
&lt;li&gt;没有所谓的非空闲时间，即 keepAliveTime 为 0&lt;/li&gt;
&lt;li&gt;阻塞队列为无界队列 LinkedBlockingQueue，可能会导致 OOM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-73.png"/&gt;&lt;br/&gt;工作流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提交任务&lt;/li&gt;
&lt;li&gt;如果线程数少于核心线程，创建核心线程执行任务&lt;/li&gt;
&lt;li&gt;如果线程数等于核心线程，把任务添加到 LinkedBlockingQueue 阻塞队列&lt;/li&gt;
&lt;li&gt;如果线程执行完任务，去阻塞队列取任务，继续执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;&lt;br/&gt;FixedThreadPool 适用于处理 CPU 密集型的任务，确保 CPU 在长期被工作线程使用的情况下，尽可能的少的分配线程，即适用执行长期的任务。&lt;br/&gt;&lt;a name="QS2H8"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="newCachedThreadPool"&gt;&lt;a class="headerlink" href="#newCachedThreadPool" title="newCachedThreadPool"&gt;&lt;/a&gt;newCachedThreadPool&lt;/h4&gt;&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; ExecutorService &lt;span class="title function_"&gt;newCachedThreadPool&lt;/span&gt;&lt;span class="params"&gt;(ThreadFactory threadFactory)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolExecutor&lt;/span&gt;(&lt;span class="number"&gt;0&lt;/span&gt;, Integer.MAX_VALUE,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                  &lt;span class="number"&gt;60L&lt;/span&gt;, TimeUnit.SECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                  &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;SynchronousQueue&lt;/span&gt;&amp;lt;Runnable&amp;gt;(),&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                                  threadFactory);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;线程池特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心线程数为 0&lt;/li&gt;
&lt;li&gt;最大线程数为 Integer.MAX_VALUE，即无限大，可能会因为无限创建线程，导致 OOM&lt;/li&gt;
&lt;li&gt;阻塞队列是 SynchronousQueue&lt;/li&gt;
&lt;li&gt;非核心线程空闲存活时间为 60 秒&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当提交任务的速度大于处理任务的速度时，每次提交一个任务，就必然会创建一个线程。极端情况下会创建过多的线程，耗尽 CPU 和内存资源。由于空闲 60 秒的线程会被终止，长时间保持空闲的 CachedThreadPool 不会占用任何资源。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-74.png"/&gt;&lt;br/&gt;工作流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提交任务&lt;/li&gt;
&lt;li&gt;因为没有核心线程，所以任务直接加到 SynchronousQueue 队列。&lt;/li&gt;
&lt;li&gt;判断是否有空闲线程，如果有，就去取出任务执行。&lt;/li&gt;
&lt;li&gt;如果没有空闲线程，就新建一个线程执行。&lt;/li&gt;
&lt;li&gt;执行完任务的线程，还可以存活 60 秒，如果在这期间，接到任务，可以继续活下去；否则，被销毁。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;br/&gt;用于并发执行大量短期的小任务。&lt;br/&gt;&lt;a name="TfZGO"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="newScheduledThreadPool"&gt;&lt;a class="headerlink" href="#newScheduledThreadPool" title="newScheduledThreadPool"&gt;&lt;/a&gt;newScheduledThreadPool&lt;/h4&gt;&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;ScheduledThreadPoolExecutor&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; corePoolSize)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;super&lt;/span&gt;(corePoolSize, Integer.MAX_VALUE, &lt;span class="number"&gt;0&lt;/span&gt;, NANOSECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;DelayedWorkQueue&lt;/span&gt;());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;线程池特点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最大线程数为 Integer.MAX_VALUE，也有 OOM 的风险&lt;/li&gt;
&lt;li&gt;阻塞队列是 DelayedWorkQueue&lt;/li&gt;
&lt;li&gt;keepAliveTime 为 0&lt;/li&gt;
&lt;li&gt;scheduleAtFixedRate() ：按某种速率周期执行&lt;/li&gt;
&lt;li&gt;scheduleWithFixedDelay()：在某个延迟后执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-75.png"/&gt;&lt;br/&gt;&lt;strong&gt;工作机制&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程从 DelayQueue 中获取已到期的 ScheduledFutureTask（DelayQueue.take()）。到期任务是指 ScheduledFutureTask 的 time 大于等于当前时间。&lt;/li&gt;
&lt;li&gt;线程执行这个 ScheduledFutureTask。&lt;/li&gt;
&lt;li&gt;线程修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间。&lt;/li&gt;
&lt;li&gt;线程把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中（DelayQueue.add()）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-76.png"/&gt;&lt;br/&gt;&lt;strong&gt;使用场景&lt;/strong&gt;&lt;br/&gt;周期性执行任务的场景，需要限制线程数量的场景&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用无界队列的线程池会导致什么问题吗？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;例如 newFixedThreadPool 使用了无界的阻塞队列 LinkedBlockingQueue，如果线程获取一个任务后，任务的执行时间比较长，会导致队列的任务越积越多，导致机器内存使用不停飙升，最终导致 OOM。&lt;br/&gt;&lt;a name="xe9ES"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="64-线程池异常怎么处理知道吗？"&gt;&lt;a class="headerlink" href="#64-线程池异常怎么处理知道吗？" title="64.线程池异常怎么处理知道吗？"&gt;&lt;/a&gt;64.线程池异常怎么处理知道吗？&lt;/h3&gt;&lt;p&gt;在使用线程池处理任务的时候，任务代码可能抛出 RuntimeException，抛出异常后，线程池可能捕获它，也可能创建一个新的线程来代替异常的线程，我们可能无法感知任务出现了异常，因此我们需要考虑线程池异常情况。&lt;br/&gt;常见的异常处理方式：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-77.png"/&gt;&lt;br/&gt;&lt;a name="kAW1S"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="65-能说一下线程池有几种状态吗？"&gt;&lt;a class="headerlink" href="#65-能说一下线程池有几种状态吗？" title="65.能说一下线程池有几种状态吗？"&gt;&lt;/a&gt;65.能说一下线程池有几种状态吗？&lt;/h3&gt;&lt;p&gt;线程池有这几个状态：RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//线程池状态&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;RUNNING&lt;/span&gt;    &lt;span class="operator"&gt;=&lt;/span&gt; -&lt;span class="number"&gt;1&lt;/span&gt; &amp;lt;&amp;lt; COUNT_BITS;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;SHUTDOWN&lt;/span&gt;   &lt;span class="operator"&gt;=&lt;/span&gt;  &lt;span class="number"&gt;0&lt;/span&gt; &amp;lt;&amp;lt; COUNT_BITS;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;STOP&lt;/span&gt;       &lt;span class="operator"&gt;=&lt;/span&gt;  &lt;span class="number"&gt;1&lt;/span&gt; &amp;lt;&amp;lt; COUNT_BITS;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;TIDYING&lt;/span&gt;    &lt;span class="operator"&gt;=&lt;/span&gt;  &lt;span class="number"&gt;2&lt;/span&gt; &amp;lt;&amp;lt; COUNT_BITS;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;TERMINATED&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt;  &lt;span class="number"&gt;3&lt;/span&gt; &amp;lt;&amp;lt; COUNT_BITS;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;线程池各个状态切换图：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-78.png"/&gt;&lt;br/&gt;&lt;strong&gt;RUNNING&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该状态的线程池会接收新任务，并处理阻塞队列中的任务;&lt;/li&gt;
&lt;li&gt;调用线程池的 shutdown()方法，可以切换到 SHUTDOWN 状态;&lt;/li&gt;
&lt;li&gt;调用线程池的 shutdownNow()方法，可以切换到 STOP 状态;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SHUTDOWN&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该状态的线程池不会接收新任务，但会处理阻塞队列中的任务；&lt;/li&gt;
&lt;li&gt;队列为空，并且线程池中执行的任务也为空,进入 TIDYING 状态;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;STOP&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该状态的线程不会接收新任务，也不会处理阻塞队列中的任务，而且会中断正在运行的任务；&lt;/li&gt;
&lt;li&gt;线程池中执行的任务为空,进入 TIDYING 状态;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TIDYING&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该状态表明所有的任务已经运行终止，记录的任务数量为 0。&lt;/li&gt;
&lt;li&gt;terminated()执行完毕，进入 TERMINATED 状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TERMINATED&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该状态表示线程池彻底终止&lt;br/&gt;&lt;a name="GmKN3"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="66-线程池如何实现参数的动态修改？"&gt;&lt;a class="headerlink" href="#66-线程池如何实现参数的动态修改？" title="66.线程池如何实现参数的动态修改？"&gt;&lt;/a&gt;66.线程池如何实现参数的动态修改？&lt;/h3&gt;&lt;p&gt;线程池提供了几个 setter 方法来设置线程池的参数。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-79.png"/&gt;&lt;br/&gt;这里主要有两个思路：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-80.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在我们微服务的架构下，可以利用配置中心如 Nacos、Apollo 等等，也可以自己开发配置中心。业务服务读取线程池配置，获取相应的线程池实例来修改线程池的参数。&lt;/li&gt;
&lt;li&gt;如果限制了配置中心的使用，也可以自己去扩展&lt;strong&gt;ThreadPoolExecutor&lt;/strong&gt;，重写方法，监听线程池参数变化，来动态修改线程池参数。&lt;br/&gt;&lt;a name="ELeMo"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="67-线程池调优了解吗？（补充）"&gt;&lt;a class="headerlink" href="#67-线程池调优了解吗？（补充）" title="67.线程池调优了解吗？（补充）"&gt;&lt;/a&gt;67.线程池调优了解吗？（补充）&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;2026 年 03 月 16 日增补&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;线程池配置没有固定的公式，通常事前会对线程池进行一定评估，常见的评估方案如下：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-81.png"/&gt;&lt;br/&gt;上线之前也要进行充分的测试，上线之后要建立完善的线程池监控机制。&lt;br/&gt;事中结合监控告警机制，分析线程池的问题，或者可优化点，结合线程池动态参数配置机制来调整配置。&lt;br/&gt;事后要注意仔细观察，随时调整。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-82.png"/&gt;&lt;br/&gt;具体的调优案例可以查看参考[7]美团技术博客。&lt;br/&gt;&lt;a name="IEq26"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="68-线程池在使用的时候需要注意什么？（补充）"&gt;&lt;a class="headerlink" href="#68-线程池在使用的时候需要注意什么？（补充）" title="68.线程池在使用的时候需要注意什么？（补充）"&gt;&lt;/a&gt;68.线程池在使用的时候需要注意什么？（补充）&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;2026 年 03 月 16 日增补&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我认为比较重要的关注点有 3 个：&lt;br/&gt;①、选择合适的线程池大小&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;过小&lt;/strong&gt;的线程池可能会导致任务一直在排队&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过大&lt;/strong&gt;的线程池可能会导致大家都在竞争 CPU 资源，增加上下文切换的开销&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以根据业务是 IO 密集型还是 CPU 密集型来选择线程池大小：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU 密集型：指的是任务主要使用来进行大量的计算，没有什么导致线程阻塞。一般这种场景的线程数设置为 CPU 核心数+1。&lt;/li&gt;
&lt;li&gt;IO 密集型：当执行任务需要大量的 io，比如磁盘 io，网络 io，可能会存在大量的阻塞，所以在 IO 密集型任务中使用多线程可以大大地加速任务的处理。一般线程数设置为 2*CPU 核心数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;②、任务队列的选择&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用有界队列可以避免资源耗尽的风险，但是可能会导致任务被拒绝&lt;/li&gt;
&lt;li&gt;使用无界队列虽然可以避免任务被拒绝，但是可能会导致内存耗尽&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般需要设置有界队列的大小，比如 LinkedBlockingQueue 在构造的时候可以传入参数来限制队列中任务数据的大小，这样就不会因为无限往队列中扔任务导致系统的 oom。&lt;br/&gt;③、尽量使用自定义的线程池，而不是使用 Executors 创建的线程池，因为 newFixedThreadPool 线程池由于使用了 LinkedBlockingQueue，队列的容量默认无限大，实际使用中出现任务过多时会导致内存溢出；newCachedThreadPool 线程池由于核心线程数无限大，当任务过多的时候会导致创建大量的线程，可能机器负载过高导致服务宕机。&lt;br/&gt;&lt;a name="JyDvu"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="69-你能设计实现一个线程池吗？"&gt;&lt;a class="headerlink" href="#69-你能设计实现一个线程池吗？" title="69.你能设计实现一个线程池吗？"&gt;&lt;/a&gt;69.你能设计实现一个线程池吗？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://mp.weixin.qq.com/s/Exy7pRGND9TCjRd9TZK4jg"&gt;三分恶线程池原理&lt;/a&gt;&lt;br/&gt;线程池的设计需要考虑这几个关键因素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;核心线程池类：包含核心线程数、最大线程数。&lt;/li&gt;
&lt;li&gt;工作线程：线程池中实际工作的线程，从任务队列中获取任务并执行。&lt;/li&gt;
&lt;li&gt;任务队列：存放待执行任务的队列，可以使用阻塞队列实现。&lt;/li&gt;
&lt;li&gt;拒绝策略：当任务队列满时，处理新任务的策略。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-83.png"/&gt;&lt;br/&gt;核心线程池类：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;53&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;54&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;55&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;56&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;57&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;58&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;59&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;60&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;61&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;62&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;63&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;64&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;65&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;66&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;67&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;68&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;69&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;70&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;71&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;72&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;73&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;74&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;75&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;76&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;77&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;78&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;79&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;80&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;81&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;82&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;83&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;84&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;85&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;86&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;87&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;88&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;89&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;90&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;91&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;92&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;93&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;94&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;95&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;96&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;97&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;98&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;99&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;100&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;101&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;102&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;103&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;104&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;105&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;106&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;107&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;108&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; * CustomThreadPoolExecutor is a simple implementation of a thread pool.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CustomThreadPoolExecutor&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; corePoolSize;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; maximumPoolSize;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;long&lt;/span&gt; keepAliveTime;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; TimeUnit unit;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; BlockingQueue&amp;lt;Runnable&amp;gt; workQueue;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; RejectedExecutionHandler handler;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;volatile&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="variable"&gt;isShutdown&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;currentPoolSize&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * Constructs a CustomThreadPoolExecutor.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     *&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; corePoolSize    the number of core threads.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; maximumPoolSize the maximum number of threads.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; keepAliveTime   the time to keep extra threads alive.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; unit            the time unit for keepAliveTime.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; workQueue       the queue to hold runnable tasks.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; handler         the handler to use when execution is blocked.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;CustomThreadPoolExecutor&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; corePoolSize, &lt;span class="type"&gt;int&lt;/span&gt; maximumPoolSize, &lt;span class="type"&gt;long&lt;/span&gt; keepAliveTime, TimeUnit unit,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;                                    BlockingQueue&amp;lt;Runnable&amp;gt; workQueue, RejectedExecutionHandler handler)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.corePoolSize = corePoolSize;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.maximumPoolSize = maximumPoolSize;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.keepAliveTime = keepAliveTime;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.unit = unit;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.workQueue = workQueue;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.handler = handler;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * Executes a given task using the thread pool.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     *&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; task the task to execute.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;execute&lt;/span&gt;&lt;span class="params"&gt;(Runnable task)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (isShutdown) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;IllegalStateException&lt;/span&gt;(&lt;span class="string"&gt;"ThreadPool is shutdown"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;synchronized&lt;/span&gt; (&lt;span class="built_in"&gt;this&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// If current pool size is less than core pool size, create a new worker thread&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; (currentPoolSize &amp;lt; corePoolSize) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Worker&lt;/span&gt;(task).start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                currentPoolSize++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// Try to add task to the queue, if full create a new worker thread if possible&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; (!workQueue.offer(task)) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;if&lt;/span&gt; (currentPoolSize &amp;lt; maximumPoolSize) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Worker&lt;/span&gt;(task).start();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    currentPoolSize++;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    &lt;span class="comment"&gt;// If maximum pool size reached, apply the rejection handler&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    handler.rejectedExecution(task, &lt;span class="literal"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * Shuts down the thread pool.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;shutdown&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        isShutdown = &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * Worker is an internal class that represents a worker thread in the pool.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;Worker&lt;/span&gt; &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;Thread&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;private&lt;/span&gt; Runnable task;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Worker(Runnable task) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="built_in"&gt;this&lt;/span&gt;.task = task;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;run&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;while&lt;/span&gt; (task != &lt;span class="literal"&gt;null&lt;/span&gt; || (task = getTask()) != &lt;span class="literal"&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    task.run();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    task = &lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;         * Gets a task from the work queue, waiting up to keepAliveTime if necessary.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;         *&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;         * &lt;span class="doctag"&gt;@return&lt;/span&gt; a task to run, or null if the keepAliveTime expires.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;         */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;private&lt;/span&gt; Runnable &lt;span class="title function_"&gt;getTask&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;return&lt;/span&gt; workQueue.poll(keepAliveTime, unit);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;拒绝策略：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; * CustomRejectedExecutionHandler contains several common rejection policies.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CustomRejectedExecutionHandler&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * AbortPolicy throws a RuntimeException when the task is rejected.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;AbortPolicy&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;RejectedExecutionHandler&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;rejectedExecution&lt;/span&gt;&lt;span class="params"&gt;(Runnable r, ThreadPoolExecutor e)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;RuntimeException&lt;/span&gt;(&lt;span class="string"&gt;"Task "&lt;/span&gt; + r.toString() + &lt;span class="string"&gt;" rejected from "&lt;/span&gt; + e.toString());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * DiscardPolicy silently discards the rejected task.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DiscardPolicy&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;RejectedExecutionHandler&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;rejectedExecution&lt;/span&gt;&lt;span class="params"&gt;(Runnable r, ThreadPoolExecutor e)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// Do nothing&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * CallerRunsPolicy runs the rejected task in the caller's thread.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CallerRunsPolicy&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;RejectedExecutionHandler&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;rejectedExecution&lt;/span&gt;&lt;span class="params"&gt;(Runnable r, ThreadPoolExecutor e)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; (!e.isShutdown()) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                r.run();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;使用示例：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;package&lt;/span&gt; com.github.paicoding.forum.web.javabetter.thread1;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; java.util.concurrent.LinkedBlockingQueue;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; java.util.concurrent.TimeUnit;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;ThreadPoolTest&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// Create a thread pool with core size 2, max size 4, and a queue capacity of 2&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;CustomThreadPoolExecutor&lt;/span&gt; &lt;span class="variable"&gt;executor&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CustomThreadPoolExecutor&lt;/span&gt;(&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="number"&gt;2&lt;/span&gt;, &lt;span class="number"&gt;4&lt;/span&gt;, &lt;span class="number"&gt;10&lt;/span&gt;, TimeUnit.SECONDS,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;LinkedBlockingQueue&lt;/span&gt;&amp;lt;&amp;gt;(&lt;span class="number"&gt;2&lt;/span&gt;),&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CustomRejectedExecutionHandler&lt;/span&gt;.AbortPolicy());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// Submit 10 tasks to the pool&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;; i &amp;lt; &lt;span class="number"&gt;10&lt;/span&gt;; i++) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;index&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; i;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            executor.execute(() -&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"Task "&lt;/span&gt; + index + &lt;span class="string"&gt;" is running"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    Thread.sleep(&lt;span class="number"&gt;2000&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    e.printStackTrace();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// Shutdown the thread pool&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        executor.shutdown();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;执行结果：&lt;a name="Mc61k"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="70-单机线程池执行断电了应该怎么处理？"&gt;&lt;a class="headerlink" href="#70-单机线程池执行断电了应该怎么处理？" title="70.单机线程池执行断电了应该怎么处理？"&gt;&lt;/a&gt;70.单机线程池执行断电了应该怎么处理？&lt;/h3&gt;&lt;p&gt;我们可以对正在处理和阻塞队列的任务做事务管理或者对阻塞队列中的任务持久化处理，并且当断电或者系统崩溃，操作无法继续下去的时候，可以通过回溯日志的方式来撤销&lt;code&gt;正在处理&lt;/code&gt;的已经执行成功的操作。然后重新执行整个阻塞队列。&lt;br/&gt;也就是说，对阻塞队列持久化；正在处理任务事务控制；断电之后正在处理任务的回滚，通过日志恢复该次操作；服务器重启后阻塞队列中的数据再加载。&lt;br/&gt;&lt;a name="RqB1i"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="并发容器和框架"&gt;&lt;a class="headerlink" href="#并发容器和框架" title="并发容器和框架"&gt;&lt;/a&gt;并发容器和框架&lt;/h2&gt;&lt;p&gt;关于一些并发容器，可以去看看 &lt;a href="https://mp.weixin.qq.com/s/SHkQ7LEOT0itt4bXMoDBPw"&gt;面渣逆袭：Java 集合连环三十问 &lt;/a&gt;，里面有&lt;code&gt;CopyOnWriteArrayList&lt;/code&gt;和&lt;code&gt;ConcurrentHashMap&lt;/code&gt;这两种线程安全容器类的问答。。&lt;br/&gt;&lt;a name="EaRcS"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="71-Fork-Join-框架了解吗？"&gt;&lt;a class="headerlink" href="#71-Fork-Join-框架了解吗？" title="71.Fork/Join 框架了解吗？"&gt;&lt;/a&gt;71.Fork/Join 框架了解吗？&lt;/h3&gt;&lt;p&gt;Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架，是一个把大任务分割成若干个小任务，最终汇总每个小任务结果后得到大任务结果的框架。&lt;br/&gt;要想掌握 Fork/Join 框架，首先需要理解两个点，&lt;strong&gt;分而治之&lt;/strong&gt;和&lt;strong&gt;工作窃取算法&lt;/strong&gt;。&lt;br/&gt;&lt;strong&gt;分而治之&lt;/strong&gt;&lt;br/&gt;Fork/Join 框架的定义，其实就体现了分治思想：将一个规模为 N 的问题分解为 K 个规模较小的子问题，这些子问题相互独立且与原问题性质相同。求出子问题的解，就可得到原问题的解。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-85.png"/&gt;&lt;br/&gt;&lt;strong&gt;工作窃取算法&lt;/strong&gt;&lt;br/&gt;大任务拆成了若干个小任务，把这些小任务放到不同的队列里，各自创建单独线程来执行队列里的任务。&lt;br/&gt;那么问题来了，有的线程干活块，有的线程干活慢。干完活的线程不能让它空下来，得让它去帮没干完活的线程干活。它去其它线程的队列里窃取一个任务来执行，这就是所谓的&lt;strong&gt;工作窃取&lt;/strong&gt;。&lt;br/&gt;工作窃取发生的时候，它们会访问同一个队列，为了减少窃取任务线程和被窃取任务线程之间的竞争，通常任务会使用双端队列，被窃取任务线程永远从双端队列的头部拿，而窃取任务的线程永远从双端队列的尾部拿任务执行。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/javathread-86.png"/&gt;&lt;br/&gt;看一个 Fork/Join 框架应用的例子，计算 1~n 之间的和：1+2+3+…+n&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置一个分割阈值，任务大于阈值就拆分任务&lt;/li&gt;
&lt;li&gt;任务有结果，所以需要继承 RecursiveTask&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;CountTask&lt;/span&gt; &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;RecursiveTask&lt;/span&gt;&amp;lt;Integer&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;THRESHOLD&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;16&lt;/span&gt;; &lt;span class="comment"&gt;// 阈值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; start;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; end;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;CountTask&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; start, &lt;span class="type"&gt;int&lt;/span&gt; end)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.start = start;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="built_in"&gt;this&lt;/span&gt;.end = end;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;protected&lt;/span&gt; Integer &lt;span class="title function_"&gt;compute&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;sum&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 如果任务足够小就计算任务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="variable"&gt;canCompute&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (end - start) &amp;lt;= THRESHOLD;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (canCompute) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; start; i &amp;lt;= end; i++) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                sum += i;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        } &lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// 如果任务大于阈值，就分裂成两个子任务计算&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;middle&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (start + end) / &lt;span class="number"&gt;2&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;CountTask&lt;/span&gt; &lt;span class="variable"&gt;leftTask&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CountTask&lt;/span&gt;(start, middle);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;CountTask&lt;/span&gt; &lt;span class="variable"&gt;rightTask&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CountTask&lt;/span&gt;(middle + &lt;span class="number"&gt;1&lt;/span&gt;, end);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;// 执行子任务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            leftTask.fork();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            rightTask.fork(); &lt;span class="comment"&gt;// 等待子任务执行完，并得到其结果&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;leftResult&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; leftTask.join();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;rightResult&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; rightTask.join(); &lt;span class="comment"&gt;// 合并子任务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            sum = leftResult + rightResult;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; sum;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;ForkJoinPool&lt;/span&gt; &lt;span class="variable"&gt;forkJoinPool&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ForkJoinPool&lt;/span&gt;(); &lt;span class="comment"&gt;// 生成一个计算任务，负责计算1+2+3+4&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;CountTask&lt;/span&gt; &lt;span class="variable"&gt;task&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CountTask&lt;/span&gt;(&lt;span class="number"&gt;1&lt;/span&gt;, &lt;span class="number"&gt;100&lt;/span&gt;); &lt;span class="comment"&gt;// 执行一个任务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Future&amp;lt;Integer&amp;gt; result = forkJoinPool.submit(task);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            System.out.println(result.get());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        } &lt;span class="keyword"&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        } &lt;span class="keyword"&gt;catch&lt;/span&gt; (ExecutionException e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
ForkJoinTask 与一般 Task 的主要区别在于它需要实现 compute 方法，在这个方法里，首先需要判断任务是否足够小，如果足够小就直接执行任务。如果比较大，就必须分割成两个子任务，每个子任务在调用 fork 方法时，又会进 compute 方法，看看当前子任务是否需要继续分割成子任务，如果不需要继续分割，则执行当前子任务并返回结果。使用 join 方法会等待子任务执行完并得到其结果。&lt;/li&gt;
&lt;/ul&gt;
</content>
    <summary type="html">八股文系列-并发编程 ...</summary>
    <category term="八股文" scheme="https://aspirant02.github.io/categories/%E5%85%AB%E8%82%A1%E6%96%87/" />
    <category term="并发编程" scheme="https://aspirant02.github.io/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" />
  </entry>
  <entry>
    <title>八股文系列-Java集合框架</title>
    <link href="https://aspirant02.github.io/2026/08/01/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/" />
    <id>https://aspirant02.github.io/2026/08/01/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/</id>
    <published>2026-08-01T05:22:24Z</published>
    <updated>2026-08-01T05:22:24Z</updated>
    <content type="html">&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2 id="引言"&gt;&lt;a class="headerlink" href="#引言" title="引言"&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;h3 id="1-说说有哪些常见的集合框架？"&gt;&lt;a class="headerlink" href="#1-说说有哪些常见的集合框架？" title="1.说说有哪些常见的集合框架？"&gt;&lt;/a&gt;1.说说有哪些常见的集合框架？&lt;/h3&gt;&lt;p&gt;Java 集合框架可以分为两条大的支线：&lt;br/&gt;①、Collection，主要由 List、Set、Queue 组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List 代表有序、可重复的集合，典型代表就是封装了动态数组的 &lt;a href="https://javabetter.cn/collection/arraylist.html"&gt;ArrayList&lt;/a&gt; 和封装了链表的 &lt;a href="https://javabetter.cn/collection/linkedlist.html"&gt;LinkedList&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;Set 代表无序、不可重复的集合，典型代表就是 HashSet 和 TreeSet；&lt;/li&gt;
&lt;li&gt;Queue 代表队列，典型代表就是双端队列 &lt;a href="https://javabetter.cn/collection/arraydeque.html"&gt;ArrayDeque&lt;/a&gt;，以及优先级队列 &lt;a href="https://javabetter.cn/collection/PriorityQueue.html"&gt;PriorityQueue&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;②、Map，代表键值对的集合，典型代表就是 &lt;a href="https://javabetter.cn/collection/hashmap.html"&gt;HashMap&lt;/a&gt;。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/gailan-01.png"/&gt;&lt;br/&gt;概览图说明：&lt;br/&gt;①、Collection 接口：最基本的集合框架表示方式，提供了添加、删除、清空等基本操作，它主要有三个子接口：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;List&lt;/code&gt;：一个有序的集合，可以包含重复的元素。实现类包括 ArrayList、LinkedList 等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Set&lt;/code&gt;：一个不包含重复元素的集合。实现类包括 HashSet、LinkedHashSet、TreeSet 等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Queue&lt;/code&gt;：一个用于保持元素队列的集合。实现类包括 PriorityQueue、ArrayDeque 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;②、&lt;code&gt;Map&lt;/code&gt; 接口：表示键值对的集合，一个键映射到一个值。键不能重复，每个键只能对应一个值。Map 接口的实现类包括 HashMap、LinkedHashMap、TreeMap 等。&lt;br/&gt;集合框架位于 java.util 包下，该包含提供了两个常用的工具类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://javabetter.cn/common-tool/collections.html"&gt;Collections&lt;/a&gt;：提供了一些对集合进行排序、二分查找、同步的静态方法。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://javabetter.cn/common-tool/arrays.html"&gt;Arrays&lt;/a&gt;：提供了一些对数组进行排序、打印、和 List 进行转换的静态方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="简单介绍一下队列-Queue"&gt;&lt;a class="headerlink" href="#简单介绍一下队列-Queue" title="简单介绍一下队列 Queue"&gt;&lt;/a&gt;简单介绍一下队列 Queue&lt;/h4&gt;&lt;p&gt;Java 中的队列主要通过 java.util.Queue 接口和 java.util.concurrent.BlockingQueue 两个接口来实现。&lt;br/&gt;PriorityQueue 是一个基于优先级堆的无界队列，它的元素按照自然顺序排序或者 Comparator 进行排序。&lt;br/&gt;ArrayDeque 是一个基于数组的双端队列，可以在两端插入和删除元素。&lt;br/&gt;LinkedList，它既可以当作 List 使用，也可以当作 Queue 使用。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/list-war-2-02.png"/&gt;&lt;/p&gt;
&lt;h4 id="用过哪些集合类，它们的优劣？"&gt;&lt;a class="headerlink" href="#用过哪些集合类，它们的优劣？" title="用过哪些集合类，它们的优劣？"&gt;&lt;/a&gt;用过哪些集合类，它们的优劣？&lt;/h4&gt;&lt;p&gt;在 Java 中，常用的集合类有 ArrayList、LinkedList、HashMap、LinkedHashMap 等。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ArrayList：ArrayList 可以看作是一个动态数组，它可以在运行时动态扩容。优点是访问速度快，可以通过索引直接查到元素。缺点是插入和删除元素可能需要移动元素，效率就会降低。&lt;/li&gt;
&lt;li&gt;LinkedList：LinkedList 是一个双向链表，它适合频繁的插入和删除操作。优点是插入和删除元素的时候只需要改变节点的前后指针，缺点是访问元素时需要遍历链表。&lt;/li&gt;
&lt;li&gt;HashMap：HashMap 是一个基于哈希表的键值对集合。优点是插入、删除和查找元素的速度都很快。缺点是它不保留键值对的插入顺序。&lt;/li&gt;
&lt;li&gt;LinkedHashMap：LinkedHashMap 在 HashMap 的基础上增加了一个双向链表来保持键值对的插入顺序。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="队列和栈的区别了解吗？"&gt;&lt;a class="headerlink" href="#队列和栈的区别了解吗？" title="队列和栈的区别了解吗？"&gt;&lt;/a&gt;队列和栈的区别了解吗？&lt;/h4&gt;&lt;p&gt;队列是一种先进先出（FIFO, First-In-First-Out）的数据结构。在队列中，第一个加入队列的元素会是第一个被移除的。队列常用于处理按顺序来的任务。&lt;br/&gt;栈是一种后进先出（LIFO, Last-In-First-Out）的数据结构。在这种结构中，最后一个加入栈的元素会是第一个被移除的。这种特性使得栈非常适合于那些需要访问最新添加的数据元素的场合。&lt;/p&gt;
&lt;h4 id="哪些是线程安全的？"&gt;&lt;a class="headerlink" href="#哪些是线程安全的？" title="哪些是线程安全的？"&gt;&lt;/a&gt;哪些是线程安全的？&lt;/h4&gt;&lt;p&gt;像 Vector、Hashtable、ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue 这些都是线程安全的。&lt;/p&gt;
&lt;h4 id="Java-集合用过哪些？Collection-继承了哪些接口？"&gt;&lt;a class="headerlink" href="#Java-集合用过哪些？Collection-继承了哪些接口？" title="Java 集合用过哪些？Collection 继承了哪些接口？"&gt;&lt;/a&gt;Java 集合用过哪些？Collection 继承了哪些接口？&lt;/h4&gt;&lt;p&gt;最经常用的就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList；以及键值对 HashMap。&lt;br/&gt;Collection 继承了 Iterable 接口，这意味着所有实现 Collection 接口的类都必须实现 &lt;code&gt;iterator()&lt;/code&gt; 方法，之后就可以使用增强型 for 循环遍历集合中的元素了。&lt;/p&gt;
&lt;h2 id="List"&gt;&lt;a class="headerlink" href="#List" title="List"&gt;&lt;/a&gt;List&lt;/h2&gt;&lt;h3 id="2-ArrayList-和-LinkedList-有什么区别？"&gt;&lt;a class="headerlink" href="#2-ArrayList-和-LinkedList-有什么区别？" title="2.ArrayList 和 LinkedList 有什么区别？"&gt;&lt;/a&gt;2.ArrayList 和 LinkedList 有什么区别？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://javabetter.cn/collection/list-war-2.html"&gt;二哥的 Java 进阶之路：ArrayList 和 LinkedList&lt;/a&gt;&lt;br/&gt;ArrayList 和 LinkedList 的区别主要体现在数据结构、用途、是否支持随机访问、内存占用等方面。&lt;/p&gt;
&lt;h4 id="数据结构有什么不同？"&gt;&lt;a class="headerlink" href="#数据结构有什么不同？" title="数据结构有什么不同？"&gt;&lt;/a&gt;数据结构有什么不同？&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;ArrayList 基于数组实现&lt;/li&gt;
&lt;li&gt;LinkedList 基于链表实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-2.png"/&gt;&lt;/p&gt;
&lt;h4 id="用途有什么不同？"&gt;&lt;a class="headerlink" href="#用途有什么不同？" title="用途有什么不同？"&gt;&lt;/a&gt;用途有什么不同？&lt;/h4&gt;&lt;p&gt;多数情况下，ArrayList 更利于查找，LinkedList 更利于增删&lt;br/&gt;①、由于 ArrayList 是基于数组实现的，所以 &lt;code&gt;get(int index)&lt;/code&gt; 可以直接通过数组下标获取，时间复杂度是 O(1)；LinkedList 是基于链表实现的，&lt;code&gt;get(int index)&lt;/code&gt; 需要遍历链表，时间复杂度是 O(n)。&lt;br/&gt;当然，&lt;code&gt;get(E element)&lt;/code&gt; 这种查找，两种集合都需要遍历通过 equals 比较获取元素，所以时间复杂度都是 O(n)。&lt;br/&gt;②、ArrayList 如果增删的是数组的尾部，直接插入或者删除就可以了，时间复杂度是 O(1)；如果 add 的时候涉及到扩容，时间复杂度会提升到 O(n)。&lt;br/&gt;但如果插入的是中间的位置，就需要把插入位置后的元素向前或者向后移动，甚至还有可能触发扩容，效率就会低很多，O(n)。&lt;br/&gt;LinkedList 因为是链表结构，插入和删除只需要改变前置节点、后置节点和插入节点的引用就行了，不需要移动元素。&lt;br/&gt;如果是在链表的头部插入或者删除，时间复杂度是 O(1)；如果是在链表的中间插入或者删除，时间复杂度是 O(n)，因为需要遍历链表找到插入位置；如果是在链表的尾部插入或者删除，时间复杂度是 O(1)。&lt;/p&gt;
&lt;p&gt;注意，这里有个陷阱，&lt;strong&gt;LinkedList 更利于增删不是体现在时间复杂度上&lt;/strong&gt;，因为二者增删的时间复杂度都是 O(n)，都需要遍历列表；而是体现在增删的效率上，因为 LinkedList 的增删只需要改变引用，而 ArrayList 的增删可能需要移动元素。&lt;/p&gt;
&lt;h4 id="是否支持随机访问？"&gt;&lt;a class="headerlink" href="#是否支持随机访问？" title="是否支持随机访问？"&gt;&lt;/a&gt;是否支持随机访问？&lt;/h4&gt;&lt;p&gt;①、ArrayList 是基于数组的，也实现了 RandomAccess 接口，所以它支持随机访问，可以通过下标直接获取元素。&lt;br/&gt;②、LinkedList 是基于链表的，所以它没法根据下标直接获取元素，不支持随机访问，所以它也没有实现 RandomAccess 接口。&lt;/p&gt;
&lt;h4 id="内存占用有何不同？"&gt;&lt;a class="headerlink" href="#内存占用有何不同？" title="内存占用有何不同？"&gt;&lt;/a&gt;内存占用有何不同？&lt;/h4&gt;&lt;p&gt;ArrayList 是基于数组的，是一块连续的内存空间，所以它的内存占用是比较紧凑的；但如果涉及到扩容，就会重新分配内存，空间是原来的 1.5 倍，存在一定的空间浪费。&lt;br/&gt;LinkedList 是基于链表的，每个节点都有一个指向下一个节点和上一个节点的引用，于是每个节点占用的内存空间稍微大一点。&lt;/p&gt;
&lt;h4 id="使用场景有什么不同？"&gt;&lt;a class="headerlink" href="#使用场景有什么不同？" title="使用场景有什么不同？"&gt;&lt;/a&gt;使用场景有什么不同？&lt;/h4&gt;&lt;p&gt;ArrayList 适用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;随机访问频繁：需要频繁通过索引访问元素的场景。&lt;/li&gt;
&lt;li&gt;读取操作远多于写入操作：如存储不经常改变的列表。&lt;/li&gt;
&lt;li&gt;末尾添加元素：需要频繁在列表末尾添加元素的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LinkedList 适用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;频繁插入和删除：在列表中间频繁插入和删除元素的场景。&lt;/li&gt;
&lt;li&gt;不需要快速随机访问：顺序访问多于随机访问的场景。&lt;/li&gt;
&lt;li&gt;队列和栈：由于其双向链表的特性，LinkedList 可以高效地实现队列（FIFO）和栈（LIFO）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-ArrayList-的扩容机制了解吗？"&gt;&lt;a class="headerlink" href="#3-ArrayList-的扩容机制了解吗？" title="3.ArrayList 的扩容机制了解吗？"&gt;&lt;/a&gt;3.ArrayList 的扩容机制了解吗？&lt;/h3&gt;&lt;p&gt;ArrayList 确切地说，应该叫做动态数组，因为它的底层是通过数组来实现的，当往 ArrayList 中添加元素时，会先检查是否需要扩容，如果当前容量+1 超过数组长度，就会进行扩容。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-5.png"/&gt;&lt;br/&gt;扩容后的新数组长度是原来的 1.5 倍，然后再把原数组的值拷贝到新数组中。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;grow&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; minCapacity)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// overflow-conscious code&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;oldCapacity&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; elementData.length;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;newCapacity&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; oldCapacity + (oldCapacity &amp;gt;&amp;gt; &lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (newCapacity - minCapacity &amp;lt; &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        newCapacity = minCapacity;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (newCapacity - MAX_ARRAY_SIZE &amp;gt; &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        newCapacity = hugeCapacity(minCapacity);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// minCapacity is usually close to size, so this is a win:&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    elementData = Arrays.copyOf(elementData, newCapacity);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://javabetter.cn/zhishixingqiu/mianshi.html"&gt;Java 面试指南（付费）&lt;/a&gt;收录的联想面经同学 7 面试原题：Java 集合类介绍，挑一个讲原理。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3 id="4-ArrayList-怎么序列化的知道吗？-为什么用-transient-修饰数组？"&gt;&lt;a class="headerlink" href="#4-ArrayList-怎么序列化的知道吗？-为什么用-transient-修饰数组？" title="4.ArrayList 怎么序列化的知道吗？ 为什么用 transient 修饰数组？"&gt;&lt;/a&gt;4.ArrayList 怎么序列化的知道吗？ 为什么用 transient 修饰数组？&lt;/h3&gt;&lt;p&gt;ArrayList 的序列化不太一样，它使用&lt;code&gt;transient&lt;/code&gt;修饰存储元素的&lt;code&gt;elementData&lt;/code&gt;的数组，&lt;code&gt;transient&lt;/code&gt;关键字的作用是让被修饰的成员属性不被序列化。&lt;br/&gt;&lt;strong&gt;为什么最 ArrayList 不直接序列化元素数组呢？&lt;/strong&gt;&lt;br/&gt;出于效率的考虑，数组可能长度 100，但实际只用了 50，剩下的 50 不用其实不用序列化，这样可以提高序列化和反序列化的效率，还可以节省内存空间。&lt;br/&gt;&lt;strong&gt;那 ArrayList 怎么序列化呢？&lt;/strong&gt;&lt;br/&gt;ArrayList 通过两个方法&lt;strong&gt;readObject、writeObject&lt;/strong&gt;自定义序列化和反序列化策略，实际直接使用两个流&lt;code&gt;ObjectOutputStream&lt;/code&gt;和&lt;code&gt;ObjectInputStream&lt;/code&gt;来进行序列化和反序列化。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-6.png"/&gt;&lt;/p&gt;
&lt;h3 id="5-快速失败-fail-fast-和安全失败-fail-safe-了解吗？"&gt;&lt;a class="headerlink" href="#5-快速失败-fail-fast-和安全失败-fail-safe-了解吗？" title="5.快速失败(fail-fast)和安全失败(fail-safe)了解吗？"&gt;&lt;/a&gt;5.快速失败(fail-fast)和安全失败(fail-safe)了解吗？&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;快速失败（fail—fast）&lt;/strong&gt;：快速失败是 Java 集合的一种错误检测机制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在用迭代器遍历一个集合对象时，如果线程 A 遍历过程中，线程 B 对集合对象的内容进行了修改（增加、删除、修改），则会抛出 Concurrent Modification Exception。&lt;/li&gt;
&lt;li&gt;原理：迭代器在遍历时直接访问集合中的内容，并且在遍历过程中使用一个 &lt;code&gt; modCount&lt;/code&gt; 变量。集合在被遍历期间如果内容发生变化，就会改变&lt;code&gt;modCount&lt;/code&gt;的值。每当迭代器使用 hashNext()/next()遍历下一个元素之前，都会检测 modCount 变量是否为 expectedmodCount 值，是的话就返回遍历；否则抛出异常，终止遍历。&lt;/li&gt;
&lt;li&gt;注意：这里异常的抛出条件是检测到 modCount！=expectedmodCount 这个条件。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值，则异常不会抛出。因此，不能依赖于这个异常是否抛出而进行并发操作的编程，这个异常只建议用于检测并发修改的 bug。&lt;/li&gt;
&lt;li&gt;场景：java.util 包下的集合类都是快速失败的，不能在多线程下发生并发修改（迭代过程中被修改），比如 ArrayList 类。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;安全失败（fail—safe）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采用安全失败机制的集合容器，在遍历时不是直接在集合内容上访问的，而是先复制原有集合内容，在拷贝的集合上进行遍历。&lt;/li&gt;
&lt;li&gt;原理：由于迭代时是对原集合的拷贝进行遍历，所以在遍历过程中对原集合所作的修改并不能被迭代器检测到，所以不会触发 Concurrent Modification Exception。&lt;/li&gt;
&lt;li&gt;缺点：基于拷贝内容的优点是避免了 Concurrent Modification Exception，但同样地，迭代器并不能访问到修改后的内容，即：迭代器遍历的是开始遍历那一刻拿到的集合拷贝，在遍历期间原集合发生的修改迭代器是不知道的。&lt;/li&gt;
&lt;li&gt;场景：java.util.concurrent 包下的容器都是安全失败，可以在多线程下并发使用，并发修改，比如 CopyOnWriteArrayList 类。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="6-有哪几种实现-ArrayList-线程安全的方法？"&gt;&lt;a class="headerlink" href="#6-有哪几种实现-ArrayList-线程安全的方法？" title="6.有哪几种实现 ArrayList 线程安全的方法？"&gt;&lt;/a&gt;6.有哪几种实现 ArrayList 线程安全的方法？&lt;/h3&gt;&lt;p&gt;可以使用 &lt;code&gt;Collections.synchronizedList()&lt;/code&gt; 方法，它将返回一个线程安全的 List。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;SynchronizedList&lt;/span&gt; &lt;span class="variable"&gt;list&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Collections.synchronizedList(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;ArrayList&lt;/span&gt;());&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;内部是通过 &lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized 关键字&lt;/a&gt;加锁来实现的。&lt;br/&gt;也可以直接使用 &lt;a href="https://javabetter.cn/thread/CopyOnWriteArrayList.html"&gt;CopyOnWriteArrayList&lt;/a&gt;，它是线程安全的，遵循写时复制的原则，每当对列表进行修改（例如添加、删除或更改元素）时，都会创建列表的一个新副本，这个新副本会替换旧的列表，而对旧列表的所有读取操作仍然可以继续。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="type"&gt;CopyOnWriteArrayList&lt;/span&gt; &lt;span class="variable"&gt;list&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;CopyOnWriteArrayList&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;通俗的讲，CopyOnWrite 就是当我们往一个容器添加元素的时候，不直接往容器中添加，而是先复制出一个新的容器，然后在新的容器里添加元素，添加完之后，再将原容器的引用指向新的容器。多个线程在读的时候，不需要加锁，因为当前容器不会添加任何元素。这样就实现了线程安全。&lt;/p&gt;
&lt;h4 id="ArrayList-和-Vector-的区别？"&gt;&lt;a class="headerlink" href="#ArrayList-和-Vector-的区别？" title="ArrayList 和 Vector 的区别？"&gt;&lt;/a&gt;ArrayList 和 Vector 的区别？&lt;/h4&gt;&lt;p&gt;Vector 属于 JDK 1.0 时期的遗留类，已不推荐使用，仍然保留着是因为 Java 希望向后兼容。&lt;br/&gt;ArrayList 是在 JDK 1.2 时引入的，用于替代 Vector 作为主要的非同步动态数组实现。因为 Vector 所有的方法都使用 synchronized 关键字进行了同步，单线程环境下效率较低。&lt;/p&gt;
&lt;h3 id="7-CopyOnWriteArrayList-了解多少？"&gt;&lt;a class="headerlink" href="#7-CopyOnWriteArrayList-了解多少？" title="7.CopyOnWriteArrayList 了解多少？"&gt;&lt;/a&gt;7.CopyOnWriteArrayList 了解多少？&lt;/h3&gt;&lt;p&gt;CopyOnWriteArrayList 就是线程安全版本的 ArrayList。&lt;br/&gt;它的名字叫&lt;code&gt;CopyOnWrite&lt;/code&gt;——写时复制，已经明示了它的原理。&lt;br/&gt;CopyOnWriteArrayList 采用了一种读写分离的并发策略。CopyOnWriteArrayList 容器允许并发读，读操作是无锁的，性能较高。至于写操作，比如向容器中添加一个元素，则首先将当前容器复制一份，然后在新副本上执行写操作，结束之后再将原容器的引用指向新容器。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-7.png"/&gt;&lt;/p&gt;
&lt;h2 id="Map"&gt;&lt;a class="headerlink" href="#Map" title="Map"&gt;&lt;/a&gt;Map&lt;/h2&gt;&lt;p&gt;Map 中，毫无疑问，最重要的就是 HashMap，面试基本被盘出包浆了，各种问法，一定要好好准备。&lt;/p&gt;
&lt;h3 id="8-能说一下-HashMap-的底层数据结构吗？"&gt;&lt;a class="headerlink" href="#8-能说一下-HashMap-的底层数据结构吗？" title="8.能说一下 HashMap 的底层数据结构吗？"&gt;&lt;/a&gt;8.能说一下 HashMap 的底层数据结构吗？&lt;/h3&gt;&lt;p&gt;JDK 8 中 HashMap 的数据结构是&lt;code&gt;数组&lt;/code&gt;+&lt;code&gt;链表&lt;/code&gt;+&lt;code&gt;红黑树&lt;/code&gt;。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-8.png"/&gt;&lt;br/&gt;HashMap 的核心是一个动态数组（&lt;code&gt;Node[] table&lt;/code&gt;），用于存储键值对。这个数组的每个元素称为一个“桶”（Bucket），每个桶的索引是通过对键的哈希值进行哈希函数处理得到的。&lt;br/&gt;当多个键经哈希处理后得到相同的索引时，会发生哈希冲突。HashMap 通过链表来解决哈希冲突——即将具有相同索引的键值对通过链表连接起来。&lt;br/&gt;不过，链表过长时，查询效率会比较低，于是当链表的长度超过 8 时（且数组的长度大于 64），链表就会转换为红黑树。红黑树的查询效率是 O(logn)，比链表的 O(n) 要快。数组的查询效率是 O(1)。&lt;br/&gt;当向 HashMap 中添加一个键值对时，会使用哈希函数计算键的哈希码，确定其在数组中的位置，哈希函数的目标是尽量减少哈希冲突，保证元素能够均匀地分布在数组的每个位置上。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; h;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (key == &lt;span class="literal"&gt;null&lt;/span&gt;) ? &lt;span class="number"&gt;0&lt;/span&gt; : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;当向 HashMap 中添加元素时，如果该位置已有元素（发生哈希冲突），则新元素将被添加到链表的末尾或红黑树中。如果键已经存在，其对应的值将被新值覆盖。&lt;br/&gt;当从 HashMap 中获取元素时，也会使用哈希函数计算键的位置，然后根据位置在数组、链表或者红黑树中查找元素。&lt;br/&gt;HashMap 的初始容量是 16，随着元素的不断添加，HashMap 的容量（也就是数组大小）可能不足，于是就需要进行扩容，阈值是&lt;code&gt;capacity * loadFactor&lt;/code&gt;，capacity 为容量，loadFactor 为负载因子，默认为 0.75。&lt;br/&gt;扩容后的数组大小是原来的 2 倍，然后把原来的元素重新计算哈希值，放到新的数组中。&lt;br/&gt;总的来说，HashMap 是一种通过哈希表实现的键值对集合，它通过将键哈希化成数组索引，并在冲突时使用链表或红黑树来存储元素，从而实现快速的查找、插入和删除操作。&lt;/p&gt;
&lt;h3 id="9-你对红黑树了解多少？为什么不用二叉树-平衡树呢？"&gt;&lt;a class="headerlink" href="#9-你对红黑树了解多少？为什么不用二叉树-平衡树呢？" title="9.你对红黑树了解多少？为什么不用二叉树/平衡树呢？"&gt;&lt;/a&gt;9.你对红黑树了解多少？为什么不用二叉树/平衡树呢？&lt;/h3&gt;&lt;p&gt;红黑树是一种自平衡的二叉查找树：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个节点要么是红色，要么是黑色；&lt;/li&gt;
&lt;li&gt;根节点永远是黑色；&lt;/li&gt;
&lt;li&gt;所有的叶子节点都是是黑色的（下图中的 NULL 节点）；&lt;/li&gt;
&lt;li&gt;红色节点的子节点一定是黑色的；&lt;/li&gt;
&lt;li&gt;从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-9.png"/&gt;&lt;/p&gt;
&lt;h4 id="为什么不用二叉树？"&gt;&lt;a class="headerlink" href="#为什么不用二叉树？" title="为什么不用二叉树？"&gt;&lt;/a&gt;为什么不用二叉树？&lt;/h4&gt;&lt;p&gt;二叉树是最基本的树结构，每个节点最多有两个子节点，但是二叉树容易出现极端情况，比如插入的数据是有序的，那么二叉树就会退化成链表，查询效率就会变成 O(n)。&lt;/p&gt;
&lt;h4 id="为什么不用平衡二叉树？"&gt;&lt;a class="headerlink" href="#为什么不用平衡二叉树？" title="为什么不用平衡二叉树？"&gt;&lt;/a&gt;为什么不用平衡二叉树？&lt;/h4&gt;&lt;p&gt;平衡二叉树比红黑树的要求更高，每个节点的左右子树的高度最多相差 1，这种高度的平衡保证了极佳的查找效率，但在进行插入和删除操作时，可能需要频繁地进行旋转来维持树的平衡，这在某些情况下可能导致更高的维护成本。&lt;br/&gt;红黑树是一种折中的方案，它在保证了树平衡的同时，插入和删除操作的性能也得到了保证，查询效率是 O(logn)。&lt;/p&gt;
&lt;h3 id="10-红黑树怎么保持平衡的？"&gt;&lt;a class="headerlink" href="#10-红黑树怎么保持平衡的？" title="10.红黑树怎么保持平衡的？"&gt;&lt;/a&gt;10.红黑树怎么保持平衡的？&lt;/h3&gt;&lt;p&gt;红黑树有两种方式保持平衡：&lt;code&gt;旋转&lt;/code&gt;和&lt;code&gt;染色&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="11-HashMap-的-put-流程知道吗？"&gt;&lt;a class="headerlink" href="#11-HashMap-的-put-流程知道吗？" title="11.HashMap 的 put 流程知道吗？"&gt;&lt;/a&gt;11.HashMap 的 put 流程知道吗？&lt;/h3&gt;&lt;p&gt;直接看流程图。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-13.jpeg"/&gt;&lt;br/&gt;第一步，通过 hash 方法计算 key 的哈希值。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; h;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (key == &lt;span class="literal"&gt;null&lt;/span&gt;) ? &lt;span class="number"&gt;0&lt;/span&gt; : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第二步，数组进行第一次扩容。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; ((tab = table) == &lt;span class="literal"&gt;null&lt;/span&gt; || (n = tab.length) == &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n = (tab = resize()).length;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第三步，根据哈希值计算 key 在数组中的下标，如果对应下标正好没有存放数据，则直接插入。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; ((p = tab[i = (n - &lt;span class="number"&gt;1&lt;/span&gt;) &amp;amp; hash]) == &lt;span class="literal"&gt;null&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    tab[i] = newNode(hash, key, value, &lt;span class="literal"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;如果对应下标已经有数据了，就需要判断是否为相同的 key，是则覆盖 value，否则需要判断是否为树节点，是则向树中插入节点，否则向链表中插入数据。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Node&amp;lt;K,V&amp;gt; e; K k;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (p.hash == hash &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        ((k = p.key) == key || (key != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; key.equals(k))))&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        e = p;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt; &lt;span class="keyword"&gt;if&lt;/span&gt; (p &lt;span class="keyword"&gt;instanceof&lt;/span&gt; TreeNode)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        e = ((TreeNode&amp;lt;K,V&amp;gt;)p).putTreeVal(&lt;span class="built_in"&gt;this&lt;/span&gt;, tab, hash, key, value);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;binCount&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;; ; ++binCount) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; ((e = p.next) == &lt;span class="literal"&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                p.next = newNode(hash, key, value, &lt;span class="literal"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;if&lt;/span&gt; (binCount &amp;gt;= TREEIFY_THRESHOLD - &lt;span class="number"&gt;1&lt;/span&gt;) &lt;span class="comment"&gt;// -1 for 1st&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    treeifyBin(tab, hash);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; (e.hash == hash &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                ((k = e.key) == key || (key != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; key.equals(k))))&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            p = e;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;注意，在链表中插入节点的时候，如果链表长度大于等于 8，则需要把链表转换为红黑树。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; (binCount &amp;gt;= TREEIFY_THRESHOLD - &lt;span class="number"&gt;1&lt;/span&gt;) &lt;span class="comment"&gt;// -1 for 1st&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    treeifyBin(tab, hash);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;所有元素处理完后，还需要判断是否超过阈值&lt;code&gt;threshold&lt;/code&gt;，超过则扩容。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; (++size &amp;gt; threshold)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    resize();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h4 id="只重写-equals-没重写-hashcode，map-put-的时候会发生什么"&gt;&lt;a class="headerlink" href="#只重写-equals-没重写-hashcode，map-put-的时候会发生什么" title="只重写 equals 没重写 hashcode，map put 的时候会发生什么?"&gt;&lt;/a&gt;只重写 equals 没重写 hashcode，map put 的时候会发生什么?&lt;/h4&gt;&lt;p&gt;如果只重写 equals 方法，没有重写 hashcode 方法，那么会导致 equals 相等的两个对象，hashcode 不相等，这样的话，这两个对象会被放到不同的桶中，这样就会导致 get 的时候，找不到对应的值。&lt;/p&gt;
&lt;h3 id="12-HashMap-怎么查找元素的呢？"&gt;&lt;a class="headerlink" href="#12-HashMap-怎么查找元素的呢？" title="12.HashMap 怎么查找元素的呢？"&gt;&lt;/a&gt;12.HashMap 怎么查找元素的呢？&lt;/h3&gt;&lt;p&gt;先看流程图：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-14.png"/&gt;&lt;br/&gt;HashMap 的查找就简单很多：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用扰动函数，获取新的哈希值&lt;/li&gt;
&lt;li&gt;计算数组下标，获取节点&lt;/li&gt;
&lt;li&gt;当前节点和 key 匹配，直接返回&lt;/li&gt;
&lt;li&gt;否则，当前节点是否为树节点，查找红黑树&lt;/li&gt;
&lt;li&gt;否则，遍历链表查找&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="13-HashMap-的-hash-函数是怎么设计的"&gt;&lt;a class="headerlink" href="#13-HashMap-的-hash-函数是怎么设计的" title="13.HashMap 的 hash 函数是怎么设计的?"&gt;&lt;/a&gt;13.HashMap 的 hash 函数是怎么设计的?&lt;/h3&gt;&lt;p&gt;HashMap 的哈希函数是先拿到 key 的 hashcode，是一个 32 位的 int 类型的数值，然后让 hashcode 的高 16 位和低 16 位进行异或操作。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; h;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// key的hashCode和key的hashCode右移16位做异或运算&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (key == &lt;span class="literal"&gt;null&lt;/span&gt;) ? &lt;span class="number"&gt;0&lt;/span&gt; : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这么设计是为了降低哈希碰撞的概率。&lt;/p&gt;
&lt;h3 id="14-为什么-hash-函数能降哈希碰撞？"&gt;&lt;a class="headerlink" href="#14-为什么-hash-函数能降哈希碰撞？" title="14.为什么 hash 函数能降哈希碰撞？"&gt;&lt;/a&gt;14.为什么 hash 函数能降哈希碰撞？&lt;/h3&gt;&lt;p&gt;在 hash 函数中，先调用了 key 的&lt;code&gt;hashCode()&lt;/code&gt; 方法，这将会返回一个 int 类型的哈希值，比如说字符串的 hashCode。&lt;br/&gt;int 的范围是 &lt;strong&gt;-2147483648~2147483647&lt;/strong&gt;，加起来大概 40 亿上下的浮动。&lt;br/&gt;只要 key 的 hashCode 方法设计的比较合理，一般是很难出现碰撞的。但问题是，不可能直接搞一个 40 亿长度的数组啊，那也太铺张浪费了。&lt;br/&gt;我们一般会设置一个较小的数组长度，比如说 HashMap 的数组初始大小才 16，当发现容量不满足的时候再扩容，避免浪费。&lt;br/&gt;那当数组长度比较小的时候，我们就需要设计一种比较巧妙的 hash 算法，来避免发生哈希冲突，尽可能地让元素均匀地分布在数组当中。&lt;br/&gt;要达到这个目的，HashMap 在两方面下足了功夫，第一个就是数组的长度必须是 2 的整数次幂，这样可以保证 &lt;code&gt;hash &amp;amp; (n-1)&lt;/code&gt; 的结果能均匀地分布在数组中。&lt;br/&gt;其作用就相当于 hash % n，n 为数组的长度，比如说数组长度是 16，hash 值为 20，那么 20 % 16 = 4，也就是说 20 这个元素应该放在数组的第 4 个位置；hash 值为 23，那么 23 % 16 = 7，也就是说 23 这个元素应该放在数组的第 7 个位置。&lt;br/&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; 操作的结果就是哈希值的高位全部归零，只保留 n 个低位，用来做数组下标访问。&lt;br/&gt;比如说 hash &amp;amp; ($2^{4} - 1$) 的结果实际上是取 hash 的低 4 位，这四位能表示的取值范围刚好是 0000 到 1111，也就是 0 到 15，正好是数组长度为 16 的下标范围。&lt;br/&gt;以初始长度 16 为例，16-1=15。2 进制表示是&lt;code&gt;0000 0000 0000 0000 0000 0000 0000 1111&lt;/code&gt;。和某个哈希值做 &lt;code&gt;&amp;amp;&lt;/code&gt; 运算，结果就是截取了最低的四位。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-15.png"/&gt;&lt;br/&gt;那问题又来了，那么大一个哈希值，也只取最后 4 位，不就等于哈希值的高位都丢弃了吗？&lt;br/&gt;比如说 1111 1111 1111 1111 1111 1111 1111 1111，取最后 4 位，也就是 1111。&lt;br/&gt;比如说 1110 1111 1111 1111 1111 1111 1111 1111，取最后 4 位，也是 1111。&lt;br/&gt;不就发生哈希冲突了吗？&lt;br/&gt;这时候 hash 函数 &lt;code&gt;(h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; 16)&lt;/code&gt; 就派上用场了呀。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-16.jpeg"/&gt;&lt;br/&gt;将哈希值无符号右移 16 位，意味着原哈希值的高 16 位被移到了低 16 位的位置。这样，原始哈希值的高 16 位和低 16 位就可以参与到最终用于索引计算的低位中。&lt;br/&gt;选择 16 位是因为它是 32 位整数的一半，这样处理既考虑了高位的信息，又没有完全忽视低位原本的信息，尝试达到一个平衡状态。&lt;br/&gt;举个例子（数组长度为 16）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个数：h1 = 0001 0010 0011 0100 0101 0110 0111 1000&lt;/li&gt;
&lt;li&gt;第二个数：h2 = 0001 0010 0011 0101 0101 0110 0111 1000&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果没有 hash 函数，直接取低 4 位，那么 h1 和 h2 的低 4 位都是 1000，也就是两个数都会放在数组的第 8 个位置。&lt;br/&gt;来看一下 hash 函数的处理过程。&lt;br/&gt;①、对于第一个数&lt;code&gt;h1&lt;/code&gt;的计算：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;原始: 0001 0010 0011 0100 0101 0110 0111 1000&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;右移: 0000 0000 0000 0000 0001 0010 0011 0100&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;异或: ---------------------------------------&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;结果: 0001 0010 0011 0100 0100 0100 0100 1100&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;②、对于第二个数&lt;code&gt;h2&lt;/code&gt;的计算：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;原始: 0001 0010 0011 0101 0101 0110 0111 1000&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;右移: 0000 0000 0000 0000 0001 0010 0011 0101&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;异或: ---------------------------------------&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;结果: 0001 0010 0011 0101 0100 0100 0100 1101&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;通过上述计算，我们可以看到&lt;code&gt;h1&lt;/code&gt;和&lt;code&gt;h2&lt;/code&gt;经过&lt;code&gt;h ^ (h &amp;gt;&amp;gt;&amp;gt; 16)&lt;/code&gt;操作后得到了不同的结果。&lt;br/&gt;现在，考虑数组长度为 16 时（需要最低 4 位来确定索引）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于&lt;code&gt;h1&lt;/code&gt;的最低 4 位是&lt;code&gt;1100&lt;/code&gt;（十进制中为 12）&lt;/li&gt;
&lt;li&gt;对于&lt;code&gt;h2&lt;/code&gt;的最低 4 位是&lt;code&gt;1101&lt;/code&gt;（十进制中为 13）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样，&lt;code&gt;h1&lt;/code&gt;和&lt;code&gt;h2&lt;/code&gt;就会被分别放在数组的第 12 个位置和第 13 个位置上，避免了哈希冲突。&lt;/p&gt;
&lt;h3 id="15-为什么-HashMap-的容量是-2-的倍数呢？"&gt;&lt;a class="headerlink" href="#15-为什么-HashMap-的容量是-2-的倍数呢？" title="15.为什么 HashMap 的容量是 2 的倍数呢？"&gt;&lt;/a&gt;15.为什么 HashMap 的容量是 2 的倍数呢？&lt;/h3&gt;&lt;p&gt;HashMap 的容量是 2 的倍数，或者说是 2 的整数次幂，是为了快速定位元素的下标：&lt;br/&gt;HashMap 在定位元素位置时，先通过 &lt;code&gt;hash(key) = (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; 16)&lt;/code&gt; 计算出哈希值，再通过 &lt;code&gt;hash &amp;amp; (n-1)&lt;/code&gt; 来定位元素位置的，n 为数组的大小，也就是 HashMap 的容量。&lt;br/&gt;因为（数组长度-1）正好相当于一个“低位掩码”——这个掩码的低位最好全是 1，这样 &amp;amp; 操作才有意义，否则结果就肯定是 0。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a&amp;amp;b 操作的结果是：a、b 中对应位同时为 1，则对应结果位为 1，否则为 0。例如 5&amp;amp;3=1，5 的二进制是 0101，3 的二进制是 0011，5&amp;amp;3=0001=1。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2 的整次幂（或者叫 2 的整数倍）刚好是偶数，偶数-1 是奇数，奇数的二进制最后一位是 1，保证了 &lt;code&gt;hash &amp;amp;(length-1)&lt;/code&gt; 的最后一位可能为 0，也可能为 1（取决于 hash 的值），即 &amp;amp; 运算后的结果可能为偶数，也可能为奇数，这样便可以保证哈希值的均匀分布。&lt;br/&gt;换句话说，&amp;amp; 操作的结果就是将哈希值的高位全部归零，只保留低位值。&lt;br/&gt;假设某哈希值的二进制为 &lt;code&gt;10100101 11000100 00100101&lt;/code&gt;，用它来做 &amp;amp; 运算，我们来看一下结果。&lt;br/&gt;我们知道，HashMap 的初始长度为 16，16-1=15，二进制是 &lt;code&gt;00000000 00000000 00001111&lt;/code&gt;（高位用 0 来补齐）：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;     10100101 11000100 00100101&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&amp;amp;	 00000000 00000000 00001111&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;----------------------------------&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     00000000 00000000 00000101&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;因为 15 的高位全部是 0，所以 &amp;amp; 运算后的高位结果肯定也是 0，只剩下 4 个低位 &lt;code&gt;0101&lt;/code&gt;，也就是十进制的 5。&lt;br/&gt;这样，哈希值为 &lt;code&gt;10100101 11000100 00100101&lt;/code&gt; 的键就会放在数组的第 5 个位置上。&lt;/p&gt;
&lt;h4 id="hashCode-对数组长度取模定位数组下标，这块有没有优化策略？"&gt;&lt;a class="headerlink" href="#hashCode-对数组长度取模定位数组下标，这块有没有优化策略？" title="hashCode 对数组长度取模定位数组下标，这块有没有优化策略？"&gt;&lt;/a&gt;hashCode 对数组长度取模定位数组下标，这块有没有优化策略？&lt;/h4&gt;&lt;p&gt;从此，两个运算 &lt;code&gt;hash % length&lt;/code&gt; 和 &lt;code&gt;hash &amp;amp; (length - 1)&lt;/code&gt; 有了完美的闭环。在计算机中，位运算的速度要远高于取余运算，因为计算机本质上就是二进制嘛。&lt;/p&gt;
&lt;h3 id="16-如果初始化-HashMap，传一个-17-容量，它会怎么处理？"&gt;&lt;a class="headerlink" href="#16-如果初始化-HashMap，传一个-17-容量，它会怎么处理？" title="16.如果初始化 HashMap，传一个 17 容量，它会怎么处理？"&gt;&lt;/a&gt;16.如果初始化 HashMap，传一个 17 容量，它会怎么处理？&lt;/h3&gt;&lt;p&gt;HashMap 会将这个值转换为大于或等于 17 的最小的 2 的幂。这是因为 HashMap 的设计是基于哈希表的，而哈希表的大小最好是 2 的幂，这样可以优化哈希值的计算，并减少哈希冲突。&lt;br/&gt;所以，如果你传入 17 作为初始容量，HashMap 实际上会被初始化为大小为 32 的哈希表。&lt;br/&gt;在 HashMap 的初始化构造方法中，有这样⼀段代码：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="title function_"&gt;HashMap&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; initialCapacity, &lt;span class="type"&gt;float&lt;/span&gt; loadFactor)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt; ...&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt; &lt;span class="built_in"&gt;this&lt;/span&gt;.loadFactor = loadFactor;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt; &lt;span class="built_in"&gt;this&lt;/span&gt;.threshold = tableSizeFor(initialCapacity);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;阀值 threshold 会通过⽅法&lt;code&gt; tableSizeFor()&lt;/code&gt; 进⾏计算。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;tableSizeFor&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; cap)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;n&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; cap - &lt;span class="number"&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n |= n &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n |= n &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;2&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n |= n &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;4&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n |= n &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;8&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n |= n &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (n &amp;lt; &lt;span class="number"&gt;0&lt;/span&gt;) ? &lt;span class="number"&gt;1&lt;/span&gt; : (n &amp;gt;= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + &lt;span class="number"&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h4 id="初始化-HashMap-的时候需要传入容量值吗？"&gt;&lt;a class="headerlink" href="#初始化-HashMap-的时候需要传入容量值吗？" title="初始化 HashMap 的时候需要传入容量值吗？"&gt;&lt;/a&gt;初始化 HashMap 的时候需要传入容量值吗？&lt;/h4&gt;&lt;p&gt;在创建 HashMap 时可以指定初始容量值。这个容量是指 Map 内部用于存储数据的数组大小。&lt;br/&gt;如果预先知道 Map 将存储大量键值对，提前指定一个足够大的初始容量可以减少因扩容导致的重哈希（rehashing）操作，从而提高性能。&lt;br/&gt;因为每次扩容时，HashMap 需要新分配一个更大的数组并重新将现有的元素插入到这个新数组中，这个过程相对耗时，尤其是当 Map 中已有大量数据时。&lt;br/&gt;当然了，过大的初始容量会浪费内存，特别是当实际存储的元素远少于初始容量时。如果不指定初始容量，HashMap 将使用默认的初始容量 16。&lt;/p&gt;
&lt;h3 id="17-你还知道哪些哈希函数的构造方法呢？"&gt;&lt;a class="headerlink" href="#17-你还知道哪些哈希函数的构造方法呢？" title="17.你还知道哪些哈希函数的构造方法呢？"&gt;&lt;/a&gt;17.你还知道哪些哈希函数的构造方法呢？&lt;/h3&gt;&lt;p&gt;HashMap 里哈希构造函数的方法叫：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;除留取余法&lt;/strong&gt;：&lt;code&gt;H(key)=key%p(p&amp;lt;=N)&lt;/code&gt;，关键字除以一个不大于哈希表长度的正整数 p，所得余数为地址，当然 HashMap 里进行了优化改造，效率更高，散列也更均衡。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除此之外，还有这几种常见的哈希函数构造方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;直接定址法&lt;/strong&gt;直接根据&lt;code&gt;key&lt;/code&gt;来映射到对应的数组位置，例如 1232 放到下标 1232 的位置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数字分析法&lt;/strong&gt;取&lt;code&gt;key&lt;/code&gt;的某些数字（例如十位和百位）作为映射的位置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;平方取中法&lt;/strong&gt;取&lt;code&gt;key&lt;/code&gt;平方的中间几位作为映射的位置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;折叠法&lt;/strong&gt;将&lt;code&gt;key&lt;/code&gt;分割成位数相同的几段，然后把它们的叠加和作为映射的位置&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="18-解决哈希冲突有哪些方法呢？"&gt;&lt;a class="headerlink" href="#18-解决哈希冲突有哪些方法呢？" title="18.解决哈希冲突有哪些方法呢？"&gt;&lt;/a&gt;18.解决哈希冲突有哪些方法呢？&lt;/h3&gt;&lt;p&gt;解决哈希冲突的方法我知道的有 3 种：&lt;br/&gt;①、再哈希法&lt;br/&gt;准备两套哈希算法，当发生哈希冲突的时候，使用另外一种哈希算法，直到找到空槽为止。对哈希算法的设计要求比较高。&lt;br/&gt;②、开放地址法&lt;br/&gt;遇到哈希冲突的时候，就去寻找下一个空的槽。有 3 种方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线性探测：从冲突的位置开始，依次往后找，直到找到空槽。&lt;/li&gt;
&lt;li&gt;二次探测：从冲突的位置 x 开始，第一次增加 $1^2$ 个位置，第二次增加 $2^2$，直到找到空槽。&lt;/li&gt;
&lt;li&gt;双重哈希：和再哈希法类似，准备多个哈希函数，发生冲突的时候，使用另外一个哈希函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;③、拉链法&lt;br/&gt;也就是所谓的链地址法，当发生哈希冲突的时候，使用链表将冲突的元素串起来。HashMap 采用的正是拉链法。&lt;/p&gt;
&lt;h4 id="怎么判断-key-相等呢？"&gt;&lt;a class="headerlink" href="#怎么判断-key-相等呢？" title="怎么判断 key 相等呢？"&gt;&lt;/a&gt;怎么判断 key 相等呢？&lt;/h4&gt;&lt;p&gt;&lt;code&gt;HashMap&lt;/code&gt;判断两个&lt;code&gt;key&lt;/code&gt;是否相等，依赖于&lt;code&gt;key&lt;/code&gt;的&lt;code&gt;equals()&lt;/code&gt;方法和&lt;code&gt;hashCode()&lt;/code&gt;方法，以及 &lt;code&gt;==&lt;/code&gt; 运算符。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; (e.hash == hash &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;((k = e.key) == key || (key != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; key.equals(k))))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;①、&lt;strong&gt;hashCode()&lt;/strong&gt; ：首先，使用&lt;code&gt;key&lt;/code&gt;的&lt;code&gt;hashCode()&lt;/code&gt;方法计算&lt;code&gt;key&lt;/code&gt;的哈希码。由于不同的&lt;code&gt;key&lt;/code&gt;可能有相同的哈希码，&lt;code&gt;hashCode()&lt;/code&gt;只是第一步筛选。&lt;br/&gt;②、&lt;strong&gt;equals()&lt;/strong&gt; ：当两个&lt;code&gt;key&lt;/code&gt;的哈希码相同时，&lt;code&gt;HashMap&lt;/code&gt;还会调用&lt;code&gt;key&lt;/code&gt;的&lt;code&gt;equals()&lt;/code&gt;方法进行精确比较。只有当&lt;code&gt;equals()&lt;/code&gt;方法返回&lt;code&gt;true&lt;/code&gt;时，两个&lt;code&gt;key&lt;/code&gt;才被认为是完全相同的。&lt;br/&gt;③、：当然了，如果两个&lt;code&gt;key&lt;/code&gt;的引用指向同一个对象，那么它们的&lt;code&gt;hashCode()&lt;/code&gt;和&lt;code&gt;equals()&lt;/code&gt;方法都会返回&lt;code&gt;true&lt;/code&gt;，所以在 equals 判断之前会优先使用``运算符判断一次。&lt;/p&gt;
&lt;h3 id="19-为什么-HashMap-链表转红黑树的阈值为-8-呢？"&gt;&lt;a class="headerlink" href="#19-为什么-HashMap-链表转红黑树的阈值为-8-呢？" title="19.为什么 HashMap 链表转红黑树的阈值为 8 呢？"&gt;&lt;/a&gt;19.为什么 HashMap 链表转红黑树的阈值为 8 呢？&lt;/h3&gt;&lt;p&gt;树化发生在 table 数组的长度大于 64，且链表的长度大于 8 的时候。&lt;br/&gt;为什么是 8 呢？源码的注释也给出了答案。&lt;br/&gt;红黑树节点的大小大概是普通节点大小的两倍，所以转红黑树，牺牲了空间换时间，更多的是一种兜底的策略，保证极端情况下的查找效率。&lt;br/&gt;阈值为什么要选 8 呢？和统计学有关。理想情况下，使用随机哈希码，链表里的节点符合泊松分布，出现节点个数的概率是递减的，节点个数为 8 的情况，发生概率仅为&lt;code&gt;0.00000006&lt;/code&gt;。&lt;br/&gt;至于红黑树转回链表的阈值为什么是 6，而不是 8？是因为如果这个阈值也设置成 8，假如发生碰撞，节点增减刚好在 8 附近，会发生链表和红黑树的不断转换，导致资源浪费。&lt;/p&gt;
&lt;h3 id="20-扩容在什么时候呢？为什么扩容因子是-0-75？"&gt;&lt;a class="headerlink" href="#20-扩容在什么时候呢？为什么扩容因子是-0-75？" title="20.扩容在什么时候呢？为什么扩容因子是 0.75？"&gt;&lt;/a&gt;20.扩容在什么时候呢？为什么扩容因子是 0.75？&lt;/h3&gt;&lt;p&gt;HashMap 会在存储的键值对数量超过阈值（即容量 * 加载因子）时进行扩容。&lt;br/&gt;默认的加载因子是 0.75，这意味着当 HashMap 填满了大约 75%的容量时，就会进行扩容。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;DEFAULT_INITIAL_CAPACITY&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt; &amp;lt;&amp;lt; &lt;span class="number"&gt;4&lt;/span&gt;; &lt;span class="comment"&gt;// aka 16&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;默认的初始容量是 16，那就是大于&lt;code&gt;16x0.75=12&lt;/code&gt;时，就会触发第一次扩容操作。&lt;/p&gt;
&lt;h4 id="那么为什么选择了-0-75-作为-HashMap-的默认加载因子呢？"&gt;&lt;a class="headerlink" href="#那么为什么选择了-0-75-作为-HashMap-的默认加载因子呢？" title="那么为什么选择了 0.75 作为 HashMap 的默认加载因子呢？"&gt;&lt;/a&gt;那么为什么选择了 0.75 作为 HashMap 的默认加载因子呢？&lt;/h4&gt;&lt;p&gt;简单来说，这是对&lt;code&gt;空间&lt;/code&gt;成本和&lt;code&gt;时间&lt;/code&gt;成本平衡的考虑。&lt;br/&gt;在 HashMap 中有这样一段注释：&lt;br/&gt;我们都知道，HashMap 的散列构造方式是 Hash 取余，负载因子决定元素个数达到多少时候扩容。&lt;br/&gt;假如我们设的比较大，元素比较多，空位比较少的时候才扩容，那么发生哈希冲突的概率就增加了，查找的时间成本就增加了。&lt;br/&gt;我们设的比较小的话，元素比较少，空位比较多的时候就扩容了，发生哈希碰撞的概率就降低了，查找时间成本降低，但是就需要更多的空间去存储元素，空间成本就增加了。&lt;/p&gt;
&lt;h3 id="21-那扩容机制了解吗？"&gt;&lt;a class="headerlink" href="#21-那扩容机制了解吗？" title="21.那扩容机制了解吗？"&gt;&lt;/a&gt;21.那扩容机制了解吗？&lt;/h3&gt;&lt;p&gt;扩容时，HashMap 会创建一个新的数组，其容量是原数组容量的两倍。然后将键值对放到新计算出的索引位置上。一部分索引不变，另一部分索引为“原索引+旧容量”。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为了便于理解，我会结合 JDK7 和 JDK8 两个版本来讲。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 JDK 7 中，定位元素位置的代码是这样的：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;indexFor&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="type"&gt;int&lt;/span&gt; h, &lt;span class="type"&gt;int&lt;/span&gt; length)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; h &amp;amp; (length-&lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其实就相当于用键的哈希值和数组大小取模，也就是 &lt;code&gt;hashCode % table.length&lt;/code&gt;。&lt;br/&gt;那我们来假设：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数组 table 的长度为 2&lt;/li&gt;
&lt;li&gt;键的哈希值为 3、7、5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;取模运算后，键发生了哈希冲突，都到 &lt;code&gt;table[1]&lt;/code&gt; 上了。那么扩容前就是这个样子。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/hashmap-resize-01.png"/&gt;&lt;br/&gt;数组的容量为 2，key 为 3、7、5 的元素在 &lt;code&gt;table[1]&lt;/code&gt; 上，需要通过拉链法来解决哈希冲突。&lt;br/&gt;假设负载因子 loadFactor 为 1，也就是当元素的个数大于 table 的长度时进行扩容。&lt;br/&gt;扩容后的数组容量为 4。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;key 3 取模（3%4）后是 3，放在 &lt;code&gt;table[3]&lt;/code&gt; 上。&lt;/li&gt;
&lt;li&gt;key 7 取模（7%4）后是 3，放在 &lt;code&gt;table[3]&lt;/code&gt; 上的链表头部。&lt;/li&gt;
&lt;li&gt;key 5 取模（5%4）后是 1，放在 &lt;code&gt;table[1]&lt;/code&gt; 上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/hashmap-resize-02.png"/&gt;&lt;br/&gt;7 跑到 3 的前面了，因为 JDK 7 使用的是头插法。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;e.next = newTable[i];&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;同时，扩容后的 5 跑到了下标为 1 的位置。&lt;br/&gt;最好的情况就是，扩容后的 7 在 3 的后面，5 在 7 的后面，保持原来的顺序。&lt;br/&gt;JDK 8 完全扭转了这个局面，因为 JDK 8 的哈希算法进行了优化，当数组长度为 2 的幂次方时，能够很巧妙地解决 JDK 7 中遇到的问题。&lt;br/&gt;JDK 8 的扩容代码如下所示：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;Node&amp;lt;K,V&amp;gt;[] newTab = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Node&lt;/span&gt;[newCapacity];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;j&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;; j &amp;lt; oldTab.length; j++) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Node&amp;lt;K,V&amp;gt; e = oldTab[j];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (e != &lt;span class="literal"&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;hash&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; e.hash;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;newIndex&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; hash &amp;amp; (newCapacity - &lt;span class="number"&gt;1&lt;/span&gt;); &lt;span class="comment"&gt;// 计算在新数组中的位置&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;// 将节点移动到新数组的对应位置&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        newTab[newIndex] = e;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;新索引的计算方式是 &lt;code&gt;hash &amp;amp; (newCapacity - 1)&lt;/code&gt;，和 JDK 7 的 &lt;code&gt;h &amp;amp; (length-1)&lt;/code&gt;没什么大的差别，差别主要在 hash 方法上，JDK 8 是这样：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; h;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (key == &lt;span class="literal"&gt;null&lt;/span&gt;) ? &lt;span class="number"&gt;0&lt;/span&gt; : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;过将键的&lt;code&gt;hashCode()&lt;/code&gt;返回的 32 位哈希值与这个哈希值无符号右移 16 位的结果进行异或。&lt;br/&gt;JDK 7 是这样：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object k)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;h&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; hashSeed;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="number"&gt;0&lt;/span&gt; != h &amp;amp;&amp;amp; k &lt;span class="keyword"&gt;instanceof&lt;/span&gt; String) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; sun.misc.Hashing.stringHash32((String) k);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    h ^= k.hashCode();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// This function ensures that hashCodes that differ only by&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// constant multiples at each bit position have a bounded&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// number of collisions (approximately 8 at default load factor).&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    h ^= (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;20&lt;/span&gt;) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;12&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; h ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;7&lt;/span&gt;) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;4&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们用 JDK 8 的哈希算法来计算一下哈希值，就会发现别有洞天。&lt;br/&gt;假设扩容前的数组长度为 16（n-1 也就是二进制的 0000 1111，1X${2^0}$+1X${2^1}$+1X${2^2}$+1X${2^3}$=1+2+4+8=15），key1 为 5（二进制为 0000 0101），key2 为 21（二进制为 0001 0101）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;key1 和 n-1 做 &amp;amp; 运算后为 0000 0101，也就是 5；&lt;/li&gt;
&lt;li&gt;key2 和 n-1 做 &amp;amp; 运算后为 0000 0101，也就是 5。&lt;/li&gt;
&lt;li&gt;此时哈希冲突了，用拉链法来解决哈希冲突。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在，HashMap 进行了扩容，容量为原来的 2 倍，也就是 32（n-1 也就是二进制的 0001 1111，1X${2^0}$+1X${2^1}$+1X${2^2}$+1X${2^3}$+1X${2^4}$=1+2+4+8+16=31）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;key1 和 n-1 做 &amp;amp; 运算后为 0000 0101，也就是 5；&lt;/li&gt;
&lt;li&gt;key2 和 n-1 做 &amp;amp; 运算后为 0001 0101，也就是 21=5+16，也就是数组扩容前的位置+原数组的长度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;神奇吧？&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-26.png"/&gt;&lt;br/&gt;换句话说，在 JDK 8 的新 hash 算法下，数组扩容后的索引位置，要么就是原来的索引位置，要么就是“原索引+原来的容量”，遵循一定的规律。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-27.png"/&gt;&lt;br/&gt;当然了，这个功劳既属于新的哈希算法，也离不开 n 为 2 的整数次幂这个前提，这是它俩通力合作后的结果 &lt;code&gt;hash &amp;amp; (newCapacity - 1)&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id="那你说说扩容的时候每个节点都要进行位运算吗，如果我这个-HashMap-里面有几十万条数据，都要进行位运算吗？"&gt;&lt;a class="headerlink" href="#那你说说扩容的时候每个节点都要进行位运算吗，如果我这个-HashMap-里面有几十万条数据，都要进行位运算吗？" title="那你说说扩容的时候每个节点都要进行位运算吗，如果我这个 HashMap 里面有几十万条数据，都要进行位运算吗？"&gt;&lt;/a&gt;那你说说扩容的时候每个节点都要进行位运算吗，如果我这个 HashMap 里面有几十万条数据，都要进行位运算吗？&lt;/h4&gt;&lt;p&gt;在 JDK 8 的新 hash 算法下，数组扩容后的索引位置，要么就是原来的索引位置，要么就是“原索引+原来的容量”，遵循一定的规律。&lt;br/&gt;具体来说，就是判断原哈希值的高位中新增的那一位是否为 1，如果是，该元素会被移动到原位置加上旧容量的位置；如果不是，则保持在原位置。&lt;br/&gt;所以，尽管有几十万条数据，每个数据项的位置决定仅需要一次简单的位运算。位运算的计算速度非常快，因此，尽管扩容操作涉及遍历整个哈希表并对每个节点进行操作，但这部分操作的计算成本是相对较低的。&lt;/p&gt;
&lt;h3 id="22-JDK-8-对-HashMap-主要做了哪些优化呢？为什么？"&gt;&lt;a class="headerlink" href="#22-JDK-8-对-HashMap-主要做了哪些优化呢？为什么？" title="22.JDK 8 对 HashMap 主要做了哪些优化呢？为什么？"&gt;&lt;/a&gt;22.JDK 8 对 HashMap 主要做了哪些优化呢？为什么？&lt;/h3&gt;&lt;p&gt;相比较 JDK 7，JDK 8 的 HashMap 主要做了四点优化：&lt;br/&gt;①、底层数据结构由数组 + 链表改成了数组 + 链表或红黑树的结构。&lt;br/&gt;原因：如果多个键映射到了同一个哈希值，链表会变得很长，在最坏的情况下，当所有的键都映射到同一个桶中时，性能会退化到 O(n)，而红黑树的时间复杂度是 O(logn)。&lt;br/&gt;②、链表的插入方式由头插法改为了尾插法。&lt;br/&gt;原因：头插法虽然简单快捷，但扩容后容易改变原来链表的顺序。&lt;br/&gt;③、扩容的时机由插入时判断改为插入后判断。&lt;br/&gt;原因：可以避免在每次插入时都进行不必要的扩容检查，因为有可能插入后仍然不需要扩容。&lt;br/&gt;④、优化了哈希算法。&lt;br/&gt;JDK 7 进行了多次移位和异或操作来计算元素的哈希值。&lt;br/&gt;JDK 8 优化了这个算法，只进行了一次异或操作，但仍然能有效地减少冲突。&lt;br/&gt;并且能够保证扩容后，元素的新位置要么是原位置，要么是原位置加上旧容量大小。&lt;/p&gt;
&lt;h3 id="23-你能自己设计实现一个-HashMap-吗？"&gt;&lt;a class="headerlink" href="#23-你能自己设计实现一个-HashMap-吗？" title="23.你能自己设计实现一个 HashMap 吗？"&gt;&lt;/a&gt;23.你能自己设计实现一个 HashMap 吗？&lt;/h3&gt;&lt;p&gt;这道题&lt;strong&gt;快手&lt;/strong&gt;常考。&lt;br/&gt;不要慌，红黑树版咱们多半是写不出来，但是数组+链表版还是问题不大的，详细可见： &lt;a href="https://mp.weixin.qq.com/s/Z9yoRZW5itrtgbS-cj0bUg"&gt;手写 HashMap，快手面试官直呼内行！&lt;/a&gt;。&lt;br/&gt;整体的设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;散列函数：hashCode()+除留余数法&lt;/li&gt;
&lt;li&gt;冲突解决：链地址法&lt;/li&gt;
&lt;li&gt;扩容：节点重新 hash 获取位置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-29.png"/&gt;&lt;br/&gt;完整代码：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-30.png"/&gt;&lt;/p&gt;
&lt;h3 id="24-HashMap-是线程安全的吗？多线程下会有什么问题？"&gt;&lt;a class="headerlink" href="#24-HashMap-是线程安全的吗？多线程下会有什么问题？" title="24.HashMap 是线程安全的吗？多线程下会有什么问题？"&gt;&lt;/a&gt;24.HashMap 是线程安全的吗？多线程下会有什么问题？&lt;/h3&gt;&lt;p&gt;推荐阅读：&lt;a href="https://javabetter.cn/collection/hashmap.html#_04%E3%80%81%E7%BA%BF%E7%A8%8B%E4%B8%8D%E5%AE%89%E5%85%A8"&gt;HashMap 详解&lt;/a&gt;&lt;br/&gt;HashMap 不是线程安全的，主要有以下几个问题：&lt;br/&gt;①、多线程下扩容会死循环。JDK1.7 中的 HashMap 使用的是头插法插入元素，在多线程的环境下，扩容的时候就有可能导致出现环形链表，造成死循环。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/hashmap-thread-nosafe-07.png"/&gt;&lt;br/&gt;不过，JDK 8 时已经修复了这个问题，扩容时会保持链表原来的顺序。&lt;br/&gt;②、多线程的 put 可能会导致元素的丢失。因为计算出来的位置可能会被其他线程的 put 覆盖。本来哈希冲突是应该用链表的，但多线程时由于没有加锁，相同位置的元素可能就被干掉了。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/hashmap-thread-nosafe-10.png"/&gt;&lt;br/&gt;③、put 和 get 并发时，可能导致 get 为 null。线程 1 执行 put 时，因为元素个数超出阈值而导致出现扩容，线程 2 此时执行 get，就有可能出现这个问题。&lt;br/&gt;因为线程 1 执行完 table = newTab 之后，线程 2 中的 table 此时也发生了变化，此时去 get 的时候当然会 get 到 null 了，因为元素还没有转移。&lt;/p&gt;
&lt;h3 id="25-有什么办法能解决-HashMap-线程不安全的问题呢？"&gt;&lt;a class="headerlink" href="#25-有什么办法能解决-HashMap-线程不安全的问题呢？" title="25.有什么办法能解决 HashMap 线程不安全的问题呢？"&gt;&lt;/a&gt;25.有什么办法能解决 HashMap 线程不安全的问题呢？&lt;/h3&gt;&lt;p&gt;在 Java 中，有 3 种线程安全的 Map 实现，最常用的是&lt;a href="https://javabetter.cn/thread/ConcurrentHashMap.html"&gt;ConcurrentHashMap&lt;/a&gt;和&lt;code&gt;Collections.synchronizedMap(Map)&lt;/code&gt;包装器。&lt;br/&gt;Hashtable 也是线程安全的，但它的使用已经不再推荐使用，因为 ConcurrentHashMap 提供了更高的并发性和性能。&lt;br/&gt;①、HashTable 是直接在方法上加 &lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized 关键字&lt;/a&gt;，比较粗暴。&lt;br/&gt;②、&lt;code&gt;Collections.synchronizedMap&lt;/code&gt; 返回的是 &lt;a href="https://javabetter.cn/common-tool/collections.html"&gt;Collections&lt;/a&gt; 工具类的内部类。&lt;br/&gt;内部是通过 synchronized 对象锁来保证线程安全的。&lt;br/&gt;③、&lt;a href="https://javabetter.cn/thread/ConcurrentHashMap.html"&gt;ConcurrentHashMap&lt;/a&gt; 在 JDK 7 中使用分段锁，在 JKD 8 中使用了 &lt;a href="https://javabetter.cn/thread/cas.html"&gt;CAS（Compare-And-Swap）&lt;/a&gt;+ &lt;a href="https://javabetter.cn/thread/synchronized-1.html"&gt;synchronized 关键字&lt;/a&gt;，性能得到进一步提升。&lt;/p&gt;
&lt;h3 id="26-HashMap-内部节点是有序的吗？"&gt;&lt;a class="headerlink" href="#26-HashMap-内部节点是有序的吗？" title="26.HashMap 内部节点是有序的吗？"&gt;&lt;/a&gt;26.HashMap 内部节点是有序的吗？&lt;/h3&gt;&lt;p&gt;HashMap 是无序的，根据 hash 值随机插入。如果想使用有序的 Map，可以使用 LinkedHashMap 或者 TreeMap。&lt;/p&gt;
&lt;h3 id="27-讲讲-LinkedHashMap-怎么实现有序的？"&gt;&lt;a class="headerlink" href="#27-讲讲-LinkedHashMap-怎么实现有序的？" title="27.讲讲 LinkedHashMap 怎么实现有序的？"&gt;&lt;/a&gt;27.讲讲 LinkedHashMap 怎么实现有序的？&lt;/h3&gt;&lt;p&gt;LinkedHashMap 维护了一个双向链表，有头尾节点，同时 LinkedHashMap 节点 Entry 内部除了继承 HashMap 的 Node 属性，还有 before 和 after 用于标识前置节点和后置节点。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-33.png"/&gt;&lt;br/&gt;可以实现按插入的顺序或访问顺序排序。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-34.png"/&gt;&lt;/p&gt;
&lt;h3 id="28-讲讲-TreeMap-怎么实现有序的？"&gt;&lt;a class="headerlink" href="#28-讲讲-TreeMap-怎么实现有序的？" title="28.讲讲 TreeMap 怎么实现有序的？"&gt;&lt;/a&gt;28.讲讲 TreeMap 怎么实现有序的？&lt;/h3&gt;&lt;p&gt;TreeMap 通过 key 的比较器来决定元素的顺序，如果没有指定比较器，那么 key 必须实现 &lt;a href="https://javabetter.cn/collection/comparable-omparator.html"&gt;Comparable 接口&lt;/a&gt;。&lt;br/&gt;TreeMap 的底层是红黑树，红黑树是一种自平衡的二叉查找树，每个节点都大于其左子树中的任何节点，小于其右子节点树种的任何节点。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-35.png"/&gt;&lt;br/&gt;插入或者删除元素时通过旋转和着色来保持树的平衡。&lt;br/&gt;查找的时候通过从根节点开始，利用二叉查找树的性质，逐步向左或者右子树递归查找，直到找到目标元素。&lt;/p&gt;
&lt;h3 id="29-TreeMap-和-HashMap-的区别"&gt;&lt;a class="headerlink" href="#29-TreeMap-和-HashMap-的区别" title="29.TreeMap 和 HashMap 的区别"&gt;&lt;/a&gt;29.TreeMap 和 HashMap 的区别&lt;/h3&gt;&lt;p&gt;①、HashMap 是基于数组+链表+红黑树实现的，put 元素的时候会先计算 key 的哈希值，然后通过哈希值计算出数组的索引，然后将元素插入到数组中，如果发生哈希冲突，会使用链表来解决，如果链表长度大于 8，会转换为红黑树。&lt;br/&gt;get 元素的时候同样会先计算 key 的哈希值，然后通过哈希值计算出数组的索引，如果遇到链表或者红黑树，会通过 key 的 equals 方法来判断是否是要找的元素。&lt;br/&gt;②、TreeMap 是基于红黑树实现的，put 元素的时候会先判断根节点是否为空，如果为空，直接插入到根节点，如果不为空，会通过 key 的比较器来判断元素应该插入到左子树还是右子树。&lt;br/&gt;get 元素的时候会通过 key 的比较器来判断元素的位置，然后递归查找。&lt;br/&gt;由于 HashMap 是基于哈希表实现的，所以在没有发生哈希冲突的情况下，HashMap 的查找效率是 O(1)。适用于查找操作比较频繁的场景。&lt;br/&gt;而 TreeMap 是基于红黑树实现的，所以 TreeMap 的查找效率是 O(logn)。并且保证了元素的顺序，因此适用于需要大量范围查找或者有序遍历的场景。&lt;/p&gt;
&lt;h2 id="Set"&gt;&lt;a class="headerlink" href="#Set" title="Set"&gt;&lt;/a&gt;Set&lt;/h2&gt;&lt;h3 id="30-讲讲-HashSet-的底层实现？"&gt;&lt;a class="headerlink" href="#30-讲讲-HashSet-的底层实现？" title="30.讲讲 HashSet 的底层实现？"&gt;&lt;/a&gt;30.讲讲 HashSet 的底层实现？&lt;/h3&gt;&lt;p&gt;HashSet 其实是由 HashMap 实现的，只不过值由一个固定的 Object 对象填充，而键用于操作。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;HashSet&lt;/span&gt;&amp;lt;E&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;extends&lt;/span&gt; &lt;span class="title class_"&gt;AbstractSet&lt;/span&gt;&amp;lt;E&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Set&lt;/span&gt;&amp;lt;E&amp;gt;, Cloneable, java.io.Serializable&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;serialVersionUID&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; -&lt;span class="number"&gt;5024744406713321676L&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;transient&lt;/span&gt; HashMap&amp;lt;E,Object&amp;gt; map;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// Dummy value to associate with an Object in the backing Map&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;PRESENT&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Object&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;// ……&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;实际开发中，HashSet 并不常用，比如，如果我们需要按照顺序存储一组元素，那么 ArrayList 和 LinkedList 可能更适合；如果我们需要存储键值对并根据键进行查找，那么 HashMap 可能更适合。&lt;br/&gt;HashSet 主要用于去重，比如，我们需要统计一篇文章中有多少个不重复的单词，就可以使用 HashSet 来实现。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 创建一个 HashSet 对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;HashSet&amp;lt;String&amp;gt; set = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;HashSet&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 添加元素&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;set.add(&lt;span class="string"&gt;"沉默"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;set.add(&lt;span class="string"&gt;"王二"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;set.add(&lt;span class="string"&gt;"陈清扬"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;set.add(&lt;span class="string"&gt;"沉默"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 输出 HashSet 的元素个数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;System.out.println(&lt;span class="string"&gt;"HashSet size: "&lt;/span&gt; + set.size()); &lt;span class="comment"&gt;// output: 3&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 遍历 HashSet&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;for&lt;/span&gt; (String s : set) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    System.out.println(s);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;HashSet 会自动去重，因为它是用 HashMap 实现的，HashMap 的键是唯一的（哈希值），相同键的值会覆盖掉原来的值，于是第二次 set.add(“沉默”) 的时候就覆盖了第一次的 set.add(“沉默”)。&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-36.png"/&gt;&lt;/p&gt;
&lt;h4 id="HashSet-和-ArrayList-的区别"&gt;&lt;a class="headerlink" href="#HashSet-和-ArrayList-的区别" title="HashSet 和 ArrayList 的区别"&gt;&lt;/a&gt;HashSet 和 ArrayList 的区别&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;ArrayList 是基于动态数组实现的，HashSet 是基于 HashMap 实现的。&lt;/li&gt;
&lt;li&gt;ArrayList 允许重复元素和 null 值，可以有多个相同的元素；HashSet 保证每个元素唯一，不允许重复元素，基于元素的 hashCode 和 equals 方法来确定元素的唯一性。&lt;/li&gt;
&lt;li&gt;ArrayList 保持元素的插入顺序，可以通过索引访问元素；HashSet 不保证元素的顺序，元素的存储顺序依赖于哈希算法，并且可能随着元素的添加或删除而改变。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="HashSet-怎么判断元素重复，重复了是否-put"&gt;&lt;a class="headerlink" href="#HashSet-怎么判断元素重复，重复了是否-put" title="HashSet 怎么判断元素重复，重复了是否 put"&gt;&lt;/a&gt;HashSet 怎么判断元素重复，重复了是否 put&lt;/h4&gt;&lt;p&gt;HashSet 的 add 方法是通过调用 HashMap 的 put 方法实现的：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;add&lt;/span&gt;&lt;span class="params"&gt;(E e)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; map.put(e, PRESENT)==&lt;span class="literal"&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;所以 HashSet 判断元素重复的逻辑底层依然是 HashMap 的底层逻辑：&lt;br/&gt;&lt;img src="/./img/%E5%85%AB%E8%82%A1%E6%96%87%E7%B3%BB%E5%88%97-Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/collection-13.jpeg"/&gt;&lt;br/&gt;HashMap 在插入元素时，通常需要三步：&lt;br/&gt;第一步，通过 hash 方法计算 key 的哈希值。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;final&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;hash&lt;/span&gt;&lt;span class="params"&gt;(Object key)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;int&lt;/span&gt; h;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; (key == &lt;span class="literal"&gt;null&lt;/span&gt;) ? &lt;span class="number"&gt;0&lt;/span&gt; : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; &lt;span class="number"&gt;16&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第二步，数组进行第一次扩容。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; ((tab = table) == &lt;span class="literal"&gt;null&lt;/span&gt; || (n = tab.length) == &lt;span class="number"&gt;0&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    n = (tab = resize()).length;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;第三步，根据哈希值计算 key 在数组中的下标，如果对应下标正好没有存放数据，则直接插入。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; ((p = tab[i = (n - &lt;span class="number"&gt;1&lt;/span&gt;) &amp;amp; hash]) == &lt;span class="literal"&gt;null&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    tab[i] = newNode(hash, key, value, &lt;span class="literal"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;如果对应下标已经有数据了，就需要判断是否为相同的 key，是则覆盖 value，否则需要判断是否为树节点，是则向树中插入节点，否则向链表中插入数据。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Node&amp;lt;K,V&amp;gt; e; K k;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (p.hash == hash &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        ((k = p.key) == key || (key != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; key.equals(k))))&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        e = p;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt; &lt;span class="keyword"&gt;if&lt;/span&gt; (p &lt;span class="keyword"&gt;instanceof&lt;/span&gt; TreeNode)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        e = ((TreeNode&amp;lt;K,V&amp;gt;)p).putTreeVal(&lt;span class="built_in"&gt;this&lt;/span&gt;, tab, hash, key, value);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;binCount&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;; ; ++binCount) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; ((e = p.next) == &lt;span class="literal"&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                p.next = newNode(hash, key, value, &lt;span class="literal"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;if&lt;/span&gt; (binCount &amp;gt;= TREEIFY_THRESHOLD - &lt;span class="number"&gt;1&lt;/span&gt;) &lt;span class="comment"&gt;// -1 for 1st&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                    treeifyBin(tab, hash);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt; (e.hash == hash &amp;amp;&amp;amp;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                ((k = e.key) == key || (key != &lt;span class="literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; key.equals(k))))&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            p = e;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;也就是说，HashSet 通过元素的哈希值来判断元素是否重复，如果重复了，会覆盖原来的值。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; (e != &lt;span class="literal"&gt;null&lt;/span&gt;) { &lt;span class="comment"&gt;// existing mapping for key&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;V&lt;/span&gt; &lt;span class="variable"&gt;oldValue&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; e.value;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (!onlyIfAbsent || oldValue == &lt;span class="literal"&gt;null&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        e.value = value;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    afterNodeAccess(e);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; oldValue;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
</content>
    <summary type="html">八股文系列-Java集合框架 ...</summary>
    <category term="八股文" scheme="https://aspirant02.github.io/categories/%E5%85%AB%E8%82%A1%E6%96%87/" />
    <category term="Java集合" scheme="https://aspirant02.github.io/tags/Java%E9%9B%86%E5%90%88/" />
  </entry>
  <entry>
    <title>AI开发-Harness：驾驭大模型的 Agent 挽具</title>
    <link href="https://aspirant02.github.io/2026/07/23/AI%E5%BC%80%E5%8F%91-Harness%EF%BC%9A%E7%94%A8lm-evaluation-harness%E8%AF%84%E6%B5%8B%E6%A8%A1%E5%9E%8B/" />
    <id>https://aspirant02.github.io/2026/07/23/AI%E5%BC%80%E5%8F%91-Harness%EF%BC%9A%E7%94%A8lm-evaluation-harness%E8%AF%84%E6%B5%8B%E6%A8%A1%E5%9E%8B/</id>
    <published>2026-07-23T13:01:24Z</published>
    <updated>2026-07-23T13:01:24Z</updated>
    <content type="html">&lt;h1 id="前言"&gt;&lt;a class="headerlink" href="#前言" title="前言"&gt;&lt;/a&gt;前言&lt;/h1&gt;
&lt;p&gt;这里说的 Harness，不是某个模型评测框架。它更接近英文里“挽具”的本意：把一个力量很大的东西套住，让它能够被驾驭、被导向、被限制，而不是到处乱冲。放到 Agent 语境里，Harness 就是一套用来驾驭大模型的工程结构。&lt;/p&gt;
&lt;p&gt;大模型本身像一股很强的推理和生成能力，但它没有天然的边界感。它可能把任务做偏，也可能调用不该调用的工具，还可能在没有证据时编造结论。Harness 的作用不是削弱模型，而是把模型放进一个可执行、可观察、可回滚的轨道里。&lt;/p&gt;
&lt;p&gt;如果说 Agent 是“能行动的模型系统”，那 Harness 就是让这个行动系统稳定工作的挽具。它规定任务入口是什么，模型能看到什么上下文，可以调用哪些工具，每一步如何记录，什么时候停止，怎么验证结果，以及失败后如何恢复。&lt;/p&gt;
&lt;h1 id="为什么 Agent 需要 Harness"&gt;&lt;a class="headerlink" href="#为什么 Agent 需要 Harness" title="为什么 Agent 需要 Harness"&gt;&lt;/a&gt;为什么 Agent 需要 Harness&lt;/h1&gt;
&lt;p&gt;很多 Agent Demo 看起来很聪明，但一接近真实任务就容易失控。原因通常不是模型太差，而是系统没有 Harness。用户说“帮我修一下这个问题”，模型拿到一句模糊目标，就开始读文件、改代码、运行命令。中间没有预算、没有权限边界、没有状态记录、没有验收标准，最后结果只能靠感觉判断。&lt;/p&gt;
&lt;p&gt;Harness 要解决的正是这些问题。它把自然语言目标变成任务契约，把工具调用放进权限闸门，把长任务拆成状态机，把每一步观察写入 trace，把最终结果交给验证器。模型仍然负责理解、计划和生成，但它不再裸奔。&lt;/p&gt;
&lt;p&gt;一个没有 Harness 的 Agent 像是在直接拉一辆车；一个有 Harness 的 Agent 则像是有缰绳、车辕、刹车和仪表盘。力量还是同一个力量，但可控性完全不同。&lt;/p&gt;
&lt;h1 id="Harness 的组成"&gt;&lt;a class="headerlink" href="#Harness 的组成" title="Harness 的组成"&gt;&lt;/a&gt;Harness 的组成&lt;/h1&gt;
&lt;p&gt;我会把 Agent Harness 拆成七个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task Contract：任务契约，定义目标、输入、输出、验收标准和禁止事项。&lt;/li&gt;
&lt;li&gt;Context Builder：上下文装配器，只把当前步骤需要的信息交给模型。&lt;/li&gt;
&lt;li&gt;Planner：计划器，把目标拆成可执行步骤。&lt;/li&gt;
&lt;li&gt;Tool Gate：工具闸门，决定某个工具调用是否允许执行。&lt;/li&gt;
&lt;li&gt;Executor：执行器，真正调用文件、网络、数据库、浏览器等外部能力。&lt;/li&gt;
&lt;li&gt;Observer：观察器，把工具结果压缩成模型可读的反馈。&lt;/li&gt;
&lt;li&gt;Verifier：验证器，用测试、规则、截图、人工检查等方式判断任务是否完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这七个部分不一定都要做得很重，但概念上最好分开。否则所有逻辑都塞进一个大 prompt，后面一定难调。&lt;/p&gt;
&lt;h1 id="任务契约"&gt;&lt;a class="headerlink" href="#任务契约" title="任务契约"&gt;&lt;/a&gt;任务契约&lt;/h1&gt;
&lt;p&gt;Harness 的第一步是把用户目标变成任务契约。契约不是为了增加形式感，而是为了让 Agent 知道什么叫完成，什么叫越界。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;task_id: fix-category-page
objective: 修复分类和标签页面的展示问题
inputs:
  - screenshots
  - repo_path
allowed_actions:
  - read_file
  - search_text
  - edit_static_html
  - run_git_diff_check
  - push_to_remote
forbidden_actions:
  - delete_repository
  - reset_unrelated_changes
acceptance:
  - AI开发分类页标题不再出现“分类:”前缀
  - 分类页不再显示个人信息侧栏
  - 标签页采用同样规则
  - 修改必须提交并推送&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了契约，模型就不只是“尽量帮忙”，而是在一组边界里工作。后续每一次工具调用，都可以拿契约做判断：这个动作是否被允许，是否和目标相关，是否需要用户确认。&lt;/p&gt;
&lt;h1 id="工具闸门"&gt;&lt;a class="headerlink" href="#工具闸门" title="工具闸门"&gt;&lt;/a&gt;工具闸门&lt;/h1&gt;
&lt;p&gt;Agent 最容易出事的地方是工具调用。读文件通常风险较低，写文件风险更高，删除、部署、支付、发邮件则需要更严格的确认。Harness 要把工具分级，而不是把所有工具都裸露给模型。&lt;/p&gt;
&lt;p&gt;下面是一个极简工具闸门示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;class ToolGate:
    def __init__(self, contract):
        self.contract = contract

    def allow(self, tool_name, args):
        if tool_name not in self.contract["allowed_actions"]:
            return False, "tool is not allowed by task contract"

        if tool_name.startswith("delete_"):
            return False, "destructive action requires human approval"

        if tool_name == "edit_static_html":
            path = args.get("path", "")
            if not path.endswith("index.html"):
                return False, "this task only allows editing generated html pages"

        return True, "ok"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码很简单，但思想重要：模型提出 action，Harness 决定 action 是否能落地。模型负责想，Harness 负责管。&lt;/p&gt;
&lt;h1 id="执行循环"&gt;&lt;a class="headerlink" href="#执行循环" title="执行循环"&gt;&lt;/a&gt;执行循环&lt;/h1&gt;
&lt;p&gt;一个 Harness 驱动的 Agent 循环通常不是“模型一次性回答完”，而是多轮推进。每一轮包含：构造上下文、让模型选择下一步、通过工具闸门、执行工具、观察结果、记录 trace、判断是否完成。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;class AgentHarness:
    def __init__(self, model, tools, verifier, max_steps=12):
        self.model = model
        self.tools = tools
        self.verifier = verifier
        self.max_steps = max_steps
        self.trace = []

    def run(self, contract):
        state = {"contract": contract, "observations": []}
        gate = ToolGate(contract)

        for step in range(self.max_steps):
            action = self.model.next_action(state)
            allowed, reason = gate.allow(action.name, action.args)

            if not allowed:
                self.trace.append({"step": step, "blocked": action.name, "reason": reason})
                state["observations"].append(f"blocked: {reason}")
                continue

            result = self.tools[action.name](**action.args)
            self.trace.append({"step": step, "action": action.name, "result": result.summary})
            state["observations"].append(result.summary)

            if self.verifier.passed(state):
                return {"status": "done", "trace": self.trace}

        return {"status": "need_review", "trace": self.trace}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里最关键的是 max_steps 和 verifier。没有步数上限，Agent 可能陷入循环；没有验证器，Agent 会把“我觉得完成了”当成完成。Harness 要求系统用外部证据收尾，比如测试通过、页面检查通过、接口返回正确、用户确认通过。&lt;/p&gt;
&lt;h1 id="上下文装配"&gt;&lt;a class="headerlink" href="#上下文装配" title="上下文装配"&gt;&lt;/a&gt;上下文装配&lt;/h1&gt;
&lt;p&gt;很多人做 Agent 时喜欢把所有信息塞进上下文：用户需求、历史对话、全部文件、全部工具说明、全部规则。这样模型看起来知道得很多，但实际更容易混乱。Harness 里的 Context Builder 应该按步骤装配上下文。&lt;/p&gt;
&lt;p&gt;比如当前步骤是“判断分类页为什么显示错标题”，上下文只需要相关 HTML 片段、页面路径、用户截图里的现象、已知约束。它不需要整个仓库，也不需要所有历史任务。上下文越干净，模型越容易做出稳定决策。&lt;/p&gt;
&lt;p&gt;一个实用做法是把上下文分成四层：任务契约永远保留，最近观察保留，相关文件片段按检索加入，长期记忆只在命中时加入。这样 Harness 像一个过滤器，决定模型此刻应该看什么。&lt;/p&gt;
&lt;h1 id="验证器"&gt;&lt;a class="headerlink" href="#验证器" title="验证器"&gt;&lt;/a&gt;验证器&lt;/h1&gt;
&lt;p&gt;Verifier 是 Harness 的刹车和仪表盘。它不是为了否定模型，而是为了告诉系统“这一步是否真的有效”。不同任务需要不同验证器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码任务：单元测试、构建、lint、类型检查。&lt;/li&gt;
&lt;li&gt;前端任务：截图对比、DOM 查询、响应式检查。&lt;/li&gt;
&lt;li&gt;数据任务：行数、schema、统计分布、异常值检查。&lt;/li&gt;
&lt;li&gt;文档任务：标题、链接、摘要、关键词和格式检查。&lt;/li&gt;
&lt;li&gt;自动化任务：dry-run、审计日志、幂等性检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果没有验证器，Agent 很容易停在“看起来对了”。Harness 要让“看起来”变成“证据显示”。例如页面侧栏问题，不应该只凭肉眼说修好了，而应该查询主内容里是否还有 aside-content、检查页面是否启用了 hide-aside、再线上请求确认部署结果。&lt;/p&gt;
&lt;h1 id="Harness 和 Skill"&gt;&lt;a class="headerlink" href="#Harness 和 Skill" title="Harness 和 Skill"&gt;&lt;/a&gt;Harness 和 Skill&lt;/h1&gt;
&lt;p&gt;Harness 和 Hermes 里的 Skill 是互补关系。Skill 告诉 Agent “类似任务通常怎么做”，Harness 则告诉 Agent “这次任务允许怎么做、做到什么程度、如何验证”。一个偏经验，一个偏约束。&lt;/p&gt;
&lt;p&gt;没有 Skill，Agent 每次都要从零规划，效率低；没有 Harness，Agent 拿到 Skill 也可能用错地方。比如一个 Skill 建议“批量替换 HTML 标题”，Harness 需要检查这个批量替换是否只作用在目标页面，是否会改坏其它分类，是否需要同步搜索索引和订阅文件。&lt;/p&gt;
&lt;p&gt;所以比较理想的 Agent 架构是：Harness 管住任务生命周期，Skill 提供可复用方法。模型在两者之间做推理和选择。&lt;/p&gt;
&lt;h1 id="落地建议"&gt;&lt;a class="headerlink" href="#落地建议" title="落地建议"&gt;&lt;/a&gt;落地建议&lt;/h1&gt;
&lt;p&gt;如果要从零实现一个 Harness，不建议一开始就做成大平台。可以先做一个窄场景，例如“静态站点维护 Agent”或“仓库测试修复 Agent”。先定义任务契约，再接入少量工具，然后加上 trace 和验证器。&lt;/p&gt;
&lt;p&gt;第一版只需要做到：每一步 action 都有记录，每个工具调用都有权限检查，每个任务结束都有验收项。等这个闭环稳定后，再加入自动规划、上下文检索、Skill 召回和长期记忆。&lt;/p&gt;
&lt;p&gt;Harness 的价值不是让 Agent 显得复杂，而是让它可靠。一个能被追踪、能被限制、能被验证的 Agent，才适合真正接入工作流。&lt;/p&gt;
&lt;h1 id="小结"&gt;&lt;a class="headerlink" href="#小结" title="小结"&gt;&lt;/a&gt;小结&lt;/h1&gt;
&lt;p&gt;Harness 是驾驭大模型的挽具。它用任务契约定义目标和边界，用上下文装配器控制模型看到什么，用工具闸门限制模型能做什么，用执行器和观察器推进任务，用验证器判断结果是否可信。&lt;/p&gt;
&lt;p&gt;做 Agent 时，prompt 很重要，但 prompt 不是全部。真正让 Agent 从 Demo 走向可用的，是 Harness 这种工程结构。它让模型的能力不再是散开的冲劲，而是被导向一个可控、可复盘、可持续改进的执行系统。&lt;/p&gt;</content>
    <summary type="html">Harness 是驾驭大模型和 Agent 的挽具，用任务契约、工具闸门、状态机和验证器把模型能力导向可控执行。 ...</summary>
    <category term="AI开发" scheme="https://aspirant02.github.io/categories/AI%E5%BC%80%E5%8F%91/" />
    <category term="AI开发" scheme="https://aspirant02.github.io/tags/AI%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>我的博客优化历程—自动初始化Gitalk</title>
    <link href="https://aspirant02.github.io/2026/07/11/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96%E2%80%94%E8%87%AA%E5%8A%A8%E5%88%9D%E5%A7%8B%E5%8C%96Gitalk/" />
    <id>https://aspirant02.github.io/2026/07/11/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96%E2%80%94%E8%87%AA%E5%8A%A8%E5%88%9D%E5%A7%8B%E5%8C%96Gitalk/</id>
    <published>2026-07-11T13:01:24Z</published>
    <updated>2026-07-11T13:01:24Z</updated>
    <content type="html">&lt;h1 id="准备工作"&gt;&lt;a class="headerlink" href="#准备工作" title="准备工作"&gt;&lt;/a&gt;准备工作&lt;/h1&gt;&lt;p&gt;Gitalk。它是一个基于 GitHub Issue 和 Preact 开发的评论插件，除了 GitHub 账号之外不需要其他依赖，也不需要注册其他账号。&lt;/p&gt;
&lt;p&gt;本文简要介绍一下如何在博客使用 Gitalk 评论区，并移除其他评论区的过程。对于具体的细节则不会做过多解释，主要展示步骤。&lt;/p&gt;
&lt;p&gt;本文运行环境：&lt;/p&gt;
&lt;figure class="highlight yml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;node:&lt;/span&gt; &lt;span class="string"&gt;v18.16.1&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;npm:&lt;/span&gt; &lt;span class="number"&gt;9.5&lt;/span&gt;&lt;span class="number"&gt;.1&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;hexo:&lt;/span&gt; &lt;span class="number"&gt;6.3&lt;/span&gt;&lt;span class="number"&gt;.0&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;hexo-cli:&lt;/span&gt; &lt;span class="number"&gt;4.3&lt;/span&gt;&lt;span class="number"&gt;.1&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;NexT:&lt;/span&gt; &lt;span class="number"&gt;8.17&lt;/span&gt;&lt;span class="number"&gt;.0&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h1 id="注册-GitHub-Application"&gt;&lt;a class="headerlink" href="#注册-GitHub-Application" title="注册 GitHub Application"&gt;&lt;/a&gt;注册 GitHub Application&lt;/h1&gt;&lt;p&gt;使用 Gitalk 的前提是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个公共的 GitHub 存储库用于存储评论&lt;/li&gt;
&lt;li&gt;创建一个 GitHub Application&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中第一步当前已经满足，你的博客本身就是你的 GitHub 公共存储库，直接使用即可。当然你也可以新建一个存储库专门用于评论区。下面完成第二步。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/settings/applications/new"&gt;点击这里&lt;/a&gt;申请 GitHub Application，网页标题为 “Register a new OAuth application”。需要填写如下内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application name：应用名称，我在这里填写了 &lt;code&gt;blog&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Homepage URL：你的应用主页，这里填写博客主页地址。记住要携带 &lt;code&gt;https&lt;/code&gt; 协议，如果你的博客是个人域名，请使用域名而不是 &lt;code&gt;*.github.io&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Application description：你的应用描述，任何语言皆可。用你自己喜欢的文字描述。&lt;/li&gt;
&lt;li&gt;Authorization callback URL：这里要填写当前使用插件页面的域名，与 Homepage URL 相同即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点击 “Register application”，在新页面可以看到 Client ID，先记下。随后在 “Client secrets” 右侧点击 “Generate a new client secret”，并记下生成的 client secret。它只会显示一次，如果没有记下或者丢失了，就只好重建新的再删除旧的了。&lt;/p&gt;
&lt;p&gt;以上准备工作完毕。如果你需要一个存储库专门用于评论区，直接在 GitHub &lt;a href="https://github.com/new"&gt;新建 repository&lt;/a&gt; 即可，这里就不赘述了。&lt;/p&gt;
&lt;h1 id="配置-Gitalk"&gt;&lt;a class="headerlink" href="#配置-Gitalk" title="配置 Gitalk"&gt;&lt;/a&gt;配置 Gitalk&lt;/h1&gt;&lt;p&gt;在&lt;code&gt;_config.yml&lt;/code&gt; 中进行配置：&lt;/p&gt;
&lt;p&gt;_config.next.yml&lt;/p&gt;
&lt;figure class="highlight yml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;# Comments System （评论系统）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;# 评论配置&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;comments:&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;# Up to two comments system, the first will be shown as default&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;# Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;use:&lt;/span&gt; &lt;span class="string"&gt;gitalk&lt;/span&gt; &lt;span class="comment"&gt;# Valine,Disqus&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;text:&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt; &lt;span class="comment"&gt;# Display the comment name next to the button&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;# lazyload: The comment system will be load when comment element enters the browser's viewport.&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;# If you set it to true, the comment count will be invalid&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;lazyload:&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;count:&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt; &lt;span class="comment"&gt;# Display comment count in post's top_img&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;card_post_count:&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt; &lt;span class="comment"&gt;# Display comment count in Home Page&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;# gitalk&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;# https://github.com/gitalk/gitalk&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;gitalk:&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;enable:&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;    &lt;span class="comment"&gt;#是否开启Gitalk评论&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;client_id:&lt;/span&gt; &lt;span class="string"&gt;Ov23liwBeMYsKAurz69S&lt;/span&gt;                            &lt;span class="comment"&gt;#生成的clientID&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;client_secret:&lt;/span&gt; &lt;span class="string"&gt;***&lt;/span&gt;    &lt;span class="comment"&gt;#生成的clientSecret&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;repo:&lt;/span&gt; &lt;span class="string"&gt;***&lt;/span&gt;    &lt;span class="comment"&gt;#仓库名称&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;owner:&lt;/span&gt; &lt;span class="string"&gt;***&lt;/span&gt;    &lt;span class="comment"&gt;#github用户名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;admin:&lt;/span&gt;  &lt;span class="comment"&gt;# GitHub repo 的拥有者和合作者，这些人具有初始化 issue 的权限&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="bullet"&gt;-&lt;/span&gt; &lt;span class="string"&gt;***&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;distractionFreeMode:&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt; &lt;span class="comment"&gt;#是否启用类似FB的阴影遮罩&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;labels:&lt;/span&gt; &lt;span class="string"&gt;'gitalk'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;如果你之前有其他的评论区，记得取消掉。具体方法就是查看附近的评论区设置，如果有 &lt;code&gt;enable: true&lt;/code&gt; 要改为 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;
&lt;h1 id="评论区批量初始化"&gt;&lt;a class="headerlink" href="#评论区批量初始化" title="评论区批量初始化"&gt;&lt;/a&gt;评论区批量初始化&lt;/h1&gt;&lt;p&gt;现在部署之后应该可以看到 Gitalk 评论区，但是处于未初始化的状态，而且每一篇文章都是这样的。显示的文字如下：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ylcheng2022/pic_bed/main/img/202408302205616.png"/&gt;&lt;/p&gt;
&lt;p&gt;如果是全新的博客或许可以手动初始化，但是对于老博客来讲，这个成本有点高，因此需要批量初始化。本部分参考了&lt;a href="https://blog.jijian.link/2020-01-10/hexo-gitalk-auto-init/"&gt;这篇文章&lt;/a&gt;与&lt;a href="https://www.toimc.com/hexo-usage-3/"&gt;这篇文章&lt;/a&gt;，并做了一些修改与优化。&lt;/p&gt;
&lt;h1 id="申请-Personal-Access-Token"&gt;&lt;a class="headerlink" href="#申请-Personal-Access-Token" title="申请 Personal Access Token"&gt;&lt;/a&gt;申请 Personal Access Token&lt;/h1&gt;&lt;p&gt;因为 GitHub API 对接口请求有限制，OAuth 方式限制太低，不足以用于初始化评论区，因此需要 Personal Access Token 的方式来完成。在&lt;a href="https://github.com/settings/tokens/new"&gt;这里&lt;/a&gt;申请一个新的 Token，网页标题为 “New personal access token (classic)”。填写如下信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Note：Token 的使用目的，例如 &lt;code&gt;hexo-blog-comments-init&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Expiration：选择 “No expiration”，也就是无过期时间。&lt;/li&gt;
&lt;li&gt;Select scopes：选择 Token 的权限。这里需要以下权限：&lt;ul&gt;
&lt;li&gt;&lt;code&gt;repo:status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repo_deployment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public_repo&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点击 “Generate token”，获取新的 Token。这里 Token 只会出现一次，请务必记下，否则又要重新创建了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ylcheng2022/pic_bed/main/img/202408302205617.png"/&gt;&lt;/p&gt;
&lt;h1 id="安装相关依赖"&gt;&lt;a class="headerlink" href="#安装相关依赖" title="安装相关依赖"&gt;&lt;/a&gt;安装相关依赖&lt;/h1&gt;&lt;p&gt;在项目根目录下运行命令：&lt;/p&gt;
&lt;figure class="highlight cmd"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;npm i request xml-parser blueimp-md5 moment hexo-generator-sitemap md5 -S&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h1 id="配置-hexo-generator-sitemap"&gt;&lt;a class="headerlink" href="#配置-hexo-generator-sitemap" title="配置 hexo-generator-sitemap"&gt;&lt;/a&gt;配置 &lt;code&gt;hexo-generator-sitemap&lt;/code&gt;&lt;/h1&gt;&lt;p&gt;安装 &lt;code&gt;hexo-generator-sitemap&lt;/code&gt;，随后在&lt;code&gt;_config.yml&lt;/code&gt; 添加或修改：&lt;/p&gt;
&lt;figure class="highlight yaml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;sitemap:&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;path:&lt;/span&gt; &lt;span class="string"&gt;sitemap.xml&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;template:&lt;/span&gt; &lt;span class="string"&gt;sitemap-template.xml&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在项目根目录新建文件 &lt;code&gt;sitemap-template.xml&lt;/code&gt;：&lt;/p&gt;
&lt;figure class="highlight xml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;&amp;lt;?xml version=&lt;span class="string"&gt;"1.0"&lt;/span&gt; encoding=&lt;span class="string"&gt;"UTF-8"&lt;/span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;urlset&lt;/span&gt; &lt;span class="attr"&gt;xmlns&lt;/span&gt;=&lt;span class="string"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  {% for post in posts %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;url&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;loc&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.permalink | uriencode }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;loc&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {% if post.updated %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.updated.toISOString() }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {% elif post.date %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.date.toISOString() }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {% endif %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;date&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.date }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;date&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.title + ' | ' + config.title }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {# nunjucks 模版语法 https://github.com/mozilla/nunjucks #}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;desc&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.description | default(post.excerpt) | default(post.content) | default(config.description) | striptags | truncate(200, true, '') }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;desc&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;url&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  {% endfor %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;urlset&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;配置完成后使用 &lt;code&gt;hexo clean &amp;amp; hexo g&lt;/code&gt; 可以看到生成了 &lt;code&gt;public/sitemap.xml&lt;/code&gt;。&lt;/p&gt;
&lt;h1 id="添加自动初始化脚本"&gt;&lt;a class="headerlink" href="#添加自动初始化脚本" title="添加自动初始化脚本"&gt;&lt;/a&gt;添加自动初始化脚本&lt;/h1&gt;&lt;p&gt;新建文件 &lt;code&gt;source/_scripts/gitalk-auto-init.js&lt;/code&gt;，并写入如下内容（注意 &lt;code&gt;config&lt;/code&gt; 中的配置信息要符合你的博客）：&lt;/p&gt;
&lt;figure class="highlight js"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;53&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;54&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;55&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;56&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;57&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;58&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;59&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;60&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;61&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;62&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;63&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;64&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;65&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;66&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;67&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;68&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;69&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;70&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;71&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;72&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;73&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;74&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;75&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;76&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;77&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;78&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;79&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;80&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;81&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;82&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;83&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;84&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;85&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;86&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;87&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;88&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;89&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;90&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;91&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;92&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;93&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;94&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;95&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;96&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;97&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;98&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;99&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;100&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;101&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;102&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;103&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;104&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;105&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;106&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;107&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;108&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;109&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;110&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;111&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;112&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;113&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;114&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;115&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;116&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;117&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;118&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;119&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;120&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;121&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;122&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;123&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;124&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;125&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;126&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;127&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;128&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;129&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;130&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;131&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;132&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;133&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;134&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;135&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;136&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;137&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;138&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;139&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;140&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;141&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;142&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;143&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;144&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;145&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;146&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;147&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;148&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;149&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;150&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;151&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;152&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;153&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;154&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;155&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;156&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;157&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;158&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;159&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;160&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;161&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;162&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;163&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;164&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;165&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;166&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;167&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;168&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;169&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;170&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;171&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;172&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;173&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;174&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;175&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;176&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;177&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;178&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;179&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;180&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;181&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;182&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;183&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;184&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;185&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;186&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;187&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;188&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;189&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;190&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;191&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;192&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;193&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;194&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;195&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;196&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;197&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;198&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;199&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;200&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;201&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;202&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;203&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;204&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;205&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;206&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;207&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;208&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;209&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;210&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;211&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;212&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;213&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;214&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;215&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;216&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;217&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;218&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;219&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;220&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;221&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;222&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;223&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;224&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;225&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;226&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;227&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;228&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;229&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;230&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;231&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;232&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;233&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;234&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;235&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;236&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;237&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;238&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;239&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;240&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;241&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;242&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;243&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;244&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;245&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;246&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;247&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;248&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;249&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;250&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;251&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;252&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;253&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;254&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;255&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;256&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;257&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;258&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;259&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;260&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;261&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;262&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;263&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;264&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;265&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;266&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;267&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;268&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;269&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;270&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;271&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;272&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;273&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;274&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;275&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;276&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;277&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;278&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;279&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;280&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;281&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;282&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;283&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;284&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;285&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;286&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;287&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;288&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;289&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;290&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;291&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;292&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;293&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;294&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;295&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;296&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;297&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;298&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;299&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;300&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;301&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;302&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; fs = &lt;span class="built_in"&gt;require&lt;/span&gt;(&lt;span class="string"&gt;'fs'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; path = &lt;span class="built_in"&gt;require&lt;/span&gt;(&lt;span class="string"&gt;'path'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; url = &lt;span class="built_in"&gt;require&lt;/span&gt;(&lt;span class="string"&gt;'url'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; request = &lt;span class="built_in"&gt;require&lt;/span&gt;(&lt;span class="string"&gt;'request'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; xmlParser = &lt;span class="built_in"&gt;require&lt;/span&gt;(&lt;span class="string"&gt;'xml-parser'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; md5 = &lt;span class="built_in"&gt;require&lt;/span&gt;(&lt;span class="string"&gt;'md5'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 配置信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; config = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;username&lt;/span&gt;: &lt;span class="string"&gt;'xxx'&lt;/span&gt;, &lt;span class="comment"&gt;// GitHub repository 所有者，可以是个人或者组织。对应Gitalk配置中的owner&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;repo&lt;/span&gt;: &lt;span class="string"&gt;'xxx'&lt;/span&gt;, &lt;span class="comment"&gt;// 储存评论issue的github仓库名，仅需要仓库名字即可。对应 Gitalk配置中的repo&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;token&lt;/span&gt;: &lt;span class="string"&gt;'xxx'&lt;/span&gt;, &lt;span class="comment"&gt;// 前面申请的 personal access token&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;sitemap&lt;/span&gt;: path.&lt;span class="title function_"&gt;join&lt;/span&gt;(__dirname, &lt;span class="string"&gt;'../../public/sitemap.xml'&lt;/span&gt;), &lt;span class="comment"&gt;// 自己站点的 sitemap 文件地址&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;cache&lt;/span&gt;: &lt;span class="literal"&gt;true&lt;/span&gt;, &lt;span class="comment"&gt;// 是否启用缓存，启用缓存会将已经初始化的数据写入配置的 gitalkCacheFile 文件，下一次直接通过缓存文件判断&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;gitalkCacheFile&lt;/span&gt;: path.&lt;span class="title function_"&gt;join&lt;/span&gt;(__dirname, &lt;span class="string"&gt;'./gitalk-init-cache.json'&lt;/span&gt;), &lt;span class="comment"&gt;// 用于保存 gitalk 已经初始化的 id 列表&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;gitalkErrorFile&lt;/span&gt;: path.&lt;span class="title function_"&gt;join&lt;/span&gt;(__dirname, &lt;span class="string"&gt;'./gitalk-init-error.json'&lt;/span&gt;), &lt;span class="comment"&gt;// 用于保存 gitalk 初始化报错的数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; api = &lt;span class="string"&gt;'https://api.github.com/repos/'&lt;/span&gt; + config.&lt;span class="property"&gt;username&lt;/span&gt; + &lt;span class="string"&gt;'/'&lt;/span&gt; + config.&lt;span class="property"&gt;repo&lt;/span&gt; + &lt;span class="string"&gt;'/issues'&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* 读取 sitemap 文件&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* 远程 sitemap 文件获取可参考 https://www.npmjs.com/package/sitemapper&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;sitemapXmlReader&lt;/span&gt; = (&lt;span class="params"&gt;file&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;const&lt;/span&gt; data = fs.&lt;span class="title function_"&gt;readFileSync&lt;/span&gt;(file, &lt;span class="string"&gt;'utf8'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;const&lt;/span&gt; sitemap = &lt;span class="title function_"&gt;xmlParser&lt;/span&gt;(data);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;let&lt;/span&gt; ret = [];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    sitemap.&lt;span class="property"&gt;root&lt;/span&gt;.&lt;span class="property"&gt;children&lt;/span&gt;.&lt;span class="title function_"&gt;forEach&lt;/span&gt;(&lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;url&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;const&lt;/span&gt; loc = url.&lt;span class="property"&gt;children&lt;/span&gt;.&lt;span class="title function_"&gt;find&lt;/span&gt;(&lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;item&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; item.&lt;span class="property"&gt;name&lt;/span&gt; === &lt;span class="string"&gt;'loc'&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (!loc) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;const&lt;/span&gt; title = url.&lt;span class="property"&gt;children&lt;/span&gt;.&lt;span class="title function_"&gt;find&lt;/span&gt;(&lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;item&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; item.&lt;span class="property"&gt;name&lt;/span&gt; === &lt;span class="string"&gt;'title'&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;const&lt;/span&gt; desc = url.&lt;span class="property"&gt;children&lt;/span&gt;.&lt;span class="title function_"&gt;find&lt;/span&gt;(&lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;item&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; item.&lt;span class="property"&gt;name&lt;/span&gt; === &lt;span class="string"&gt;'desc'&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;const&lt;/span&gt; date = url.&lt;span class="property"&gt;children&lt;/span&gt;.&lt;span class="title function_"&gt;find&lt;/span&gt;(&lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;item&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; item.&lt;span class="property"&gt;name&lt;/span&gt; === &lt;span class="string"&gt;'date'&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      ret.&lt;span class="title function_"&gt;push&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;url&lt;/span&gt;: loc.&lt;span class="property"&gt;content&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;title&lt;/span&gt;: title.&lt;span class="property"&gt;content&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;desc&lt;/span&gt;: desc.&lt;span class="property"&gt;content&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;date&lt;/span&gt;: date.&lt;span class="property"&gt;content&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; ret;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  } &lt;span class="keyword"&gt;catch&lt;/span&gt; (e) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; [];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 获取 gitalk 使用的 id&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;getGitalkId&lt;/span&gt; = (&lt;span class="params"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  url: u,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  date&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;}&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; link = url.&lt;span class="title function_"&gt;parse&lt;/span&gt;(u);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 链接不存在，不需要初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (!link || !link.&lt;span class="property"&gt;pathname&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (!date) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;md5&lt;/span&gt;(link.&lt;span class="property"&gt;pathname&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* 通过以请求判断是否已经初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* &lt;span class="doctag"&gt;@param&lt;/span&gt; {&lt;span class="type"&gt;string&lt;/span&gt;} gitalk 初始化的id&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* &lt;span class="doctag"&gt;@return&lt;/span&gt; {&lt;span class="type"&gt;[boolean, boolean]&lt;/span&gt;} 第一个值表示是否出错，第二个值 false 表示没初始化， true 表示已经初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;getIsInitByRequest&lt;/span&gt; = (&lt;span class="params"&gt;id&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; options = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;headers&lt;/span&gt;: {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Authorization'&lt;/span&gt;: &lt;span class="string"&gt;'token '&lt;/span&gt; + config.&lt;span class="property"&gt;token&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'User-Agent'&lt;/span&gt;: &lt;span class="string"&gt;'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Accept'&lt;/span&gt;: &lt;span class="string"&gt;'application/json'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    },&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;url&lt;/span&gt;: api + &lt;span class="string"&gt;'?labels='&lt;/span&gt; + id + &lt;span class="string"&gt;',Gitalk'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;'GET'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Promise&lt;/span&gt;(&lt;span class="function"&gt;(&lt;span class="params"&gt;resolve&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="title function_"&gt;request&lt;/span&gt;(options, &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;err, response, body&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (response.&lt;span class="property"&gt;statusCode&lt;/span&gt; != &lt;span class="number"&gt;200&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([response, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;const&lt;/span&gt; res = &lt;span class="title class_"&gt;JSON&lt;/span&gt;.&lt;span class="title function_"&gt;parse&lt;/span&gt;(body);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (res.&lt;span class="property"&gt;length&lt;/span&gt; &amp;gt; &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* 通过缓存判断是否已经初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* &lt;span class="doctag"&gt;@param&lt;/span&gt; {&lt;span class="type"&gt;string&lt;/span&gt;} gitalk 初始化的id&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* &lt;span class="doctag"&gt;@return&lt;/span&gt; {&lt;span class="type"&gt;boolean&lt;/span&gt;} false 表示没初始化， true 表示已经初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; getIsInitByCache = (&lt;span class="function"&gt;() =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 判断缓存文件是否存在&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;let&lt;/span&gt; gitalkCache = &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    gitalkCache = &lt;span class="built_in"&gt;require&lt;/span&gt;(config.&lt;span class="property"&gt;gitalkCacheFile&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  } &lt;span class="keyword"&gt;catch&lt;/span&gt; (e) {}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;id&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (!gitalkCache) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (gitalkCache.&lt;span class="title function_"&gt;find&lt;/span&gt;(&lt;span class="function"&gt;(&lt;span class="params"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;&lt;span class="function"&gt;        id: itemId&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;&lt;span class="function"&gt;      }&lt;/span&gt;) =&amp;gt;&lt;/span&gt; (itemId === id))) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;})();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 根据缓存，判断链接是否已经初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 第一个值表示是否出错，第二个值 false 表示没初始化， true 表示已经初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;idIsInit&lt;/span&gt; = &lt;span class="keyword"&gt;async&lt;/span&gt; (&lt;span class="params"&gt;id&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (!config.&lt;span class="property"&gt;cache&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;await&lt;/span&gt; &lt;span class="title function_"&gt;getIsInitByRequest&lt;/span&gt;(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 如果通过缓存查询到的数据是未初始化，则再通过请求判断是否已经初始化，防止多次初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="title function_"&gt;getIsInitByCache&lt;/span&gt;(id) === &lt;span class="literal"&gt;false&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;await&lt;/span&gt; &lt;span class="title function_"&gt;getIsInitByRequest&lt;/span&gt;(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; [&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 初始化&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;gitalkInit&lt;/span&gt; = (&lt;span class="params"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  url,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  id,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  title,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  desc&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;}&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;//创建issue&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; reqBody = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="string"&gt;'title'&lt;/span&gt;: title,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="string"&gt;'labels'&lt;/span&gt;: [id, &lt;span class="string"&gt;'Gitalk'&lt;/span&gt;],&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="string"&gt;'body'&lt;/span&gt;: url + &lt;span class="string"&gt;'\r\n\r\n'&lt;/span&gt; + desc&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; options = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;headers&lt;/span&gt;: {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Authorization'&lt;/span&gt;: &lt;span class="string"&gt;'token '&lt;/span&gt; + config.&lt;span class="property"&gt;token&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'User-Agent'&lt;/span&gt;: &lt;span class="string"&gt;'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Accept'&lt;/span&gt;: &lt;span class="string"&gt;'application/json'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="string"&gt;'application/json;charset=UTF-8'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    },&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;url&lt;/span&gt;: api,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;body&lt;/span&gt;: &lt;span class="title class_"&gt;JSON&lt;/span&gt;.&lt;span class="title function_"&gt;stringify&lt;/span&gt;(reqBody),&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;'POST'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Promise&lt;/span&gt;(&lt;span class="function"&gt;(&lt;span class="params"&gt;resolve&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="title function_"&gt;request&lt;/span&gt;(options, &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;err, response, body&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (response.&lt;span class="property"&gt;statusCode&lt;/span&gt; != &lt;span class="number"&gt;201&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([response, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* 写入内容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* &lt;span class="doctag"&gt;@param&lt;/span&gt; {&lt;span class="type"&gt;string&lt;/span&gt;} fileName 文件名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;* &lt;span class="doctag"&gt;@param&lt;/span&gt; {&lt;span class="type"&gt;string&lt;/span&gt;} content 内容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;write&lt;/span&gt; = &lt;span class="keyword"&gt;async&lt;/span&gt; (&lt;span class="params"&gt;fileName, content, flag = &lt;span class="string"&gt;'w+'&lt;/span&gt;&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Promise&lt;/span&gt;(&lt;span class="function"&gt;(&lt;span class="params"&gt;resolve&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    fs.&lt;span class="title function_"&gt;open&lt;/span&gt;(fileName, flag, &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;err, fd&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      fs.&lt;span class="title function_"&gt;writeFile&lt;/span&gt;(fd, content, &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;err&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        fs.&lt;span class="title function_"&gt;close&lt;/span&gt;(fd, &lt;span class="function"&gt;(&lt;span class="params"&gt;err&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;init&lt;/span&gt; = &lt;span class="keyword"&gt;async&lt;/span&gt; (&lt;span class="params"&gt;&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; urls = &lt;span class="title function_"&gt;sitemapXmlReader&lt;/span&gt;(config.&lt;span class="property"&gt;sitemap&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 报错的数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; errorData = [];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 已经初始化的数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; initializedData = [];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 成功初始化数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; successData = [];&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="keyword"&gt;const&lt;/span&gt; item &lt;span class="keyword"&gt;of&lt;/span&gt; urls) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;const&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      url,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      date,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      title,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      desc&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    } = item;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;const&lt;/span&gt; id = &lt;span class="title function_"&gt;getGitalkId&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      url,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      date&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (!id) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`id: 生成失败 [ &lt;span class="subst"&gt;${id}&lt;/span&gt; ] `&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      errorData.&lt;span class="title function_"&gt;push&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        ...item,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;info&lt;/span&gt;: &lt;span class="string"&gt;'id 生成失败'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;const&lt;/span&gt; [err, res] = &lt;span class="keyword"&gt;await&lt;/span&gt; &lt;span class="title function_"&gt;idIsInit&lt;/span&gt;(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`Error: 查询评论异常 [ &lt;span class="subst"&gt;${title}&lt;/span&gt; ] , 信息：`&lt;/span&gt;, err || &lt;span class="string"&gt;'无'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      errorData.&lt;span class="title function_"&gt;push&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        ...item,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;info&lt;/span&gt;: &lt;span class="string"&gt;'查询评论异常'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (res === &lt;span class="literal"&gt;true&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="comment"&gt;// console.log(`--- Gitalk 已经初始化 --- [ ${title} ] `);&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      initializedData.&lt;span class="title function_"&gt;push&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        id,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        url,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        title,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`Gitalk 初始化开始... [ &lt;span class="subst"&gt;${title}&lt;/span&gt; ] `&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;const&lt;/span&gt; [e, r] = &lt;span class="keyword"&gt;await&lt;/span&gt; &lt;span class="title function_"&gt;gitalkInit&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      id,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      url,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      title,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      desc&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (e || !r) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`Error: Gitalk 初始化异常 [ &lt;span class="subst"&gt;${title}&lt;/span&gt; ] , 信息：`&lt;/span&gt;, e || &lt;span class="string"&gt;'无'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      errorData.&lt;span class="title function_"&gt;push&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        ...item,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="attr"&gt;info&lt;/span&gt;: &lt;span class="string"&gt;'初始化异常'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="keyword"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    successData.&lt;span class="title function_"&gt;push&lt;/span&gt;({&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      id,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      url,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      title,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`Gitalk 初始化成功! [ &lt;span class="subst"&gt;${title}&lt;/span&gt; ] - &lt;span class="subst"&gt;${id}&lt;/span&gt;`&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;''&lt;/span&gt;); &lt;span class="comment"&gt;// 空输出，用于换行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;'--------- 运行结果 ---------'&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;''&lt;/span&gt;); &lt;span class="comment"&gt;// 空输出，用于换行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (errorData.&lt;span class="property"&gt;length&lt;/span&gt; !== &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`报错数据： &lt;span class="subst"&gt;${errorData.length}&lt;/span&gt; 条。参考文件 &lt;span class="subst"&gt;${config.gitalkErrorFile}&lt;/span&gt;。`&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;await&lt;/span&gt; &lt;span class="title function_"&gt;write&lt;/span&gt;(config.&lt;span class="property"&gt;gitalkErrorFile&lt;/span&gt;, &lt;span class="title class_"&gt;JSON&lt;/span&gt;.&lt;span class="title function_"&gt;stringify&lt;/span&gt;(errorData, &lt;span class="literal"&gt;null&lt;/span&gt;, &lt;span class="number"&gt;2&lt;/span&gt;));&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`本次成功： &lt;span class="subst"&gt;${successData.length}&lt;/span&gt; 条。`&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 写入缓存&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (config.&lt;span class="property"&gt;cache&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`写入缓存： &lt;span class="subst"&gt;${(initializedData.length + successData.length)}&lt;/span&gt; 条，已初始化 &lt;span class="subst"&gt;${initializedData.length}&lt;/span&gt; 条，本次成功： &lt;span class="subst"&gt;${successData.length}&lt;/span&gt; 条。参考文件 &lt;span class="subst"&gt;${config.gitalkCacheFile}&lt;/span&gt;。`&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;await&lt;/span&gt; &lt;span class="title function_"&gt;write&lt;/span&gt;(config.&lt;span class="property"&gt;gitalkCacheFile&lt;/span&gt;, &lt;span class="title class_"&gt;JSON&lt;/span&gt;.&lt;span class="title function_"&gt;stringify&lt;/span&gt;(initializedData.&lt;span class="title function_"&gt;concat&lt;/span&gt;(successData), &lt;span class="literal"&gt;null&lt;/span&gt;, &lt;span class="number"&gt;2&lt;/span&gt;));&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  } &lt;span class="keyword"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="variable language_"&gt;console&lt;/span&gt;.&lt;span class="title function_"&gt;log&lt;/span&gt;(&lt;span class="string"&gt;`已初始化： &lt;span class="subst"&gt;${initializedData.length}&lt;/span&gt; 条。`&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="title function_"&gt;init&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;随后在 &lt;code&gt;package.json&lt;/code&gt; 新增：&lt;/p&gt;
&lt;figure class="highlight json"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;"scripts"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="punctuation"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"build"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"hexo generate &amp;amp;&amp;amp; node source/_scripts/gitalk-auto-init.js"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"clean"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"hexo clean"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"deploy"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"hexo deploy"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"server"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"hexo server"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"talk"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"node source/_scripts/gitalk-auto-init.js"&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;}&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h1 id="本地运行测试"&gt;&lt;a class="headerlink" href="#本地运行测试" title="本地运行测试"&gt;&lt;/a&gt;本地运行测试&lt;/h1&gt;&lt;p&gt;执行 &lt;code&gt;hexo clean; npm run build; hexo s&lt;/code&gt; 进行本地部署。这里使用 &lt;code&gt;npm run build&lt;/code&gt; 的原因是上面在 &lt;code&gt;package.json&lt;/code&gt; 中进行了定义，使它兼具了 &lt;code&gt;hexo g&lt;/code&gt; 与 Gitalk 批量脚本的功能。也可以不使用 &lt;code&gt;npm run build&lt;/code&gt; 而单独执行这两条命令。&lt;/p&gt;
&lt;p&gt;日志中应该出现如下内容：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;Gitalk 初始化开始... [ xxx | xxx ]&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;Gitalk 初始化成功! [ xxx | xxx ] - xxxxxxxxx&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这说明 Issue 评论区正在建立中。你可以去实际的评论区 GitHub 仓库里查看 Issues 是否存在来验证是否成功。&lt;/p&gt;
&lt;h1 id="不为某些文章生成评论区"&gt;&lt;a class="headerlink" href="#不为某些文章生成评论区" title="不为某些文章生成评论区"&gt;&lt;/a&gt;不为某些文章生成评论区&lt;/h1&gt;&lt;p&gt;在本地测试中发现，无论文章是否具有 &lt;code&gt;comments: false&lt;/code&gt;，都生成了对应的评论区 Issue（虽然评论区依旧是关闭的，但是 Issue 存在）。如果不想看到此类行为，则需要做一些修改。&lt;/p&gt;
&lt;p&gt;首先修改 &lt;code&gt;sitemap-template.xml&lt;/code&gt;，加入如下内容：&lt;/p&gt;
&lt;figure class="highlight xml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;&amp;lt;?xml version=&lt;span class="string"&gt;"1.0"&lt;/span&gt; encoding=&lt;span class="string"&gt;"UTF-8"&lt;/span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;urlset&lt;/span&gt; &lt;span class="attr"&gt;xmlns&lt;/span&gt;=&lt;span class="string"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  {% for post in posts %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;+ {% if post.comments %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;url&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;loc&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.permalink | uriencode }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;loc&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {% if post.updated %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.updated.toISOString() }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {% elif post.date %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.date.toISOString() }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;lastmod&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {% endif %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;date&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.date }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;date&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.title + ' | ' + config.title }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    {# nunjucks 模版语法 https://github.com/mozilla/nunjucks #}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;desc&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ post.description | default(post.excerpt) | default(post.content) | default(config.description) | striptags | truncate(200, true, '') }}&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;desc&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;url&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;+ {% endif %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  {% endfor %}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;urlset&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这是为了在 &lt;code&gt;public/sitemap.xml&lt;/code&gt; 生成时检测评论区是否开启，如果不开启，则直接不生成对应内容。&lt;/p&gt;
&lt;p&gt;随后重新部署验证。可能需要清理一下已经存在的 Issues。&lt;/p&gt;
&lt;h1 id="远程部署"&gt;&lt;a class="headerlink" href="#远程部署" title="远程部署"&gt;&lt;/a&gt;远程部署&lt;/h1&gt;&lt;p&gt;最后，修改工作流的如下内容（首先需要配置博客的自动部署，详情可以参考 &lt;a href="/20211125-hexo-blog-guide/" title="Hexo静态博客指南：本站是如何诞生的（2021年版）"&gt;Hexo 静态博客指南：本站是如何诞生的（2021 年版）&lt;/a&gt;）：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;    # Deploy&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    - name: Deploy hexo&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      run: |&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        hexo clean&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;-       hexo generate&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;+       npm run build&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        hexo deploy&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;随后提交代码出发自动部署即可。&lt;/p&gt;
&lt;h1 id="Secondary-rate-limits-受限问题"&gt;&lt;a class="headerlink" href="#Secondary-rate-limits-受限问题" title="Secondary rate limits 受限问题"&gt;&lt;/a&gt;Secondary rate limits 受限问题&lt;/h1&gt;&lt;p&gt;虽然 Personal Access Token 有每日 5000 次的限制，足够使用，但仍然可能出发 GitHub 的 Secondary rate limits：&lt;/p&gt;
&lt;p&gt;这会让远程部署报错。你可以尝试更改请求间隔来规避这个问题。示例更改代码如下：&lt;/p&gt;
&lt;figure class="highlight js"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;53&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;54&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;55&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;56&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;57&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;58&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;59&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;60&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;61&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;62&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;63&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;64&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;65&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;66&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;67&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;68&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;69&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;70&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;71&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;72&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;73&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 上面省略&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;getIsInitByRequest&lt;/span&gt; = (&lt;span class="params"&gt;id&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; options = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;headers&lt;/span&gt;: {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Authorization'&lt;/span&gt;: &lt;span class="string"&gt;'token '&lt;/span&gt; + config.&lt;span class="property"&gt;token&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'User-Agent'&lt;/span&gt;: &lt;span class="string"&gt;'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Accept'&lt;/span&gt;: &lt;span class="string"&gt;'application/json'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    },&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;url&lt;/span&gt;: api + &lt;span class="string"&gt;'?labels='&lt;/span&gt; + id + &lt;span class="string"&gt;',Gitalk'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;'GET'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Promise&lt;/span&gt;(&lt;span class="function"&gt;(&lt;span class="params"&gt;resolve&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;setTimeout&lt;/span&gt;(&lt;span class="function"&gt;() =&amp;gt;&lt;/span&gt; { &lt;span class="comment"&gt;// 增加等待时间3000ms&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="title function_"&gt;request&lt;/span&gt;(options, &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;err, response, body&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (response.&lt;span class="property"&gt;statusCode&lt;/span&gt; != &lt;span class="number"&gt;200&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([response, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;const&lt;/span&gt; res = &lt;span class="title class_"&gt;JSON&lt;/span&gt;.&lt;span class="title function_"&gt;parse&lt;/span&gt;(body);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (res.&lt;span class="property"&gt;length&lt;/span&gt; &amp;gt; &lt;span class="number"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }, &lt;span class="number"&gt;3000&lt;/span&gt;); &lt;span class="comment"&gt;// 3000ms&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 中间省略&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;const&lt;/span&gt; &lt;span class="title function_"&gt;gitalkInit&lt;/span&gt; = (&lt;span class="params"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  url,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  id,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  title,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;  desc&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="params"&gt;}&lt;/span&gt;) =&amp;gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="comment"&gt;// 创建issue&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; reqBody = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="string"&gt;'title'&lt;/span&gt;: title,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="string"&gt;'labels'&lt;/span&gt;: [id, &lt;span class="string"&gt;'Gitalk'&lt;/span&gt;],&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="string"&gt;'body'&lt;/span&gt;: url + &lt;span class="string"&gt;'\r\n\r\n'&lt;/span&gt; + desc&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;const&lt;/span&gt; options = {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;headers&lt;/span&gt;: {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Authorization'&lt;/span&gt;: &lt;span class="string"&gt;'token '&lt;/span&gt; + config.&lt;span class="property"&gt;token&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'User-Agent'&lt;/span&gt;: &lt;span class="string"&gt;'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Accept'&lt;/span&gt;: &lt;span class="string"&gt;'application/json'&lt;/span&gt;,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="string"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="string"&gt;'application/json;charset=UTF-8'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    },&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;url&lt;/span&gt;: api,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;body&lt;/span&gt;: &lt;span class="title class_"&gt;JSON&lt;/span&gt;.&lt;span class="title function_"&gt;stringify&lt;/span&gt;(reqBody),&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;'POST'&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  };&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Promise&lt;/span&gt;(&lt;span class="function"&gt;(&lt;span class="params"&gt;resolve&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="built_in"&gt;setTimeout&lt;/span&gt;(&lt;span class="function"&gt;() =&amp;gt;&lt;/span&gt; { &lt;span class="comment"&gt;// 增加等待时间3000ms&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      &lt;span class="title function_"&gt;request&lt;/span&gt;(options, &lt;span class="keyword"&gt;function&lt;/span&gt; (&lt;span class="params"&gt;err, response, body&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (err) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([err, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (response.&lt;span class="property"&gt;statusCode&lt;/span&gt; != &lt;span class="number"&gt;201&lt;/span&gt;) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;          &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([response, &lt;span class="literal"&gt;false&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="title function_"&gt;resolve&lt;/span&gt;([&lt;span class="literal"&gt;false&lt;/span&gt;, &lt;span class="literal"&gt;true&lt;/span&gt;]);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;      });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }, &lt;span class="number"&gt;3000&lt;/span&gt;); &lt;span class="comment"&gt;// 3000ms&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  });&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;};&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;// 下面省略&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;之后进行重新部署测试。这可能会拉长部署时间，但只要生成了本地缓存文件就可以加速。&lt;/p&gt;
&lt;h1 id="关于权限与安全问题的讨论"&gt;&lt;a class="headerlink" href="#关于权限与安全问题的讨论" title="关于权限与安全问题的讨论"&gt;&lt;/a&gt;关于权限与安全问题的讨论&lt;/h1&gt;&lt;p&gt;对 Gitalk 这类基于 GitHub Issues 的评论系统，权限与安全是绕不开的话题。该系统面临的主要问题如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对作者：&lt;code&gt;client_id&lt;/code&gt; 与 &lt;code&gt;client_secret&lt;/code&gt; 直接暴露在前端，可能会有安全隐患；&lt;/li&gt;
&lt;li&gt;对评论者：首次使用时需要授权 GitHub 账户，而该授权竟然要求所有公开仓库的读写权限，权限过于夸张，如果有恶意网站，甚至可以删光所有公共仓库。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于第一点，&lt;a href="https://carl-zk.github.io/blog/2020/03/03/gitalk-%E8%BF%90%E4%BD%9C%E5%8E%9F%E7%90%86/"&gt;这里&lt;/a&gt;有详细讨论。也有 &lt;a href="https://github.com/gitalk/gitalk/issues/150"&gt;Issue&lt;/a&gt; 针对这个问题做了说明，简要概括如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;虽然 Gitalk 使用了第三方代理服务器来获取 &lt;code&gt;access_token&lt;/code&gt; 属于违反 OAuth2.0 规范，但是作为一个评论系统来说安全性可以不必担心，因为 GitHub 已经对 &lt;code&gt;access_token&lt;/code&gt; 做了只读限制；&lt;/li&gt;
&lt;li&gt;获取或修改 GitHub 用户数据，需要 &lt;code&gt;token&lt;/code&gt;，为了拿到 &lt;code&gt;token&lt;/code&gt;，除了需要 OAuth App 的 &lt;code&gt;client_id&lt;/code&gt; 和 &lt;code&gt;client_secret&lt;/code&gt; 外，还需要一个 Authorization Code；&lt;/li&gt;
&lt;li&gt;这个 &lt;code&gt;code&lt;/code&gt; 是 GitHub 登录授权完成时，在跳转回 &lt;code&gt;redirect_uri&lt;/code&gt; 的查询参数拿到的， &lt;code&gt;redirect_uri&lt;/code&gt; 必须是在 OAuth App 配置的 callback URL 域名下；&lt;/li&gt;
&lt;li&gt;这样即使别人用了你的 &lt;code&gt;client_id&lt;/code&gt; 和 &lt;code&gt;client_secret&lt;/code&gt; ，跳转之后也拿不到 code，所以，有 &lt;code&gt;client_id&lt;/code&gt; 和 &lt;code&gt;client_secret&lt;/code&gt; 也做不了什么。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;虽然如此，而且作为静态博客引擎，这样做是没有办法的办法，但是暴露 &lt;code&gt;client_id&lt;/code&gt; 与 &lt;code&gt;client_secret&lt;/code&gt; 终究是不太推荐的做法。&lt;/p&gt;
&lt;p&gt;对于第二点，讨论更为广泛，&lt;a href="https://github.com/gitalk/gitalk/issues/95"&gt;Issue&lt;/a&gt; 与&lt;a href="https://v2ex.com/t/535608"&gt;帖子&lt;/a&gt;都有涉及。为什么需要这么高的权限，简而言之就是 GitHub OAuth 权限设计目前只能做到这样的粒度。Gitalk 源码中，开发者没有保存 &lt;code&gt;access_token&lt;/code&gt;，只是将它保存到了 localStorage 中。但是可能会存在恶意的攻击者来扩展这一点。这对于评论者而言是一种伤害，如此之大的权限可能会降低评论者评论的意愿，毕竟网站有无恶意是很难判断的。目前的解决方案有两种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;点击评论区的统计数字，可以跳转到相应的 GitHub Issue 页面，在页面评论可以绕过验证；&lt;/li&gt;
&lt;li&gt;采用更安全的评论系统，例如 &lt;a href="https://utteranc.es/"&gt;utterances&lt;/a&gt; 这类 App，它的权限仅限于某个仓库，可以一定程度上降低破坏性。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那么本博客该如何做？本着精益求精的原则，估计是又要迁移到 utterances 上了…… 刚刚迁移评论系统还没多久，就发现了这个问题。生命在于折腾，utterances 迁移会后续写文章处理。&lt;/p&gt;
</content>
    <summary type="html">我的博客优化历程—自动初始化Gitalk ...</summary>
    <category term="Bufferfly博客" scheme="https://aspirant02.github.io/categories/Bufferfly%E5%8D%9A%E5%AE%A2/" />
    <category term="Bufferfly博客" scheme="https://aspirant02.github.io/tags/Bufferfly%E5%8D%9A%E5%AE%A2/" />
  </entry>
  <entry>
    <title>AI开发-Agent：从聊天到执行任务</title>
    <link href="https://aspirant02.github.io/2026/07/06/AI%E5%BC%80%E5%8F%91-Agent%EF%BC%9A%E4%BB%8E%E8%81%8A%E5%A4%A9%E5%88%B0%E6%89%A7%E8%A1%8C%E4%BB%BB%E5%8A%A1/" />
    <id>https://aspirant02.github.io/2026/07/06/AI%E5%BC%80%E5%8F%91-Agent%EF%BC%9A%E4%BB%8E%E8%81%8A%E5%A4%A9%E5%88%B0%E6%89%A7%E8%A1%8C%E4%BB%BB%E5%8A%A1/</id>
    <published>2026-07-06T13:01:24Z</published>
    <updated>2026-07-06T13:01:24Z</updated>
    <content type="html">&lt;h1 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h1&gt;
&lt;p&gt;Agent 这个词很容易被讲玄。我的理解很简单：普通聊天模型主要是在回答问题，而 Agent 需要围绕目标做计划、调用工具、观察结果，再继续下一步。它不是只说“应该怎么做”，而是尽量真的把事情往前推进。&lt;/p&gt;
&lt;p&gt;例如“帮我分析这个仓库的启动方式并修一个报错”，一个 Agent 可能会先列目录、读 README、运行测试、定位错误、改代码、再次验证。这里的关键不是模型说得多聪明，而是它能在环境里行动。&lt;/p&gt;
&lt;h1 id="Agent 的基本循环"&gt;&lt;a class="headerlink" href="#Agent%20%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%BE%AA%E7%8E%AF" title="Agent 的基本循环"&gt;&lt;/a&gt;Agent 的基本循环&lt;/h1&gt;
&lt;p&gt;一个常见循环是 Plan、Act、Observe、Reflect。先根据目标制定下一步，然后调用工具执行，接着读取工具返回的结果，再决定是否继续。ReAct 论文里提出把 reasoning traces 和 actions 交织起来，让模型边推理边行动，这个思路后来影响了很多 Agent 框架。&lt;/p&gt;
&lt;p&gt;这个循环看着简单，工程上却很考验边界。工具调用失败怎么办？执行结果太长怎么办？模型连续走偏怎么办？这些都需要框架层处理，而不能只靠一句“你要认真思考”。&lt;/p&gt;
&lt;h1 id="工具设计比模型更重要"&gt;&lt;a class="headerlink" href="#%E5%B7%A5%E5%85%B7%E8%AE%BE%E8%AE%A1%E6%AF%94%E6%A8%A1%E5%9E%8B%E6%9B%B4%E9%87%8D%E8%A6%81" title="工具设计比模型更重要"&gt;&lt;/a&gt;工具设计比模型更重要&lt;/h1&gt;
&lt;p&gt;Agent 的能力很大程度取决于工具。工具命名要清楚，入参要结构化，输出要可读，权限要收紧。比如 delete_file 这种工具一定要谨慎，最好先做 dry-run 或要求二次确认。&lt;/p&gt;
&lt;p&gt;我更倾向先给 Agent 提供只读工具和低风险操作：搜索代码、读取文件、运行测试、查询日志。等观察链路稳定后，再逐步开放写入和部署动作。&lt;/p&gt;
&lt;h1 id="如何判断一个 Agent 靠谱"&gt;&lt;a class="headerlink" href="#%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%20Agent%20%E9%9D%A0%E8%B0%B1" title="如何判断一个 Agent 靠谱"&gt;&lt;/a&gt;如何判断一个 Agent 靠谱&lt;/h1&gt;
&lt;p&gt;不能只看它回答像不像人，而要看它能不能稳定完成任务。可以准备一组固定任务：查资料、改一个小 bug、生成一份报告、根据日志定位异常。每个任务都要有明确的成功标准。&lt;/p&gt;
&lt;p&gt;另外要保留轨迹日志。Agent 每一步为什么调用这个工具、拿到了什么观察结果、基于什么继续下一步，都应该能追溯。没有轨迹的 Agent 很难调试，也很难让用户信任。&lt;/p&gt;
&lt;!-- ai-extra-expanded-2026 --&gt;
&lt;h1 id="任务拆解要可执行"&gt;&lt;a class="headerlink" href="#%E4%BB%BB%E5%8A%A1%E6%8B%86%E8%A7%A3%E8%A6%81%E5%8F%AF%E6%89%A7%E8%A1%8C" title="任务拆解要可执行"&gt;&lt;/a&gt;任务拆解要可执行&lt;/h1&gt;
&lt;p&gt;好的计划不是一串空泛动词，比如“分析项目、优化代码、完善文档”。这些话人看着舒服，但工具无法执行。更好的拆解应该接近操作步骤：读取 README、搜索 TODO、运行测试、修改某个文件、再次运行测试、生成总结。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;目标：修复登录接口 500

可执行计划：
1. 搜索最近的错误日志，定位异常堆栈
2. 根据堆栈打开对应文件
3. 查找相关测试或复现入口
4. 修改最小代码
5. 运行相关测试
6. 汇总变更和风险

不可执行计划：
- 深入分析登录模块
- 优化异常处理
- 提升系统稳定性&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也是为什么很多 Agent 框架会强调 task decomposition。不是为了让模型显得更会思考，而是为了把复杂目标拆成可执行、可观察、可验证的小块。&lt;/p&gt;
&lt;h1 id="一个最小 Agent 循环"&gt;&lt;a class="headerlink" href="#%E4%B8%80%E4%B8%AA%E6%9C%80%E5%B0%8F%20Agent%20%E5%BE%AA%E7%8E%AF" title="一个最小 Agent 循环"&gt;&lt;/a&gt;一个最小 Agent 循环&lt;/h1&gt;
&lt;p&gt;下面是一个最小化伪代码。它没有复杂框架，只展示 Agent 的骨架：模型决定 action，系统执行工具，观察结果再喂回模型。&lt;/p&gt;
&lt;pre&gt;&lt;code class="py"&gt;MAX_STEPS = 8

def run_agent(goal):
    messages = [
        {"role": "system", "content": "你是一个谨慎的任务执行助手。"},
        {"role": "user", "content": goal},
    ]

    for step in range(MAX_STEPS):
        decision = llm.plan_next_action(messages, tools=TOOLS)
        if decision.type == "finish":
            return decision.final_answer

        if decision.tool_name not in TOOLS:
            messages.append({"role": "tool", "content": "unknown tool"})
            continue

        result = TOOLS[decision.tool_name](**decision.arguments)
        messages.append({
            "role": "tool",
            "name": decision.tool_name,
            "content": result.to_json()
        })

    return "已达到最大执行步数，需要人工确认下一步。"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真实系统还要有权限检查、工具超时、重试策略、成本控制、人工确认和审计日志。但核心循环就是这样：计划、行动、观察，然后继续。理解这个骨架，比一开始研究一堆框架名更重要。&lt;/p&gt;
&lt;h1 id="记忆和状态不要混在一起"&gt;&lt;a class="headerlink" href="#%E8%AE%B0%E5%BF%86%E5%92%8C%E7%8A%B6%E6%80%81%E4%B8%8D%E8%A6%81%E6%B7%B7%E5%9C%A8%E4%B8%80%E8%B5%B7" title="记忆和状态不要混在一起"&gt;&lt;/a&gt;记忆和状态不要混在一起&lt;/h1&gt;
&lt;p&gt;Agent 系统里经常提 memory，但我觉得要先区分两件事：任务状态和长期记忆。任务状态是这次任务已经做了什么、工具返回了什么、下一步要做什么；长期记忆是跨任务复用的偏好、经验和知识。&lt;/p&gt;
&lt;pre&gt;&lt;code class="json"&gt;{
  "task_id": "fix-login-500",
  "steps": [
    {
      "tool": "search_logs",
      "args": { "keyword": "NullPointerException" },
      "result": { "matches": 3 },
      "status": "ok"
    }
  ],
  "constraints": ["不改数据库结构", "只修复登录接口"],
  "done": false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了这种状态记录，Agent 即使中断也能恢复；任务失败时，也能复盘是第几步出了问题。对于执行型系统来说，可回放性非常关键。&lt;/p&gt;
&lt;h1 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h1&gt;
&lt;p&gt;Agent 是把模型能力和工具执行结合起来的工程系统。它的难点不只是 prompt，而是工具边界、任务拆解、记忆管理、失败恢复和验证闭环。独立开发者做 Agent，最好从一个窄场景开始，比如“自动整理 issue”或“代码仓库问答+小修复”，不要一上来就做全能助手。&lt;/p&gt;
&lt;p&gt;参考资料：&lt;a href="https://arxiv.org/abs/2210.03629" rel="noopener" target="_blank"&gt;ReAct: Synergizing Reasoning and Acting in Language Models&lt;/a&gt;。&lt;/p&gt;</content>
    <summary type="html">Agent 不只是会聊天的模型，而是能围绕目标做计划、调用工具、观察结果并持续推进任务的工程系统。 ...</summary>
    <category term="AI开发" scheme="https://aspirant02.github.io/categories/AI%E5%BC%80%E5%8F%91/" />
    <category term="AI开发" scheme="https://aspirant02.github.io/tags/AI%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—7. RabbitMQ回调、退单补偿与部署复盘</title>
    <link href="https://aspirant02.github.io/2026/06/30/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%947.%20RabbitMQ%E5%9B%9E%E8%B0%83%E3%80%81%E9%80%80%E5%8D%95%E8%A1%A5%E5%81%BF%E4%B8%8E%E9%83%A8%E7%BD%B2%E5%A4%8D%E7%9B%98/" />
    <id>https://aspirant02.github.io/2026/06/30/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%947.%20RabbitMQ%E5%9B%9E%E8%B0%83%E3%80%81%E9%80%80%E5%8D%95%E8%A1%A5%E5%81%BF%E4%B8%8E%E9%83%A8%E7%BD%B2%E5%A4%8D%E7%9B%98/</id>
    <published>2026-06-30T13:01:24Z</published>
    <updated>2026-06-30T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;最后一篇写收口：支付之后拼团成功怎么回调交易域，用户退单怎么反向通知营销域，部署时 MySQL、Redis、RabbitMQ、应用之间怎么组织。相比前面几篇，这一篇更像项目上线前的检查单。&lt;/p&gt;
&lt;p&gt;简历里写到 RabbitMQ 实现异步事件处理、设计 HTTP + MQ 双通道回调与消息持久化重试机制，这不是为了堆技术，而是因为支付和拼团天然跨系统。跨系统就要接受一个事实：调用可能失败，消息可能重复，状态一定要能幂等推进。&lt;/p&gt;
&lt;h2 id="HTTP与MQ双通道"&gt;&lt;a class="headerlink" href="#HTTP%E4%B8%8EMQ%E5%8F%8C%E9%80%9A%E9%81%93" title="HTTP与MQ双通道"&gt;&lt;/a&gt;HTTP与MQ双通道&lt;/h2&gt;
&lt;p&gt;营销域组队成功后，可以通过 HTTP 回调交易域，也可以发 MQ。HTTP 简单直接，适合低延迟；MQ 解耦更好，适合削峰和失败重试。项目里交易域同时保留 group_buy_notify 接口和 TeamSuccessTopicListener，便于根据配置切换通道。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;@RequestMapping(value = "group_buy_notify", method = RequestMethod.POST)
public String groupBuyNotify(@RequestBody NotifyRequestDTO requestDTO) {
    try {
        orderService.changeOrderMarketSettlement(requestDTO.getOutTradeNoList());
        return "success";
    } catch (Exception e) {
        return "error";
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP 回调一定要返回明确结果，营销域才能判断是否重试。交易域处理时要按 orderId 列表幂等更新，不能因为重复回调就把状态推进两次。&lt;/p&gt;
&lt;h2 id="组队成功监听"&gt;&lt;a class="headerlink" href="#%E7%BB%84%E9%98%9F%E6%88%90%E5%8A%9F%E7%9B%91%E5%90%AC" title="组队成功监听"&gt;&lt;/a&gt;组队成功监听&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;@RabbitListener(
    bindings = @QueueBinding(
        value = @Queue(value = "${spring.rabbitmq.config.consumer.topic_team_success.queue}"),
        exchange = @Exchange(value = "${spring.rabbitmq.config.consumer.topic_team_success.exchange}", type = ExchangeTypes.TOPIC),
        key = "${spring.rabbitmq.config.consumer.topic_team_success.routing_key}"
    )
)
public void listener(String message) {
    try {
        NotifyRequestDTO requestDTO = JSON.parseObject(message, NotifyRequestDTO.class);
        orderService.changeOrderMarketSettlement(requestDTO.getOutTradeNoList());
    } catch (Exception e) {
        throw e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;监听器里捕获异常后重新抛出，是为了让 RabbitMQ 的重试或死信机制接管，而不是把失败消息吞掉。消息消费端最怕“日志里报错但消息已经 ack”，那样状态就只能人工补。&lt;/p&gt;
&lt;h2 id="退单链路"&gt;&lt;a class="headerlink" href="#%E9%80%80%E5%8D%95%E9%93%BE%E8%B7%AF" title="退单链路"&gt;&lt;/a&gt;退单链路&lt;/h2&gt;
&lt;p&gt;退单分两层：交易侧先校验订单是否存在、是否属于当前用户、是否已经关闭；如果是营销订单，先通知营销域退拼团；然后根据订单状态决定是否需要支付宝退款。CREATE 和 PAY_WAIT 状态还没真正支付，可以直接关闭；PAY_SUCCESS 或 DEAL_DONE 才需要走退款。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;public boolean refundMarketOrder(String userId, String orderId) {
    OrderEntity orderEntity = repository.queryOrderByUserIdAndOrderId(userId, orderId);
    if (null == orderEntity) return false;

    String status = orderEntity.getOrderStatusVO().getCode();
    if (OrderStatusVO.CLOSE.getCode().equals(status)) {
        return false;
    }

    port.refundMarketPayOrder(userId, orderId);

    if (OrderStatusVO.CREATE.getCode().equals(status)
            || OrderStatusVO.PAY_WAIT.getCode().equals(status)) {
        return repository.refundOrder(userId, orderId);
    }
    return repository.refundMarketOrder(userId, orderId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段逻辑的重点是先校验所有权和状态，再调用外部域。退款类接口必须天然幂等：同一 orderId 重复请求，最多只会把订单保持在已退单状态，而不能重复释放库存或重复退款。&lt;/p&gt;
&lt;h2 id="部署与环境"&gt;&lt;a class="headerlink" href="#%E9%83%A8%E7%BD%B2%E4%B8%8E%E7%8E%AF%E5%A2%83" title="部署与环境"&gt;&lt;/a&gt;部署与环境&lt;/h2&gt;
&lt;p&gt;本地开发时我把 MySQL、Redis、RabbitMQ 放到 docker-compose 里，应用分 dev、test、prod 配置。营销域端口 8091，交易域单独启动，通过 Retrofit2 调用营销 API。部署脚本里要先拉起基础设施，再启动应用，最后检查健康接口和日志。&lt;/p&gt;
&lt;pre&gt;&lt;code class="yaml"&gt;services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: group_buy_market

  redis:
    image: redis:7
    ports:
      - "16379:6379"

  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "5672:5672"
      - "15672:15672"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真正上线前还要把日志级别、连接池、线程池、Redis 连接池和 MQ 重试策略检查一遍。下午茶拼单这种活动流量会集中在午后和傍晚，峰值不一定大到离谱，但会很尖。&lt;/p&gt;
&lt;h2 id="复盘"&gt;&lt;a class="headerlink" href="#%E5%A4%8D%E7%9B%98" title="复盘"&gt;&lt;/a&gt;复盘&lt;/h2&gt;
&lt;p&gt;这 8 篇文章把项目从架构、规则树、优惠策略、并发加载、Redis 标签、DCC、交易锁单、MQ 回调串了一遍。回到简历，它不再只是“Spring Boot + MySQL + Redis + RabbitMQ”，而是一套能解释业务复杂度的后端项目。&lt;/p&gt;
&lt;p&gt;如果后续继续完善，我会优先补三块：第一，营销域锁单和结算的完整幂等表；第二，DCC 配置后台和变更审计；第三，基于压测数据调整线程池、连接池和 MQ 消费并发。项目经历真正有说服力的地方，往往就在这些复盘和取舍里。&lt;/p&gt;</content>
    <summary type="html">前言 最后一篇写收口：支付之后拼团成功怎么回调交易域，用户退单怎么反向通知营销域，部署时 MySQL、Redis、RabbitMQ、应用之间怎么组织。相比前面几 ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—6. 交易锁单、支付宝支付与最终一致性</title>
    <link href="https://aspirant02.github.io/2026/06/26/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%946.%20%E4%BA%A4%E6%98%93%E9%94%81%E5%8D%95%E3%80%81%E6%94%AF%E4%BB%98%E5%AE%9D%E6%94%AF%E4%BB%98%E4%B8%8E%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A7/" />
    <id>https://aspirant02.github.io/2026/06/26/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%946.%20%E4%BA%A4%E6%98%93%E9%94%81%E5%8D%95%E3%80%81%E6%94%AF%E4%BB%98%E5%AE%9D%E6%94%AF%E4%BB%98%E4%B8%8E%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A7/</id>
    <published>2026-06-26T13:01:24Z</published>
    <updated>2026-06-26T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;拼单系统最容易出问题的地方是交易链路。用户点下单后，系统要创建本地订单、锁定拼团优惠、生成支付宝支付单；用户支付成功后，还要通知营销域做拼团结算。任何一步失败，都可能出现本地订单和营销单状态不一致。&lt;/p&gt;
&lt;p&gt;所以这一篇重点写交易锁单和最终一致性。这里的关键不是一次 HTTP 调用成功，而是当网络抖动、支付回调重复、营销结算失败时，系统仍然能靠状态机和补偿任务恢复。&lt;/p&gt;
&lt;h2 id="交易域为什么先落本地单"&gt;&lt;a class="headerlink" href="#%E4%BA%A4%E6%98%93%E5%9F%9F%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%88%E8%90%BD%E6%9C%AC%E5%9C%B0%E5%8D%95" title="交易域为什么先落本地单"&gt;&lt;/a&gt;交易域为什么先落本地单&lt;/h2&gt;
&lt;p&gt;创建订单时，我先查询用户是否已有未支付订单，避免同一个用户对同一商品反复生成支付单。如果已有 PAY_WAIT，直接返回原支付链接；如果已有 CREATE 但没有支付单，则继续补创建支付单。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;OrderEntity unpaidOrderEntity = repository.queryUnPayOrder(shopCartEntity);

if (null != unpaidOrderEntity &amp;amp;&amp;amp; OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) {
    return PayOrderEntity.builder()
            .orderId(unpaidOrderEntity.getOrderId())
            .payUrl(unpaidOrderEntity.getPayUrl())
            .build();
}

ProductEntity productEntity = port.queryProductByProductId(shopCartEntity.getProductId());
OrderEntity orderEntity = CreateOrderAggregate.buildOrderEntity(
        productEntity.getProductId(),
        productEntity.getProductName(),
        shopCartEntity.getMarketTypeVO().getCode());

this.doSaveOrder(orderAggregate);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先落本地单有两个好处。第一，后续锁单或支付失败时有状态可查，不会只剩一条日志。第二，可以通过 Job 扫描 CREATE 或 PAY_WAIT 状态做补偿。&lt;/p&gt;
&lt;h2 id="营销锁单"&gt;&lt;a class="headerlink" href="#%E8%90%A5%E9%94%80%E9%94%81%E5%8D%95" title="营销锁单"&gt;&lt;/a&gt;营销锁单&lt;/h2&gt;
&lt;p&gt;如果订单走拼团营销，交易域调用营销域 lockMarketPayOrder，把 userId、teamId、activityId、productId、orderId 传过去。营销域校验活动和库存后返回优惠金额、支付金额和组队信息。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;if (MarketTypeVO.GROUP_BUY_MARKET.equals(shopCartEntity.getMarketTypeVO())) {
    marketPayDiscountEntity = this.lockMarketPayOrder(
            shopCartEntity.getUserId(),
            shopCartEntity.getTeamId(),
            shopCartEntity.getActivityId(),
            productEntity.getProductId(),
            orderEntity.getOrderId());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 orderId 是非常关键的幂等键。营销域锁单不能只按 userId 和 activityId 判断，否则用户重试下单时很难识别是否同一笔交易。用 orderId 作为外部交易号，可以保证锁单、结算、退单都围绕同一个业务单据。&lt;/p&gt;
&lt;h2 id="创建支付单"&gt;&lt;a class="headerlink" href="#%E5%88%9B%E5%BB%BA%E6%94%AF%E4%BB%98%E5%8D%95" title="创建支付单"&gt;&lt;/a&gt;创建支付单&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;BigDecimal payAmount = null == marketPayDiscountEntity
        ? totalAmount
        : marketPayDiscountEntity.getPayPrice();

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(notifyUrl);
request.setReturnUrl(returnUrl);

JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderId);
bizContent.put("total_amount", payAmount);
bizContent.put("subject", productName);
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toString());

String form = alipayClient.pageExecute(request).getBody();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;支付宝支付单里 out_trade_no 使用本地 orderId，回调时就可以直接定位订单。支付金额使用营销锁单返回的 payPrice，而不是前端传来的金额，避免客户端篡改。&lt;/p&gt;
&lt;h2 id="支付成功后的结算"&gt;&lt;a class="headerlink" href="#%E6%94%AF%E4%BB%98%E6%88%90%E5%8A%9F%E5%90%8E%E7%9A%84%E7%BB%93%E7%AE%97" title="支付成功后的结算"&gt;&lt;/a&gt;支付成功后的结算&lt;/h2&gt;
&lt;p&gt;支付宝回调通过验签后，交易域更新订单支付成功。如果订单是拼团营销类型，还要调用营销域 settlementMarketPayOrder 做结算。这里同步调用可能失败，所以需要依赖支付宝回调重试、主动查询支付状态 Job 或独立补偿 Job。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(orderEntity.getMarketType())) {
    repository.changeMarketOrderPaySuccess(orderId);
    port.settlementMarketPayOrder(orderEntity.getUserId(), orderId, payTime);
} else {
    repository.changeOrderPaySuccess(orderId, payTime);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码体现的是最终一致性：交易域先把支付成功事实落下来，再推动营销结算。如果营销结算失败，不应该回滚支付成功，而应该通过状态和补偿继续推进。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;交易锁单链路要把幂等键、状态机和补偿考虑在一起。先落本地单，营销锁单返回权威优惠金额，支付宝回调验签后更新支付状态，再推动营销结算。这样系统面对重复请求和异步失败时，有机会自己恢复，而不是依赖人工查库。&lt;/p&gt;</content>
    <summary type="html">前言 拼单系统最容易出问题的地方是交易链路。用户点下单后，系统要创建本地订单、锁定拼团优惠、生成支付宝支付单；用户支付成功后，还要通知营销域做拼团结算。任何一步 ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—5. DCC动态配置中心：秒级调整业务字段</title>
    <link href="https://aspirant02.github.io/2026/06/22/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%945.%20DCC%E5%8A%A8%E6%80%81%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%EF%BC%9A%E7%A7%92%E7%BA%A7%E8%B0%83%E6%95%B4%E4%B8%9A%E5%8A%A1%E5%AD%97%E6%AE%B5/" />
    <id>https://aspirant02.github.io/2026/06/22/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%945.%20DCC%E5%8A%A8%E6%80%81%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%EF%BC%9A%E7%A7%92%E7%BA%A7%E8%B0%83%E6%95%B4%E4%B8%9A%E5%8A%A1%E5%AD%97%E6%AE%B5/</id>
    <published>2026-06-22T13:01:24Z</published>
    <updated>2026-06-22T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;拼单系统里有些配置变化很快，比如活动开关、接口降级开关、试算超时时间、库存保护阈值、回调通道选择。如果每次调整都要发版，运营节奏会很慢，也容易因为临时改代码引入风险。&lt;/p&gt;
&lt;p&gt;我在简历里把这块写成 Redis 发布/订阅实现动态配置中心，也就是 DCC。当前仓库里基础配置还主要放在 application.yml，但这篇记录的是我设计 DCC 时的落地思路：通过注解标记可刷新字段，Redis Pub/Sub 推送变更，监听器收到后用反射更新内存值。&lt;/p&gt;
&lt;h2 id="DCC要解决的问题"&gt;&lt;a class="headerlink" href="#DCC%E8%A6%81%E8%A7%A3%E5%86%B3%E7%9A%84%E9%97%AE%E9%A2%98" title="DCC要解决的问题"&gt;&lt;/a&gt;DCC要解决的问题&lt;/h2&gt;
&lt;p&gt;DCC 不需要一开始做成 Nacos 或 Apollo 那么完整。对这个项目来说，第一阶段只要解决三个问题：配置能集中存储，变更能秒级通知应用，业务代码读取的是最新值。&lt;/p&gt;
&lt;p&gt;比如试算接口的超时时间默认 300ms，高峰期 Redis 抖动时可以临时调到 500ms；活动开关发现异常时可以立刻关闭；回调通道从 HTTP 切到 MQ，也不希望重启服务。&lt;/p&gt;
&lt;h2 id="注解标记字段"&gt;&lt;a class="headerlink" href="#%E6%B3%A8%E8%A7%A3%E6%A0%87%E8%AE%B0%E5%AD%97%E6%AE%B5" title="注解标记字段"&gt;&lt;/a&gt;注解标记字段&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DccValue {
    String key();
    String defaultValue() default "";
}

@Component
public class TrialConfig {
    @DccValue(key = "trial.query.timeout.ms", defaultValue = "300")
    private volatile long trialTimeoutMs;

    @DccValue(key = "trial.degrade.enable", defaultValue = "false")
    private volatile boolean degradeEnable;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字段用 volatile 是为了保证刷新后其他线程能尽快看到新值。注解里放 key 和默认值，启动时扫描 Bean，把字段和配置 key 建立映射。这样业务代码可以继续注入 TrialConfig，不需要每次都主动查 Redis。&lt;/p&gt;
&lt;h2 id="Redis发布订阅"&gt;&lt;a class="headerlink" href="#Redis%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85" title="Redis发布订阅"&gt;&lt;/a&gt;Redis发布订阅&lt;/h2&gt;
&lt;p&gt;配置变更时，后台或脚本先写 Redis Hash，再向某个 channel 发布 key。应用订阅 channel，收到 key 后重新读取配置并更新字段。这个模式很轻，适合单体或中小规模服务。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;// 配置后台伪代码
redisTemplate.opsForHash().put("dcc:config", key, value);
redisTemplate.convertAndSend("dcc:changed", key);

// 应用监听伪代码
public void onMessage(String key) {
    String value = redisTemplate.opsForHash().get("dcc:config", key).toString();
    dccRegistry.refresh(key, value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发布订阅只负责通知，不负责持久化，所以真实值仍然要存在 Redis Hash 或数据库里。否则应用重启后就不知道当前配置是什么。&lt;/p&gt;
&lt;h2 id="配置刷新"&gt;&lt;a class="headerlink" href="#%E9%85%8D%E7%BD%AE%E5%88%B7%E6%96%B0" title="配置刷新"&gt;&lt;/a&gt;配置刷新&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;public void refresh(String key, String value) {
    List&amp;lt;FieldRef&amp;gt; refs = fieldIndex.get(key);
    if (refs == null) return;

    for (FieldRef ref : refs) {
        Object converted = convert(value, ref.field().getType());
        ReflectionUtils.makeAccessible(ref.field());
        ReflectionUtils.setField(ref.field(), ref.bean(), converted);
        log.info("dcc refresh key={} value={}", key, value);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反射更新字段时要特别注意类型转换和异常处理。配置值从 Redis 读出来都是字符串，落到字段时可能是 boolean、int、long、BigDecimal。转换失败不能把旧值覆盖掉，应该保留旧值并报警。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;DCC 的目标不是替代成熟配置中心，而是在拼单系统里解决快速开关和运行时参数调整问题。注解负责声明，Redis Hash 负责存储，Pub/Sub 负责通知，反射负责刷新。做到这一步，简历里“配置变更时秒级热更新、无感配置下发”的描述就有了具体实现路径。&lt;/p&gt;</content>
    <summary type="html">前言 拼单系统里有些配置变化很快，比如活动开关、接口降级开关、试算超时时间、库存保护阈值、回调通道选择。如果每次调整都要发版，运营节奏会很慢，也容易因为临时改代 ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>AI开发-RAG：让模型回答时先查资料</title>
    <link href="https://aspirant02.github.io/2026/06/20/AI%E5%BC%80%E5%8F%91-RAG%EF%BC%9A%E8%AE%A9%E6%A8%A1%E5%9E%8B%E5%9B%9E%E7%AD%94%E6%97%B6%E5%85%88%E6%9F%A5%E8%B5%84%E6%96%99/" />
    <id>https://aspirant02.github.io/2026/06/20/AI%E5%BC%80%E5%8F%91-RAG%EF%BC%9A%E8%AE%A9%E6%A8%A1%E5%9E%8B%E5%9B%9E%E7%AD%94%E6%97%B6%E5%85%88%E6%9F%A5%E8%B5%84%E6%96%99/</id>
    <published>2026-06-20T13:01:24Z</published>
    <updated>2026-06-20T13:01:24Z</updated>
    <content type="html">&lt;h1 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h1&gt;
&lt;p&gt;RAG 是 Retrieval-Augmented Generation 的缩写，直译就是检索增强生成。这个概念这些年很火，但它的基本想法并不复杂：不要让大模型只凭训练参数回答，而是在生成之前先去知识库里检索相关资料，再把资料一起交给模型。&lt;/p&gt;
&lt;p&gt;很多企业内部问答、文档助手、代码库问答，本质上都是 RAG。因为这些知识经常更新，而且不一定出现在模型训练数据里。与其每次重新训练模型，不如维护一套可更新的外部知识库。&lt;/p&gt;
&lt;h1 id="为什么需要 RAG"&gt;&lt;a class="headerlink" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%20RAG" title="为什么需要 RAG"&gt;&lt;/a&gt;为什么需要 RAG&lt;/h1&gt;
&lt;p&gt;大模型擅长语言组织和模式归纳，但它不擅长保证知识一定最新、一定来自某个指定来源。RAG 把外部文档引入生成过程，可以缓解幻觉问题，也能让答案带上出处。&lt;/p&gt;
&lt;p&gt;原始 RAG 论文把模型参数里的知识称为 parametric memory，把外部索引里的知识称为 non-parametric memory。这个区分很有用：模型负责理解和表达，知识库负责提供可更新、可追溯的信息。&lt;/p&gt;
&lt;h1 id="基本流程"&gt;&lt;a class="headerlink" href="#%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B" title="基本流程"&gt;&lt;/a&gt;基本流程&lt;/h1&gt;
&lt;p&gt;最常见的流程是四步：文档切分、向量化入库、问题检索、带上下文生成。文档切分不要太粗也不要太碎，太粗会把无关内容塞进上下文，太碎又容易丢失完整语义。&lt;/p&gt;
&lt;p&gt;检索阶段通常先用向量相似度召回，再用关键词、rerank 或业务规则二次排序。最后把最相关的片段放进 prompt，让模型基于这些片段回答。&lt;/p&gt;
&lt;h1 id="开发时容易踩的坑"&gt;&lt;a class="headerlink" href="#%E5%BC%80%E5%8F%91%E6%97%B6%E5%AE%B9%E6%98%93%E8%B8%A9%E7%9A%84%E5%9D%91" title="开发时容易踩的坑"&gt;&lt;/a&gt;开发时容易踩的坑&lt;/h1&gt;
&lt;p&gt;第一个坑是只关注 embedding 模型，忽略了文档清洗。脏数据、重复段落、标题丢失，会让检索结果很飘。第二个坑是 chunk 设计过于随意，导致召回片段缺上下文。第三个坑是 prompt 没约束好，模型拿不到资料时仍然硬答。&lt;/p&gt;
&lt;p&gt;我一般会在 prompt 里明确写：如果资料中没有答案，就回答不知道，并说明缺少什么资料。同时把引用片段 ID 保留下来，方便前端展示出处，也方便调试检索质量。&lt;/p&gt;
&lt;!-- ai-extra-expanded-2026 --&gt;
&lt;h1 id="切片不是随便切"&gt;&lt;a class="headerlink" href="#%E5%88%87%E7%89%87%E4%B8%8D%E6%98%AF%E9%9A%8F%E4%BE%BF%E5%88%87" title="切片不是随便切"&gt;&lt;/a&gt;切片不是随便切&lt;/h1&gt;
&lt;p&gt;很多 RAG 系统一开始效果差，问题不一定在 embedding 模型，而在文档切片。比如 Markdown 标题丢了，只留下正文，检索结果就会缺少上下文；又比如按固定字数硬切，把一个完整步骤拆成两半，模型拿到的片段自然答不完整。&lt;/p&gt;
&lt;p&gt;更稳的做法是保留层级标题，把标题、来源、更新时间一起放进 metadata。技术文档可以按标题、段落和代码块切；制度文档可以按条款切；FAQ 可以一问一答作为最小单元。&lt;/p&gt;
&lt;pre&gt;&lt;code class="json"&gt;{
  "id": "blog-rag-001",
  "title": "AI开发-RAG：让模型回答时先查资料",
  "section": "开发时容易踩的坑",
  "content": "第一个坑是只关注 embedding 模型，忽略了文档清洗...",
  "metadata": {
    "source": "blog",
    "url": "/2026/06/20/AI开发-RAG/",
    "updated_at": "2026-06-20"
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;metadata 不只是附属信息，它会参与过滤和展示。比如用户只想查博客，就用 source 过滤；前端展示答案出处，就用 title 和 url；评估召回质量，也可以根据 section 判断命中的片段是否正确。&lt;/p&gt;
&lt;h1 id="一个最小 RAG 示例"&gt;&lt;a class="headerlink" href="#%E4%B8%80%E4%B8%AA%E6%9C%80%E5%B0%8F%20RAG%20%E7%A4%BA%E4%BE%8B" title="一个最小 RAG 示例"&gt;&lt;/a&gt;一个最小 RAG 示例&lt;/h1&gt;
&lt;p&gt;下面用 Python 写一个最小流程，省略具体 embedding 服务，只展示工程结构。真正上线时，可以把向量库换成 Milvus、pgvector、Qdrant、Elasticsearch 或云服务。&lt;/p&gt;
&lt;pre&gt;&lt;code class="py"&gt;def retrieve(question, top_k=4):
    query_vector = embed(question)
    return vector_store.search(query_vector, top_k=top_k)

def build_prompt(question, chunks):
    context = "\n\n".join(
        f"[{c['id']}] {c['text']}" for c in chunks
    )
    return f"""你只能根据下面资料回答问题。
如果资料里没有答案，请直接说不知道，并说明缺少什么资料。

资料：
{context}

问题：{question}
回答时请标注引用片段 ID。
"""

def answer(question):
    chunks = retrieve(question)
    prompt = build_prompt(question, chunks)
    return llm.generate(prompt)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个版本很朴素，但它把关键点放进去了：先检索，再拼上下文，再约束模型必须基于资料回答。很多问题不是靠复杂框架解决的，而是靠这些基础约束一点点收紧。&lt;/p&gt;
&lt;h1 id="怎么评估 RAG 好不好"&gt;&lt;a class="headerlink" href="#%E6%80%8E%E4%B9%88%E8%AF%84%E4%BC%B0%20RAG%20%E5%A5%BD%E4%B8%8D%E5%A5%BD" title="怎么评估 RAG 好不好"&gt;&lt;/a&gt;怎么评估 RAG 好不好&lt;/h1&gt;
&lt;p&gt;我会把 RAG 评估拆成两层：检索层和生成层。检索层看 top_k 里有没有命中正确片段，常用指标有 recall@k、MRR、nDCG；生成层看答案是否准确、是否忠实于资料、引用是否正确。&lt;/p&gt;
&lt;pre&gt;&lt;code class="yaml"&gt;cases:
  - question: "报销单最晚什么时候提交？"
    gold_chunks: ["policy-2026-03"]
    expected_keywords: ["7天", "出差结束"]
  - question: "博客 Gitalk 初始化脚本做了什么？"
    gold_chunks: ["blog-gitalk-init"]
    expected_keywords: ["自动初始化", "issue"]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最开始不一定要搭复杂平台，先做几十个手工样本就很有用。改切片、换 embedding、调 rerank 时都跑一遍，系统变好还是变差就不会只靠感觉。&lt;/p&gt;
&lt;h1 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h1&gt;
&lt;p&gt;RAG 不是万能补丁，它更像一个工程化套路：把可变知识交给外部检索，把语言生成交给模型。一个好用的 RAG 系统，核心不是“接上向量数据库”这么简单，而是资料质量、召回质量、上下文组织和答案约束共同作用。&lt;/p&gt;
&lt;p&gt;参考资料：&lt;a href="https://arxiv.org/abs/2005.11401" rel="noopener" target="_blank"&gt;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks&lt;/a&gt;。&lt;/p&gt;</content>
    <summary type="html">RAG 的核心是先从外部知识库检索相关资料，再让模型基于资料生成回答，从而提升事实性、时效性和可追溯性。 ...</summary>
    <category term="AI开发" scheme="https://aspirant02.github.io/categories/AI%E5%BC%80%E5%8F%91/" />
    <category term="AI开发" scheme="https://aspirant02.github.io/tags/AI%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—4. Redis BitMap人群标签与库存可见性</title>
    <link href="https://aspirant02.github.io/2026/06/18/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%944.%20Redis%20BitMap%E4%BA%BA%E7%BE%A4%E6%A0%87%E7%AD%BE%E4%B8%8E%E5%BA%93%E5%AD%98%E5%8F%AF%E8%A7%81%E6%80%A7/" />
    <id>https://aspirant02.github.io/2026/06/18/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%944.%20Redis%20BitMap%E4%BA%BA%E7%BE%A4%E6%A0%87%E7%AD%BE%E4%B8%8E%E5%BA%93%E5%AD%98%E5%8F%AF%E8%A7%81%E6%80%A7/</id>
    <published>2026-06-18T13:01:24Z</published>
    <updated>2026-06-18T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;拼单活动不是所有用户都能看到。比如校园店只给某栋楼的用户发券，写字楼活动只给企业群用户展示，或者新用户专享一杯咖啡 9.9 元。这个时候人群标签就成了营销系统的基础能力。&lt;/p&gt;
&lt;p&gt;如果每次判断用户是否命中标签都查数据库，高峰期商品页会把数据库打得很难受。Redis BitMap 的优势是用很低的空间成本保存大量布尔状态，判断用户是否在标签里只需要一次 bit 查询。&lt;/p&gt;
&lt;h2 id="为什么用BitMap"&gt;&lt;a class="headerlink" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E7%94%A8BitMap" title="为什么用BitMap"&gt;&lt;/a&gt;为什么用BitMap&lt;/h2&gt;
&lt;p&gt;BitMap 本质上是按位存储。用户 ID 经过映射得到一个整数下标，命中标签就把该下标设置为 1。判断时读取同一个下标即可。对于只需要“是否属于某人群”的场景，它比 Set 更省内存，也更适合做批量统计。&lt;/p&gt;
&lt;p&gt;当然，BitMap 要求用户 ID 能稳定映射到下标。项目里通过 RedisService 的 getIndexFromUserId 做映射。实际系统里我会把这个映射策略固定下来，并避免不同环境产生不同下标。&lt;/p&gt;
&lt;h2 id="标签写入"&gt;&lt;a class="headerlink" href="#%E6%A0%87%E7%AD%BE%E5%86%99%E5%85%A5" title="标签写入"&gt;&lt;/a&gt;标签写入&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;@Override
public void addCrowdTagsUserId(String tagId, String userId) {
    CrowdTagsDetail detail = new CrowdTagsDetail();
    detail.setTagId(tagId);
    detail.setUserId(userId);

    try {
        crowdTagsDetailDao.addCrowdTagsUserId(detail);

        RBitSet bitSet = redisService.getBitSet(tagId);
        bitSet.set(redisService.getIndexFromUserId(userId), true);
    } catch (DuplicateKeyException ignore) {
        // 忽略唯一索引冲突
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段实现做了两件事：先把明细写入 MySQL，再同步写入 Redis BitSet。MySQL 负责可追溯和离线统计，Redis 负责线上快速判断。DuplicateKeyException 被忽略，是因为标签导入任务可能重复执行，唯一索引用来保证幂等。&lt;/p&gt;
&lt;h2 id="用户ID映射"&gt;&lt;a class="headerlink" href="#%E7%94%A8%E6%88%B7ID%E6%98%A0%E5%B0%84" title="用户ID映射"&gt;&lt;/a&gt;用户ID映射&lt;/h2&gt;
&lt;p&gt;BitMap 最怕的是下标冲突和下标过大。如果 userId 是纯数字，可以直接转 long；如果是字符串，就需要 hash 或维护映射表。hash 会有冲突风险，映射表更准确但成本更高。我的取舍是：业务 userId 可控时优先使用数字化 ID，不可控时单独设计映射服务。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;public int getIndexFromUserId(String userId) {
    // 示例：实际项目要保证映射稳定，避免环境差异。
    return Math.abs(userId.hashCode());
}

public boolean isTagged(String tagId, String userId) {
    RBitSet bitSet = redisService.getBitSet(tagId);
    return bitSet.get(getIndexFromUserId(userId));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果用户规模很大，hashCode 直接取绝对值可能导致 BitMap 过稀，内存不划算。更稳的方案是给用户分配连续数字 ID，或者按标签分桶。文章里写这个细节，是因为面试官很容易追问 BitMap 的下标怎么来。&lt;/p&gt;
&lt;h2 id="可见性判断"&gt;&lt;a class="headerlink" href="#%E5%8F%AF%E8%A7%81%E6%80%A7%E5%88%A4%E6%96%AD" title="可见性判断"&gt;&lt;/a&gt;可见性判断&lt;/h2&gt;
&lt;p&gt;标签不仅能控制优惠，还能控制活动可见性。比如某个拼单活动只允许标签用户看到，此时 TagNode 可以读取 groupBuyDiscount 的 tagId，再查询 Redis BitMap。命中则 visible=true，否则返回普通商品信息。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;boolean visible = true;
String tagId = dynamicContext.getGroupBuyActivityDiscountVO()
        .getGroupBuyDiscount()
        .getTagId();

if (StringUtils.isNotBlank(tagId)) {
    visible = redisService.getBitSet(tagId)
            .get(redisService.getIndexFromUserId(request.getUserId()));
}

dynamicContext.setVisible(visible);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我不会把“不命中标签”当成系统异常，而是业务结果。用户不在活动人群内，前端仍然可以展示商品原价，只是不展示拼单优惠。这样活动配置错误也不会影响普通购买路径。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;Redis BitMap 适合承载高频、简单、布尔型的人群判断。MySQL 保留明细，Redis 做在线判断，唯一索引保证导入幂等，规则树节点把标签结果转成可见性和参与状态。这个设计正好对应简历里的“用户标签与可售库存、人群可见性控制”。&lt;/p&gt;</content>
    <summary type="html">前言 拼单活动不是所有用户都能看到。比如校园店只给某栋楼的用户发券，写字楼活动只给企业群用户展示，或者新用户专享一杯咖啡 9.9 元。这个时候人群标签就成了营销 ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—3. FutureTask与线程池：降低试算接口等待时间</title>
    <link href="https://aspirant02.github.io/2026/06/14/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%943.%20FutureTask%E4%B8%8E%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9A%E9%99%8D%E4%BD%8E%E8%AF%95%E7%AE%97%E6%8E%A5%E5%8F%A3%E7%AD%89%E5%BE%85%E6%97%B6%E9%97%B4/" />
    <id>https://aspirant02.github.io/2026/06/14/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%943.%20FutureTask%E4%B8%8E%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9A%E9%99%8D%E4%BD%8E%E8%AF%95%E7%AE%97%E6%8E%A5%E5%8F%A3%E7%AD%89%E5%BE%85%E6%97%B6%E9%97%B4/</id>
    <published>2026-06-14T13:01:24Z</published>
    <updated>2026-06-14T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;试算接口是用户进入商品页时最先触发的后端链路。如果它慢，用户看到的就是价格迟迟不出来、拼团按钮一直加载。这个接口里至少要查活动配置、商品信息、标签可见性，后面还可能查库存和动态配置。串行做当然简单，但会把多个 IO 等待累加起来。&lt;/p&gt;
&lt;p&gt;我的处理方式是在 MarketNode 里使用 FutureTask + 线程池并行加载活动配置和 SKU 信息。这样两个互不依赖的查询可以同时进行，接口耗时接近两者中的最大值，而不是两者之和。&lt;/p&gt;
&lt;h2 id="试算接口为什么慢"&gt;&lt;a class="headerlink" href="#%E8%AF%95%E7%AE%97%E6%8E%A5%E5%8F%A3%E4%B8%BA%E4%BB%80%E4%B9%88%E6%85%A2" title="试算接口为什么慢"&gt;&lt;/a&gt;试算接口为什么慢&lt;/h2&gt;
&lt;p&gt;商品信息和活动配置属于不同数据源或不同表，真实生产里商品可能来自商品中心 RPC，活动来自营销库，标签来自 Redis。它们之间没有强依赖，只有最终计算优惠时需要同时拿到。这样的场景很适合做前置并行加载。&lt;/p&gt;
&lt;p&gt;当然，并行不是免费午餐。线程池大小、队列长度、超时时间和拒绝策略都要配置好，否则高峰期可能把自己打满。&lt;/p&gt;
&lt;h2 id="线程池配置"&gt;&lt;a class="headerlink" href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%85%8D%E7%BD%AE" title="线程池配置"&gt;&lt;/a&gt;线程池配置&lt;/h2&gt;
&lt;pre&gt;&lt;code class="yaml"&gt;thread:
  pool:
    executor:
      config:
        core-pool-size: 20
        max-pool-size: 50
        keep-alive-time: 5000
        block-queue-size: 5000
        policy: CallerRunsPolicy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CallerRunsPolicy 的含义是队列满时由调用线程执行任务。它不会直接丢任务，但会反向拖慢入口线程，形成一种温和的背压。对于试算接口，我更愿意让请求变慢一点，也不希望静默丢掉活动或商品查询。&lt;/p&gt;
&lt;h2 id="FutureTask并行加载"&gt;&lt;a class="headerlink" href="#FutureTask%E5%B9%B6%E8%A1%8C%E5%8A%A0%E8%BD%BD" title="FutureTask并行加载"&gt;&lt;/a&gt;FutureTask并行加载&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;QueryGroupBuyActivityDiscountVOThreadTask activityTask =
        new QueryGroupBuyActivityDiscountVOThreadTask(
                requestParameter.getSource(),
                requestParameter.getChannel(),
                requestParameter.getGoodsId(),
                repository);
FutureTask&amp;lt;GroupBuyActivityDiscountVO&amp;gt; activityFuture = new FutureTask&amp;lt;&amp;gt;(activityTask);
threadPoolExecutor.execute(activityFuture);

QuerySkuVOFromDBThreadTask skuTask =
        new QuerySkuVOFromDBThreadTask(requestParameter.getGoodsId(), repository);
FutureTask&amp;lt;SkuVO&amp;gt; skuFuture = new FutureTask&amp;lt;&amp;gt;(skuTask);
threadPoolExecutor.execute(skuFuture);

dynamicContext.setGroupBuyActivityDiscountVO(activityFuture.get(timeout, TimeUnit.MINUTES));
dynamicContext.setSkuVO(skuFuture.get(timeout, TimeUnit.MINUTES));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的意图很明确：并行查活动和 SKU，然后写入动态上下文。为什么不用 CompletableFuture？也可以用。这里选 FutureTask 是因为链路简单，只需要提交任务、等待结果、设置超时。等后续节点更多、需要组合编排时，CompletableFuture 的 thenCombine 会更自然。&lt;/p&gt;
&lt;h2 id="超时和降级"&gt;&lt;a class="headerlink" href="#%E8%B6%85%E6%97%B6%E5%92%8C%E9%99%8D%E7%BA%A7" title="超时和降级"&gt;&lt;/a&gt;超时和降级&lt;/h2&gt;
&lt;p&gt;并发查询一定要配超时。没有超时的 Future.get 会把请求线程卡死，最终把 Tomcat 线程池拖住。当前代码用 `get(timeout, TimeUnit.MINUTES)`，实际生产我会把单位调到毫秒或秒级，并根据节点重要性做降级。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;try {
    GroupBuyActivityDiscountVO activity = activityFuture.get(300, TimeUnit.MILLISECONDS);
    SkuVO sku = skuFuture.get(300, TimeUnit.MILLISECONDS);
    context.setGroupBuyActivityDiscountVO(activity);
    context.setSkuVO(sku);
} catch (TimeoutException e) {
    activityFuture.cancel(true);
    skuFuture.cancel(true);
    context.setEnable(false);
    log.warn("trial query timeout, goodsId={}", request.getGoodsId(), e);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于下午茶商品页来说，营销试算失败时可以返回原价和不可参团，不应该让整个页面不可用。这个降级策略和简历里写的“降低接口响应时间”是一体两面：一方面做并行加载，一方面给慢依赖设置边界。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;FutureTask 不是为了展示并发 API，而是为了解决试算接口的真实延迟问题。活动、商品、标签这些数据可以并行加载，但线程池、超时、取消和降级必须一起设计。否则并发只是把串行慢改成并发乱。&lt;/p&gt;</content>
    <summary type="html">前言 试算接口是用户进入商品页时最先触发的后端链路。如果它慢，用户看到的就是价格迟迟不出来、拼团按钮一直加载。这个接口里至少要查活动配置、商品信息、标签可见性， ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>AI开发-MCP：把工具接到大模型身边</title>
    <link href="https://aspirant02.github.io/2026/06/12/AI%E5%BC%80%E5%8F%91-MCP%EF%BC%9A%E6%8A%8A%E5%B7%A5%E5%85%B7%E6%8E%A5%E5%88%B0%E5%A4%A7%E6%A8%A1%E5%9E%8B%E8%BA%AB%E8%BE%B9/" />
    <id>https://aspirant02.github.io/2026/06/12/AI%E5%BC%80%E5%8F%91-MCP%EF%BC%9A%E6%8A%8A%E5%B7%A5%E5%85%B7%E6%8E%A5%E5%88%B0%E5%A4%A7%E6%A8%A1%E5%9E%8B%E8%BA%AB%E8%BE%B9/</id>
    <published>2026-06-12T13:01:24Z</published>
    <updated>2026-06-12T13:01:24Z</updated>
    <content type="html">&lt;h1 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h1&gt;
&lt;p&gt;最近做 AI 应用时，我越来越觉得模型本身只是其中一部分。真正让一个 AI 应用变得有用的，往往是它能不能读取项目文件、查询数据库、调用内部系统、按固定流程完成任务。以前这些能力基本靠每个应用自己写一套插件系统，写到最后就会出现大量重复的适配代码。&lt;/p&gt;
&lt;p&gt;MCP，全称 Model Context Protocol，可以理解为给 AI 应用和外部系统之间加了一层标准协议。官方文档把它描述为一个开源标准，用来把 AI 应用连接到外部数据源、工具和工作流上。这个定义很朴素，但它解决的是 AI 应用里非常现实的问题：模型如何以统一方式拿到上下文，并安全地执行动作。&lt;/p&gt;
&lt;h1 id="MCP 想解决什么问题"&gt;&lt;a class="headerlink" href="#MCP%20%E6%83%B3%E8%A7%A3%E5%86%B3%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98" title="MCP 想解决什么问题"&gt;&lt;/a&gt;MCP 想解决什么问题&lt;/h1&gt;
&lt;p&gt;如果没有 MCP，一个编辑器、一个聊天机器人、一个自动化 Agent 想分别接入 Git、数据库、浏览器、工单系统，就需要各自维护一套接口。工具一多，就会变成 N 个客户端乘 M 个工具的组合复杂度。&lt;/p&gt;
&lt;p&gt;MCP 的思路是把外部能力封装成 MCP Server，AI 应用侧通过 MCP Client 连接它。这样工具只要按协议暴露能力，支持 MCP 的客户端就都可以接入。类比一下，它不是让每个模型都懂所有系统，而是提供一个通用插座。&lt;/p&gt;
&lt;h1 id="几个核心角色"&gt;&lt;a class="headerlink" href="#%E5%87%A0%E4%B8%AA%E6%A0%B8%E5%BF%83%E8%A7%92%E8%89%B2" title="几个核心角色"&gt;&lt;/a&gt;几个核心角色&lt;/h1&gt;
&lt;p&gt;Host 是真正承载用户交互的 AI 应用，比如桌面助手、IDE、聊天客户端。Client 在 Host 内部，负责和某个 MCP Server 建立连接。Server 则负责暴露外部能力，比如文件读取、数据库查询、搜索、内部 API、固定提示模板等。&lt;/p&gt;
&lt;p&gt;从开发者视角看，Server 是最容易上手的入口。你可以先写一个只暴露 read_file、search_docs、query_issue 三个工具的小服务，让 AI 应用通过 MCP 调用它。以后换客户端时，只要协议支持，工具层不用跟着大改。&lt;/p&gt;
&lt;h1 id="一个最小实践思路"&gt;&lt;a class="headerlink" href="#%E4%B8%80%E4%B8%AA%E6%9C%80%E5%B0%8F%E5%AE%9E%E8%B7%B5%E6%80%9D%E8%B7%AF" title="一个最小实践思路"&gt;&lt;/a&gt;一个最小实践思路&lt;/h1&gt;
&lt;p&gt;我会先从“只读工具”开始做 MCP Server，例如只允许读取某个项目目录、查询只读数据库视图、搜索文档索引。等权限边界和日志都清楚了，再开放写操作。&lt;/p&gt;
&lt;p&gt;实现时可以把每个工具当成普通后端接口：入参要有 schema，输出要结构化，错误也要明确。不要把“让模型自己猜”当成接口设计，工具越清晰，模型越不容易误用。&lt;/p&gt;
&lt;!-- ai-extra-expanded-2026 --&gt;
&lt;h1 id="资源、提示词和工具怎么分"&gt;&lt;a class="headerlink" href="#%E8%B5%84%E6%BA%90%E3%80%81%E6%8F%90%E7%A4%BA%E8%AF%8D%E5%92%8C%E5%B7%A5%E5%85%B7%E6%80%8E%E4%B9%88%E5%88%86" title="资源、提示词和工具怎么分"&gt;&lt;/a&gt;资源、提示词和工具怎么分&lt;/h1&gt;
&lt;p&gt;MCP 里经常会看到 Resources、Prompts 和 Tools 三类东西。我自己的理解是：Resources 是资料入口，Prompts 是工作方式，Tools 是动作按钮。比如博客文章、接口文档、数据库记录更像资源；“按我的博客风格扩写”更像提示词模板；“搜索文章”“读取文件”“查询 issue”才是工具。&lt;/p&gt;
&lt;p&gt;这三类最好不要混在一起。资源应该尽量只读，提示词应该可复用，工具应该有清楚的入参和返回值。这样以后排查问题时会很直观：回答不准看资源召回，动作失败看工具日志，表达风格不统一看 prompt。&lt;/p&gt;
&lt;pre&gt;&lt;code class="json"&gt;{
  "name": "search_posts",
  "description": "按关键词搜索博客文章，只返回标题、链接和摘要",
  "inputSchema": {
    "type": "object",
    "properties": {
      "keyword": { "type": "string" },
      "limit": { "type": "number", "default": 5 }
    },
    "required": ["keyword"]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面这个工具描述看起来很普通，但比一个模糊的 search 要可靠很多。模型知道它只能搜索博客文章，也知道必须传 keyword。工具越窄，越容易稳定；工具越泛，越需要额外权限和审计。&lt;/p&gt;
&lt;h1 id="用 TypeScript 写一个最小工具"&gt;&lt;a class="headerlink" href="#%E7%94%A8%20TypeScript%20%E5%86%99%E4%B8%80%E4%B8%AA%E6%9C%80%E5%B0%8F%E5%B7%A5%E5%85%B7" title="用 TypeScript 写一个最小工具"&gt;&lt;/a&gt;用 TypeScript 写一个最小工具&lt;/h1&gt;
&lt;p&gt;下面是一个偏伪代码的 TypeScript 示例，表达的是 MCP Server 暴露工具时的思路。真实项目可以直接使用官方 SDK，把传输、注册和参数校验交给框架处理。&lt;/p&gt;
&lt;pre&gt;&lt;code class="ts"&gt;import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "blog-tools",
  version: "0.1.0"
});

server.tool(
  "search_posts",
  {
    keyword: z.string().min(1),
    limit: z.number().int().min(1).max(10).default(5)
  },
  async ({ keyword, limit }) =&amp;gt; {
    const rows = await searchLocalIndex(keyword, limit);
    return {
      content: [{
        type: "text",
        text: JSON.stringify({ status: "ok", data: rows })
      }]
    };
  }
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里最值得注意的是 limit 被限制在 1 到 10，keyword 不能为空。很多 Agent 不稳定，并不是模型突然变差，而是工具边界太松，导致它一次性请求太多内容，或者拿到一个格式很飘的返回值。&lt;/p&gt;
&lt;h1 id="权限和日志要一开始就设计"&gt;&lt;a class="headerlink" href="#%E6%9D%83%E9%99%90%E5%92%8C%E6%97%A5%E5%BF%97%E8%A6%81%E4%B8%80%E5%BC%80%E5%A7%8B%E5%B0%B1%E8%AE%BE%E8%AE%A1" title="权限和日志要一开始就设计"&gt;&lt;/a&gt;权限和日志要一开始就设计&lt;/h1&gt;
&lt;p&gt;MCP Server 不等于把整个系统权限交给模型。Server 应该像普通后端服务一样设计边界：文件工具限制目录，数据库工具只允许查询白名单视图，部署工具必须二次确认。能只读就先只读，等日志和权限都稳定了，再开放写操作。&lt;/p&gt;
&lt;pre&gt;&lt;code class="ts"&gt;const allowList = ["E:/blog/source/_posts"];

function assertReadable(path: string) {
  const normalized = normalizePath(path);
  const ok = allowList.some(root =&amp;gt; normalized.startsWith(root));
  if (!ok) throw new Error("path is outside readable allow list");
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日志也要从第一天开始收。至少记录工具名、入参摘要、耗时、结果状态和错误信息。不要把敏感数据完整写进日志，但要能在出问题时复盘：模型为什么调用这个工具，工具返回了什么，下一步又发生了什么。&lt;/p&gt;
&lt;h1 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h1&gt;
&lt;p&gt;MCP 的价值不在于让模型变神奇，而在于让工具接入这件事更标准。对于独立开发者来说，它适合用来沉淀自己的常用能力：项目知识库、脚本工具、部署查询、数据库只读分析，都可以慢慢做成可复用的 MCP Server。&lt;/p&gt;
&lt;p&gt;参考资料：&lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener" target="_blank"&gt;Model Context Protocol 官方文档&lt;/a&gt;。&lt;/p&gt;</content>
    <summary type="html">MCP 可以理解为 AI 应用和外部工具之间的一层标准协议，让模型以统一方式读取上下文、调用工具和执行工作流。 ...</summary>
    <category term="AI开发" scheme="https://aspirant02.github.io/categories/AI%E5%BC%80%E5%8F%91/" />
    <category term="AI开发" scheme="https://aspirant02.github.io/tags/AI%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—2. 优惠计算：满减、折扣、N元购与直减</title>
    <link href="https://aspirant02.github.io/2026/06/10/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%942.%20%E4%BC%98%E6%83%A0%E8%AE%A1%E7%AE%97%EF%BC%9A%E6%BB%A1%E5%87%8F%E3%80%81%E6%8A%98%E6%89%A3%E3%80%81N%E5%85%83%E8%B4%AD%E4%B8%8E%E7%9B%B4%E5%87%8F/" />
    <id>https://aspirant02.github.io/2026/06/10/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%942.%20%E4%BC%98%E6%83%A0%E8%AE%A1%E7%AE%97%EF%BC%9A%E6%BB%A1%E5%87%8F%E3%80%81%E6%8A%98%E6%89%A3%E3%80%81N%E5%85%83%E8%B4%AD%E4%B8%8E%E7%9B%B4%E5%87%8F/</id>
    <published>2026-06-10T13:01:24Z</published>
    <updated>2026-06-10T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;营销系统里最容易变化的是优惠规则。今天是满 100 减 10，明天是 8 折，后天是 N 元购，再过几天运营想给某个标签用户做直减。如果每次都改同一个大方法，代码会很快变成一团业务条件。&lt;/p&gt;
&lt;p&gt;所以我把优惠计算做成策略模式。抽象类负责公共流程，比如人群标签过滤、最低金额保护；具体策略只关心自己的表达式怎么解释、优惠金额怎么算。这样规则可以横向扩展。&lt;/p&gt;
&lt;h2 id="策略接口"&gt;&lt;a class="headerlink" href="#%E7%AD%96%E7%95%A5%E6%8E%A5%E5%8F%A3" title="策略接口"&gt;&lt;/a&gt;策略接口&lt;/h2&gt;
&lt;pre&gt;&lt;code class="java"&gt;public interface IDiscountCalculateService {
    BigDecimal calculate(
            String userId,
            BigDecimal originalPrice,
            GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接口入参看起来很少，实际上已经够用。userId 用来做人群过滤，originalPrice 是商品原价，groupBuyDiscount 包含优惠类型、优惠表达式、标签 ID、活动编码等配置。计算结果统一返回应付金额，而不是返回优惠金额，这样上层不用关心每种策略的差异。&lt;/p&gt;
&lt;h2 id="满减实现"&gt;&lt;a class="headerlink" href="#%E6%BB%A1%E5%87%8F%E5%AE%9E%E7%8E%B0" title="满减实现"&gt;&lt;/a&gt;满减实现&lt;/h2&gt;
&lt;p&gt;满减策略的表达式设计成 `100,10`，含义是满 100 减 10。实现时先解析门槛 x 和减免 y，不满足门槛直接返回原价，满足后做扣减，并保证最低支付 0.01。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;@Slf4j
@Service("MJ")
public class MJCalculateService extends AbstractDiscountCalculateService {

    @Override
    public BigDecimal doCalculate(BigDecimal originalPrice,
                                  GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount) {
        String marketExpr = groupBuyDiscount.getMarketExpr();
        String[] split = marketExpr.split(Constants.SPLIT);
        BigDecimal x = new BigDecimal(split[0].trim());
        BigDecimal y = new BigDecimal(split[1].trim());

        if (originalPrice.compareTo(x) &amp;lt; 0) {
            return originalPrice;
        }

        BigDecimal deductionPrice = originalPrice.subtract(y);
        if (deductionPrice.compareTo(BigDecimal.ZERO) &amp;lt;= 0) {
            return new BigDecimal("0.01");
        }
        return deductionPrice;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里没有把 `100,10` 写死在代码里，而是存在数据库配置里。这样运营调整活动时，只要改表达式，不需要发布应用。表达式虽然简单，但要做格式校验，否则配置成 `100-10` 就会在运行时炸掉。&lt;/p&gt;
&lt;h2 id="人群过滤"&gt;&lt;a class="headerlink" href="#%E4%BA%BA%E7%BE%A4%E8%BF%87%E6%BB%A4" title="人群过滤"&gt;&lt;/a&gt;人群过滤&lt;/h2&gt;
&lt;p&gt;抽象类里先做人群标签过滤，再调用 doCalculate。这样所有优惠策略都天然支持标签型优惠，不需要每个实现类都写一遍 userId 判断。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;public abstract class AbstractDiscountCalculateService implements IDiscountCalculateService {

    @Override
    public BigDecimal calculate(String userId,
                                BigDecimal originalPrice,
                                GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount) {
        if (DiscountTypeEnum.TAG.equals(groupBuyDiscount.getDiscountType())) {
            boolean isCrowdRange = filterTagId(userId, groupBuyDiscount.getTagId());
            if (!isCrowdRange) return originalPrice;
        }
        return doCalculate(originalPrice, groupBuyDiscount);
    }

    protected abstract BigDecimal doCalculate(BigDecimal originalPrice,
                                              GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这块和简历里写的 Redis + BitMap 人群管理是连着的。标签过滤不是只在优惠计算里做一个布尔判断，背后要有标签导入、去重、快速判断和统计更新。后面会单独写 BitMap 这一篇。&lt;/p&gt;
&lt;h2 id="表达式解析"&gt;&lt;a class="headerlink" href="#%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%A7%A3%E6%9E%90" title="表达式解析"&gt;&lt;/a&gt;表达式解析&lt;/h2&gt;
&lt;p&gt;表达式越灵活，越要控制边界。满减可以是 `x,y`，折扣可以是 `0.8`，N 元购可以是 `9.9`，直减可以是 `5`。我不会一开始做一个复杂 DSL，而是先让每种策略定义自己的表达式格式，并在后台配置时校验。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;enum MarketPlan {
    MJ("MJ", "满减", "x,y"),
    ZK("ZK", "折扣", "rate"),
    N("N", "N元购", "price"),
    ZJ("ZJ", "直减", "amount");

    private final String code;
    private final String name;
    private final String exprPattern;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;面试时如果被问为什么不用 Drools 或 Aviator，我会解释：当前系统规则种类有限，且规则主要是价格计算和可见性判断，策略模式更轻、更容易测试。等规则组合复杂到需要运营自行编排，再考虑规则引擎。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;优惠计算的核心是让变化变成扩展，而不是反复修改老代码。抽象类收公共流程，具体策略处理表达式，Spring Bean 名称承接 marketPlan，规则树负责调度。这样一条链路既能解释清楚，也能支撑后续扩展。&lt;/p&gt;</content>
    <summary type="html">前言 营销系统里最容易变化的是优惠规则。今天是满 100 减 10，明天是 8 折，后天是 N 元购，再过几天运营想给某个标签用户做直减。如果每次都改同一个大方 ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—1. 规则树驱动的试算链路</title>
    <link href="https://aspirant02.github.io/2026/06/06/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%941.%20%E8%A7%84%E5%88%99%E6%A0%91%E9%A9%B1%E5%8A%A8%E7%9A%84%E8%AF%95%E7%AE%97%E9%93%BE%E8%B7%AF/" />
    <id>https://aspirant02.github.io/2026/06/06/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%941.%20%E8%A7%84%E5%88%99%E6%A0%91%E9%A9%B1%E5%8A%A8%E7%9A%84%E8%AF%95%E7%AE%97%E9%93%BE%E8%B7%AF/</id>
    <published>2026-06-06T13:01:24Z</published>
    <updated>2026-06-06T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;拼单系统最核心的入口之一是商品试算。用户还没有真正下单，只是在页面上看一杯奶茶能不能参团、优惠后多少钱、是否命中人群限制。这个接口一旦写成一串 if else，后面加活动开关、渠道限制、人群标签、库存校验时会越来越难维护。&lt;/p&gt;
&lt;p&gt;所以我把试算链路设计成规则树。每个节点只处理一个判断或计算，然后把结果写入动态上下文，再由节点决定下一步走向。这个设计和责任链类似，但规则树更强调分支：成功继续、失败进入错误节点、命中标签后进入结束节点。&lt;/p&gt;
&lt;h2 id="为什么用规则树"&gt;&lt;a class="headerlink" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E7%94%A8%E8%A7%84%E5%88%99%E6%A0%91" title="为什么用规则树"&gt;&lt;/a&gt;为什么用规则树&lt;/h2&gt;
&lt;p&gt;营销规则的特点是变化频繁，并且经常有组合关系。比如活动必须处于有效期内，商品要绑定活动，用户要命中标签，优惠策略要存在，最后才能返回试算结果。如果写在一个方法里，很难知道某个规则失败后应该返回什么，也很难给不同活动插拔节点。&lt;/p&gt;
&lt;p&gt;规则树把流程拆成 RootNode、SwitchNode、MarketNode、TagNode、EndNode、ErrorNode。RootNode 做入口校验，SwitchNode 判断活动开关，MarketNode 加载活动和商品并计算优惠，TagNode 判断可见性和参与资格，EndNode 组装结果，ErrorNode 兜底异常返回。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;@Service
public class DefaultActivityStrategyFactory {

    private final RootNode rootNode;

    public DefaultActivityStrategyFactory(RootNode rootNode) {
        this.rootNode = rootNode;
    }

    public StrategyHandler&amp;lt;MarketProductEntity, DynamicContext, TrialBalanceEntity&amp;gt; strategyHandler() {
        return rootNode;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;工厂只暴露根节点，外部调用方不需要知道树里有几个节点。以后要新增一个库存节点，只需要改树的路由关系，而不是把调用方一起拖进来。&lt;/p&gt;
&lt;h2 id="动态上下文"&gt;&lt;a class="headerlink" href="#%E5%8A%A8%E6%80%81%E4%B8%8A%E4%B8%8B%E6%96%87" title="动态上下文"&gt;&lt;/a&gt;动态上下文&lt;/h2&gt;
&lt;p&gt;规则树里最重要的是 DynamicContext。它不是随便放数据的 Map，而是一次试算过程的业务快照。活动配置、商品信息、优惠后价格、可见性、可参与状态都在这里流转。这样节点之间不需要互相调用，数据通过上下文传递。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class DynamicContext {
    private GroupBuyActivityDiscountVO groupBuyActivityDiscountVO;
    private SkuVO skuVO;
    private BigDecimal deductionPrice;
    private boolean visible;
    private boolean enable;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个上下文也方便做日志。线上排查试算异常时，我不只看入口参数，还会看每个节点写入了什么字段。比如 groupBuyActivityDiscountVO 为空，说明活动配置没有查到；deductionPrice 为空，说明优惠策略没有成功计算；visible 为 false，说明人群或活动限制没有通过。&lt;/p&gt;
&lt;h2 id="节点如何流转"&gt;&lt;a class="headerlink" href="#%E8%8A%82%E7%82%B9%E5%A6%82%E4%BD%95%E6%B5%81%E8%BD%AC" title="节点如何流转"&gt;&lt;/a&gt;节点如何流转&lt;/h2&gt;
&lt;p&gt;MarketNode 是试算链路里最重的节点，它负责加载活动配置和 SKU，并调用不同优惠策略。这里有两个关键设计：第一，活动和商品并行查询，避免串行等待；第二，优惠策略通过 Spring Map 注入，避免硬编码策略选择。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;IDiscountCalculateService discountCalculateService =
        discountCalculateServiceMap.get(groupBuyDiscount.getMarketPlan());

if (null == discountCalculateService) {
    throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0001.getInfo());
}

BigDecimal deductionPrice = discountCalculateService.calculate(
        requestParameter.getUserId(),
        skuVO.getOriginalPrice(),
        groupBuyDiscount);

dynamicContext.setDeductionPrice(deductionPrice);
return router(requestParameter, dynamicContext);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 marketPlan 可以是 MJ、ZK、N、ZJ 等策略标识。策略 Bean 使用 @Service("MJ") 这样的名称注册，Map 的 key 就自然成为策略编码。这样新增一种优惠，只需要新增一个实现类并约定编码。&lt;/p&gt;
&lt;h2 id="异常节点和收口"&gt;&lt;a class="headerlink" href="#%E5%BC%82%E5%B8%B8%E8%8A%82%E7%82%B9%E5%92%8C%E6%94%B6%E5%8F%A3" title="异常节点和收口"&gt;&lt;/a&gt;异常节点和收口&lt;/h2&gt;
&lt;p&gt;规则树一定要有失败出口。活动不存在、商品不存在、策略不存在、用户不在可见范围内，都不能让接口返回空指针。ErrorNode 的意义是把不可试算的情况变成可解释结果，EndNode 则把正常结果组装成前端可使用的 TrialBalanceEntity。&lt;/p&gt;
&lt;p&gt;我喜欢把错误节点看成业务兜底，而不是异常吞掉。比如活动没配置，前端可以展示原价和不可参团；用户没命中标签，可以展示普通价；系统异常才返回错误码。营销系统不能因为一个活动配错就拖垮整个商品页。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;规则树的价值不是把代码写复杂，而是让复杂规则有地方放。试算链路天然会越来越长，如果一开始就把节点、上下文、路由和异常收口设计好，后面加 DCC、标签、库存和渠道限制都会更从容。&lt;/p&gt;</content>
    <summary type="html">前言 拼单系统最核心的入口之一是商品试算。用户还没有真正下单，只是在页面上看一杯奶茶能不能参团、优惠后多少钱、是否命中人群限制。这个接口一旦写成一串 if el ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>下午茶拼单系统—0. 从需求场景到DDD分层</title>
    <link href="https://aspirant02.github.io/2026/06/02/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%940.%20%E4%BB%8E%E9%9C%80%E6%B1%82%E5%9C%BA%E6%99%AF%E5%88%B0DDD%E5%88%86%E5%B1%82/" />
    <id>https://aspirant02.github.io/2026/06/02/%E4%B8%8B%E5%8D%88%E8%8C%B6%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F%E2%80%940.%20%E4%BB%8E%E9%9C%80%E6%B1%82%E5%9C%BA%E6%99%AF%E5%88%B0DDD%E5%88%86%E5%B1%82/</id>
    <published>2026-06-02T13:01:24Z</published>
    <updated>2026-06-02T13:01:24Z</updated>
    <content type="html">&lt;h2 id="前言"&gt;&lt;a class="headerlink" href="#%E5%89%8D%E8%A8%80" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;
&lt;p&gt;做这个下午茶拼单系统时，我最先想清楚的不是接口怎么写，而是它到底要解决什么问题。场景很具体：写字楼、校园或者团队下午茶，用户围绕奶茶、咖啡、甜点发起拼单，系统要覆盖优惠试算、营销锁单、支付、组队结算、逆向退单等流程。简历里写成一句话很短，但真正落到后端，里面有活动、人群、交易三个完全不同的变化方向。&lt;/p&gt;
&lt;p&gt;我参考 MYDB 系列文章的写法，把过程拆成一组开发笔记。MYDB 是从底层模块一步步往上搭，这个项目也类似：先搭 DDD 分层，再做规则树试算，然后处理优惠策略、Redis 标签、动态配置、交易锁单、MQ 回调和部署复盘。不同的是，拼单系统更偏业务复杂度和高并发链路，而不是单机存储结构。&lt;/p&gt;
&lt;h2 id="为什么先拆领域"&gt;&lt;a class="headerlink" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%88%E6%8B%86%E9%A2%86%E5%9F%9F" title="为什么先拆领域"&gt;&lt;/a&gt;为什么先拆领域&lt;/h2&gt;
&lt;p&gt;如果把所有逻辑都塞进一个 OrderService，最开始会很快，后面会非常难改。活动规则、用户标签、交易订单、支付回调、库存可见性这些事情看起来都和“下单”相关，但变化原因不同。DDD 的价值就在这里：不是为了显得架构很重，而是把变化点隔开。&lt;/p&gt;
&lt;p&gt;我把项目拆成营销拼团域和交易支付域。营销域负责活动配置、优惠试算、人群控制、锁单结算；交易域负责订单、支付、退款和用户订单列表。两个域之间通过端口和网关交互，交易域不直接关心营销规则细节，只关心锁单后返回的优惠金额和 teamId。&lt;/p&gt;
&lt;pre&gt;&lt;code class="java"&gt;public interface IProductPort {
    ProductEntity queryProductByProductId(String productId);

    MarketPayDiscountEntity lockMarketPayOrder(
            String userId, String teamId, Long activityId,
            String productId, String orderId);

    void settlementMarketPayOrder(String userId, String orderId, Date orderTime);

    void refundMarketPayOrder(String userId, String orderId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段端口定义把交易侧和营销侧隔离开了。交易订单只需要知道“我要锁一笔营销单”“我要结算”“我要退单”，至于拼团活动是否开启、活动规则如何算、人群标签怎么查，都留给营销域处理。&lt;/p&gt;
&lt;h2 id="两条主链路"&gt;&lt;a class="headerlink" href="#%E4%B8%A4%E6%9D%A1%E4%B8%BB%E9%93%BE%E8%B7%AF" title="两条主链路"&gt;&lt;/a&gt;两条主链路&lt;/h2&gt;
&lt;p&gt;第一条是营销试算链路：用户进入商品详情页时，系统根据 source、channel、goodsId、userId 查询活动配置、商品价格和用户标签，计算出是否可见、是否可参与、优惠后价格。这个链路直接影响页面打开速度，所以后面会引入 FutureTask 和线程池做并行查询。&lt;/p&gt;
&lt;p&gt;第二条是交易链路：用户选择拼团下单，交易域创建本地订单，调用营销域锁定拼团资格和优惠金额，再创建支付宝支付单。支付成功后，交易域触发营销结算，拼团成功再通过 HTTP 或 RabbitMQ 回调交易域做最终状态变更。这里的关键词不是“同步调用”，而是最终一致性。&lt;/p&gt;
&lt;h2 id="工程结构"&gt;&lt;a class="headerlink" href="#%E5%B7%A5%E7%A8%8B%E7%BB%93%E6%9E%84" title="工程结构"&gt;&lt;/a&gt;工程结构&lt;/h2&gt;
&lt;p&gt;项目使用 Spring Boot、MyBatis、MySQL、Redis、RabbitMQ 等技术栈，代码按 api、app、domain、infrastructure、trigger、types 分层。api 层定义对外契约，domain 层承载业务模型，infrastructure 适配数据库、Redis 和外部 HTTP，trigger 层放 Controller、Job、Listener。&lt;/p&gt;
&lt;pre&gt;&lt;code class="text"&gt;group-buy-market
├── group-buy-market-api
├── group-buy-market-app
├── group-buy-market-domain
├── group-buy-market-infrastructure
├── group-buy-market-trigger
└── group-buy-market-types

s-pay-mall-ddd-market
├── s-pay-mall-ddd-api
├── s-pay-mall-ddd-app
├── s-pay-mall-ddd-domain
├── s-pay-mall-ddd-infrastructure
├── s-pay-mall-ddd-trigger
└── s-pay-mall-ddd-types&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样的目录看起来文件多，但每个包的职责相对清楚。比如 Redis BitMap 的实现放在 infrastructure，规则树节点放在 domain，HTTP/RabbitMQ 回调放在 trigger。写文章复盘时，我也按这个分层顺序展开，避免只讲 Controller 到 Mapper 的 CRUD。&lt;/p&gt;
&lt;h2 id="从简历落到代码"&gt;&lt;a class="headerlink" href="#%E4%BB%8E%E7%AE%80%E5%8E%86%E8%90%BD%E5%88%B0%E4%BB%A3%E7%A0%81" title="从简历落到代码"&gt;&lt;/a&gt;从简历落到代码&lt;/h2&gt;
&lt;p&gt;简历里提到的几个关键词，分别对应后续文章的重点：DDD 领域驱动设计对应工程拆分；规则树和责任链对应营销试算流程；FutureTask 和线程池对应并行加载活动与商品数据；Redis 发布订阅和 DCC 对应动态配置；Redis BitMap 对应人群标签与库存可见性；RabbitMQ 和 HTTP 双通道对应支付后的异步回调与消息可靠性。&lt;/p&gt;
&lt;p&gt;我希望这组文章看起来像真实开发过程，而不是把技术名词堆在一起。每一篇都围绕一个具体问题：接口慢了怎么办、规则变化怎么办、用户标签怎么存、锁单失败怎么补偿、回调重复怎么处理。这样写出来的项目经历，在面试里也更容易展开。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a class="headerlink" href="#%E5%B0%8F%E7%BB%93" title="小结"&gt;&lt;/a&gt;小结&lt;/h2&gt;
&lt;p&gt;第一篇先把边界划清楚。下午茶拼单系统不是一个简单的下单页，而是营销和交易协作的系统。后面几篇会从营销试算开始，一层层展开到支付、结算、退单和部署。和 MYDB 一样，先有整体结构，后面每个模块才有落点。&lt;/p&gt;</content>
    <summary type="html">前言 做这个下午茶拼单系统时，我最先想清楚的不是接口怎么写，而是它到底要解决什么问题。场景很具体：写字楼、校园或者团队下午茶，用户围绕奶茶、咖啡、甜点发起拼单， ...</summary>
    <category term="拼单系统" scheme="https://aspirant02.github.io/categories/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
    <category term="拼单系统" scheme="https://aspirant02.github.io/tags/%E6%8B%BC%E5%8D%95%E7%B3%BB%E7%BB%9F/" />
  </entry>
  <entry>
    <title>SpringBoot-AOP切面编程</title>
    <link href="https://aspirant02.github.io/2026/05/12/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/" />
    <id>https://aspirant02.github.io/2026/05/12/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/</id>
    <published>2026-05-12T08:18:17Z</published>
    <updated>2026-05-12T08:18:17Z</updated>
    <content type="html">&lt;h2 id="1-AOP基础"&gt;&lt;a class="headerlink" href="#1-AOP基础" title="1. AOP基础"&gt;&lt;/a&gt;1. AOP基础&lt;/h2&gt;&lt;p&gt;学习完spring的事务管理之后，接下来我们进入到AOP的学习。 AOP也是spring框架的第二大核心，我们先来学习AOP的基础。&lt;/p&gt;
&lt;p&gt;在AOP基础这个阶段，我们首先介绍一下什么是AOP，再通过一个快速入门程序，让大家快速体验AOP程序的开发。最后再介绍AOP当中所涉及到的一些核心的概念。&lt;/p&gt;
&lt;h3 id="1-1-AOP概述"&gt;&lt;a class="headerlink" href="#1-1-AOP概述" title="1.1 AOP概述"&gt;&lt;/a&gt;1.1 AOP概述&lt;/h3&gt;&lt;p&gt;什么是AOP？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AOP英文全称：Aspect Oriented Programming（面向切面编程、面向方面编程），其实说白了，面向切面编程就是面向特定方法编程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那什么又是面向方法编程呢，为什么又需要面向方法编程呢？来我们举个例子做一个说明：&lt;/p&gt;
&lt;p&gt;比如，我们这里有一个项目，项目中开发了很多的业务功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104855599-70d041ce-a181-4a3f-8199-c7a63c627b6d.png"/&gt;&lt;/p&gt;
&lt;p&gt;然而有一些业务功能执行效率比较低，执行耗时较长，我们需要针对于这些业务方法进行优化。 那首先第一步就需要定位出执行耗时比较长的业务方法，再针对于业务方法再来进行优化。&lt;/p&gt;
&lt;p&gt;此时我们就需要统计当前这个项目当中每一个业务方法的执行耗时。那么统计每一个业务方法的执行耗时该怎么实现？&lt;/p&gt;
&lt;p&gt;可能多数人首先想到的就是在每一个业务方法运行之前，记录这个方法运行的开始时间。在这个方法运行完毕之后，再来记录这个方法运行的结束时间。拿结束时间减去开始时间，不就是这个方法的执行耗时吗？&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104855725-f857e5a9-1996-4441-86c8-cfd1347bdbba.png"/&gt;&lt;br/&gt;以上分析的实现方式是可以解决需求问题的。但是对于一个项目来讲，里面会包含很多的业务模块，每个业务模块又包含很多增删改查的方法，如果我们要在每一个模块下的业务方法中，添加记录开始时间、结束时间、计算执行耗时的代码，就会让程序员的工作变得非常繁琐。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104855829-9a430163-ef1e-4b11-b188-fedcd7a21e88.png"/&gt;&lt;br/&gt;而AOP面向方法编程，就可以做到在不改动这些原始方法的基础上，针对特定的方法进行功能的增强。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AOP的作用：在程序运行期间在不修改源代码的基础上对已有方法进行增强（无侵入性: 解耦）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们要想完成统计各个业务方法执行耗时的需求，我们只需要定义一个模板方法，将记录方法执行耗时这一部分公共的逻辑代码，定义在模板方法当中，在这个方法开始运行之前，来记录这个方法运行的开始时间，在方法结束运行的时候，再来记录方法运行的结束时间，中间就来运行原始的业务方法。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104855932-9c96f2ae-f948-4d11-8d65-c24a66f5290f.png"/&gt;&lt;br/&gt;而中间运行的原始业务方法，可能是其中的一个业务方法，比如：我们只想通过 部门管理的 list 方法的执行耗时，那就只有这一个方法是原始业务方法。  而如果，我们是先想统计所有部门管理的业务方法执行耗时，那此时，所有的部门管理的业务方法都是 原始业务方法。 &lt;strong&gt;那面向这样的指定的一个或多个方法进行编程，我们就称之为 面向切面编程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那此时，当我们再调用部门管理的 list 业务方法时啊，并不会直接执行 list 方法的逻辑，而是会执行我们所定义的 模板方法 ， 然后再模板方法中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;记录方法运行开始时间&lt;/li&gt;
&lt;li&gt;运行原始的业务方法（那此时原始的业务方法，就是 list 方法）&lt;/li&gt;
&lt;li&gt;记录方法运行结束时间，计算方法执行耗时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104856042-2626d21c-cb6a-48a5-a77d-99b539190c06.png"/&gt;&lt;br/&gt;不论，我们运行的是那个业务方法，最后其实运行的就是我们定义的模板方法，而在模板方法中，就完成了原始方法执行耗时的统计操作 。(那这样呢，我们就通过一个模板方法就完成了指定的一个或多个业务方法执行耗时的统计)&lt;/p&gt;
&lt;p&gt;而大家会发现，这个流程，我们是不是似曾相识啊？&lt;/p&gt;
&lt;p&gt;对了，就是和我们之前所学习的动态代理技术是非常类似的。 我们所说的模板方法，其实就是代理对象中所定义的方法，那代理对象中的方法以及根据对应的业务需要， 完成了对应的业务功能，当运行原始业务方法时，就会运行代理对象中的方法，从而实现统计业务方法执行耗时的操作。&lt;/p&gt;
&lt;p&gt;其实，AOP面向切面编程和OOP面向对象编程一样，它们都仅仅是一种编程思想，而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术，旨在管理bean对象的过程中底层使用动态代理机制，对特定的方法进行编程(功能增强)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AOP的优势：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;减少重复代码&lt;/li&gt;
&lt;li&gt;提高开发效率&lt;/li&gt;
&lt;li&gt;维护方便&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3 id="1-2-AOP快速入门"&gt;&lt;a class="headerlink" href="#1-2-AOP快速入门" title="1.2 AOP快速入门"&gt;&lt;/a&gt;1.2 AOP快速入门&lt;/h3&gt;&lt;p&gt;在了解了什么是AOP后，我们下面通过一个快速入门程序，体验下AOP的开发，并掌握Spring中AOP的开发步骤。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需求：&lt;/strong&gt;统计各个业务层方法执行耗时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导入依赖：在pom.xml中导入AOP的依赖&lt;/li&gt;
&lt;li&gt;编写AOP程序：针对于特定方法根据业务需要进行编程&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;为演示方便，可以自建新项目或导入提供的&lt;code&gt;springboot-aop-quickstart&lt;/code&gt;项目工程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;pom.xml&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight xml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;spring-boot-starter-aop&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;AOP程序：TimeAspect&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt; &lt;span class="comment"&gt;//当前类为切面类&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;TimeAspect&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Around("execution(* com.itheima.service.*.*(..))")&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Object &lt;span class="title function_"&gt;recordTime&lt;/span&gt;&lt;span class="params"&gt;(ProceedingJoinPoint pjp)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Throwable {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//记录方法执行开始时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;begin&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; System.currentTimeMillis();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//执行原始方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;result&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; pjp.proceed();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//记录方法执行结束时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;end&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; System.currentTimeMillis();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//计算方法执行耗时&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(pjp.getSignature()+&lt;span class="string"&gt;"执行耗时: {}毫秒"&lt;/span&gt;,end-begin);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; result;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务测试程序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查询3号部门信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104856159-864dc44b-c2bf-4d6d-9aee-3cba91412f1b.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104856278-6384883d-781a-4564-a869-e1b2745f5a9b.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们可以再测试下：查询所有部门信息（同样执行AOP程序）&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104856384-09f1e105-f091-4334-a275-64f8f9ac2c41.png"/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们通过AOP入门程序完成了业务方法执行耗时的统计，那其实AOP的功能远不止于此，常见的应用场景如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;记录系统的操作日志&lt;/li&gt;
&lt;li&gt;权限控制&lt;/li&gt;
&lt;li&gt;事务管理：我们前面所讲解的Spring事务管理，底层其实也是通过AOP来实现的，只要添加@Transactional注解之后，AOP程序自动会在原始方法运行前先来开启事务，在原始方法运行完毕之后提交或回滚事务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些都是AOP应用的典型场景。&lt;/p&gt;
&lt;p&gt;通过入门程序，我们也应该感受到了AOP面向切面编程的一些优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码无侵入：没有修改原始的业务方法，就已经对原始的业务方法进行了功能的增强或者是功能的改变 &lt;/li&gt;
&lt;li&gt;减少了重复代码 &lt;/li&gt;
&lt;li&gt;提高开发效率 &lt;/li&gt;
&lt;li&gt;维护方便&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="1-3-AOP核心概念"&gt;&lt;a class="headerlink" href="#1-3-AOP核心概念" title="1.3 AOP核心概念"&gt;&lt;/a&gt;1.3 AOP核心概念&lt;/h3&gt;&lt;p&gt;通过SpringAOP的快速入门，感受了一下AOP面向切面编程的开发方式。下面我们再来学习AOP当中涉及到的一些核心概念。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 连接点：JoinPoint&lt;/strong&gt;，可以被AOP控制的方法（暗含方法执行时的相关信息）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;连接点指的是可以被aop控制的方法。例如：入门程序当中所有的业务方法都是可以被aop控制的方法。

![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706104856530-87584748-a1cb-4c8b-ad78-798700d025c3.png#averageHue=%23fdd7d6&amp;amp;id=YCiZK&amp;amp;originHeight=593&amp;amp;originWidth=614&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)

在SpringAOP提供的JoinPoint当中，封装了连接点方法在执行时的相关信息。（后面会有具体的讲解）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 通知：Advice&lt;/strong&gt;，指哪些重复的逻辑，也就是共性功能（最终体现为一个方法）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;在入门程序中是需要统计各个业务方法的执行耗时的，此时我们就需要在这些业务方法运行开始之前，先记录这个方法运行的开始时间，在每一个业务方法运行结束的时候，再来记录这个方法运行的结束时间。

但是在AOP面向切面编程当中，我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑，也就是共性的功能。

![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706104856655-cbf83365-e484-487b-8992-6b5e17c4e40a.png#averageHue=%23cceddb&amp;amp;id=arddk&amp;amp;originHeight=365&amp;amp;originWidth=895&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 切入点：PointCut&lt;/strong&gt;，匹配连接点的条件，通知仅会在切入点方法执行时被应用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;在通知当中，我们所定义的共性功能到底要应用在哪些方法上？此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。

在aop的开发当中，我们通常会通过一个切入点表达式来描述切入点(后面会有详解)。

![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706104856760-a9cda4c5-9303-4a38-a731-42d066b94b1a.png#averageHue=%23cceddb&amp;amp;id=cRcvX&amp;amp;originHeight=386&amp;amp;originWidth=890&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)

假如：切入点表达式改为DeptServiceImpl.list()，此时就代表仅仅只有list这一个方法是切入点。只有list()方法在运行的时候才会应用通知。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 切面：Aspect&lt;/strong&gt;，描述通知与切入点的对应关系（通知+切入点）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;当通知和切入点结合在一起，就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法，在什么时候执行什么样的操作。

![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706104856928-891fee85-6bb0-4b14-a17b-763dac0fa860.png#averageHue=%23e2dcb6&amp;amp;id=ERZEv&amp;amp;originHeight=468&amp;amp;originWidth=966&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)

切面所在的类，我们一般称为**切面类**（被@Aspect注解标识的类）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. 目标对象：Target&lt;/strong&gt;，通知所应用的对象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;目标对象指的就是通知所应用的对象，我们就称之为目标对象。

![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706104857091-b54b27f4-3f73-42c4-956b-bc35d0862594.png#averageHue=%23fddad9&amp;amp;id=U6gFT&amp;amp;originHeight=630&amp;amp;originWidth=636&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AOP的核心概念我们介绍完毕之后，接下来我们再来分析一下我们所定义的通知是如何与目标对象结合在一起，对目标对象当中的方法进行功能增强的。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857289-1203a2de-c045-4cdb-b025-d4d7c3763483.png"/&gt;&lt;br/&gt;Spring的AOP底层是基于动态代理技术来实现的，也就是说在程序运行的时候，会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。&lt;/p&gt;
&lt;h2 id="2-AOP进阶"&gt;&lt;a class="headerlink" href="#2-AOP进阶" title="2. AOP进阶"&gt;&lt;/a&gt;2. AOP进阶&lt;/h2&gt;&lt;p&gt;AOP的基础知识学习完之后，下面我们对AOP当中的各个细节进行详细的学习。主要分为4个部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通知类型&lt;/li&gt;
&lt;li&gt;通知顺序&lt;/li&gt;
&lt;li&gt;切入点表达式&lt;/li&gt;
&lt;li&gt;连接点&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们先来学习第一部分通知类型。&lt;/p&gt;
&lt;h3 id="2-1-通知类型"&gt;&lt;a class="headerlink" href="#2-1-通知类型" title="2.1 通知类型"&gt;&lt;/a&gt;2.1 通知类型&lt;/h3&gt;&lt;p&gt;在入门程序当中，我们已经使用了一种功能最为强大的通知类型：Around环绕通知。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Around("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; Object &lt;span class="title function_"&gt;recordTime&lt;/span&gt;&lt;span class="params"&gt;(ProceedingJoinPoint pjp)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Throwable {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//记录方法执行开始时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;begin&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; System.currentTimeMillis();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//执行原始方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;result&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; pjp.proceed();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//记录方法执行结束时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;end&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; System.currentTimeMillis();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//计算方法执行耗时&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    log.info(pjp.getSignature()+&lt;span class="string"&gt;"执行耗时: {}毫秒"&lt;/span&gt;,end-begin);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; result;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;只要我们在通知方法上加上了@Around注解，就代表当前通知是一个环绕通知。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Spring中AOP的通知类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Around：环绕通知，此注解标注的通知方法在目标方法前、后都被执行&lt;/li&gt;
&lt;li&gt;@Before：前置通知，此注解标注的通知方法在目标方法前被执行&lt;/li&gt;
&lt;li&gt;&lt;a href="/After"&gt;@After &lt;/a&gt; ：后置通知，此注解标注的通知方法在目标方法后被执行，无论是否有异常都会执行 &lt;/li&gt;
&lt;li&gt;&lt;a href="/AfterReturning"&gt;@AfterReturning &lt;/a&gt; ： 返回后通知，此注解标注的通知方法在目标方法后被执行，有异常不会执行 &lt;/li&gt;
&lt;li&gt;&lt;a href="/AfterThrowing"&gt;@AfterThrowing &lt;/a&gt; ： 异常后通知，此注解标注的通知方法发生异常后执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面我们通过代码演示，来加深对于不同通知类型的理解：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect1&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//环绕通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Around("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Object &lt;span class="title function_"&gt;around&lt;/span&gt;&lt;span class="params"&gt;(ProceedingJoinPoint proceedingJoinPoint)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Throwable {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"around before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//调用目标对象的原始方法执行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;result&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; proceedingJoinPoint.proceed();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//原始方法如果执行时有异常，环绕通知中的后置代码不会在执行了&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"around after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; result;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//返回后通知（程序在正常执行的情况下，会执行的后置通知）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@AfterReturning("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;afterReturning&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"afterReturning ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//异常通知（程序在出现异常的情况下，执行的后置通知）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@AfterThrowing("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;afterThrowing&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"afterThrowing ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务，进行测试：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 没有异常情况下：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用postman测试查询所有部门数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857403-13ec42d9-d7f8-4289-a93e-a3fcac19e549.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看idea中控制台日志输出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857527-2825d422-39cd-4787-8871-4edb90114857.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;程序没有发生异常的情况下，@AfterThrowing标识的通知方法不会执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;2. 出现异常情况下：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;修改DeptServiceImpl业务实现类中的代码： 添加异常&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; List&amp;lt;Dept&amp;gt; &lt;span class="title function_"&gt;list&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        List&amp;lt;Dept&amp;gt; deptList = deptMapper.list();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;num&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;10&lt;/span&gt;/&lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; deptList;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//省略其他代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务，测试发生异常情况下通知的执行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看idea中控制台日志输出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857667-fb1ec8a0-4e26-4e50-a1c3-deda0d25a12c.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;程序发生异常的情况下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@AfterReturning标识的通知方法不会执行，@AfterThrowing标识的通知方法执行了 &lt;/li&gt;
&lt;li&gt;@Around环绕通知中原始方法调用时有异常，通知中的环绕后的代码逻辑也不会在执行了 （因为原始方法调用已经出异常了）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;在使用通知时的注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行，其他通知不需要考虑目标方法执行&lt;/li&gt;
&lt;li&gt;@Around环绕通知方法的返回值，必须指定为Object，来接收原始方法的返回值，否则原始方法执行完毕，是获取不到返回值的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;五种常见的通知类型，我们已经测试完毕了，此时我们再来看一下刚才所编写的代码，有什么问题吗？&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//环绕通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Around("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//返回后通知（程序在正常执行的情况下，会执行的后置通知）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@AfterReturning("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//异常通知（程序在出现异常的情况下，执行的后置通知）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@AfterThrowing("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们发现啊，每一个注解里面都指定了切入点表达式，而且这些切入点表达式都一模一样。此时我们的代码当中就存在了大量的重复性的切入点表达式，假如此时切入点表达式需要变动，就需要将所有的切入点表达式一个一个的来改动，就变得非常繁琐了。&lt;/p&gt;
&lt;p&gt;怎么来解决这个切入点表达式重复的问题？ 答案就是：&lt;strong&gt;抽取&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Spring提供了@PointCut注解，该注解的作用是将公共的切入点表达式抽取出来，需要用到时引用该切入点表达式即可。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect1&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//切入点方法（公共的切入点表达式）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Pointcut("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;pt&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知（引用切入点）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//环绕通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Around("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Object &lt;span class="title function_"&gt;around&lt;/span&gt;&lt;span class="params"&gt;(ProceedingJoinPoint proceedingJoinPoint)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Throwable {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"around before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//调用目标对象的原始方法执行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;result&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; proceedingJoinPoint.proceed();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//原始方法在执行时：发生异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//后续代码不在执行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"around after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; result;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//返回后通知（程序在正常执行的情况下，会执行的后置通知）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@AfterReturning("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;afterReturning&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"afterReturning ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//异常通知（程序在出现异常的情况下，执行的后置通知）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@AfterThrowing("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;afterThrowing&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"afterThrowing ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;需要注意的是：当切入点方法使用private修饰时，仅能在当前切面类中引用该表达式， 当外部其他切面类中也要引用当前类中的切入点表达式，就需要把private改为public，而在引用的时候，具体的语法为：&lt;/p&gt;
&lt;p&gt;全类名.方法名()，具体形式如下：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect2&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//引用MyAspect1切面类中的切入点表达式&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("com.itheima.aspect.MyAspect1.pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect2 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id="2-2-通知顺序"&gt;&lt;a class="headerlink" href="#2-2-通知顺序" title="2.2 通知顺序"&gt;&lt;/a&gt;2.2 通知顺序&lt;/h3&gt;&lt;p&gt;讲解完了Spring中AOP所支持的5种通知类型之后，接下来我们再来研究通知的执行顺序。&lt;/p&gt;
&lt;p&gt;当在项目开发当中，我们定义了多个切面类，而多个切面类中多个切入点都匹配到了同一个目标方法。此时当目标方法在运行的时候，这多个切面类当中的这些通知方法都会运行。&lt;/p&gt;
&lt;p&gt;此时我们就有一个疑问，这多个通知方法到底哪个先运行，哪个后运行？ 下面我们通过程序来验证（这里呢，我们就定义两种类型的通知进行测试，一种是前置通知@Before，一种是后置通知@After）&lt;/p&gt;
&lt;p&gt;定义多个切面类：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect2&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect2 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect2 -&amp;gt; after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect3&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect3 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect3 -&amp;gt;  after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect4&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect4 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect4 -&amp;gt; after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务，测试通知的执行顺序：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;备注：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把DeptServiceImpl实现类中模拟异常的代码删除或注释掉。 &lt;/li&gt;
&lt;li&gt;注释掉其他切面类(把@Aspect注释即可)，仅保留MyAspect2、MyAspect3、MyAspect4 ，这样就可以清晰看到执行的结果，而不被其他切面类干扰。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用postman测试查询所有部门数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857403-13ec42d9-d7f8-4289-a93e-a3fcac19e549.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看idea中控制台日志输出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857773-5073aecb-c5ff-4ca1-86dd-9ed71a9acd20.png"/&gt;&lt;/p&gt;
&lt;p&gt;通过以上程序运行可以看出在不同切面类中，默认按照切面类的类名字母排序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目标方法前的通知方法：字母排名靠前的先执行&lt;/li&gt;
&lt;li&gt;目标方法后的通知方法：字母排名靠前的后执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果我们想控制通知的执行顺序有两种方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;修改切面类的类名（这种方式非常繁琐、而且不便管理）&lt;/li&gt;
&lt;li&gt;使用Spring提供的@Order注解&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用@Order注解，控制通知的执行顺序：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Order(2)&lt;/span&gt;  &lt;span class="comment"&gt;//切面类的执行顺序（前置通知：数字越小先执行; 后置通知：数字越小越后执行）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect2&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect2 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知 &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect2 -&amp;gt; after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Order(3)&lt;/span&gt;  &lt;span class="comment"&gt;//切面类的执行顺序（前置通知：数字越小先执行; 后置通知：数字越小越后执行）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect3&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect3 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect3 -&amp;gt;  after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Order(1)&lt;/span&gt; &lt;span class="comment"&gt;//切面类的执行顺序（前置通知：数字越小先执行; 后置通知：数字越小越后执行）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect4&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect4 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("execution(* com.itheima.service.*.*(..))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect4 -&amp;gt; after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务，测试通知执行顺序：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857891-635c373b-284d-4a51-b4fb-82f646c24ad1.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;通知的执行顺序大家主要知道两点即可：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不同的切面类当中，默认情况下通知的执行顺序是与切面类的类名字母排序是有关系的&lt;/li&gt;
&lt;li&gt;可以在切面类上面加上@Order注解，来控制不同的切面类通知的执行顺序&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3 id="2-3-切入点表达式"&gt;&lt;a class="headerlink" href="#2-3-切入点表达式" title="2.3 切入点表达式"&gt;&lt;/a&gt;2.3 切入点表达式&lt;/h3&gt;&lt;p&gt;从AOP的入门程序到现在，我们一直都在使用切入点表达式来描述切入点。下面我们就来详细的介绍一下切入点表达式的具体写法。&lt;/p&gt;
&lt;p&gt;切入点表达式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;描述切入点方法的一种表达式 &lt;/li&gt;
&lt;li&gt;作用：主要用来决定项目中的哪些方法需要加入通知 &lt;/li&gt;
&lt;li&gt;常见形式： &lt;ol&gt;
&lt;li&gt;execution(……)：根据方法的签名来匹配&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104857994-9b31731c-b630-4bda-8749-4be55974d873.png"/&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;@annotation(……) ：根据注解匹配&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104858144-ebdeb86c-c20e-4242-aa12-407aa3415c07.png"/&gt;&lt;/p&gt;
&lt;p&gt;首先我们先学习第一种最为常见的execution切入点表达式。&lt;/p&gt;
&lt;h4 id="2-3-1-execution"&gt;&lt;a class="headerlink" href="#2-3-1-execution" title="2.3.1 execution"&gt;&lt;/a&gt;2.3.1 execution&lt;/h4&gt;&lt;p&gt;execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配，语法为：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中带&lt;code&gt;?&lt;/code&gt;的表示可以省略的部分&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;访问修饰符：可省略（比如: public、protected） &lt;/li&gt;
&lt;li&gt;包名.类名： 可省略 &lt;/li&gt;
&lt;li&gt;throws 异常：可省略（注意是方法上声明抛出的异常，不是实际抛出的异常）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;可以使用通配符描述切入点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; ：单个独立的任意符号，可以通配任意返回值、包名、类名、方法名、任意类型的一个参数，也可以通配包、类、方法名的一部分 &lt;/li&gt;
&lt;li&gt;&lt;code&gt;..&lt;/code&gt; ：多个连续的任意符号，可以通配任意层级的包，或任意类型、任意个数的参数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;切入点表达式的语法规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;方法的访问修饰符可以省略&lt;/li&gt;
&lt;li&gt;返回值可以使用&lt;code&gt;*&lt;/code&gt;号代替（任意返回值类型）&lt;/li&gt;
&lt;li&gt;包名可以使用&lt;code&gt;*&lt;/code&gt;号代替，代表任意包（一层包使用一个&lt;code&gt;*&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;..&lt;/code&gt;配置包名，标识此包以及此包下的所有子包&lt;/li&gt;
&lt;li&gt;类名可以使用&lt;code&gt;*&lt;/code&gt;号代替，标识任意类&lt;/li&gt;
&lt;li&gt;方法名可以使用&lt;code&gt;*&lt;/code&gt;号代替，表示任意方法&lt;/li&gt;
&lt;li&gt;可以使用 &lt;code&gt;*&lt;/code&gt;  配置参数，一个任意类型的参数&lt;/li&gt;
&lt;li&gt;可以使用&lt;code&gt;..&lt;/code&gt; 配置参数，任意个任意类型的参数&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;切入点表达式示例&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;省略方法的修饰符号 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(&lt;span class="keyword"&gt;void&lt;/span&gt; com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;*&lt;/code&gt;代替返回值类型 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;*&lt;/code&gt;代替包名（一层包使用一个&lt;code&gt;*&lt;/code&gt;） &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;..&lt;/code&gt;省略包名 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com..DeptServiceImpl.delete(java.lang.Integer))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;*&lt;/code&gt;代替类名 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com..*.delete(java.lang.Integer))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;*&lt;/code&gt;代替方法名 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com..*.*(java.lang.Integer))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;*&lt;/code&gt; 代替参数 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用&lt;code&gt;..&lt;/code&gt;省略参数 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com..*.*(..))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据业务需要，可以使用 且（&amp;amp;&amp;amp;）、或（||）、非（!） 来组合比较复杂的切入点表达式。 &lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;切入点表达式的书写建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有业务方法名在命名时尽量规范，方便切入点表达式快速匹配。如：查询类方法都是 find 开头，更新类方法都是update开头 &lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//业务类&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; List&amp;lt;Dept&amp;gt; &lt;span class="title function_"&gt;findAllDept&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//省略代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Dept &lt;span class="title function_"&gt;findDeptById&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//省略代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;updateDeptById&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//省略代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;updateDeptByMoreCondition&lt;/span&gt;&lt;span class="params"&gt;(Dept dept)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//省略代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//其他代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//匹配DeptServiceImpl类中以find开头的方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;execution(* com.itheima.service.impl.DeptServiceImpl.find*(..))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;描述切入点方法通常基于接口描述，而不是直接描述实现类，增强拓展性 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com.itheima.service.DeptService.*(..))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;在满足业务需要的前提下，尽量缩小切入点的匹配范围。如：包名匹配尽量不使用 ..，使用 * 匹配单个包 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;execution(* com.itheima.*.*.DeptServiceImpl.find*(..))&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-3-2-annotation"&gt;&lt;a class="headerlink" href="#2-3-2-annotation" title="2.3.2 @annotation "&gt;&lt;/a&gt;2.3.2 &lt;a href="/annotation"&gt;@annotation &lt;/a&gt;&lt;/h4&gt;&lt;p&gt;已经学习了execution切入点表达式的语法。那么如果我们要匹配多个无规则的方法，比如：list()和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。而在之前我们是将两个切入点表达式组合在了一起完成的需求，这个是比较繁琐的。&lt;/p&gt;
&lt;p&gt;我们可以借助于另一种切入点表达式annotation来描述这一类的切入点，从而来简化切入点表达式的书写。&lt;/p&gt;
&lt;p&gt;实现步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;编写自定义注解 &lt;/li&gt;
&lt;li&gt;在业务类要做为连接点的方法上添加自定义注解&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;自定义注解&lt;/strong&gt;：MyLog&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Target(ElementType.METHOD)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Retention(RetentionPolicy.RUNTIME)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="meta"&gt;@interface&lt;/span&gt; MyLog {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;业务类&lt;/strong&gt;：DeptServiceImpl&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@MyLog&lt;/span&gt; &lt;span class="comment"&gt;//自定义注解（表示：当前方法属于目标方法）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; List&amp;lt;Dept&amp;gt; &lt;span class="title function_"&gt;list&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        List&amp;lt;Dept&amp;gt; deptList = deptMapper.list();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//int num = 10/0;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; deptList;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@MyLog&lt;/span&gt;  &lt;span class="comment"&gt;//自定义注解（表示：当前方法属于目标方法）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//1. 删除部门&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.delete(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;save&lt;/span&gt;&lt;span class="params"&gt;(Dept dept)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        dept.setCreateTime(LocalDateTime.now());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        dept.setUpdateTime(LocalDateTime.now());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.save(dept);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Dept &lt;span class="title function_"&gt;getById&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; deptMapper.getById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;update&lt;/span&gt;&lt;span class="params"&gt;(Dept dept)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        dept.setUpdateTime(LocalDateTime.now());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.update(dept);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;切面类&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect6&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//针对list方法、delete方法进行前置通知和后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("@annotation(com.itheima.anno.MyLog)")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect6 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@After("@annotation(com.itheima.anno.MyLog)")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"MyAspect6 -&amp;gt; after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重启SpringBoot服务，测试查询所有部门数据，查看控制台日志：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104858263-d9b42481-0e38-4466-9b40-07a515518239.png"/&gt;&lt;/p&gt;
&lt;p&gt;到此我们两种常见的切入点表达式我已经介绍完了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;execution切入点表达式 &lt;ul&gt;
&lt;li&gt;根据我们所指定的方法的描述信息来匹配切入点方法，这种方式也是最为常用的一种方式&lt;/li&gt;
&lt;li&gt;如果我们要匹配的切入点方法的方法名不规则，或者有一些比较特殊的需求，通过execution切入点表达式描述比较繁琐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;annotation 切入点表达式 &lt;ul&gt;
&lt;li&gt;基于注解的方式来匹配切入点方法。这种方式虽然多一步操作，我们需要自定义一个注解，但是相对来比较灵活。我们需要匹配哪个方法，就在方法上加上对应的注解就可以了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-4-连接点"&gt;&lt;a class="headerlink" href="#2-4-连接点" title="2.4 连接点"&gt;&lt;/a&gt;2.4 连接点&lt;/h3&gt;&lt;p&gt;讲解完了切入点表达式之后，接下来我们再来讲解最后一个部分连接点。我们前面在讲解AOP核心概念的时候，我们提到过什么是连接点，连接点可以简单理解为可以被AOP控制的方法。&lt;/p&gt;
&lt;p&gt;我们目标对象当中所有的方法是不是都是可以被AOP控制的方法。而在SpringAOP当中，连接点又特指方法的执行。&lt;/p&gt;
&lt;p&gt;在Spring中用JoinPoint抽象了连接点，用它可以获得方法执行时的相关信息，如目标类名、方法名、方法参数等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于@Around通知，获取连接点信息只能使用ProceedingJoinPoint类型 &lt;/li&gt;
&lt;li&gt;对于其他四种通知，获取连接点信息只能使用JoinPoint，它是ProceedingJoinPoint的父类型&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;MyAspect7&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Pointcut("@annotation(com.itheima.anno.MyLog)")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;pt&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;before&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(joinPoint.getSignature().getName() + &lt;span class="string"&gt;" MyAspect7 -&amp;gt; before ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//后置通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Before("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;after&lt;/span&gt;&lt;span class="params"&gt;(JoinPoint joinPoint)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(joinPoint.getSignature().getName() + &lt;span class="string"&gt;" MyAspect7 -&amp;gt; after ..."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//环绕通知&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Around("pt()")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Object &lt;span class="title function_"&gt;around&lt;/span&gt;&lt;span class="params"&gt;(ProceedingJoinPoint pjp)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Throwable {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//获取目标类名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;name&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; pjp.getTarget().getClass().getName();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"目标类名：{}"&lt;/span&gt;,name);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//目标方法名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;methodName&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; pjp.getSignature().getName();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"目标方法名：{}"&lt;/span&gt;,methodName);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//获取方法执行时需要的参数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Object[] args = pjp.getArgs();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"目标方法参数：{}"&lt;/span&gt;, Arrays.toString(args));&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//执行原始方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;returnValue&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; pjp.proceed();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; returnValue;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务，执行查询部门数据的功能：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104858364-cae09b28-157a-448f-9796-03ed5039f156.png"/&gt;&lt;/p&gt;
&lt;h2 id="3-AOP案例"&gt;&lt;a class="headerlink" href="#3-AOP案例" title="3. AOP案例"&gt;&lt;/a&gt;3. AOP案例&lt;/h2&gt;&lt;p&gt;SpringAOP的相关知识我们就已经全部学习完毕了。最后我们要通过一个案例来对AOP进行一个综合的应用。&lt;/p&gt;
&lt;h3 id="3-1-需求"&gt;&lt;a class="headerlink" href="#3-1-需求" title="3.1 需求"&gt;&lt;/a&gt;3.1 需求&lt;/h3&gt;&lt;p&gt;需求：将案例中增、删、改相关接口的操作日志记录到数据库表中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;就是当访问部门管理和员工管理当中的增、删、改相关功能接口时，需要详细的操作日志，并保存在数据表中，便于后期数据追踪。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;操作日志信息包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;所记录的日志信息包括当前接口的操作人是谁操作的，什么时间点操作的，以及访问的是哪个类当中的哪个方法，在访问这个方法的时候传入进来的参数是什么，访问这个方法最终拿到的返回值是什么，以及整个接口方法的运行时长是多长时间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="3-2-分析"&gt;&lt;a class="headerlink" href="#3-2-分析" title="3.2 分析"&gt;&lt;/a&gt;3.2 分析&lt;/h3&gt;&lt;p&gt;问题1：项目当中增删改相关的方法是不是有很多？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;很多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;问题2：我们需要针对每一个功能接口方法进行修改，在每一个功能接口当中都来记录这些操作日志吗？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这种做法比较繁琐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上两个问题的解决方案：可以使用AOP解决(每一个增删改功能接口中要实现的记录操作日志的逻辑代码是相同)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以把这部分记录操作日志的通用的、重复性的逻辑代码抽取出来定义在一个通知方法当中，我们通过AOP面向切面编程的方式，在不改动原始功能的基础上来对原始的功能进行增强。目前我们所增强的功能就是来记录操作日志，所以也可以使用AOP的技术来实现。使用AOP的技术来实现也是最为简单，最为方便的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;问题3：既然要基于AOP面向切面编程的方式来完成的功能，那么我们要使用 AOP五种通知类型当中的哪种通知类型？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;答案：环绕通知&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;所记录的操作日志当中包括：操作人、操作时间，访问的是哪个类、哪个方法、方法运行时参数、方法的返回值、方法的运行时长。&lt;/p&gt;
&lt;p&gt;方法返回值，是在原始方法执行后才能获取到的。&lt;/p&gt;
&lt;p&gt;方法的运行时长，需要原始方法运行之前记录开始时间，原始方法运行之后记录结束时间。通过计算获得方法的执行耗时。&lt;/p&gt;
&lt;p&gt;基于以上的分析我们确定要使用Around环绕通知。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;问题4：最后一个问题，切入点表达式我们该怎么写？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;答案：使用annotation来描述表达式&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;要匹配业务接口当中所有的增删改的方法，而增删改方法在命名上没有共同的前缀或后缀。此时如果使用execution切入点表达式也可以，但是会比较繁琐。 当遇到增删改的方法名没有规律时，就可以使用 annotation切入点表达式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="3-3-步骤"&gt;&lt;a class="headerlink" href="#3-3-步骤" title="3.3 步骤"&gt;&lt;/a&gt;3.3 步骤&lt;/h3&gt;&lt;p&gt;简单分析了一下大概的实现思路后，接下来我们就要来完成案例了。案例的实现步骤其实就两步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;准备工作 &lt;ol&gt;
&lt;li&gt;引入AOP的起步依赖&lt;/li&gt;
&lt;li&gt;导入资料中准备好的数据库表结构，并引入对应的实体类&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;编码实现 &lt;ol&gt;
&lt;li&gt;自定义注解&lt;a href="/Log"&gt;@Log &lt;/a&gt; &lt;/li&gt;
&lt;li&gt;定义切面类，完成记录操作日志的逻辑&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-4-实现"&gt;&lt;a class="headerlink" href="#3-4-实现" title="3.4 实现"&gt;&lt;/a&gt;3.4 实现&lt;/h3&gt;&lt;h4 id="3-4-1-准备工作"&gt;&lt;a class="headerlink" href="#3-4-1-准备工作" title="3.4.1 准备工作"&gt;&lt;/a&gt;3.4.1 准备工作&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;AOP起步依赖&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight xml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;&amp;lt;!--AOP起步依赖--&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;spring-boot-starter-aop&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="2"&gt;
&lt;li&gt;导入资料中准备好的数据库表结构，并引入对应的实体类&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;数据表&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;-- 操作日志表&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;create table operate_log(&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    id int unsigned primary key auto_increment comment 'ID',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    operate_user int unsigned comment '操作人',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    operate_time datetime comment '操作时间',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    class_name varchar(100) comment '操作的类名',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    method_name varchar(100) comment '操作的方法名',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    method_params varchar(1000) comment '方法参数',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    return_value varchar(2000) comment '返回值',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    cost_time bigint comment '方法执行耗时, 单位:ms'&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;) comment '操作日志表';&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;实体类&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//操作日志实体类&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Data&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@NoArgsConstructor&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@AllArgsConstructor&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;OperateLog&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; Integer id; &lt;span class="comment"&gt;//主键ID&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; Integer operateUser; &lt;span class="comment"&gt;//操作人ID&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LocalDateTime operateTime; &lt;span class="comment"&gt;//操作时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String className; &lt;span class="comment"&gt;//操作类名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String methodName; &lt;span class="comment"&gt;//操作方法名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String methodParams; &lt;span class="comment"&gt;//操作方法参数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String returnValue; &lt;span class="comment"&gt;//操作方法返回值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; Long costTime; &lt;span class="comment"&gt;//操作耗时&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Mapper接口&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Mapper&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;interface&lt;/span&gt; &lt;span class="title class_"&gt;OperateLogMapper&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//插入日志数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;insert&lt;/span&gt;&lt;span class="params"&gt;(OperateLog log)&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h4 id="3-4-2-编码实现"&gt;&lt;a class="headerlink" href="#3-4-2-编码实现" title="3.4.2 编码实现"&gt;&lt;/a&gt;3.4.2 编码实现&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;自定义注解&lt;a href="/Log"&gt;@Log &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; * 自定义Log注解&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Target({ElementType.METHOD})&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Documented&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Retention(RetentionPolicy.RUNTIME)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="meta"&gt;@interface&lt;/span&gt; Log {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;修改业务实现类，在增删改业务方法上添加@Log注解&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;EmpServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;EmpService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpMapper empMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Log&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;update&lt;/span&gt;&lt;span class="params"&gt;(Emp emp)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        emp.setUpdateTime(LocalDateTime.now()); &lt;span class="comment"&gt;//更新修改时间为当前时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.update(emp);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Log&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;save&lt;/span&gt;&lt;span class="params"&gt;(Emp emp)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//补全数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        emp.setCreateTime(LocalDateTime.now());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        emp.setUpdateTime(LocalDateTime.now());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//调用添加方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.insert(emp);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Log&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(List&amp;lt;Integer&amp;gt; ids)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.delete(ids);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//省略其他代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;以同样的方式，修改EmpServiceImpl业务类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义切面类，完成记录操作日志的逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;53&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;54&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Aspect&lt;/span&gt; &lt;span class="comment"&gt;//切面类&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LogAspect&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; HttpServletRequest request;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; OperateLogMapper operateLogMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Around("@annotation(com.itheima.anno.Log)")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Object &lt;span class="title function_"&gt;recordLog&lt;/span&gt;&lt;span class="params"&gt;(ProceedingJoinPoint joinPoint)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Throwable {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//操作人ID - 当前登录员工ID&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//获取请求头中的jwt令牌, 解析令牌&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;jwt&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; request.getHeader(&lt;span class="string"&gt;"token"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Claims&lt;/span&gt; &lt;span class="variable"&gt;claims&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JwtUtils.parseJWT(jwt);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Integer&lt;/span&gt; &lt;span class="variable"&gt;operateUser&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (Integer) claims.get(&lt;span class="string"&gt;"id"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//操作时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;LocalDateTime&lt;/span&gt; &lt;span class="variable"&gt;operateTime&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; LocalDateTime.now();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//操作类名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;className&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; joinPoint.getTarget().getClass().getName();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//操作方法名&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;methodName&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; joinPoint.getSignature().getName();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//操作方法参数&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Object[] args = joinPoint.getArgs();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;methodParams&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Arrays.toString(args);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;begin&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; System.currentTimeMillis();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//调用原始目标方法运行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;result&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; joinPoint.proceed();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;long&lt;/span&gt; &lt;span class="variable"&gt;end&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; System.currentTimeMillis();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//方法返回值&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;returnValue&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JSONObject.toJSONString(result);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//操作耗时&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Long&lt;/span&gt; &lt;span class="variable"&gt;costTime&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; end - begin;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//记录操作日志&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;OperateLog&lt;/span&gt; &lt;span class="variable"&gt;operateLog&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;OperateLog&lt;/span&gt;(&lt;span class="literal"&gt;null&lt;/span&gt;,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        operateLogMapper.insert(operateLog);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"AOP记录操作日志: {}"&lt;/span&gt; , operateLog);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; result;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;代码实现细节： 获取request对象，从请求头中获取到jwt令牌，解析令牌获取出当前用户的id。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;重启SpringBoot服务，测试操作日志记录功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加一个新的部门&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104858468-c9a6a6c9-f7b2-48ee-9172-4b2a1e415105.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据表&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-AOP%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/1706104858577-0808df11-8784-4677-a481-682a2b8e1069.png"/&gt;&lt;/p&gt;
</content>
    <summary type="html">SpringBoot-AOP切面编程 ...</summary>
    <category term="SpringBoot" scheme="https://aspirant02.github.io/categories/SpringBoot/" />
    <category term="SpringBoot" scheme="https://aspirant02.github.io/tags/SpringBoot/" />
    <category term="Java开发" scheme="https://aspirant02.github.io/tags/Java%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>SpringBoot-事务管理</title>
    <link href="https://aspirant02.github.io/2026/05/11/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/" />
    <id>https://aspirant02.github.io/2026/05/11/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/</id>
    <published>2026-05-11T03:44:53Z</published>
    <updated>2026-05-11T03:44:53Z</updated>
    <content type="html">&lt;h2 id="1-事务管理"&gt;&lt;a class="headerlink" href="#1-事务管理" title="1. 事务管理"&gt;&lt;/a&gt;1. 事务管理&lt;/h2&gt;&lt;h3 id="1-1-事务回顾"&gt;&lt;a class="headerlink" href="#1-1-事务回顾" title="1.1 事务回顾"&gt;&lt;/a&gt;1.1 事务回顾&lt;/h3&gt;&lt;p&gt;在数据库阶段我们已学习过事务了，我们讲到：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事务&lt;/strong&gt;是一组操作的集合，它是一个不可分割的工作单位。事务会把所有的操作作为一个整体，一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功，要么同时失败。&lt;/p&gt;
&lt;p&gt;怎么样来控制这组操作，让这组操作同时成功或同时失败呢？此时就要涉及到事务的具体操作了。&lt;/p&gt;
&lt;p&gt;事务的操作主要有三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开启事务（一组操作开始前，开启事务）：start transaction / begin ;&lt;/li&gt;
&lt;li&gt;提交事务（这组操作全部成功后，提交事务）：commit ;&lt;/li&gt;
&lt;li&gt;回滚事务（中间任何一个操作出现异常，回滚事务）：rollback ;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="1-2-Spring事务管理"&gt;&lt;a class="headerlink" href="#1-2-Spring事务管理" title="1.2 Spring事务管理"&gt;&lt;/a&gt;1.2 Spring事务管理&lt;/h3&gt;&lt;h4 id="1-2-1-案例"&gt;&lt;a class="headerlink" href="#1-2-1-案例" title="1.2.1 案例"&gt;&lt;/a&gt;1.2.1 案例&lt;/h4&gt;&lt;p&gt;简单的回顾了事务的概念以及事务的基本操作之后，接下来我们看一个事务管理案例：解散部门 （解散部门就是删除部门）&lt;/p&gt;
&lt;p&gt;需求：当部门解散了不仅需要把部门信息删除了，还需要把该部门下的员工数据也删除了。&lt;/p&gt;
&lt;p&gt;步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据ID删除部门数据&lt;/li&gt;
&lt;li&gt;根据部门ID删除该部门下的员工&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代码实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DeptServiceImpl&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpMapper empMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//根据部门id，删除部门信息及部门下的所有员工&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.deleteByDeptId(id);   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="2"&gt;
&lt;li&gt;DeptMapper&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Mapper&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;interface&lt;/span&gt; &lt;span class="title class_"&gt;DeptMapper&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * 根据id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; id   部门id&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Delete("delete from dept where id = #{id}")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;deleteById&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="3"&gt;
&lt;li&gt;EmpMapper&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Mapper&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;interface&lt;/span&gt; &lt;span class="title class_"&gt;EmpMapper&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//根据部门id删除部门下所有员工&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Delete("delete from emp where dept_id=#{deptId}")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="title function_"&gt;deleteByDeptId&lt;/span&gt;&lt;span class="params"&gt;(Integer deptId)&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重启SpringBoot服务，使用postman测试部门删除：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853218-7685a24f-4874-44ce-bf2c-012ebcc44789.png"/&gt;&lt;/p&gt;
&lt;p&gt;代码正常情况下，dept表和Em表中的数据已删除&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853354-bf8d25fd-f966-4970-8777-52d0898f3a04.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853539-379b334e-a005-4d4d-93e8-77a929581816.png"/&gt;&lt;/p&gt;
&lt;p&gt;修改DeptServiceImpl类中代码，添加可能出现异常的代码：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpMapper empMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//根据部门id，删除部门信息及部门下的所有员工&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟：异常发生&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;/&lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.deleteByDeptId(id);   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重启SpringBoot服务，使用postman测试部门删除：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853655-0b253962-52c1-411c-b143-4e541ab220f8.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853767-f098c8e4-b8cb-444d-810c-1c948db46031.png"/&gt;&lt;/p&gt;
&lt;p&gt;查看数据库表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;删除了2号部门&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853873-8dd3ff91-5530-4f6a-8264-0a7952715380.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2号部门下的员工数据没有删除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853539-379b334e-a005-4d4d-93e8-77a929581816.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以上程序出现的问题：即使程序运行抛出了异常，部门依然删除了，但是部门下的员工却没有删除，造成了数据的不一致。&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="1-2-2-原因分析"&gt;&lt;a class="headerlink" href="#1-2-2-原因分析" title="1.2.2 原因分析"&gt;&lt;/a&gt;1.2.2 原因分析&lt;/h4&gt;&lt;p&gt;原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先执行根据id删除部门的操作，这步执行完毕，数据库表 dept 中的数据就已经删除了。&lt;/li&gt;
&lt;li&gt;执行 1/0 操作，抛出异常&lt;/li&gt;
&lt;li&gt;抛出异常之前，下面所有的代码都不会执行了，根据部门ID删除该部门下的员工，这个操作也不会执行 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时就出现问题了，部门删除了，部门下的员工还在，业务操作前后数据不一致。&lt;/p&gt;
&lt;p&gt;而要想保证操作前后，数据的一致性，就需要让解散部门中涉及到的两个业务操作，要么全部成功，要么全部失败 。 那我们如何，让这两个操作要么全部成功，要么全部失败呢 ？&lt;/p&gt;
&lt;p&gt;那就可以通过事务来实现，因为一个事务中的多个业务操作，要么全部成功，要么全部失败。&lt;/p&gt;
&lt;p&gt;此时，我们就需要在delete删除业务功能中添加事务。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853987-52851c4f-244a-4c4d-8440-dec89963cc93.png"/&gt;&lt;/p&gt;
&lt;p&gt;在方法运行之前，开启事务，如果方法成功执行，就提交事务，如果方法执行的过程当中出现异常了，就回滚事务。&lt;/p&gt;
&lt;p&gt;思考：开发中所有的业务操作，一旦我们要进行控制事务，是不是都是这样的套路？&lt;/p&gt;
&lt;p&gt;答案：是的。&lt;/p&gt;
&lt;p&gt;所以在spring框架当中就已经把事务控制的代码都已经封装好了，并不需要我们手动实现。我们使用了spring框架，我们只需要通过一个简单的注解@Transactional就搞定了。&lt;/p&gt;
&lt;h4 id="1-2-3-Transactional注解"&gt;&lt;a class="headerlink" href="#1-2-3-Transactional注解" title="1.2.3 Transactional注解"&gt;&lt;/a&gt;1.2.3 Transactional注解&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;@Transactional作用：就是在当前这个方法执行开始之前来开启事务，方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常，就会进行事务的回滚操作。&lt;/p&gt;
&lt;p&gt;@Transactional注解：我们一般会在业务层当中来控制事务，因为在业务层当中，一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务，我们就可以将多个数据访问操作控制在一个事务范围内。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;@Transactional注解书写位置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方法 &lt;ul&gt;
&lt;li&gt;当前方法交给spring进行事务管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;类 &lt;ul&gt;
&lt;li&gt;当前类中所有的方法都交由spring进行事务管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;接口 &lt;ul&gt;
&lt;li&gt;接口下所有的实现类当中所有的方法都交给spring 进行事务管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来，我们就可以在业务方法delete上加上 &lt;a href="/Transactional"&gt;@Transactional &lt;/a&gt; 来控制事务 。 &lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpMapper empMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Transactional&lt;/span&gt;  &lt;span class="comment"&gt;//当前方法添加了事务管理&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟：异常发生&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;/&lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.deleteByDeptId(id);   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在业务功能上添加@Transactional注解进行事务管理后，我们重启SpringBoot服务，使用postman测试：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854098-da304cee-3ffc-4c61-8532-985a60193665.png"/&gt;&lt;/p&gt;
&lt;p&gt;添加Spring事务管理后，由于服务端程序引发了异常，所以事务进行回滚。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854193-4eff5b65-badd-43c3-a046-6443c1ad9a95.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854308-8bf0fffa-5b83-4b37-b0e7-e7ff6ea7a127.png"/&gt;&lt;/p&gt;
&lt;p&gt;说明：可以在application.yml配置文件中开启事务管理日志，这样就可以在控制看到和事务相关的日志信息了&lt;/p&gt;
&lt;figure class="highlight yaml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;#spring事务管理日志&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="attr"&gt;logging:&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;level:&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;org.springframework.jdbc.support.JdbcTransactionManager:&lt;/span&gt; &lt;span class="string"&gt;debug&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id="1-3-事务进阶"&gt;&lt;a class="headerlink" href="#1-3-事务进阶" title="1.3 事务进阶"&gt;&lt;/a&gt;1.3 事务进阶&lt;/h3&gt;&lt;p&gt;前面我们通过spring事务管理注解@Transactional已经控制了业务层方法的事务。接下来我们要来详细的介绍一下@Transactional事务管理注解的使用细节。我们这里主要介绍@Transactional注解当中的两个常见的属性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;异常回滚的属性：rollbackFor&lt;/li&gt;
&lt;li&gt;事务传播行为：propagation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们先来学习下rollbackFor属性。&lt;/p&gt;
&lt;h4 id="1-3-1-rollbackFor"&gt;&lt;a class="headerlink" href="#1-3-1-rollbackFor" title="1.3.1 rollbackFor"&gt;&lt;/a&gt;1.3.1 rollbackFor&lt;/h4&gt;&lt;p&gt;我们在之前编写的业务方法上添加了@Transactional注解，来实现事务管理。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Transactional&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟：异常发生&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;i&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;/&lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.deleteByDeptId(id);   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;以上业务功能delete()方法在运行时，会引发除0的算数运算异常(运行时异常)，出现异常之后，由于我们在方法上加了@Transactional注解进行事务管理，所以发生异常会执行rollback回滚操作，从而保证事务操作前后数据是一致的。&lt;/p&gt;
&lt;p&gt;下面我们在做一个测试，我们修改业务功能代码，在模拟异常的位置上直接抛出Exception异常（编译时异常）&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Transactional&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟：异常发生&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt;(&lt;span class="literal"&gt;true&lt;/span&gt;){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Exception&lt;/span&gt;(&lt;span class="string"&gt;"出现异常了~~~"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.deleteByDeptId(id);   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;说明：在service中向上抛出一个Exception编译时异常之后，由于是controller调用service，所以在controller中要有异常处理代码，此时我们选择在controller中继续把异常向上抛。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@DeleteMapping("/depts/{id}")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="meta"&gt;@PathVariable&lt;/span&gt; Integer id)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     &lt;span class="comment"&gt;//日志记录&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     log.info(&lt;span class="string"&gt;"根据id删除部门"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     &lt;span class="comment"&gt;//调用service层功能&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     deptService.delete(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     &lt;span class="comment"&gt;//响应&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;     &lt;span class="keyword"&gt;return&lt;/span&gt; Result.success();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动服务后测试：&lt;/p&gt;
&lt;p&gt;抛出异常之后事务会不会回滚&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;现有表中数据：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104853873-8dd3ff91-5530-4f6a-8264-0a7952715380.png"/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用postman测试，删除5号部门&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854433-887919a3-152e-4411-9877-3c0e7ea9b30e.png"/&gt;&lt;/p&gt;
&lt;p&gt;发生了Exception异常，但事务依然提交了&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854574-cba749d5-c13d-495c-b194-360361874b7d.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;dept表中数据：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854690-4d497dad-3d7b-4069-a518-f45d5f3ef18a.png"/&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过以上测试可以得出一个结论：默认情况下，只有出现RuntimeException(运行时异常)才会回滚事务。&lt;/p&gt;
&lt;p&gt;假如我们想让所有的异常都回滚，需要来配置@Transactional注解当中的rollbackFor属性，通过rollbackFor这个属性可以指定出现何种异常类型回滚事务。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpMapper empMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Transactional(rollbackFor=Exception.class)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//模拟：异常发生&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;int&lt;/span&gt; &lt;span class="variable"&gt;num&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; id/&lt;span class="number"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        empMapper.deleteByDeptId(id);   &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;接下来我们重新启动服务，测试删除部门的操作：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854794-102e812a-04ec-43ea-af7c-07a0df35963b.png"/&gt;&lt;/p&gt;
&lt;p&gt;控制台日志：执行了删除3号部门的操作， 因为异常又进行了事务回滚&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854900-1bac0b55-b418-4f9d-afe0-e31e1ec2e17f.png"/&gt;&lt;/p&gt;
&lt;p&gt;数据表：3号部门没有删除&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104854308-8bf0fffa-5b83-4b37-b0e7-e7ff6ea7a127.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在Spring的事务管理中，默认只有运行时异常 RuntimeException才会回滚。&lt;/li&gt;
&lt;li&gt;如果还需要回滚指定类型的异常，可以通过rollbackFor属性来指定。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4 id="1-3-3-propagation"&gt;&lt;a class="headerlink" href="#1-3-3-propagation" title="1.3.3 propagation"&gt;&lt;/a&gt;1.3.3 propagation&lt;/h4&gt;&lt;h5 id="1-3-3-1-介绍"&gt;&lt;a class="headerlink" href="#1-3-3-1-介绍" title="1.3.3.1 介绍"&gt;&lt;/a&gt;1.3.3.1 介绍&lt;/h5&gt;&lt;p&gt;我们接着继续学习@Transactional注解当中的第二个属性propagation，这个属性是用来配置事务的传播行为的。&lt;/p&gt;
&lt;p&gt;什么是事务的传播行为呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;就是当一个事务方法被另一个事务方法调用时，这个事务方法应该如何进行事务控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如：两个事务方法，一个A方法，一个B方法。在这两个方法上都添加了@Transactional注解，就代表这两个方法都具有事务，而在A方法当中又去调用了B方法。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104855025-6cae1bda-0f86-4493-8f69-d80269b802be.png"/&gt;&lt;/p&gt;
&lt;p&gt;所谓事务的传播行为，指的就是在A方法运行的时候，首先会开启一个事务，在A方法当中又调用了B方法， B方法自身也具有事务，那么B方法在运行的时候，到底是加入到A方法的事务当中来，还是B方法在运行的时候新建一个事务？这个就涉及到了事务的传播行为。&lt;/p&gt;
&lt;p&gt;我们要想控制事务的传播行为，在@Transactional注解的后面指定一个属性propagation，通过 propagation 属性来指定传播行为。接下来我们就来介绍一下常见的事务传播行为。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;属性值&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;含义&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;【默认值】需要事务，有则加入，无则创建新事务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REQUIRES_NEW&lt;/td&gt;
&lt;td&gt;需要新事务，无论有无，总是创建新事务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SUPPORTS&lt;/td&gt;
&lt;td&gt;支持事务，有则加入，无则在无事务状态中运行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOT_SUPPORTED&lt;/td&gt;
&lt;td&gt;不支持事务，在无事务状态下运行,如果当前存在已有事务,则挂起当前事务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MANDATORY&lt;/td&gt;
&lt;td&gt;必须有事务，否则抛异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NEVER&lt;/td&gt;
&lt;td&gt;必须没事务，否则抛异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;对于这些事务传播行为，我们只需要关注以下两个就可以了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;REQUIRED（默认值）&lt;/li&gt;
&lt;li&gt;REQUIRES_NEW&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h5 id="1-3-3-2-案例"&gt;&lt;a class="headerlink" href="#1-3-3-2-案例" title="1.3.3.2 案例"&gt;&lt;/a&gt;1.3.3.2 案例&lt;/h5&gt;&lt;p&gt;接下来我们就通过一个案例来演示下事务传播行为propagation属性的使用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需求：&lt;/strong&gt;解散部门时需要记录操作日志&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        由于解散部门是一个非常重要而且非常危险的操作，所以在业务当中要求每一次执行解散部门的操作都需要留下痕迹，就是要记录操作日志。而且还要求无论是执行成功了还是执行失败了，都需要留下痕迹。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行解散部门的业务：先删除部门，再删除部门下的员工（前面已实现）&lt;/li&gt;
&lt;li&gt;记录解散部门的日志，到日志表（未实现）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;准备工作：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建数据库表 dept_log 日志表：&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;create table dept_log(&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;   	id int auto_increment comment '主键ID' primary key,&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    create_time datetime null comment '操作时间',&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    description varchar(300) null comment '操作描述'&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;)comment '部门操作日志表';&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="2"&gt;
&lt;li&gt;引入资料中提供的实体类：DeptLog&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Data&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@NoArgsConstructor&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@AllArgsConstructor&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptLog&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; Integer id;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LocalDateTime createTime;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; String description;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="3"&gt;
&lt;li&gt;引入资料中提供的Mapper接口：DeptLogMapper&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Mapper&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;interface&lt;/span&gt; &lt;span class="title class_"&gt;DeptLogMapper&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;insert&lt;/span&gt;&lt;span class="params"&gt;(DeptLog log)&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="4"&gt;
&lt;li&gt;引入资料中提供的业务接口：DeptLogService&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;interface&lt;/span&gt; &lt;span class="title class_"&gt;DeptLogService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;insert&lt;/span&gt;&lt;span class="params"&gt;(DeptLog deptLog)&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol start="5"&gt;
&lt;li&gt;引入资料中提供的业务实现类：DeptLogServiceImpl&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptLogServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptLogService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptLogMapper deptLogMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Transactional&lt;/span&gt; &lt;span class="comment"&gt;//事务传播行为：有事务就加入、没有事务就新建事务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;insert&lt;/span&gt;&lt;span class="params"&gt;(DeptLog deptLog)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptLogMapper.insert(deptLog);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;代码实现:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;业务实现类：DeptServiceImpl&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//@Transactional //当前业务实现类中的所有的方法，都添加了spring事务管理机制&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptMapper deptMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpMapper empMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptLogService deptLogService;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//根据部门id，删除部门信息及部门下的所有员工&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Log&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Transactional(rollbackFor = Exception.class)&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;delete&lt;/span&gt;&lt;span class="params"&gt;(Integer id)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//根据部门id删除部门信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            deptMapper.deleteById(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//模拟：异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt;(&lt;span class="literal"&gt;true&lt;/span&gt;){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Exception&lt;/span&gt;(&lt;span class="string"&gt;"出现异常了~~~"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//删除部门下的所有员工信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            empMapper.deleteByDeptId(id);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;span class="keyword"&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//不论是否有异常，最终都要执行的代码：记录日志&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;DeptLog&lt;/span&gt; &lt;span class="variable"&gt;deptLog&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;DeptLog&lt;/span&gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            deptLog.setCreateTime(LocalDateTime.now());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            deptLog.setDescription(&lt;span class="string"&gt;"执行了解散部门的操作，此时解散的是"&lt;/span&gt;+id+&lt;span class="string"&gt;"号部门"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//调用其他业务类中的方法&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            deptLogService.insert(deptLog);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//省略其他代码...&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;测试:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;重新启动SpringBoot服务，测试删除3号部门后会发生什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行了删除3号部门操作&lt;/li&gt;
&lt;li&gt;执行了插入部门日志操作&lt;/li&gt;
&lt;li&gt;程序发生Exception异常&lt;/li&gt;
&lt;li&gt;执行事务回滚（删除、插入操作因为在一个事务范围内，两个操作都会被回滚）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104855160-07a55265-83fd-4a0b-8157-43edd68a23d7.png"/&gt;&lt;/p&gt;
&lt;p&gt;然后在dept_log表中没有记录日志数据&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104855255-e9ec2b26-28bd-46c9-9bd5-b2e1a5b184c9.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因分析:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;接下来我们就需要来分析一下具体是什么原因导致的日志没有成功的记录。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在执行delete操作时开启了一个事务 &lt;/li&gt;
&lt;li&gt;当执行insert操作时，insert设置的事务传播行是默认值REQUIRED，表示有事务就加入，没有则新建事务 &lt;/li&gt;
&lt;li&gt;此时：delete和insert操作使用了同一个事务，同一个事务中的多个操作，要么同时成功，要么同时失败，所以当异常发生时进行事务回滚，就会回滚delete和insert操作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104855357-30d78f43-675e-4fd1-8aec-558a914face4.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在DeptLogServiceImpl类中insert方法上，添加&lt;a href="/Transactional(propagation"&gt;@Transactional(propagation &lt;/a&gt; = Propagation.REQUIRES_NEW) &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Propagation.REQUIRES_NEW  ：不论是否有事务，都创建新事务  ，运行在一个独立的事务中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Service&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DeptLogServiceImpl&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;DeptLogService&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; DeptLogMapper deptLogMapper;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Transactional(propagation = Propagation.REQUIRES_NEW)&lt;/span&gt;&lt;span class="comment"&gt;//事务传播行为：不论是否有事务，都新建事务&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;insert&lt;/span&gt;&lt;span class="params"&gt;(DeptLog deptLog)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        deptLogMapper.insert(deptLog);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重启SpringBoot服务，再次测试删除3号部门：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86/1706104855464-b402faf7-9f87-4c1e-a579-95d744624d6a.png"/&gt;&lt;/p&gt;
&lt;p&gt;那此时，DeptServiceImpl中的delete方法运行时，会开启一个事务。 当调用  deptLogService.insert(deptLog)  时，也会创建一个新的事务，那此时，当insert方法运行完毕之后，事务就已经提交了。 即使外部的事务出现异常，内部已经提交的事务，也不会回滚了，因为是两个独立的事务。&lt;/p&gt;
&lt;p&gt;到此事务传播行为已演示完成，事务的传播行为我们只需要掌握两个：REQUIRED、REQUIRES_NEW。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;REQUIRED ：大部分情况下都是用该传播行为即可。 &lt;/li&gt;
&lt;li&gt;REQUIRES_NEW ：当我们不希望事务之间相互影响时，可以使用该传播行为。比如：下订单前需要记录日志，不论订单保存成功与否，都需要保证日志记录能够记录成功。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=""&gt;&lt;a class="headerlink" href="#" title=""&gt;&lt;/a&gt;&lt;/h2&gt;</content>
    <summary type="html">SpringBoot-事务管理 ...</summary>
    <category term="SpringBoot" scheme="https://aspirant02.github.io/categories/SpringBoot/" />
    <category term="SpringBoot" scheme="https://aspirant02.github.io/tags/SpringBoot/" />
    <category term="Java开发" scheme="https://aspirant02.github.io/tags/Java%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>SpringBoot-全局异常处理器</title>
    <link href="https://aspirant02.github.io/2026/05/10/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/" />
    <id>https://aspirant02.github.io/2026/05/10/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/</id>
    <published>2026-05-10T13:18:17Z</published>
    <updated>2026-05-10T13:18:17Z</updated>
    <content type="html">&lt;h2 id="1-异常处理"&gt;&lt;a class="headerlink" href="#1-异常处理" title="1. 异常处理"&gt;&lt;/a&gt;1. 异常处理&lt;/h2&gt;&lt;h3 id="1-1-当前问题"&gt;&lt;a class="headerlink" href="#1-1-当前问题" title="1.1 当前问题"&gt;&lt;/a&gt;1.1 当前问题&lt;/h3&gt;&lt;p&gt;异常处理。首先我们先来看一下系统出现异常之后会发生什么现象，再来介绍异常处理的方案。&lt;/p&gt;
&lt;p&gt;我们打开浏览器，访问系统中的新增部门操作，系统中已经有了 “就业部” 这个部门，我们再来增加一个就业部，看看会发生什么现象。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105170529-9b6f57ae-f36a-4009-8a1d-811e848a835b.png"/&gt;&lt;br/&gt;点击确定之后，窗口关闭了，页面没有任何反应，就业部也没有添加上。 而此时，大家会发现，网络请求报错了。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105170620-606cf096-60ec-4b11-a150-026b78e203ba.png"/&gt;&lt;br/&gt;状态码为500，表示服务器端异常，我们打开idea，来看一下，服务器端出了什么问题。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105170711-82631c8c-aa77-4c10-92ff-74d093b5f481.png"/&gt;&lt;/p&gt;
&lt;p&gt;上述错误信息的含义是，dept部门表的name字段的值 就业部 重复了，因为在数据库表dept中已经有了就业部，我们之前设计这张表时，为name字段建议了唯一约束，所以该字段的值是不能重复的。&lt;/p&gt;
&lt;p&gt;而当我们再添加就业部，这个部门时，就违反了唯一约束，此时就会报错。&lt;/p&gt;
&lt;p&gt;我们来看一下出现异常之后，最终服务端给前端响应回来的数据长什么样。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105170865-344d7948-f4b6-4760-a159-77bed6ec3d46.png"/&gt;&lt;/p&gt;
&lt;p&gt;响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据还是我们开发规范当中所提到的统一响应结果Result吗？显然并不是。由于返回的数据不符合开发规范，所以前端并不能解析出响应的JSON数据。&lt;/p&gt;
&lt;p&gt;接下来我们需要思考的是出现异常之后，当前案例项目的异常是怎么处理的？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;答案：没有做任何的异常处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105170961-4bb3041d-cd6b-4ce0-ad95-a5e23fd48fb8.png"/&gt;&lt;/p&gt;
&lt;p&gt;当我们没有做任何的异常处理时，我们三层架构处理异常的方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mapper接口在操作数据库的时候出错了，此时异常会往上抛(谁调用Mapper就抛给谁)，会抛给service。&lt;/li&gt;
&lt;li&gt;service 中也存在异常了，会抛给controller。&lt;/li&gt;
&lt;li&gt;而在controller当中，我们也没有做任何的异常处理，所以最终异常会再往上抛。最终抛给框架之后，框架就会返回一个JSON格式的数据，里面封装的就是错误的信息，但是框架返回的JSON格式的数据并不符合我们的开发规范。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="1-2-解决方案"&gt;&lt;a class="headerlink" href="#1-2-解决方案" title="1.2 解决方案"&gt;&lt;/a&gt;1.2 解决方案&lt;/h3&gt;&lt;p&gt;那么在三层构架项目中，出现了异常，该如何处理?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方案一：在所有Controller的所有方法中进行try…catch处理 &lt;ul&gt;
&lt;li&gt;缺点：代码臃肿（不推荐）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;方案二：全局异常处理器 &lt;ul&gt;
&lt;li&gt;好处：简单、优雅（推荐）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105171060-b6a3ef29-5ca3-470c-87de-adc1ed72404d.png"/&gt;&lt;/p&gt;
&lt;h3 id="1-3-全局异常处理器"&gt;&lt;a class="headerlink" href="#1-3-全局异常处理器" title="1.3 全局异常处理器"&gt;&lt;/a&gt;1.3 全局异常处理器&lt;/h3&gt;&lt;p&gt;我们该怎么样定义全局异常处理器？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义全局异常处理器非常简单，就是定义一个类，在类上加上一个注解@RestControllerAdvice，加上这个注解就代表我们定义了一个全局异常处理器。&lt;/li&gt;
&lt;li&gt;在全局异常处理器当中，需要定义一个方法来捕获异常，在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@RestControllerAdvice&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;GlobalExceptionHandler&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//处理异常&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@ExceptionHandler(Exception.class)&lt;/span&gt; &lt;span class="comment"&gt;//指定能够处理的异常类型&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;ex&lt;/span&gt;&lt;span class="params"&gt;(Exception e)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        e.printStackTrace();&lt;span class="comment"&gt;//打印堆栈中的异常信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//捕获到异常之后，响应一个标准的Result&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; Result.error(&lt;span class="string"&gt;"对不起,操作失败,请联系管理员"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="/RestControllerAdvice"&gt;@RestControllerAdvice &lt;/a&gt; = &lt;a href="/ControllerAdvice"&gt;@ControllerAdvice &lt;/a&gt; + &lt;a href="/ResponseBody"&gt;@ResponseBody &lt;/a&gt; &lt;/p&gt;
&lt;p&gt;处理异常的方法返回值会转换为json后再响应给前端&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;重新启动SpringBoot服务，打开浏览器，再来测试一下添加部门这个操作，我们依然添加已存在的 “就业部” 这个部门：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105171183-eb407579-2a09-4ccb-8770-07e0a1673552.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%99%A8/1706105171280-e7dd5e22-7297-4081-8f5a-b74ee87dffb1.png"/&gt;&lt;/p&gt;
&lt;p&gt;此时，我们可以看到，出现异常之后，异常已经被全局异常处理器捕获了。然后返回的错误信息，被前端程序正常解析，然后提示出了对应的错误提示信息。&lt;/p&gt;
&lt;p&gt;以上就是全局异常处理器的使用，主要涉及到两个注解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/RestControllerAdvice"&gt;@RestControllerAdvice &lt;/a&gt;  //表示当前类为全局异常处理器 &lt;/li&gt;
&lt;li&gt;&lt;a href="/ExceptionHandler"&gt;@ExceptionHandler &lt;/a&gt;  //指定可以捕获哪种类型的异常进行处理&lt;/li&gt;
&lt;/ul&gt;
</content>
    <summary type="html">SpringBoot-全局异常处理器 ...</summary>
    <category term="SpringBoot" scheme="https://aspirant02.github.io/categories/SpringBoot/" />
    <category term="SpringBoot" scheme="https://aspirant02.github.io/tags/SpringBoot/" />
    <category term="Java开发" scheme="https://aspirant02.github.io/tags/Java%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>SpringBoot-过滤器拦截器</title>
    <link href="https://aspirant02.github.io/2026/05/10/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/" />
    <id>https://aspirant02.github.io/2026/05/10/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/</id>
    <published>2026-05-10T12:08:17Z</published>
    <updated>2026-05-10T12:08:17Z</updated>
    <content type="html">&lt;h1 id="过滤器Filter"&gt;&lt;a class="headerlink" href="#过滤器Filter" title="过滤器Filter"&gt;&lt;/a&gt;过滤器Filter&lt;/h1&gt;&lt;p&gt;刚才通过浏览器的开发者工具，我们可以看到在后续的请求当中，都会在请求头中携带JWT令牌到服务端，而服务端需要统一拦截所有的请求，从而判断是否携带的有合法的JWT令牌。&lt;br/&gt;那怎么样来统一拦截到所有的请求校验令牌的有效性呢？这里我们会学习两种解决方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Filter过滤器&lt;/li&gt;
&lt;li&gt;Interceptor拦截器&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们首先来学习过滤器Filter。&lt;/p&gt;
&lt;h2 id="快速入门"&gt;&lt;a class="headerlink" href="#快速入门" title="快速入门"&gt;&lt;/a&gt;快速入门&lt;/h2&gt;&lt;p&gt;什么是Filter？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filter表示过滤器，是 JavaWeb三大组件(Servlet、Filter、Listener)之一。&lt;/li&gt;
&lt;li&gt;过滤器可以把对资源的请求拦截下来，从而实现一些特殊的功能 &lt;ul&gt;
&lt;li&gt;使用了过滤器之后，要想访问web服务器上的资源，必须先经过滤器，过滤器处理完毕之后，才可以访问对应的资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;过滤器一般完成一些通用的操作，比如：登录校验、统一编码处理、敏感字符处理等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168198-59fdf6f4-f1f6-4a65-ab84-406a5eb15990.png"/&gt;&lt;/p&gt;
&lt;p&gt;下面我们通过Filter快速入门程序掌握过滤器的基本使用操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1步，定义过滤器 ：1.定义一个类，实现 Filter 接口，并重写其所有方法。&lt;/li&gt;
&lt;li&gt;第2步，配置过滤器：Filter类上加 &lt;a href="/WebFilter"&gt;@WebFilter &lt;/a&gt; 注解，配置拦截资源的路径。引导类上加 &lt;a href="/ServletComponentScan"&gt;@ServletComponentScan &lt;/a&gt; 开启Servlet组件支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;定义过滤器&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//定义一个类，实现一个标准的Filter过滤器的接口&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//初始化方法, 只调用一次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;init&lt;/span&gt;&lt;span class="params"&gt;(FilterConfig filterConfig)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"init 初始化方法执行了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//拦截到请求之后调用, 调用多次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest request, ServletResponse response, FilterChain chain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"Demo 拦截到了请求...放行前逻辑"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        chain.doFilter(request,response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//销毁方法, 只调用一次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;destroy&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"destroy 销毁方法执行了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;init方法：过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象，在创建过滤器对象的时候会自动调用init初始化方法，这个方法只会被调用一次。 &lt;/li&gt;
&lt;li&gt;doFilter方法：这个方法是在每一次拦截到请求之后都会被调用，所以这个方法是会被调用多次的，每拦截到一次请求就会调用一次doFilter()方法。 &lt;/li&gt;
&lt;li&gt;destroy方法： 是销毁的方法。当我们关闭服务器的时候，它会自动的调用销毁方法destroy，而这个销毁方法也只会被调用一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;在定义完Filter之后，Filter其实并不会生效，还需要完成Filter的配置，Filter的配置非常简单，只需要在Filter类上添加一个注解：@WebFilter，并指定属性urlPatterns，通过这个属性指定过滤器要拦截哪些请求&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt; &lt;span class="comment"&gt;//配置过滤器要拦截的请求路径（ /* 表示拦截浏览器的所有请求 ）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//初始化方法, 只调用一次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;init&lt;/span&gt;&lt;span class="params"&gt;(FilterConfig filterConfig)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"init 初始化方法执行了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//拦截到请求之后调用, 调用多次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest request, ServletResponse response, FilterChain chain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"Demo 拦截到了请求...放行前逻辑"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        chain.doFilter(request,response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//销毁方法, 只调用一次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;destroy&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"destroy 销毁方法执行了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;当我们在Filter类上面加了@WebFilter注解之后，接下来我们还需要在启动类上面加上一个注解@ServletComponentScan，通过这个@ServletComponentScan注解来开启SpringBoot项目对于Servlet组件的支持。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@ServletComponentScan&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@SpringBootApplication&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;TliasWebManagementApplication&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;main&lt;/span&gt;&lt;span class="params"&gt;(String[] args)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        SpringApplication.run(TliasWebManagementApplication.class, args);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动服务，打开浏览器，执行部门管理的请求，可以看到控制台输出了过滤器中的内容：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168282-ce7afe5e-695b-416f-bb7b-23f715d1315e.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;在过滤器Filter中，如果不执行放行操作，将无法访问后面的资源。 放行操作：chain.doFilter(request, response);
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在我们已完成了Filter过滤器的基本使用，下面我们将学习Filter过滤器在使用过程中的一些细节。&lt;/p&gt;
&lt;h2 id="Filter详解"&gt;&lt;a class="headerlink" href="#Filter详解" title="Filter详解"&gt;&lt;/a&gt;Filter详解&lt;/h2&gt;&lt;p&gt;Filter过滤器的快速入门程序我们已经完成了，接下来我们就要详细的介绍一下过滤器Filter在使用中的一些细节。主要介绍以下3个方面的细节：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;过滤器的执行流程&lt;/li&gt;
&lt;li&gt;过滤器的拦截路径配置&lt;/li&gt;
&lt;li&gt;过滤器链&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="执行流程"&gt;&lt;a class="headerlink" href="#执行流程" title="执行流程"&gt;&lt;/a&gt;执行流程&lt;/h3&gt;&lt;p&gt;首先我们先来看下过滤器的执行流程：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168373-e62904f3-06fe-4afb-bf2e-b77d7cb640a1.png"/&gt;&lt;/p&gt;
&lt;p&gt;过滤器当中我们拦截到了请求之后，如果希望继续访问后面的web资源，就要执行放行操作，放行就是调用 FilterChain对象当中的doFilter()方法，在调用doFilter()这个方法之前所编写的代码属于放行之前的逻辑。&lt;/p&gt;
&lt;p&gt;在放行后访问完 web 资源之后还会回到过滤器当中，回到过滤器之后如有需求还可以执行放行之后的逻辑，放行之后的逻辑我们写在doFilter()这行代码之后。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//初始化方法, 只调用一次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;init&lt;/span&gt;&lt;span class="params"&gt;(FilterConfig filterConfig)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"init 初始化方法执行了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行前逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        filterChain.doFilter(servletRequest,servletResponse);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行后逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt; &lt;span class="comment"&gt;//销毁方法, 只调用一次&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;destroy&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"destroy 销毁方法执行了"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168463-1770a831-ae68-4014-b890-4acf3736bba5.png"/&gt;&lt;/p&gt;
&lt;h3 id="拦截路径"&gt;&lt;a class="headerlink" href="#拦截路径" title="拦截路径"&gt;&lt;/a&gt;拦截路径&lt;/h3&gt;&lt;p&gt;执行流程我们搞清楚之后，接下来再来介绍一下过滤器的拦截路径，Filter可以根据需求，配置不同的拦截资源路径：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;拦截路径&lt;/th&gt;
&lt;th&gt;urlPatterns值&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;拦截具体路径&lt;/td&gt;
&lt;td&gt;/login&lt;/td&gt;
&lt;td&gt;只有访问 /login 路径时，才会被拦截&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目录拦截&lt;/td&gt;
&lt;td&gt;/emps/*&lt;/td&gt;
&lt;td&gt;访问/emps下的所有资源，都会被拦截&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;拦截所有&lt;/td&gt;
&lt;td&gt;/*&lt;/td&gt;
&lt;td&gt;访问所有资源，都会被拦截&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;下面我们来测试”拦截具体路径”：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/login")&lt;/span&gt;  &lt;span class="comment"&gt;//拦截/login具体路径&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行前逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        filterChain.doFilter(servletRequest,servletResponse);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行后逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;init&lt;/span&gt;&lt;span class="params"&gt;(FilterConfig filterConfig)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Filter.&lt;span class="built_in"&gt;super&lt;/span&gt;.init(filterConfig);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;destroy&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Filter.&lt;span class="built_in"&gt;super&lt;/span&gt;.destroy();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;测试1：访问部门管理请求，发现过滤器没有拦截请求&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168556-ba4e1cae-2da6-4da2-b957-5004af4d56d7.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168647-04d28c3c-5b87-47f1-acc2-f825a234493a.png"/&gt;&lt;/p&gt;
&lt;p&gt;测试2：访问登录请求/login，发现过滤器拦截请求&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168752-4d4b12e9-b534-4df6-b3f9-013856255b9b.png"/&gt;&lt;/p&gt;
&lt;p&gt;下面我们来测试”目录拦截”：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/depts/*")&lt;/span&gt; &lt;span class="comment"&gt;//拦截所有以/depts开头，后面是什么无所谓&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行前逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        filterChain.doFilter(servletRequest,servletResponse);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行后逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;init&lt;/span&gt;&lt;span class="params"&gt;(FilterConfig filterConfig)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Filter.&lt;span class="built_in"&gt;super&lt;/span&gt;.init(filterConfig);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;destroy&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Filter.&lt;span class="built_in"&gt;super&lt;/span&gt;.destroy();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;测试1：访问部门管理请求，发现过滤器拦截了请求&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168880-e16032e8-05e9-4d16-aeac-f384c1102382.png"/&gt;&lt;/p&gt;
&lt;p&gt;测试2：访问登录请求/login，发现过滤器没有拦截请求&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105168975-32a73986-8829-425e-8f70-5f5c5282b937.png"/&gt;&lt;/p&gt;
&lt;h3 id="过滤器链"&gt;&lt;a class="headerlink" href="#过滤器链" title="过滤器链"&gt;&lt;/a&gt;过滤器链&lt;/h3&gt;&lt;p&gt;最后我们在来介绍下过滤器链，什么是过滤器链呢？所谓过滤器链指的是在一个web应用程序当中，可以配置多个过滤器，多个过滤器就形成了一个过滤器链。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169081-f945b75d-76ea-42a0-9684-deaedd8e2f64.png"/&gt;&lt;/p&gt;
&lt;p&gt;比如：在我们web服务器当中，定义了两个过滤器，这两个过滤器就形成了一个过滤器链。&lt;/p&gt;
&lt;p&gt;而这个链上的过滤器在执行的时候会一个一个的执行，会先执行第一个Filter，放行之后再来执行第二个Filter，如果执行到了最后一个过滤器放行之后，才会访问对应的web资源。&lt;/p&gt;
&lt;p&gt;访问完web资源之后，按照我们刚才所介绍的过滤器的执行流程，还会回到过滤器当中来执行过滤器放行后的逻辑，而在执行放行后的逻辑的时候，顺序是反着的。&lt;/p&gt;
&lt;p&gt;先要执行过滤器2放行之后的逻辑，再来执行过滤器1放行之后的逻辑，最后在给浏览器响应数据。&lt;/p&gt;
&lt;p&gt;以上就是当我们在web应用当中配置了多个过滤器，形成了这样一个过滤器链以及过滤器链的执行顺序。下面我们通过idea来验证下过滤器链。&lt;/p&gt;
&lt;p&gt;验证步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在filter包下再来新建一个Filter过滤器类：AbcFilter&lt;/li&gt;
&lt;li&gt;在AbcFilter过滤器中编写放行前和放行后逻辑&lt;/li&gt;
&lt;li&gt;配置AbcFilter过滤器拦截请求路径为：/*&lt;/li&gt;
&lt;li&gt;重启SpringBoot服务，查看DemoFilter、AbcFilter的执行日志&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169174-f2d35412-424d-471f-844c-04d71f7d0b7d.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AbcFilter过滤器&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;AbcFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest request, ServletResponse response, FilterChain chain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"Abc 拦截到了请求... 放行前逻辑"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        chain.doFilter(request,response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"Abc 拦截到了请求... 放行后逻辑"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;DemoFilter过滤器&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行前逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        filterChain.doFilter(servletRequest,servletResponse);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行后逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;打开浏览器访问登录接口：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169269-b3e44c35-3a3d-474d-bc91-e839ccc33fba.png"/&gt;&lt;/p&gt;
&lt;p&gt;通过控制台日志的输出，大家发现AbcFilter先执行DemoFilter后执行，这是为什么呢？&lt;/p&gt;
&lt;p&gt;其实是和过滤器的类名有关系。以注解方式配置的Filter过滤器，它的执行优先级是按时过滤器类名的自动排序确定的，类名排名越靠前，优先级越高。&lt;/p&gt;
&lt;p&gt;假如我们想让DemoFilter先执行，怎么办呢？答案就是修改类名。&lt;/p&gt;
&lt;p&gt;测试：修改AbcFilter类名为XbcFilter，运行程序查看控制台日志&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;XbcFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest request, ServletResponse response, FilterChain chain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"Xbc 拦截到了请求...放行前逻辑"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        chain.doFilter(request,response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"Xbc 拦截到了请求...放行后逻辑"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169369-b41f579b-6845-4014-9ea7-f2ca0594f60e.png"/&gt;&lt;/p&gt;
&lt;p&gt;到此，关于过滤器的使用细节，我们已经全部介绍完毕了。&lt;/p&gt;
&lt;h2 id="登录校验-Filter"&gt;&lt;a class="headerlink" href="#登录校验-Filter" title="登录校验-Filter"&gt;&lt;/a&gt;登录校验-Filter&lt;/h2&gt;&lt;h3 id="分析"&gt;&lt;a class="headerlink" href="#分析" title="分析"&gt;&lt;/a&gt;分析&lt;/h3&gt;&lt;p&gt;过滤器Filter的快速入门以及使用细节我们已经介绍完了，接下来最后一步，我们需要使用过滤器Filter来完成案例当中的登录校验功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169467-ef00ff1e-4e3f-45fa-920d-494879350628.png"/&gt;&lt;/p&gt;
&lt;p&gt;我们先来回顾下前面分析过的登录校验的基本流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要进入到后台管理系统，我们必须先完成登录操作，此时就需要访问登录接口login。 &lt;/li&gt;
&lt;li&gt;登录成功之后，我们会在服务端生成一个JWT令牌，并且把JWT令牌返回给前端，前端会将JWT令牌存储下来。 &lt;/li&gt;
&lt;li&gt;在后续的每一次请求当中，都会将JWT令牌携带到服务端，请求到达服务端之后，要想去访问对应的业务功能，此时我们必须先要校验令牌的有效性。 &lt;/li&gt;
&lt;li&gt;对于校验令牌的这一块操作，我们使用登录校验的过滤器，在过滤器当中来校验令牌的有效性。如果令牌是无效的，就响应一个错误的信息，也不会再去放行访问对应的资源了。如果令牌存在，并且它是有效的，此时就会放行去访问对应的web资源，执行相应的业务操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大概清楚了在Filter过滤器的实现步骤了，那在正式开发登录校验过滤器之前，我们思考两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;所有的请求，拦截到了之后，都需要校验令牌吗？&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;答案：&lt;strong&gt;登录请求例外&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;拦截到请求后，什么情况下才可以放行，执行业务操作？&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;答案：&lt;strong&gt;有令牌，且令牌校验通过(合法)；否则都返回未登录错误结果&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="具体流程"&gt;&lt;a class="headerlink" href="#具体流程" title="具体流程"&gt;&lt;/a&gt;具体流程&lt;/h3&gt;&lt;p&gt;我们要完成登录校验，主要是利用Filter过滤器实现，而Filter过滤器的流程步骤：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169559-e3eb8525-fa48-4387-9e79-65923810d870.png"/&gt;&lt;/p&gt;
&lt;p&gt;基于上面的业务流程，我们分析出具体的操作步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取请求url&lt;/li&gt;
&lt;li&gt;判断请求url中是否包含login，如果包含，说明是登录操作，放行&lt;/li&gt;
&lt;li&gt;获取请求头中的令牌（token）&lt;/li&gt;
&lt;li&gt;判断令牌是否存在，如果不存在，返回错误结果（未登录）&lt;/li&gt;
&lt;li&gt;解析token，如果解析失败，返回错误结果（未登录）&lt;/li&gt;
&lt;li&gt;放行&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="代码实现"&gt;&lt;a class="headerlink" href="#代码实现" title="代码实现"&gt;&lt;/a&gt;代码实现&lt;/h3&gt;&lt;p&gt;分析清楚了以上的问题后，我们就参照接口文档来开发登录功能了，登录接口描述如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;基本信息 &lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;请求路径：/login&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;请求方式：POST&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;接口描述：该接口用于员工登录Tlias智能学习辅助系统，登录完毕后，系统下发JWT令牌。&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;请求参数&lt;br/&gt;参数格式：application/json&lt;br/&gt;参数说明： &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;是否必须&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;username&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;必须&lt;/td&gt;
&lt;td&gt;用户名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;必须&lt;/td&gt;
&lt;td&gt;密码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;请求数据样例： &lt;/p&gt;
&lt;figure class="highlight json"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"username"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"jinyong"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="attr"&gt;"password"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"123456"&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;响应数据&lt;br/&gt;参数格式：application/json&lt;br/&gt;参数说明： &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;是否必须&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;th&gt;其他信息&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;code&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;必须&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;响应码, 1 成功 ; 0  失败&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;msg&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;非必须&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;提示信息&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;data&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;必须&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;返回的数据 , jwt令牌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;响应数据样例： &lt;/p&gt;
&lt;figure class="highlight json"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"code"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"msg"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"success"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"data"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;备注说明&lt;br/&gt;用户登录成功后，系统会自动下发JWT令牌，然后在后续的每次请求中，都需要在请求头header中携带到服务端，请求头的名称为 token ，值为 登录时下发的JWT令牌。&lt;br/&gt;如果检测到用户未登录，则会返回如下固定错误信息： &lt;/p&gt;
&lt;figure class="highlight json"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"code"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"msg"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"NOT_LOGIN"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"data"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="literal"&gt;&lt;span class="keyword"&gt;null&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;登录校验过滤器：LoginCheckFilter&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;53&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;54&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;55&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;56&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;57&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;58&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;59&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;60&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;61&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;62&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;63&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt; &lt;span class="comment"&gt;//拦截所有请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LoginCheckFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//前置：强制转换为http协议的请求对象、响应对象 （转换原因：要使用子类中特有方法）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="variable"&gt;request&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (HttpServletRequest) servletRequest;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="variable"&gt;response&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; (HttpServletResponse) servletResponse;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//1.获取请求url&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;url&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; request.getRequestURL().toString();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"请求路径：{}"&lt;/span&gt;, url); &lt;span class="comment"&gt;//请求路径：http://localhost:8080/login&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//2.判断请求url中是否包含login，如果包含，说明是登录操作，放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt;(url.contains(&lt;span class="string"&gt;"/login"&lt;/span&gt;)){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            chain.doFilter(request, response);&lt;span class="comment"&gt;//放行请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;span class="comment"&gt;//结束当前方法的执行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//3.获取请求头中的令牌（token）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;token&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; request.getHeader(&lt;span class="string"&gt;"token"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"从请求头中获取的令牌：{}"&lt;/span&gt;,token);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//4.判断令牌是否存在，如果不存在，返回错误结果（未登录）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt;(!StringUtils.hasLength(token)){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            log.info(&lt;span class="string"&gt;"Token不存在"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;Result&lt;/span&gt; &lt;span class="variable"&gt;responseResult&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Result.error(&lt;span class="string"&gt;"NOT_LOGIN"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;json&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JSONObject.toJSONString(responseResult);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.setContentType(&lt;span class="string"&gt;"application/json;charset=utf-8"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//响应&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.getWriter().write(json);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//5.解析token，如果解析失败，返回错误结果（未登录）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            JwtUtils.parseJWT(token);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;span class="keyword"&gt;catch&lt;/span&gt; (Exception e){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            log.info(&lt;span class="string"&gt;"令牌解析失败!"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;Result&lt;/span&gt; &lt;span class="variable"&gt;responseResult&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Result.error(&lt;span class="string"&gt;"NOT_LOGIN"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;json&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JSONObject.toJSONString(responseResult);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.setContentType(&lt;span class="string"&gt;"application/json;charset=utf-8"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//响应&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.getWriter().write(json);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//6.放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        chain.doFilter(request, response);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在上述过滤器的功能实现中，我们使用到了一个第三方json处理的工具包fastjson。我们要想使用，需要引入如下依赖：&lt;/p&gt;
&lt;figure class="highlight xml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;com.alibaba&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;fastjson&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;1.2.76&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;登录校验的过滤器我们编写完成了，接下来我们就可以重新启动服务来做一个测试：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;测试前先把之前所编写的测试使用的过滤器，暂时注释掉。直接将@WebFilter注解给注释掉即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;测试1：未登录是否可以访问部门管理页面&lt;br/&gt;首先关闭浏览器，重新打开浏览器，在地址栏中输入：&lt;a href="http://localhost:9528/#/system/dept"&gt;http://localhost:9528/#/system/dept&lt;/a&gt;&lt;br/&gt;由于用户没有登录，登录校验过滤器返回错误信息，前端页面根据返回的错误信息结果，自动跳转到登录页面了&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105164488-8364b0a8-3d4e-4051-a3cc-718eb567d8e7.png"/&gt; &lt;/li&gt;
&lt;li&gt;测试2：先进行登录操作，再访问部门管理页面&lt;br/&gt;登录校验成功之后，可以正常访问相关业务操作页面&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169644-e0a70012-305e-4a1e-9750-1922f78274fb.png"/&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="拦截器Interceptor"&gt;&lt;a class="headerlink" href="#拦截器Interceptor" title="拦截器Interceptor"&gt;&lt;/a&gt;拦截器Interceptor&lt;/h1&gt;&lt;p&gt;学习完了过滤器Filter之后，接下来我们继续学习拦截器Interseptor。&lt;/p&gt;
&lt;p&gt;拦截器我们主要分为三个方面进行讲解：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;介绍下什么是拦截器，并通过快速入门程序上手拦截器&lt;/li&gt;
&lt;li&gt;拦截器的使用细节&lt;/li&gt;
&lt;li&gt;通过拦截器Interceptor完成登录校验功能&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们先学习第一块内容：拦截器快速入门&lt;/p&gt;
&lt;h2 id="快速入门-1"&gt;&lt;a class="headerlink" href="#快速入门-1" title="快速入门"&gt;&lt;/a&gt;快速入门&lt;/h2&gt;&lt;p&gt;什么是拦截器？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是一种动态拦截方法调用的机制，类似于过滤器。&lt;/li&gt;
&lt;li&gt;拦截器是Spring框架中提供的，用来动态拦截控制器方法的执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;拦截器的作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拦截请求，在指定方法调用前后，根据业务需要执行预先设定的代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在拦截器当中，我们通常也是做一些通用性的操作，比如：我们可以通过拦截器来拦截前端发起的请求，将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中，如发现用户登录了(携带JWT令牌且是合法令牌)，就可以直接放行，去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌，就可以直接给前端响应未登录的错误信息。&lt;/p&gt;
&lt;p&gt;下面我们通过快速入门程序，来学习下拦截器的基本使用。拦截器的使用步骤和过滤器类似，也分为两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;定义拦截器 &lt;/li&gt;
&lt;li&gt;注册配置拦截器&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;自定义拦截器：&lt;/strong&gt;实现HandlerInterceptor接口，并重写其所有方法&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//自定义拦截器&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LoginCheckInterceptor&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;HandlerInterceptor&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//目标资源方法执行前执行。 返回true：放行    返回false：不放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;preHandle&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"preHandle .... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;; &lt;span class="comment"&gt;//true表示放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//目标资源方法执行后执行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;postHandle&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"postHandle ... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//视图渲染完毕后执行，最后执行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;afterCompletion&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"afterCompletion .... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;preHandle方法：目标资源方法执行前执行。 返回true：放行    返回false：不放行

postHandle方法：目标资源方法执行后执行

afterCompletion方法：视图渲染完毕后执行，最后执行
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;注册配置拦截器&lt;/strong&gt;：实现WebMvcConfigurer接口，并重写addInterceptors方法&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Configuration&lt;/span&gt;  &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WebConfig&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;WebMvcConfigurer&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//自定义的拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LoginCheckInterceptor loginCheckInterceptor;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;addInterceptors&lt;/span&gt;&lt;span class="params"&gt;(InterceptorRegistry registry)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//注册自定义拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        registry.addInterceptor(loginCheckInterceptor).addPathPatterns(&lt;span class="string"&gt;"/**"&lt;/span&gt;);&lt;span class="comment"&gt;//设置拦截器拦截的请求路径（ /** 表示拦截所有请求）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重新启动SpringBoot服务，打开postman测试：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169745-04fb16f1-ee9c-454b-b6c4-987943eddced.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169835-3895ab5d-2b01-4006-9a6f-fd08c6a7eb5b.png"/&gt;&lt;/p&gt;
&lt;p&gt;接下来我们再来做一个测试：将拦截器中返回值改为false&lt;/p&gt;
&lt;p&gt;使用postman，再次点击send发送请求后，没有响应数据，说明请求被拦截了没有放行&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169924-19c3fb83-0b5b-4cfa-9dc9-25b2143ae04a.png"/&gt;&lt;/p&gt;
&lt;h2 id="Interceptor详解"&gt;&lt;a class="headerlink" href="#Interceptor详解" title="Interceptor详解"&gt;&lt;/a&gt;Interceptor详解&lt;/h2&gt;&lt;p&gt;拦截器的入门程序完成之后，接下来我们来介绍拦截器的使用细节。拦截器的使用细节我们主要介绍两个部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;拦截器的拦截路径配置&lt;/li&gt;
&lt;li&gt;拦截器的执行流程&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="拦截路径-1"&gt;&lt;a class="headerlink" href="#拦截路径-1" title="拦截路径"&gt;&lt;/a&gt;拦截路径&lt;/h3&gt;&lt;p&gt;首先我们先来看拦截器的拦截路径的配置，在注册配置拦截器的时候，我们要指定拦截器的拦截路径，通过&lt;code&gt;addPathPatterns("要拦截路径")&lt;/code&gt;方法，就可以指定要拦截哪些资源。&lt;/p&gt;
&lt;p&gt;在入门程序中我们配置的是&lt;code&gt;/**&lt;/code&gt;，表示拦截所有资源，而在配置拦截器时，不仅可以指定要拦截哪些资源，还可以指定不拦截哪些资源，只需要调用&lt;code&gt;excludePathPatterns("不拦截路径")&lt;/code&gt;方法，指定哪些资源不需要拦截。&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Configuration&lt;/span&gt;  &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WebConfig&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;WebMvcConfigurer&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LoginCheckInterceptor loginCheckInterceptor;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;addInterceptors&lt;/span&gt;&lt;span class="params"&gt;(InterceptorRegistry registry)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//注册自定义拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        registry.addInterceptor(loginCheckInterceptor)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .addPathPatterns(&lt;span class="string"&gt;"/**"&lt;/span&gt;)&lt;span class="comment"&gt;//设置拦截器拦截的请求路径（ /** 表示拦截所有请求）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .excludePathPatterns(&lt;span class="string"&gt;"/login"&lt;/span&gt;);&lt;span class="comment"&gt;//设置不拦截的请求路径&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在拦截器中除了可以设置&lt;code&gt;/**&lt;/code&gt;拦截所有资源外，还有一些常见拦截路径设置：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;拦截路径&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;举例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;/*&lt;/td&gt;
&lt;td&gt;一级路径&lt;/td&gt;
&lt;td&gt;能匹配/depts，/emps，/login，不能匹配 /depts/1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/**&lt;/td&gt;
&lt;td&gt;任意级路径&lt;/td&gt;
&lt;td&gt;能匹配/depts，/depts/1，/depts/1/2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/depts/*&lt;/td&gt;
&lt;td&gt;/depts下的一级路径&lt;/td&gt;
&lt;td&gt;能匹配/depts/1，不能匹配/depts/1/2，/depts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/depts/**&lt;/td&gt;
&lt;td&gt;/depts下的任意级路径&lt;/td&gt;
&lt;td&gt;能匹配/depts，/depts/1，/depts/1/2，不能匹配/emps/1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;下面主要来演示下&lt;code&gt;/**&lt;/code&gt;与&lt;code&gt;/*&lt;/code&gt;的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改拦截器配置，把拦截路径设置为&lt;code&gt;/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Configuration&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WebConfig&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;WebMvcConfigurer&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LoginCheckInterceptor loginCheckInterceptor;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;addInterceptors&lt;/span&gt;&lt;span class="params"&gt;(InterceptorRegistry registry)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//注册自定义拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        registry.addInterceptor(loginCheckInterceptor)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .addPathPatterns(&lt;span class="string"&gt;"/*"&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .excludePathPatterns(&lt;span class="string"&gt;"/login"&lt;/span&gt;);&lt;span class="comment"&gt;//设置不拦截的请求路径&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;使用postman测试：&lt;a href="http://localhost:8080/emps/1"&gt;http://localhost:8080/emps/1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105170008-7685a78e-4d91-4077-96ec-08b724f3f1a3.png"/&gt;&lt;/p&gt;
&lt;p&gt;控制台没有输出拦截器中的日志信息，说明&lt;code&gt;/*&lt;/code&gt;没有匹配到拦截路径&lt;code&gt;/emp/1&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105170130-9eabc982-2263-438e-b872-e4a5604e03cc.png"/&gt;&lt;/p&gt;
&lt;h3 id="执行流程-1"&gt;&lt;a class="headerlink" href="#执行流程-1" title="执行流程"&gt;&lt;/a&gt;执行流程&lt;/h3&gt;&lt;p&gt;介绍完拦截路径的配置之后，接下来我们再来介绍拦截器的执行流程。通过执行流程，大家就能够清晰的知道过滤器与拦截器的执行时机。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105170226-9887fd31-7464-4c61-97dd-b495586f1f97.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当我们打开浏览器来访问部署在web服务器当中的web应用时，此时我们所定义的过滤器会拦截到这次请求。拦截到这次请求之后，它会先执行放行前的逻辑，然后再执行放行操作。而由于我们当前是基于springboot开发的，所以放行之后是进入到了spring的环境当中，也就是要来访问我们所定义的controller当中的接口方法。 &lt;/li&gt;
&lt;li&gt;Tomcat并不识别所编写的Controller程序，但是它识别Servlet程序，所以在Spring的Web环境中提供了一个非常核心的Servlet：DispatcherServlet（前端控制器），所有请求都会先进行到DispatcherServlet，再将请求转给Controller。 &lt;/li&gt;
&lt;li&gt;当我们定义了拦截器后，会在执行Controller的方法之前，请求被拦截器拦截住。执行&lt;code&gt;preHandle()&lt;/code&gt;方法，这个方法执行完成后需要返回一个布尔类型的值，如果返回true，就表示放行本次操作，才会继续访问controller中的方法；如果返回false，则不会放行（controller中的方法也不会执行）。 &lt;/li&gt;
&lt;li&gt;在controller当中的方法执行完毕之后，再回过来执行&lt;code&gt;postHandle()&lt;/code&gt;这个方法以及&lt;code&gt;afterCompletion()&lt;/code&gt; 方法，然后再返回给DispatcherServlet，最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后，最终给浏览器响应数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来我们就来演示下过滤器和拦截器同时存在的执行流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启LoginCheckInterceptor拦截器&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LoginCheckInterceptor&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;HandlerInterceptor&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;preHandle&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"preHandle .... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;; &lt;span class="comment"&gt;//true表示放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;postHandle&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"postHandle ... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;afterCompletion&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"afterCompletion .... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Configuration&lt;/span&gt;  &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WebConfig&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;WebMvcConfigurer&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LoginCheckInterceptor loginCheckInterceptor;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;addInterceptors&lt;/span&gt;&lt;span class="params"&gt;(InterceptorRegistry registry)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//注册自定义拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        registry.addInterceptor(loginCheckInterceptor)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .addPathPatterns(&lt;span class="string"&gt;"/**"&lt;/span&gt;)&lt;span class="comment"&gt;//拦截所有请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .excludePathPatterns(&lt;span class="string"&gt;"/login"&lt;/span&gt;);&lt;span class="comment"&gt;//不拦截登录请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;开启DemoFilter过滤器&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@WebFilter(urlPatterns = "/*")&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;DemoFilter&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;Filter&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;doFilter&lt;/span&gt;&lt;span class="params"&gt;(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; IOException, ServletException {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行前逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//放行请求&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        filterChain.doFilter(servletRequest,servletResponse);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"DemoFilter   放行后逻辑....."&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重启SpringBoot服务后，清空日志，打开Postman，测试查询部门：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105170326-dee7d777-9934-40fd-9e74-0063f571589d.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105170432-2f5f6e4d-761c-4481-b031-4b149bef74cc.png"/&gt;&lt;/p&gt;
&lt;p&gt;以上就是拦截器的执行流程。通过执行流程分析，大家应该已经清楚了过滤器和拦截器之间的区别，其实它们之间的区别主要是两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接口规范不同：过滤器需要实现Filter接口，而拦截器需要实现HandlerInterceptor接口。&lt;/li&gt;
&lt;li&gt;拦截范围不同：过滤器Filter会拦截所有的资源，而Interceptor只会拦截Spring环境中的资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="登录校验-Interceptor"&gt;&lt;a class="headerlink" href="#登录校验-Interceptor" title="登录校验- Interceptor"&gt;&lt;/a&gt;登录校验- Interceptor&lt;/h2&gt;&lt;p&gt;讲解完了拦截器的基本操作之后，接下来我们需要完成最后一步操作：通过拦截器来完成案例当中的登录校验功能。&lt;/p&gt;
&lt;p&gt;登录校验的业务逻辑以及操作步骤我们前面已经分析过了，和登录校验Filter过滤器当中的逻辑是完全一致的。现在我们只需要把这个技术方案由原来的过滤器换成拦截器interceptor就可以了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;登录校验拦截器&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;33&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;34&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;35&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;36&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;37&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;38&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;39&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;40&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;41&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;42&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;43&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;44&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;45&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;46&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;47&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;48&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;49&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;50&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;51&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;52&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;//自定义拦截器&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Component&lt;/span&gt; &lt;span class="comment"&gt;//当前拦截器对象由Spring创建和管理&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LoginCheckInterceptor&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;HandlerInterceptor&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//前置方式&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="type"&gt;boolean&lt;/span&gt; &lt;span class="title function_"&gt;preHandle&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request, HttpServletResponse response, Object handler)&lt;/span&gt; &lt;span class="keyword"&gt;throws&lt;/span&gt; Exception {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        System.out.println(&lt;span class="string"&gt;"preHandle .... "&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//1.获取请求url&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//2.判断请求url中是否包含login，如果包含，说明是登录操作，放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//3.获取请求头中的令牌（token）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;token&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; request.getHeader(&lt;span class="string"&gt;"token"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"从请求头中获取的令牌：{}"&lt;/span&gt;,token);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//4.判断令牌是否存在，如果不存在，返回错误结果（未登录）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt;(!StringUtils.hasLength(token)){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            log.info(&lt;span class="string"&gt;"Token不存在"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//创建响应结果对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;Result&lt;/span&gt; &lt;span class="variable"&gt;responseResult&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Result.error(&lt;span class="string"&gt;"NOT_LOGIN"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;json&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JSONObject.toJSONString(responseResult);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//设置响应头（告知浏览器：响应的数据类型为json、响应的数据编码表为utf-8）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.setContentType(&lt;span class="string"&gt;"application/json;charset=utf-8"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//响应&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.getWriter().write(json);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;span class="comment"&gt;//不放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//5.解析token，如果解析失败，返回错误结果（未登录）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            JwtUtils.parseJWT(token);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;span class="keyword"&gt;catch&lt;/span&gt; (Exception e){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            log.info(&lt;span class="string"&gt;"令牌解析失败!"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//创建响应结果对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;Result&lt;/span&gt; &lt;span class="variable"&gt;responseResult&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Result.error(&lt;span class="string"&gt;"NOT_LOGIN"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;json&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JSONObject.toJSONString(responseResult);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//设置响应头&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.setContentType(&lt;span class="string"&gt;"application/json;charset=utf-8"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//响应&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            response.getWriter().write(json);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//6.放行&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="literal"&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;注册配置拦截器&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Configuration&lt;/span&gt;  &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;WebConfig&lt;/span&gt; &lt;span class="keyword"&gt;implements&lt;/span&gt; &lt;span class="title class_"&gt;WebMvcConfigurer&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; LoginCheckInterceptor loginCheckInterceptor;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;addInterceptors&lt;/span&gt;&lt;span class="params"&gt;(InterceptorRegistry registry)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;       &lt;span class="comment"&gt;//注册自定义拦截器对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        registry.addInterceptor(loginCheckInterceptor)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .addPathPatterns(&lt;span class="string"&gt;"/**"&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .excludePathPatterns(&lt;span class="string"&gt;"/login"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;登录校验的拦截器编写完成后，接下来我们就可以重新启动服务来做一个测试： （&lt;strong&gt;关闭登录校验Filter过滤器&lt;/strong&gt;）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;测试1：未登录是否可以访问部门管理页面&lt;br/&gt;首先关闭浏览器，重新打开浏览器，在地址栏中输入：&lt;a href="http://localhost:9528/#/system/dept"&gt;http://localhost:9528/#/system/dept&lt;/a&gt;&lt;br/&gt;由于用户没有登录，校验机制返回错误信息，前端页面根据返回的错误信息结果，自动跳转到登录页面了&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105164488-8364b0a8-3d4e-4051-a3cc-718eb567d8e7.png"/&gt; &lt;/li&gt;
&lt;li&gt;测试2：先进行登录操作，再访问部门管理页面&lt;br/&gt;登录校验成功之后，可以正常访问相关业务操作页面&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8B%A6%E6%88%AA%E5%99%A8/1706105169644-e0a70012-305e-4a1e-9750-1922f78274fb.png"/&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到此我们也就验证了所开发的登录校验的拦截器也是没问题的。登录校验的过滤器和拦截器，我们只需要使用其中的一种就可以了。&lt;/p&gt;
</content>
    <summary type="html">SpringBoot-过滤器拦截器 ...</summary>
    <category term="SpringBoot" scheme="https://aspirant02.github.io/categories/SpringBoot/" />
    <category term="SpringBoot" scheme="https://aspirant02.github.io/tags/SpringBoot/" />
    <category term="Java开发" scheme="https://aspirant02.github.io/tags/Java%E5%BC%80%E5%8F%91/" />
  </entry>
  <entry>
    <title>SpringBoot-登录校验技术</title>
    <link href="https://aspirant02.github.io/2026/04/10/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/" />
    <id>https://aspirant02.github.io/2026/04/10/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/</id>
    <published>2026-04-10T13:01:24Z</published>
    <updated>2026-04-10T13:01:24Z</updated>
    <content type="html">&lt;h1 id="登录校验"&gt;&lt;a class="headerlink" href="#登录校验" title="登录校验"&gt;&lt;/a&gt;登录校验&lt;/h1&gt;&lt;h2 id="问题分析"&gt;&lt;a class="headerlink" href="#问题分析" title="问题分析"&gt;&lt;/a&gt;问题分析&lt;/h2&gt;&lt;p&gt;我们已经完成了基础登录功能的开发与测试，在我们登录成功后就可以进入到后台管理系统中进行数据的操作。&lt;/p&gt;
&lt;p&gt;但是当我们在浏览器中新的页面上输入地址：&lt;code&gt;http://localhost:9528/#/system/dept&lt;/code&gt;，发现没有登录仍然可以进入到后端管理系统页面。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165185-73c7cded-7ab1-4353-b4d8-7b74cd4a1921.png"/&gt;&lt;/p&gt;
&lt;p&gt;而真正的登录功能应该是：登陆后才能访问后端系统页面，不登陆则跳转登陆页面进行登陆。&lt;/p&gt;
&lt;p&gt;为什么会出现这个问题？其实原因很简单，就是因为针对于我们当前所开发的部门管理、员工管理以及文件上传等相关接口来说，我们在服务器端并没有做任何的判断，没有去判断用户是否登录了。所以无论用户是否登录，都可以访问部门管理以及员工管理的相关数据。所以我们目前所开发的登录功能，它只是徒有其表。而我们要想解决这个问题，我们就需要完成一步非常重要的操作：登录校验。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165284-3f580f29-f194-4b85-8efa-9b55bbdd5488.png"/&gt;&lt;/p&gt;
&lt;p&gt;什么是登录校验？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所谓登录校验，指的是我们在服务器端接收到浏览器发送过来的请求之后，首先我们要对请求进行校验。先要校验一下用户登录了没有，如果用户已经登录了，就直接执行对应的业务操作就可以了；如果用户没有登录，此时就不允许他执行相关的业务操作，直接给前端响应一个错误的结果，最终跳转到登录页面，要求他登录成功之后，再来访问对应的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;了解完什么是登录校验之后，接下来我们分析一下登录校验大概的实现思路。&lt;/p&gt;
&lt;p&gt;首先我们在宏观上先有一个认知：&lt;/p&gt;
&lt;p&gt;前面在讲解HTTP协议的时候，我们提到HTTP协议是无状态协议。什么又是无状态的协议？&lt;/p&gt;
&lt;p&gt;所谓无状态，指的是每一次请求都是独立的，下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互，基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口，实现了登陆的操作，接下来我们在执行其他业务操作时，服务器也并不知道这个员工到底登陆了没有。因为HTTP协议是无状态的，两次请求之间是独立的，所以是无法判断这个员工到底登陆了没有。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165374-877975fe-b51e-442c-a362-6b867174f298.png"/&gt;&lt;/p&gt;
&lt;p&gt;那应该怎么来实现登录校验的操作呢？具体的实现思路可以分为两部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在员工登录成功后，需要将用户登录成功的信息存起来，记录用户已经登录成功的标记。&lt;/li&gt;
&lt;li&gt;在浏览器发起请求时，需要在服务端进行统一拦截，拦截后进行登录校验。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;想要判断员工是否已经登录，我们需要在员工登录成功之后，存储一个登录成功的标记，接下来在每一个接口方法执行之前，先做一个条件判断，判断一下这个员工到底登录了没有。如果是登录了，就可以执行正常的业务操作，如果没有登录，会直接给前端返回一个错误的信息，前端拿到这个错误信息之后会自动的跳转到登录页面。&lt;/p&gt;
&lt;p&gt;我们程序中所开发的查询功能、删除功能、添加功能、修改功能，都需要使用以上套路进行登录校验。此时就会出现：相同代码逻辑，每个功能都需要编写，就会造成代码非常繁琐。&lt;/p&gt;
&lt;p&gt;为了简化这块操作，我们可以使用一种技术：统一拦截技术。&lt;/p&gt;
&lt;p&gt;通过统一拦截的技术，我们可以来拦截浏览器发送过来的所有的请求，拦截到这个请求之后，就可以通过请求来获取之前所存入的登录标记，在获取到登录标记且标记为登录成功，就说明员工已经登录了。如果已经登录，我们就直接放行(意思就是可以访问正常的业务接口了)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们要完成以上操作，会涉及到web开发中的两个技术：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;会话技术&lt;/li&gt;
&lt;li&gt;统一拦截技术&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而统一拦截技术现实方案也有两种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Servlet规范中的Filter过滤器&lt;/li&gt;
&lt;li&gt;Spring提供的interceptor拦截器&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面我们先学习会话技术，然后再学习统一拦截技术。&lt;/p&gt;
&lt;h2 id="会话技术"&gt;&lt;a class="headerlink" href="#会话技术" title="会话技术"&gt;&lt;/a&gt;会话技术&lt;/h2&gt;&lt;p&gt;介绍了登录校验的大概思路之后，我们先来学习下会话技术。&lt;/p&gt;
&lt;h3 id="会话技术介绍"&gt;&lt;a class="headerlink" href="#会话技术介绍" title="会话技术介绍"&gt;&lt;/a&gt;会话技术介绍&lt;/h3&gt;&lt;p&gt;什么是会话？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在我们日常生活当中，会话指的就是谈话、交谈。 &lt;/li&gt;
&lt;li&gt;在web开发当中，会话指的就是浏览器与服务器之间的一次连接，我们就称为一次会话。 &lt;blockquote&gt;
&lt;p&gt;在用户打开浏览器第一次访问服务器的时候，这个会话就建立了，直到有任何一方断开连接，此时会话就结束了。在一次会话当中，是可以包含多次请求和响应的。&lt;/p&gt;
&lt;p&gt;比如：打开了浏览器来访问web服务器上的资源（浏览器不能关闭、服务器不能断开）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1次：访问的是登录的接口，完成登录操作&lt;/li&gt;
&lt;li&gt;第2次：访问的是部门管理接口，查询所有部门数据&lt;/li&gt;
&lt;li&gt;第3次：访问的是员工管理接口，查询员工数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;只要浏览器和服务器都没有关闭，以上3次请求都属于一次会话当中完成的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165485-777d91f3-e823-4ebb-9b5d-b68555c76b59.png"/&gt;&lt;/p&gt;
&lt;p&gt;需要注意的是：会话是和浏览器关联的，当有三个浏览器客户端和服务器建立了连接时，就会有三个会话。同一个浏览器在未关闭之前请求了多次服务器，这多次请求是属于同一个会话。比如：1、2、3这三个请求都是属于同一个会话。当我们关闭浏览器之后，这次会话就结束了。而如果我们是直接把web服务器关了，那么所有的会话就都结束了。&lt;/p&gt;
&lt;p&gt;知道了会话的概念了，接下来我们再来了解下会话跟踪。&lt;/p&gt;
&lt;p&gt;会话跟踪：一种维护浏览器状态的方法，服务器需要识别多次请求是否来自于同一浏览器，以便在同一次会话的多次请求间共享数据。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;服务器会接收很多的请求，但是服务器是需要识别出这些请求是不是同一个浏览器发出来的。比如：1和2这两个请求是不是同一个浏览器发出来的，3和5这两个请求不是同一个浏览器发出来的。如果是同一个浏览器发出来的，就说明是同一个会话。如果是不同的浏览器发出来的，就说明是不同的会话。而识别多次请求是否来自于同一浏览器的过程，我们就称为会话跟踪。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们使用会话跟踪技术就是要完成在同一个会话中，多个请求之间进行共享数据。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么要共享数据呢？&lt;/p&gt;
&lt;p&gt;由于HTTP是无状态协议，在后面请求中怎么拿到前一次请求生成的数据呢？此时就需要在一次会话的多次请求之间进行数据共享&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;会话跟踪技术有两种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cookie（客户端会话跟踪技术） &lt;ul&gt;
&lt;li&gt;数据存储在客户端浏览器当中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Session（服务端会话跟踪技术） &lt;ul&gt;
&lt;li&gt;数据存储在储在服务端&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;令牌技术&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="会话跟踪方案"&gt;&lt;a class="headerlink" href="#会话跟踪方案" title="会话跟踪方案"&gt;&lt;/a&gt;会话跟踪方案&lt;/h3&gt;&lt;p&gt;上面我们介绍了什么是会话，什么是会话跟踪，并且也提到了会话跟踪 3 种常见的技术方案。接下来，我们就来对比一下这 3 种会话跟踪的技术方案，来看一下具体的实现思路，以及它们之间的优缺点。&lt;/p&gt;
&lt;h4 id="方案一-Cookie"&gt;&lt;a class="headerlink" href="#方案一-Cookie" title="方案一 - Cookie"&gt;&lt;/a&gt;方案一 - Cookie&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;cookie 是客户端会话跟踪技术&lt;/strong&gt;，它是存储在客户端浏览器的，我们使用 cookie 来跟踪会话，我们就可以在浏览器第一次发起请求来请求服务器的时候，我们在服务器端来设置一个cookie。&lt;/p&gt;
&lt;p&gt;比如第一次请求了登录接口，登录接口执行完成之后，我们就可以设置一个cookie，在 cookie 当中我们就可以来存储用户相关的一些数据信息。比如我可以在 cookie 当中来存储当前登录用户的用户名，用户的ID。&lt;/p&gt;
&lt;p&gt;服务器端在给客户端在响应数据的时候，会&lt;strong&gt;自动&lt;/strong&gt;的将 cookie 响应给浏览器，浏览器接收到响应回来的 cookie 之后，会&lt;strong&gt;自动&lt;/strong&gt;的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中，都会将浏览器本地所存储的 cookie &lt;strong&gt;自动&lt;/strong&gt;地携带到服务端。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165579-a95e0d2c-3212-4dd2-b724-ebc856e1f122.png"/&gt;&lt;/p&gt;
&lt;p&gt;接下来在服务端我们就可以获取到 cookie 的值。我们可以去判断一下这个 cookie 的值是否存在，如果不存在这个cookie，就说明客户端之前是没有访问登录接口的；如果存在 cookie 的值，就说明客户端之前已经登录完成了。这样我们就可以基于 cookie 在同一次会话的不同请求之间来共享数据。&lt;/p&gt;
&lt;p&gt;我刚才在介绍流程的时候，用了 3 个自动：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务器会 &lt;strong&gt;自动&lt;/strong&gt; 的将 cookie 响应给浏览器。 &lt;/li&gt;
&lt;li&gt;浏览器接收到响应回来的数据之后，会 &lt;strong&gt;自动&lt;/strong&gt; 的将 cookie 存储在浏览器本地。 &lt;/li&gt;
&lt;li&gt;在后续的请求当中，浏览器会 &lt;strong&gt;自动&lt;/strong&gt; 的将 cookie 携带到服务器端。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么这一切都是自动化进行的？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是因为 cookie 它是 HTP 协议当中所支持的技术，而各大浏览器厂商都支持了这一标准。在 HTTP 协议官方给我们提供了一个响应头和请求头：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;响应头 Set-Cookie ：设置Cookie数据的 &lt;/li&gt;
&lt;li&gt;请求头 Cookie：携带Cookie数据的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165678-73345fc4-c455-4763-b059-7e2b1229d533.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码测试&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@RestController&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;SessionController&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//设置Cookie&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@GetMapping("/c1")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;cookie1&lt;/span&gt;&lt;span class="params"&gt;(HttpServletResponse response)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        response.addCookie(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Cookie&lt;/span&gt;(&lt;span class="string"&gt;"login_username"&lt;/span&gt;,&lt;span class="string"&gt;"itheima"&lt;/span&gt;)); &lt;span class="comment"&gt;//设置Cookie/响应Cookie&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; Result.success();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//获取Cookie&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@GetMapping("/c2")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;cookie2&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        Cookie[] cookies = request.getCookies();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (Cookie cookie : cookies) {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;if&lt;/span&gt;(cookie.getName().equals(&lt;span class="string"&gt;"login_username"&lt;/span&gt;)){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                System.out.println(&lt;span class="string"&gt;"login_username: "&lt;/span&gt;+cookie.getValue()); &lt;span class="comment"&gt;//输出name为login_username的cookie&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; Result.success();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;A. 访问c1接口，设置Cookie，&lt;a href="http://localhost:8080/c1"&gt;http://localhost:8080/c1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165792-19d666b1-48c7-4a1b-84fe-312a2a6a6edf.png"/&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到，设置的cookie，通过&lt;strong&gt;响应头Set-Cookie&lt;/strong&gt;响应给浏览器，并且浏览器会将Cookie，存储在浏览器端。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105165910-88a598d0-f7a9-49f0-9a9f-4c912c74ac0c.png"/&gt;&lt;/p&gt;
&lt;p&gt;B. 访问c2接口 &lt;a href="http://localhost:8080/c2%EF%BC%8C%E6%AD%A4%E6%97%B6%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BC%9A%E8%87%AA%E5%8A%A8%E7%9A%84%E5%B0%86Cookie%E6%90%BA%E5%B8%A6%E5%88%B0%E6%9C%8D%E5%8A%A1%E7%AB%AF%EF%BC%8C%E6%98%AF%E9%80%9A%E8%BF%87**%E8%AF%B7%E6%B1%82%E5%A4%B4Cookie**%EF%BC%8C%E6%90%BA%E5%B8%A6%E7%9A%84%E3%80%82"&gt;http://localhost:8080/c2，此时浏览器会自动的将Cookie携带到服务端，是通过**请求头Cookie**，携带的。&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166049-edd46b83-099b-4ca5-9675-b32d95a263aa.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优缺点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：HTTP协议中支持的技术（像Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带，都是浏览器自动进行的，是无需我们手动操作的）&lt;/li&gt;
&lt;li&gt;缺点： &lt;ul&gt;
&lt;li&gt;移动端APP(Android、IOS)中无法使用Cookie&lt;/li&gt;
&lt;li&gt;不安全，用户可以自己禁用Cookie&lt;/li&gt;
&lt;li&gt;Cookie不能跨域&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;跨域介绍：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706105166167-30f0ad7b-1a75-4af2-8614-ff0671aa7177.png#averageHue=%23f8f2ed&amp;amp;id=j5ptC&amp;amp;originHeight=406&amp;amp;originWidth=1000&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;现在的项目，大部分都是前后端分离的，前后端最终也会分开部署，前端部署在服务器 192.168.150.200 上，端口 80，后端部署在 192.168.150.100上，端口 8080&lt;/li&gt;
&lt;li&gt;我们打开浏览器直接访问前端工程，访问url：&lt;a href="http://_192.168.150.200_login/"&gt;http://192.168.150.200/login.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;然后在该页面发起请求到服务端，而服务端所在地址不再是localhost，而是服务器的IP地址192.168.150.100，假设访问接口地址为：&lt;a href="http://192.168.150.100:8080/login"&gt;http://192.168.150.100:8080/login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;那此时就存在跨域操作了，因为我们是在 &lt;a href="http://_192.168.150.200_login/"&gt;http://192.168.150.200/login.html&lt;/a&gt; 这个页面上访问了&lt;a href="http://192.168.150.100:8080/login"&gt;http://192.168.150.100:8080/login&lt;/a&gt; 接口&lt;/li&gt;
&lt;li&gt;此时如果服务器设置了一个Cookie，这个Cookie是不能使用的，因为Cookie无法跨域&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;区分跨域的维度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;协议&lt;/li&gt;
&lt;li&gt;IP/协议&lt;/li&gt;
&lt;li&gt;端口&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;只要上述的三个维度有任何一个维度不同，那就是跨域操作&lt;/p&gt;
&lt;p&gt;举例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[http://192.168.150.200/login.html](http:_192.168.150.200_login) ----------&amp;gt; [https://192.168.150.200/login](https://192.168.150.200/login)   		[协议不同，跨域]

[http://192.168.150.200/login.html](http:_192.168.150.200_login) ----------&amp;gt; [http://192.168.150.100/login](http://192.168.150.100/login)     		[IP不同，跨域]

[http://192.168.150.200/login.html](http:_192.168.150.200_login) ----------&amp;gt; [http://192.168.150.200:8080/login](http://192.168.150.200:8080/login)   [端口不同，跨域]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;    &lt;a href="http://_192.168.150.200_login/"&gt;http://192.168.150.200/login.html&lt;/a&gt; ———-&amp;gt; &lt;a href="http://192.168.150.200/login"&gt;http://192.168.150.200/login&lt;/a&gt;    		 [不跨域]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="方案二-Session"&gt;&lt;a class="headerlink" href="#方案二-Session" title="方案二 - Session"&gt;&lt;/a&gt;方案二 - Session&lt;/h4&gt;&lt;p&gt;前面介绍的时候，我们提到Session，&lt;strong&gt;它是服务器端会话跟踪技术&lt;/strong&gt;，所以它是存储在服务器端的。而 Session 的底层其实就是基于我们刚才所介绍的 Cookie 来实现的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;获取Session&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166256-bed33c3f-8a41-47ba-8b8a-2cf0bc3734f5.png"/&gt;&lt;br/&gt;如果我们现在要基于 Session 来进行会话跟踪，浏览器在第一次请求服务器的时候，我们就可以直接在服务器当中来获取到会话对象Session。如果是第一次请求Session ，会话对象是不存在的，这个时候服务器会自动的创建一个会话对象Session 。而每一个会话对象Session ，它都有一个ID（示意图中Session后面括号中的1，就表示ID），我们称之为 Session 的ID。 &lt;/li&gt;
&lt;li&gt;响应Cookie (JSESSIONID)&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166338-3c124015-5dca-4579-a8cd-0f8f33b62f44.png"/&gt;&lt;br/&gt;接下来，服务器端在给浏览器响应数据的时候，它会将 Session 的 ID 通过 Cookie 响应给浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。这个  Set-Cookie  响应头对应的值是不是cookie？ cookie 的名字是固定的 JSESSIONID 代表的服务器端会话对象 Session 的 ID。浏览器会自动识别这个响应头，然后自动将Cookie存储在浏览器本地。 &lt;/li&gt;
&lt;li&gt;查找Session&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166427-4a3ab2ff-d2aa-47f6-bdf0-94d4b579a56e.png"/&gt;&lt;br/&gt;接下来，在后续的每一次请求当中，都会将 Cookie 的数据获取出来，并且携带到服务端。接下来服务器拿到JSESSIONID这个 Cookie 的值，也就是 Session 的ID。拿到 ID 之后，就会从众多的 Session 当中来找到当前请求对应的会话对象Session。&lt;br/&gt;这样我们是不是就可以通过 Session 会话对象在同一次会话的多次请求之间来共享数据了？好，这就是基于 Session 进行会话跟踪的流程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码测试&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@RestController&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;SessionController&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@GetMapping("/s1")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;session1&lt;/span&gt;&lt;span class="params"&gt;(HttpSession session)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"HttpSession-s1: {}"&lt;/span&gt;, session.hashCode());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        session.setAttribute(&lt;span class="string"&gt;"loginUser"&lt;/span&gt;, &lt;span class="string"&gt;"tom"&lt;/span&gt;); &lt;span class="comment"&gt;//往session中存储数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; Result.success();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@GetMapping("/s2")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;session2&lt;/span&gt;&lt;span class="params"&gt;(HttpServletRequest request)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;HttpSession&lt;/span&gt; &lt;span class="variable"&gt;session&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; request.getSession();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"HttpSession-s2: {}"&lt;/span&gt;, session.hashCode());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Object&lt;/span&gt; &lt;span class="variable"&gt;loginUser&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; session.getAttribute(&lt;span class="string"&gt;"loginUser"&lt;/span&gt;); &lt;span class="comment"&gt;//从session中获取数据&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        log.info(&lt;span class="string"&gt;"loginUser: {}"&lt;/span&gt;, loginUser);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; Result.success(loginUser);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;A. 访问 s1 接口，&lt;a href="http://localhost:8080/s1"&gt;http://localhost:8080/s1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166515-054a5251-56fc-438f-ab1f-d02669a0f416.png"/&gt;&lt;/p&gt;
&lt;p&gt;请求完成之后，在响应头中，就会看到有一个Set-Cookie的响应头，里面响应回来了一个Cookie，就是JSESSIONID，这个就是服务端会话对象 Session 的ID。&lt;/p&gt;
&lt;p&gt;B. 访问 s2 接口，&lt;a href="http://localhost:8080/s2"&gt;http://localhost:8080/s2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166658-ba7a3e11-8bf5-4a41-abfa-3b718dd11f7a.png"/&gt;&lt;/p&gt;
&lt;p&gt;接下来，在后续的每次请求时，都会将Cookie的值，携带到服务端，那服务端呢，接收到Cookie之后，会自动的根据JSESSIONID的值，找到对应的会话对象Session。&lt;/p&gt;
&lt;p&gt;那经过这两步测试，大家也会看到，在控制台中输出如下日志：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105166785-20b03734-2249-4614-86de-4e7a454063da.png"/&gt;&lt;/p&gt;
&lt;p&gt;两次请求，获取到的Session会话对象的hashcode是一样的，就说明是同一个会话对象。而且，第一次请求时，往Session会话对象中存储的值，第二次请求时，也获取到了。 那这样，我们就可以通过Session会话对象，在同一个会话的多次请求之间来进行数据共享了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优缺点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：Session是存储在服务端的，安全&lt;/li&gt;
&lt;li&gt;缺点： &lt;ul&gt;
&lt;li&gt;服务器集群环境下无法直接使用Session&lt;/li&gt;
&lt;li&gt;移动端APP(Android、IOS)中无法使用Cookie&lt;/li&gt;
&lt;li&gt;用户可以自己禁用Cookie&lt;/li&gt;
&lt;li&gt;Cookie不能跨域&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PS：Session 底层是基于Cookie实现的会话跟踪，如果Cookie不可用，则该方案，也就失效了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;服务器集群环境为何无法使用Session？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![](https://cdn.nlark.com/yuque/0/2026/png/22976360/1706105166897-fd457716-58a6-4e4d-a17e-91b8cc402403.png#averageHue=%23f8eee6&amp;amp;id=lPzUp&amp;amp;originHeight=132&amp;amp;originWidth=540&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;首先第一点，我们现在所开发的项目，一般都不会只部署在一台服务器上，因为一台服务器会存在一个很大的问题，就是单点故障。所谓单点故障，指的就是一旦这台服务器挂了，整个应用都没法访问了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;    &lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167033-97bb2c75-575e-42d9-9336-7028fb075da8.png"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所以在现在的企业项目开发当中，最终部署的时候都是以集群的形式来进行部署，也就是同一个项目它会部署多份。比如这个项目我们现在就部署了 3 份。 &lt;/li&gt;
&lt;li&gt;而用户在访问的时候，到底访问这三台其中的哪一台？其实用户在访问的时候，他会访问一台前置的服务器，我们叫负载均衡服务器，我们在后面项目当中会详细讲解。目前大家先有一个印象负载均衡服务器，它的作用就是将前端发起的请求均匀的分发给后面的这三台服务器。&lt;br/&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167122-1608aec0-5860-462a-8c06-28d302527326.png"/&gt; &lt;/li&gt;
&lt;li&gt;此时假如我们通过 session 来进行会话跟踪，可能就会存在这样一个问题。用户打开浏览器要进行登录操作，此时会发起登录请求。登录请求到达负载均衡服务器，将这个请求转给了第一台 Tomcat 服务器。&lt;br/&gt;Tomcat 服务器接收到请求之后，要获取到会话对象session。获取到会话对象 session 之后，要给浏览器响应数据，最终在给浏览器响应数据的时候，就会携带这么一个 cookie 的名字，就是 JSESSIONID ，下一次再请求的时候，是不是又会将 Cookie 携带到服务端？&lt;br/&gt;好。此时假如又执行了一次查询操作，要查询部门的数据。这次请求到达负载均衡服务器之后，负载均衡服务器将这次请求转给了第二台 Tomcat 服务器，此时他就要到第二台 Tomcat 服务器当中。根据JSESSIONID 也就是对应的 session 的 ID 值，要找对应的 session 会话对象。&lt;br/&gt;我想请问在第二台服务器当中有没有这个ID的会话对象 Session， 是没有的。此时是不是就出现问题了？我同一个浏览器发起了 2 次请求，结果获取到的不是同一个会话对象，这就是Session这种会话跟踪方案它的缺点，在服务器集群环境下无法直接使用Session。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家会看到上面这两种传统的会话技术，在现在的企业开发当中是不是会存在很多的问题。 为了解决这些问题，在现在的企业开发当中，基本上都会采用第三种方案，通过令牌技术来进行会话跟踪。接下来我们就来介绍一下令牌技术，来看一下令牌技术又是如何跟踪会话的。&lt;/p&gt;
&lt;h4 id="方案三-令牌技术"&gt;&lt;a class="headerlink" href="#方案三-令牌技术" title="方案三 - 令牌技术"&gt;&lt;/a&gt;方案三 - 令牌技术&lt;/h4&gt;&lt;p&gt;这里我们所提到的令牌，其实它就是一个用户身份的标识，看似很高大上，很神秘，其实本质就是一个字符串。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167224-656fbfaa-21eb-4ffe-83cb-38501c91c38c.png"/&gt;&lt;/p&gt;
&lt;p&gt;如果通过令牌技术来跟踪会话，我们就可以在浏览器发起请求。在请求登录接口的时候，如果登录成功，我就可以生成一个令牌，令牌就是用户的合法身份凭证。接下来我在响应数据的时候，我就可以直接将令牌响应给前端。&lt;/p&gt;
&lt;p&gt;接下来我们在前端程序当中接收到令牌之后，就需要将这个令牌存储起来。这个存储可以存储在 cookie 当中，也可以存储在其他的存储空间(比如：localStorage)当中。&lt;/p&gt;
&lt;p&gt;接下来，在后续的每一次请求当中，都需要将令牌携带到服务端。携带到服务端之后，接下来我们就需要来校验令牌的有效性。如果令牌是有效的，就说明用户已经执行了登录操作，如果令牌是无效的，就说明用户之前并未执行登录操作。&lt;/p&gt;
&lt;p&gt;此时，如果是在同一次会话的多次请求之间，我们想共享数据，我们就可以将共享的数据存储在令牌当中就可以了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优缺点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点： &lt;ul&gt;
&lt;li&gt;支持PC端、移动端&lt;/li&gt;
&lt;li&gt;解决集群环境下的认证问题&lt;/li&gt;
&lt;li&gt;减轻服务器的存储压力（无需在服务器端存储）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点：需要自己实现（包括令牌的生成、令牌的传递、令牌的校验）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;针对于这三种方案，现在企业开发当中使用的最多的就是第三种令牌技术进行会话跟踪。而前面的这两种传统的方案，现在企业项目开发当中已经很少使用了。所以在我们的课程当中，我们也将会采用令牌技术来解决案例项目当中的会话跟踪问题。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="JWT令牌"&gt;&lt;a class="headerlink" href="#JWT令牌" title="JWT令牌"&gt;&lt;/a&gt;JWT令牌&lt;/h2&gt;&lt;p&gt;前面我们介绍了基于令牌技术来实现会话追踪。这里所提到的令牌就是用户身份的标识，其本质就是一个字符串。令牌的形式有很多，我们使用的是功能强大的 JWT令牌。&lt;/p&gt;
&lt;h3 id="介绍"&gt;&lt;a class="headerlink" href="#介绍" title="介绍"&gt;&lt;/a&gt;介绍&lt;/h3&gt;&lt;p&gt;JWT全称：JSON Web Token  （官网：&lt;a href="https://jwt.io/%EF%BC%89"&gt;https://jwt.io/）&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义了一种简洁的、自包含的格式，用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在，这些信息是可靠的。 &lt;blockquote&gt;
&lt;p&gt;简洁：是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。&lt;/p&gt;
&lt;p&gt;自包含：指的是jwt令牌，看似是一个随机的字符串，但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。如：可以直接在jwt令牌中存储用户的相关信息。&lt;/p&gt;
&lt;p&gt;简单来讲，jwt就是将原始的json数据格式进行了安全的封装，这样就可以直接基于jwt在通信双方安全的进行信息传输了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JWT的组成： （JWT令牌由三个部分组成，三个部分之间使用英文的点来分割）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一部分：Header(头）， 记录令牌类型、签名算法等。 例如：{“alg”:”HS256”,”type”:”JWT”} &lt;/li&gt;
&lt;li&gt;第二部分：Payload(有效载荷），携带一些自定义信息、默认信息等。 例如：{“id”:”1”,”username”:”Tom”} &lt;/li&gt;
&lt;li&gt;第三部分：Signature(签名），防止Token被篡改、确保安全性。将header、payload，并加入指定秘钥，通过指定签名算法计算而来。 &lt;blockquote&gt;
&lt;p&gt;签名的目的就是为了防jwt令牌被篡改，而正是因为jwt令牌最后一个部分数字签名的存在，所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了，整个令牌在校验的时候都会失败，所以它是非常安全可靠的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167404-2d10f9e1-d7a8-4042-8e45-0d2b382a837e.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JWT是如何将原始的JSON格式数据，转变为字符串的呢？&lt;/p&gt;
&lt;p&gt;其实在生成JWT令牌时，会对JSON格式的数据进行一次编码：进行base64编码&lt;/p&gt;
&lt;p&gt;Base64：是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码，那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9，一个加号，一个斜杠，加起来就是64个字符。任何数据经过base64编码之后，最终就会通过这64个字符来表示。当然还有一个符号，那就是等号。等号它是一个补位的符号&lt;/p&gt;
&lt;p&gt;需要注意的是Base64是编码方式，而不是加密方式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167494-86b6b78c-3114-4cf1-a435-25eef3e6ab79.png"/&gt;&lt;/p&gt;
&lt;p&gt;JWT令牌最典型的应用场景就是登录认证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在浏览器发起请求来执行登录操作，此时会访问登录的接口，如果登录成功之后，我们需要生成一个jwt令牌，将生成的 jwt令牌返回给前端。&lt;/li&gt;
&lt;li&gt;前端拿到jwt令牌之后，会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。&lt;/li&gt;
&lt;li&gt;服务端统一拦截请求之后，先来判断一下这次请求有没有把令牌带过来，如果没有带过来，直接拒绝访问，如果带过来了，还要校验一下令牌是否是有效。如果有效，就直接放行进行请求的处理。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在JWT登录认证的场景中我们发现，整个流程当中涉及到两步操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在登录成功之后，要生成令牌。&lt;/li&gt;
&lt;li&gt;每一次请求当中，要接收令牌并对令牌进行校验。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;稍后我们再来学习如何来生成jwt令牌，以及如何来校验jwt令牌。&lt;/p&gt;
&lt;h3 id="生成和校验"&gt;&lt;a class="headerlink" href="#生成和校验" title="生成和校验"&gt;&lt;/a&gt;生成和校验&lt;/h3&gt;&lt;p&gt;简单介绍了JWT令牌以及JWT令牌的组成之后，接下来我们就来学习基于Java代码如何生成和校验JWT令牌。&lt;/p&gt;
&lt;p&gt;首先我们先来实现JWT令牌的生成。要想使用JWT令牌，需要先引入JWT的依赖：&lt;/p&gt;
&lt;figure class="highlight xml"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;&amp;lt;!-- JWT依赖--&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;io.jsonwebtoken&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;jjwt&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="tag"&gt;&amp;lt;&lt;span class="name"&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;0.9.1&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="tag"&gt;&amp;lt;/&lt;span class="name"&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;在引入完JWT来赖后，就可以调用工具包中提供的API来完成JWT令牌的生成和校验&lt;/p&gt;
&lt;p&gt;工具类：Jwts&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;生成JWT代码实现：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;genJwt&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Map&amp;lt;String,Object&amp;gt; claims = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;HashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    claims.put(&lt;span class="string"&gt;"id"&lt;/span&gt;,&lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    claims.put(&lt;span class="string"&gt;"username"&lt;/span&gt;,&lt;span class="string"&gt;"Tom"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;jwt&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Jwts.builder()&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .setClaims(claims) &lt;span class="comment"&gt;//自定义内容(载荷)          &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .signWith(SignatureAlgorithm.HS256, &lt;span class="string"&gt;"itheima"&lt;/span&gt;) &lt;span class="comment"&gt;//签名算法        &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .setExpiration(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Date&lt;/span&gt;(System.currentTimeMillis() + &lt;span class="number"&gt;24&lt;/span&gt;*&lt;span class="number"&gt;3600&lt;/span&gt;*&lt;span class="number"&gt;1000&lt;/span&gt;)) &lt;span class="comment"&gt;//有效期   &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .compact();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    System.out.println(jwt);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;运行测试方法：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输出的结果就是生成的JWT令牌,，通过英文的点分割对三个部分进行分割，我们可以将生成的令牌复制一下，然后打开JWT的官网，将生成的令牌直接放在Encoded位置，此时就会自动的将令牌解析出来。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167590-7316eaf8-87e5-44ac-ac2f-b8bb7154238a.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第一部分解析出来，看到JSON格式的原始数据，所使用的签名算法为HS256。&lt;/p&gt;
&lt;p&gt;第二个部分是我们自定义的数据，之前我们自定义的数据就是id，还有一个exp代表的是我们所设置的过期时间。&lt;/p&gt;
&lt;p&gt;由于前两个部分是base64编码，所以是可以直接解码出来。但最后一个部分并不是base64编码，是经过签名算法计算出来的，所以最后一个部分是不会解析的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实现了JWT令牌的生成，下面我们接着使用Java代码来校验JWT令牌(解析生成的令牌)：&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;parseJwt&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;Claims&lt;/span&gt; &lt;span class="variable"&gt;claims&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Jwts.parser()&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .setSigningKey(&lt;span class="string"&gt;"itheima"&lt;/span&gt;)&lt;span class="comment"&gt;//指定签名密钥（必须保证和生成令牌时使用相同的签名密钥）  &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	    .parseClaimsJws(&lt;span class="string"&gt;"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk"&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .getBody();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    System.out.println(claims);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;运行测试方法：&lt;/p&gt;
&lt;figure class="highlight plaintext"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;{id=1, exp=1672729730}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;令牌解析后，我们可以看到id和过期时间，如果在解析的过程当中没有报错，就说明解析成功了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面我们做一个测试：把令牌header中的数字9变为8，运行测试方法后发现报错：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原header： eyJhbGciOiJIUzI1NiJ9&lt;/p&gt;
&lt;p&gt;修改为： eyJhbGciOiJIUzI1NiJ8&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167689-b4b49da4-73d4-4398-8835-2cecf365d003.png"/&gt;&lt;/p&gt;
&lt;p&gt;结论：篡改令牌中的任何一个字符，在对令牌进行解析时都会报错，所以JWT令牌是非常安全可靠的。&lt;/p&gt;
&lt;p&gt;我们继续测试：修改生成令牌的时指定的过期时间，修改为1分钟&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;genJwt&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    Map&amp;lt;String,Object&amp;gt; claims = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;HashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    claims.put(“id”,&lt;span class="number"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    claims.put(“username”,“Tom”);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;jwt&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Jwts.builder()&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .setClaims(claims) &lt;span class="comment"&gt;//自定义内容(载荷)          &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .signWith(SignatureAlgorithm.HS256, “itheima”) &lt;span class="comment"&gt;//签名算法        &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .setExpiration(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Date&lt;/span&gt;(System.currentTimeMillis() + &lt;span class="number"&gt;60&lt;/span&gt;*&lt;span class="number"&gt;1000&lt;/span&gt;)) &lt;span class="comment"&gt;//有效期60秒   &lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .compact();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    System.out.println(jwt);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//输出结果：eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjczMDA5NzU0fQ.RcVIR65AkGiax-ID6FjW60eLFH3tPTKdoK7UtE4A1ro&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;void&lt;/span&gt; &lt;span class="title function_"&gt;parseJwt&lt;/span&gt;&lt;span class="params"&gt;()&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="type"&gt;Claims&lt;/span&gt; &lt;span class="variable"&gt;claims&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Jwts.parser()&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .setSigningKey(&lt;span class="string"&gt;"itheima"&lt;/span&gt;)&lt;span class="comment"&gt;//指定签名密钥&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;.parseClaimsJws(&lt;span class="string"&gt;"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjczMDA5NzU0fQ.RcVIR65AkGiax-ID6FjW60eLFH3tPTKdoK7UtE4A1ro"&lt;/span&gt;)&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        .getBody();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    System.out.println(claims);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;等待1分钟之后运行测试方法发现也报错了，说明：JWT令牌过期后，令牌就失效了，解析的为非法令牌。&lt;/p&gt;
&lt;p&gt;通过以上测试，我们在使用JWT令牌时需要注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JWT校验时使用的签名秘钥，必须和生成JWT令牌时使用的秘钥是配套的。 &lt;/li&gt;
&lt;li&gt;如果JWT令牌解析校验时报错，则说明 JWT令牌被篡改 或 失效了，令牌非法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="登录下发令牌"&gt;&lt;a class="headerlink" href="#登录下发令牌" title="登录下发令牌"&gt;&lt;/a&gt;登录下发令牌&lt;/h3&gt;&lt;p&gt;JWT令牌的生成和校验的基本操作我们已经学习完了，接下来我们就需要在案例当中通过JWT令牌技术来跟踪会话。具体的思路我们前面已经分析过了，主要就是两步操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生成令牌 &lt;ul&gt;
&lt;li&gt;在登录成功之后来生成一个JWT令牌，并且把这个令牌直接返回给前端&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;校验令牌 &lt;ul&gt;
&lt;li&gt;拦截前端请求，从请求中获取到令牌，对令牌进行解析校验&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那我们首先来完成：登录成功之后生成JWT令牌，并且把令牌返回给前端。&lt;/p&gt;
&lt;p&gt;JWT令牌怎么返回给前端呢？此时我们就需要再来看一下接口文档当中关于登录接口的描述（主要看响应数据）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;响应数据&lt;br/&gt;参数格式：application/json&lt;br/&gt;参数说明： &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;是否必须&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;th&gt;其他信息&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;code&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;必须&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;响应码, 1 成功 ; 0  失败&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;msg&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;非必须&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;提示信息&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;data&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;必须&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;返回的数据 , jwt令牌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;响应数据样例： &lt;/p&gt;
&lt;figure class="highlight json"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"code"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"msg"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"success"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;  &lt;span class="attr"&gt;"data"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;备注说明&lt;br/&gt;用户登录成功后，系统会自动下发JWT令牌，然后在后续的每次请求中，都需要在请求头header中携带到服务端，请求头的名称为 token ，值为 登录时下发的JWT令牌。&lt;br/&gt;如果检测到用户未登录，则会返回如下固定错误信息： &lt;/p&gt;
&lt;figure class="highlight json"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"code"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"msg"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="string"&gt;"NOT_LOGIN"&lt;/span&gt;&lt;span class="punctuation"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;	&lt;span class="attr"&gt;"data"&lt;/span&gt;&lt;span class="punctuation"&gt;:&lt;/span&gt; &lt;span class="literal"&gt;&lt;span class="keyword"&gt;null&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="punctuation"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解读完接口文档中的描述了，目前我们先来完成令牌的生成和令牌的下发，我们只需要生成一个令牌返回给前端就可以了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;引入JWT工具类 &lt;ul&gt;
&lt;li&gt;在项目工程下创建com.itheima.utils包，并把提供JWT工具类复制到该包下&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;登录完成后，调用工具类生成JWT令牌并返回&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;JWT工具类&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;28&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;29&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;30&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;31&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;32&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;JwtUtils&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;signKey&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="string"&gt;"itheima"&lt;/span&gt;;&lt;span class="comment"&gt;//签名密钥&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; &lt;span class="type"&gt;Long&lt;/span&gt; &lt;span class="variable"&gt;expire&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; &lt;span class="number"&gt;43200000L&lt;/span&gt;; &lt;span class="comment"&gt;//有效时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * 生成JWT令牌&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; claims JWT第二部分负载 payload 中存储的内容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@return&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; String &lt;span class="title function_"&gt;generateJwt&lt;/span&gt;&lt;span class="params"&gt;(Map&amp;lt;String, Object&amp;gt; claims)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;jwt&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Jwts.builder()&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .addClaims(claims)&lt;span class="comment"&gt;//自定义信息（有效载荷）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .signWith(SignatureAlgorithm.HS256, signKey)&lt;span class="comment"&gt;//签名算法（头部）&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .setExpiration(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;Date&lt;/span&gt;(System.currentTimeMillis() + expire))&lt;span class="comment"&gt;//过期时间&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .compact();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; jwt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * 解析JWT令牌&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@param&lt;/span&gt; jwt JWT令牌&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     * &lt;span class="doctag"&gt;@return&lt;/span&gt; JWT第二部分负载 payload 中存储的内容&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="comment"&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;static&lt;/span&gt; Claims &lt;span class="title function_"&gt;parseJWT&lt;/span&gt;&lt;span class="params"&gt;(String jwt)&lt;/span&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Claims&lt;/span&gt; &lt;span class="variable"&gt;claims&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; Jwts.parser()&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .setSigningKey(signKey)&lt;span class="comment"&gt;//指定签名密钥&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .parseClaimsJws(jwt)&lt;span class="comment"&gt;//指定令牌Token&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;                .getBody();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; claims;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;登录成功，生成JWT令牌并返回&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="highlight java"&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre&gt;&lt;span class="line"&gt;1&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;2&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;3&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;4&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;5&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;6&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;7&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;8&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;9&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;10&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;11&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;12&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;13&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;14&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;15&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;16&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;17&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;18&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;19&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;20&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;21&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;22&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;23&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;24&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;25&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;26&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;27&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@RestController&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="meta"&gt;@Slf4j&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;span class="keyword"&gt;public&lt;/span&gt; &lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="title class_"&gt;LoginController&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="comment"&gt;//依赖业务层对象&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@Autowired&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;private&lt;/span&gt; EmpService empService;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="meta"&gt;@PostMapping("/login")&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    &lt;span class="keyword"&gt;public&lt;/span&gt; Result &lt;span class="title function_"&gt;login&lt;/span&gt;&lt;span class="params"&gt;(&lt;span class="meta"&gt;@RequestBody&lt;/span&gt; Emp emp)&lt;/span&gt; {&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//调用业务层：登录功能&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="type"&gt;Emp&lt;/span&gt; &lt;span class="variable"&gt;loginEmp&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; empService.login(emp);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="comment"&gt;//判断：登录用户是否存在&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt;(loginEmp !=&lt;span class="literal"&gt;null&lt;/span&gt; ){&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//自定义信息&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            Map&amp;lt;String , Object&amp;gt; claims = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="title class_"&gt;HashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            claims.put(&lt;span class="string"&gt;"id"&lt;/span&gt;, loginEmp.getId());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            claims.put(&lt;span class="string"&gt;"username"&lt;/span&gt;,loginEmp.getUsername());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            claims.put(&lt;span class="string"&gt;"name"&lt;/span&gt;,loginEmp.getName());&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="comment"&gt;//使用JWT工具类，生成身份令牌&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="type"&gt;String&lt;/span&gt; &lt;span class="variable"&gt;token&lt;/span&gt; &lt;span class="operator"&gt;=&lt;/span&gt; JwtUtils.generateJwt(claims);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;            &lt;span class="keyword"&gt;return&lt;/span&gt; Result.success(token);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; Result.error(&lt;span class="string"&gt;"用户名或密码错误"&lt;/span&gt;);&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;    }&lt;/span&gt;&lt;br/&gt;&lt;span class="line"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;重启服务，打开postman测试登录接口：&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167780-61fb5af5-684a-4fdf-9810-c090c89c0acc.png"/&gt;&lt;/p&gt;
&lt;p&gt;打开浏览器完成前后端联调操作：利用开发者工具，抓取一下网络请求&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167878-c9395452-c79d-4c78-8f0f-555fbb5140b1.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;登录请求完成后，可以看到JWT令牌已经响应给了前端，此时前端就会将JWT令牌存储在浏览器本地。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;服务器响应的JWT令牌存储在本地浏览器哪里了呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在当前案例中，JWT令牌存储在浏览器的本地存储空间local storage中了。 local storage是浏览器的本地存储，在移动端也是支持的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105167995-50c6386c-38d3-4ae3-8068-7349d25e785f.png"/&gt;&lt;/p&gt;
&lt;p&gt;我们在发起一个查询部门数据的请求，此时我们可以看到在请求头中包含一个token(JWT令牌)，后续的每一次请求当中，都会将这个令牌携带到服务端。&lt;/p&gt;
&lt;p&gt;&lt;img src="/./img/SpringBoot-%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%8A%80%E6%9C%AF/1706105168097-d26ea7d6-fec5-42ac-becf-cf8867bd0bb3.png"/&gt;&lt;/p&gt;
</content>
    <summary type="html">SpringBoot-登录校验技术 ...</summary>
    <category term="SpringBoot" scheme="https://aspirant02.github.io/categories/SpringBoot/" />
    <category term="SpringBoot" scheme="https://aspirant02.github.io/tags/SpringBoot/" />
    <category term="Java开发" scheme="https://aspirant02.github.io/tags/Java%E5%BC%80%E5%8F%91/" />
  </entry>
</feed>