<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>WanAn</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://www.wananhome.site/</id>
  <link href="https://www.wananhome.site/" rel="alternate"/>
  <link href="https://www.wananhome.site/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, WanAn</rights>
  <subtitle>WanAn的个人网站</subtitle>
  <title>WanAnHome</title>
  <updated>2026-03-23T04:54:36.811Z</updated>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="AI" scheme="https://www.wananhome.site/categories/AI/"/>
    <category term="MCP" scheme="https://www.wananhome.site/tags/MCP/"/>
    <category term="Codex" scheme="https://www.wananhome.site/tags/Codex/"/>
    <category term="Java" scheme="https://www.wananhome.site/tags/Java/"/>
    <content>
      <![CDATA[<h2 id="什么是MCP？"><a href="#什么是MCP？" class="headerlink" title="什么是MCP？"></a>什么是MCP？</h2><p><a href="https://modelcontextprotocol.io/">MCP</a>全称<em>Model Context Protocol</em>，译为<em>模型上下文协议</em>。</p><p>要理解MCP是什么，先要知道MCP可以做什么。我们知道，*LLM（大语言模型）*虽然具有很强的推理能力，但是局限于训练数据，如果训练数据只包含2024年之前的数据，那么LLM就无法分析2026年发生的事情，因为它缺少相应的数据。</p><p>对于数据时效性的问题，一般的解决方法是：提供一个包含最新数据的知识库，先在知识库中进行检索相关数据，然后将检索到的数据发送给LLM，这样，LLM就能知道最新的数据了。这个方法也就是所谓的<em>RAG（检索增强生成）</em>。</p><p>关键字：<strong>检索</strong>，谁来检索？如何检索？LLM的能力是推理，并没有检索的功能啊！</p><p>实际上，虽然我们一直在说某某模型（GPT、Qwen），但我们实际使用时并不是直接与LLM进行交互的。我们是直接与各种客户端交互（ChatGPT网页端、Codex、千问网页端），这些客户端先通过LLM分析用户的输入，LLM判断需要额外检索知识库，客户端再根据LLM的判断去检索知识库。客户端可以将检索到的数据直接返回给用户，也可以进一步调用LLM进行分析。</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/image-20260322162747396-085836f89eb3.png" class="image-20260322162747396"><p>也就是说，RAG是让客户端来进行检索。实际上，也不是客户端进行检索，而是客户端调用工具进行检索。那么，既然客户端可以调用工具进行检索，自然也可以调用工具进行地图查询、天气查询、联网搜索，等等。</p><p>那么，问题来了，不同的工具需要不同的输入参数，返回不同的结果，例如：天气查询返回文本数据，地图服务返回二进制数据。难道要客户端对每个工具都进行适配吗？各种工具，如此繁杂，太不现实了。难道要每个工具都对客户端进行适配吗？先不说客户端开发商有没有那么大的号召力，就说客户端也不止一家啊，适配了这家客户端，那别家客户端怎么办，也不现实。</p><p>据说有一句名言，忘了是谁说的了，大概意思是：所有计算机问题，都可以通过加一层中间层来解决。</p><p>说了那么多，<em>MCP</em>就是这个中间层。</p><p><strong>MCP</strong>是一个协议，实现了MCP协议的服务，承诺接收固定格式的参数，返回固定格式的结果。</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/ChatGPTImage17_04_33-7bc69f906bc2.png" class="ChatGPTImage17_04_33"><p>也就是说，现在客户端也不直接调用工具了，而是通过MCP服务来调用工具。</p><p>最后，贴一段<a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP官方</a>的定义：<code>MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.</code>（MCP（模型上下文协议）是一种用于将 AI 应用程序连接到外部系统的开源标准。）</p><p>接下来，我们边做边学，在实操中，我会解释涉及到的概念。</p><blockquote><p>后续操作主要使用Codex Desktop，和Java。若是技术栈不符，只看其中的概念，也是通用的。</p></blockquote><h2 id="本地MCP服务"><a href="#本地MCP服务" class="headerlink" title="本地MCP服务"></a>本地MCP服务</h2><p>在这一部分，我们会在本地实现一个可供Codex使用的本地文件MCP服务。</p><ol><li><p>安装<a href="https://openai.com/zh-Hans-CN/codex/get-started/">Codex Desktop</a></p></li><li><p>安装<a href="https://nodejs.org/">Node.js</a></p></li><li><p>创建一个SpringBoot项目：McpDemo：</p><p>引入pom依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>4.0.4<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>McpDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>McpDemo<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>McpDemo<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>/&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">licenses</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">license</span>/&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">licenses</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">developers</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">developer</span>/&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">developers</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">scm</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">connection</span>/&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">developerConnection</span>/&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">tag</span>/&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">url</span>/&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">scm</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-ai.version</span>&gt;</span>2.0.0-M3<span class="hljs-tag">&lt;/<span class="hljs-name">spring-ai.version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.ai<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-ai-starter-mcp-server<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.ai<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-ai-bom<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-ai.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br><br></code></pre></td></tr></table></figure></li><li><p>配置application.yaml依赖：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">McpDemo</span><br>  <span class="hljs-attr">main:</span><br>    <span class="hljs-attr">banner-mode:</span> <span class="hljs-string">off</span>  <span class="hljs-comment"># 关闭 Spring 启动时输出的 banner，避免破坏 STDIO MCP</span><br>    <span class="hljs-attr">web-application-type:</span> <span class="hljs-string">none</span> <span class="hljs-comment"># 不启用 Web容器</span><br>  <span class="hljs-attr">ai:</span><br>    <span class="hljs-attr">mcp:</span><br>      <span class="hljs-attr">server:</span><br>        <span class="hljs-attr">name:</span> <span class="hljs-string">local-file-server</span> <span class="hljs-comment"># MCP服务名称</span><br>        <span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span> <span class="hljs-comment"># 版本</span><br>        <span class="hljs-attr">stdio:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 低于 STDIO 的 MCP服务</span><br>        <span class="hljs-attr">type:</span> <span class="hljs-string">SYNC</span> <span class="hljs-comment"># 同步模式，客户端会阻塞等待服务器的返回</span><br>        <span class="hljs-attr">instructions:</span> <span class="hljs-string">|</span> <span class="hljs-comment"># MCP 服务的描述</span><br>          <span class="hljs-string">This</span> <span class="hljs-string">server</span> <span class="hljs-string">can</span> <span class="hljs-string">list</span> <span class="hljs-string">files,</span> <span class="hljs-string">read</span> <span class="hljs-string">text</span> <span class="hljs-string">files,</span> <span class="hljs-string">and</span> <span class="hljs-string">search</span> <span class="hljs-string">text</span> <span class="hljs-string">in</span> <span class="hljs-string">files</span><br>          <span class="hljs-string">under</span> <span class="hljs-string">the</span> <span class="hljs-string">configured</span> <span class="hljs-string">base</span> <span class="hljs-string">directory</span> <span class="hljs-string">only.</span><br>        <span class="hljs-attr">annotation-scanner:</span><br>          <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 启动时自动扫描项目中带 MCP 注解的 Bean 并注册为 MCP 工具，不需要手动注册每个工具</span><br><span class="hljs-attr">logging:</span><br>  <span class="hljs-attr">pattern:</span><br>    <span class="hljs-attr">console:</span> <span class="hljs-comment"># 不在控制台输出日志</span><br>  <span class="hljs-attr">file:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">./log/application.log</span> <span class="hljs-comment"># 将日志输出到文件中，默认是 stdio，但stdio要被mcp占用，如果我们用了stdio就会让mcp异常</span><br></code></pre></td></tr></table></figure><p>MCP支持基于<strong>STDIO</strong>和<strong>Streamable HTTP</strong>的服务：</p><ul><li>STDIO：作为本地进程运行的服务器（由命令启动）</li><li>Streamable HTTP：可以访问的远程服务</li></ul></li><li><p>编写处理本地文件的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.ai.mcp.annotation.McpTool;<br><span class="hljs-keyword">import</span> org.springframework.ai.mcp.annotation.McpToolParam;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component;<br><br><span class="hljs-keyword">import</span> java.io.IOException;<br><span class="hljs-keyword">import</span> java.nio.charset.MalformedInputException;<br><span class="hljs-keyword">import</span> java.nio.charset.StandardCharsets;<br><span class="hljs-keyword">import</span> java.nio.file.Files;<br><span class="hljs-keyword">import</span> java.nio.file.Path;<br><span class="hljs-keyword">import</span> java.nio.file.Paths;<br><span class="hljs-keyword">import</span> java.util.*;<br><span class="hljs-keyword">import</span> java.util.stream.Stream;<br><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LocalFileTools</span> &#123;<br><br>    <span class="hljs-comment">// 最大单文件读取大小：1MB</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">long</span> <span class="hljs-variable">MAX_FILE_SIZE</span> <span class="hljs-operator">=</span> <span class="hljs-number">1024</span> * <span class="hljs-number">1024L</span>;<br><br>    <span class="hljs-comment">// 允许读取的文本文件类型</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Set&lt;String&gt; ALLOWED_EXTENSIONS = Set.of(<br>            <span class="hljs-string">&quot;.txt&quot;</span>, <span class="hljs-string">&quot;.md&quot;</span>, <span class="hljs-string">&quot;.java&quot;</span>, <span class="hljs-string">&quot;.xml&quot;</span>, <span class="hljs-string">&quot;.yml&quot;</span>, <span class="hljs-string">&quot;.yaml&quot;</span>, <span class="hljs-string">&quot;.json&quot;</span>,<br>            <span class="hljs-string">&quot;.properties&quot;</span>, <span class="hljs-string">&quot;.js&quot;</span>, <span class="hljs-string">&quot;.ts&quot;</span>, <span class="hljs-string">&quot;.html&quot;</span>, <span class="hljs-string">&quot;.css&quot;</span>, <span class="hljs-string">&quot;.sql&quot;</span>,<br>            <span class="hljs-string">&quot;.sh&quot;</span>, <span class="hljs-string">&quot;.bat&quot;</span>, <span class="hljs-string">&quot;.ps1&quot;</span>, <span class="hljs-string">&quot;.log&quot;</span>, <span class="hljs-string">&quot;.csv&quot;</span><br>    );<br><br>    <span class="hljs-meta">@McpTool(name = &quot;get_base_dir&quot;, description = &quot;返回当前 MCP 服务允许访问的根目录&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getBaseDir</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> getBaseDirPath().toString();<br>    &#125;<br><br>    <span class="hljs-meta">@McpTool(name = &quot;list_files&quot;, description = &quot;列出指定目录下的文件&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">listFiles</span><span class="hljs-params">(</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;相对根目录的子目录&quot;, required = false)</span> String relativeDir,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;是否递归列出&quot;, required = false)</span> Boolean recursive,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;最多返回多少条&quot;, required = false)</span> Integer maxResults</span><br><span class="hljs-params">    )</span> <span class="hljs-keyword">throws</span> IOException &#123;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">dirArg</span> <span class="hljs-operator">=</span> isBlank(relativeDir) ? <span class="hljs-string">&quot;.&quot;</span> : relativeDir;<br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">recursiveFlag</span> <span class="hljs-operator">=</span> recursive == <span class="hljs-literal">null</span> || recursive;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">limit</span> <span class="hljs-operator">=</span> maxResults == <span class="hljs-literal">null</span> ? <span class="hljs-number">200</span> : Math.max(<span class="hljs-number">1</span>, maxResults);<br><br>        <span class="hljs-type">Path</span> <span class="hljs-variable">dir</span> <span class="hljs-operator">=</span> safeResolve(dirArg);<br>        <span class="hljs-keyword">if</span> (!Files.exists(dir)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;目录不存在: &quot;</span> + dirArg;<br>        &#125;<br>        <span class="hljs-keyword">if</span> (!Files.isDirectory(dir)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;不是目录: &quot;</span> + dirArg;<br>        &#125;<br><br>        List&lt;String&gt; files;<br>        <span class="hljs-keyword">try</span> (Stream&lt;Path&gt; stream = recursiveFlag ? Files.walk(dir) : Files.list(dir)) &#123;<br>            files = stream<br>                    .filter(Files::isRegularFile)<br>                    .map(<span class="hljs-built_in">this</span>::toRelativePath)<br>                    .sorted()<br>                    .limit(limit)<br>                    .toList();<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> files.isEmpty() ? <span class="hljs-string">&quot;未找到文件&quot;</span> : String.join(<span class="hljs-string">&quot;\n&quot;</span>, files);<br>    &#125;<br><br>    <span class="hljs-meta">@McpTool(name = &quot;read_file&quot;, description = &quot;读取单个文本文件的指定行范围&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">readFile</span><span class="hljs-params">(</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;相对根目录的文件路径&quot;)</span> String relativePath,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;起始行号，从1开始&quot;, required = false)</span> Integer startLine,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;结束行号&quot;, required = false)</span> Integer endLine</span><br><span class="hljs-params">    )</span> <span class="hljs-keyword">throws</span> IOException &#123;<br><br>        <span class="hljs-keyword">if</span> (isBlank(relativePath)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;relativePath 不能为空&quot;</span>;<br>        &#125;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">start</span> <span class="hljs-operator">=</span> startLine == <span class="hljs-literal">null</span> ? <span class="hljs-number">1</span> : Math.max(<span class="hljs-number">1</span>, startLine);<br>        <span class="hljs-type">int</span> <span class="hljs-variable">end</span> <span class="hljs-operator">=</span> endLine == <span class="hljs-literal">null</span> ? <span class="hljs-number">200</span> : Math.max(start, endLine);<br><br>        <span class="hljs-type">Path</span> <span class="hljs-variable">file</span> <span class="hljs-operator">=</span> safeResolve(relativePath);<br>        <span class="hljs-keyword">if</span> (!Files.exists(file)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;文件不存在: &quot;</span> + relativePath;<br>        &#125;<br>        <span class="hljs-keyword">if</span> (!Files.isRegularFile(file)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;不是文件: &quot;</span> + relativePath;<br>        &#125;<br>        <span class="hljs-keyword">if</span> (!isAllowedTextFile(file)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;不允许读取该类型文件: &quot;</span> + file.getFileName();<br>        &#125;<br>        <span class="hljs-keyword">if</span> (Files.size(file) &gt; MAX_FILE_SIZE) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;文件过大，拒绝读取&quot;</span>;<br>        &#125;<br><br>        List&lt;String&gt; lines;<br>        <span class="hljs-keyword">try</span> &#123;<br>            lines = Files.readAllLines(file, StandardCharsets.UTF_8);<br>        &#125; <span class="hljs-keyword">catch</span> (MalformedInputException e) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;文件不是 UTF-8 文本，暂不支持读取&quot;</span>;<br>        &#125;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">actualEnd</span> <span class="hljs-operator">=</span> Math.min(end, lines.size());<br>        <span class="hljs-type">StringBuilder</span> <span class="hljs-variable">sb</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();<br>        sb.append(<span class="hljs-string">&quot;# File: &quot;</span>).append(relativePath).append(<span class="hljs-string">&quot;\n&quot;</span>);<br>        sb.append(<span class="hljs-string">&quot;# Lines: &quot;</span>).append(start).append(<span class="hljs-string">&quot;-&quot;</span>).append(actualEnd).append(<span class="hljs-string">&quot;\n\n&quot;</span>);<br><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> start; i &lt;= actualEnd; i++) &#123;<br>            sb.append(i).append(<span class="hljs-string">&quot;: &quot;</span>).append(lines.get(i - <span class="hljs-number">1</span>)).append(<span class="hljs-string">&quot;\n&quot;</span>);<br>        &#125;<br>        <span class="hljs-keyword">return</span> sb.toString();<br>    &#125;<br><br>    <span class="hljs-meta">@McpTool(name = &quot;search_in_files&quot;, description = &quot;在目录内搜索关键词，返回 文件路径:行号:内容&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">searchInFiles</span><span class="hljs-params">(</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;要搜索的关键词&quot;)</span> String keyword,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;相对根目录的子目录&quot;, required = false)</span> String relativeDir,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;是否区分大小写&quot;, required = false)</span> Boolean caseSensitive,</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;最多返回多少条命中&quot;, required = false)</span> Integer maxHits</span><br><span class="hljs-params">    )</span> <span class="hljs-keyword">throws</span> IOException &#123;<br><br>        <span class="hljs-keyword">if</span> (isBlank(keyword)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;keyword 不能为空&quot;</span>;<br>        &#125;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">dirArg</span> <span class="hljs-operator">=</span> isBlank(relativeDir) ? <span class="hljs-string">&quot;.&quot;</span> : relativeDir;<br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">caseFlag</span> <span class="hljs-operator">=</span> caseSensitive != <span class="hljs-literal">null</span> &amp;&amp; caseSensitive;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">limit</span> <span class="hljs-operator">=</span> maxHits == <span class="hljs-literal">null</span> ? <span class="hljs-number">100</span> : Math.max(<span class="hljs-number">1</span>, maxHits);<br><br>        <span class="hljs-type">Path</span> <span class="hljs-variable">dir</span> <span class="hljs-operator">=</span> safeResolve(dirArg);<br>        <span class="hljs-keyword">if</span> (!Files.exists(dir) || !Files.isDirectory(dir)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;目录不存在或非法: &quot;</span> + dirArg;<br>        &#125;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">needle</span> <span class="hljs-operator">=</span> caseFlag ? keyword : keyword.toLowerCase(Locale.ROOT);<br>        List&lt;String&gt; hits = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br><br>        <span class="hljs-keyword">try</span> (Stream&lt;Path&gt; stream = Files.walk(dir)) &#123;<br>            Iterator&lt;Path&gt; iterator = stream<br>                    .filter(Files::isRegularFile)<br>                    .filter(<span class="hljs-built_in">this</span>::isAllowedTextFile)<br>                    .iterator();<br><br>            <span class="hljs-keyword">while</span> (iterator.hasNext() &amp;&amp; hits.size() &lt; limit) &#123;<br>                <span class="hljs-type">Path</span> <span class="hljs-variable">file</span> <span class="hljs-operator">=</span> iterator.next();<br><br>                <span class="hljs-keyword">if</span> (Files.size(file) &gt; MAX_FILE_SIZE) &#123;<br>                    <span class="hljs-keyword">continue</span>;<br>                &#125;<br><br>                List&lt;String&gt; lines;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    lines = Files.readAllLines(file, StandardCharsets.UTF_8);<br>                &#125; <span class="hljs-keyword">catch</span> (MalformedInputException e) &#123;<br>                    <span class="hljs-keyword">continue</span>;<br>                &#125;<br><br>                <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; lines.size() &amp;&amp; hits.size() &lt; limit; i++) &#123;<br>                    <span class="hljs-type">String</span> <span class="hljs-variable">line</span> <span class="hljs-operator">=</span> lines.get(i);<br>                    <span class="hljs-type">String</span> <span class="hljs-variable">haystack</span> <span class="hljs-operator">=</span> caseFlag ? line : line.toLowerCase(Locale.ROOT);<br>                    <span class="hljs-keyword">if</span> (haystack.contains(needle)) &#123;<br>                        hits.add(toRelativePath(file) + <span class="hljs-string">&quot;:&quot;</span> + (i + <span class="hljs-number">1</span>) + <span class="hljs-string">&quot;: &quot;</span> + line.trim());<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> hits.isEmpty() ? <span class="hljs-string">&quot;未找到匹配内容&quot;</span> : String.join(<span class="hljs-string">&quot;\n&quot;</span>, hits);<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> Path <span class="hljs-title function_">getBaseDirPath</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">baseDir</span> <span class="hljs-operator">=</span> System.getenv(<span class="hljs-string">&quot;LOCAL_FILE_MCP_BASE_DIR&quot;</span>);<br>        <span class="hljs-keyword">if</span> (isBlank(baseDir)) &#123;<br>            baseDir = <span class="hljs-string">&quot;.&quot;</span>;<br>        &#125;<br>        <span class="hljs-keyword">return</span> Paths.get(baseDir).toAbsolutePath().normalize();<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> Path <span class="hljs-title function_">safeResolve</span><span class="hljs-params">(String userPath)</span> &#123;<br>        <span class="hljs-type">Path</span> <span class="hljs-variable">baseDir</span> <span class="hljs-operator">=</span> getBaseDirPath();<br>        <span class="hljs-type">Path</span> <span class="hljs-variable">resolved</span> <span class="hljs-operator">=</span> baseDir.resolve(userPath).normalize().toAbsolutePath();<br>        <span class="hljs-keyword">if</span> (!resolved.startsWith(baseDir)) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>(<span class="hljs-string">&quot;禁止访问根目录之外的路径: &quot;</span> + userPath);<br>        &#125;<br>        <span class="hljs-keyword">return</span> resolved;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isAllowedTextFile</span><span class="hljs-params">(Path path)</span> &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">fileName</span> <span class="hljs-operator">=</span> path.getFileName().toString().toLowerCase(Locale.ROOT);<br>        <span class="hljs-keyword">return</span> ALLOWED_EXTENSIONS.stream().anyMatch(fileName::endsWith);<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> String <span class="hljs-title function_">toRelativePath</span><span class="hljs-params">(Path path)</span> &#123;<br>        <span class="hljs-keyword">return</span> getBaseDirPath().relativize(path.toAbsolutePath().normalize())<br>                .toString()<br>                .replace(<span class="hljs-string">&quot;\\&quot;</span>, <span class="hljs-string">&quot;/&quot;</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isBlank</span><span class="hljs-params">(String s)</span> &#123;<br>        <span class="hljs-keyword">return</span> s == <span class="hljs-literal">null</span> || s.isBlank();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>编写一个客户端测试一下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> io.modelcontextprotocol.client.McpClient;<br><span class="hljs-keyword">import</span> io.modelcontextprotocol.client.transport.ServerParameters;<br><span class="hljs-keyword">import</span> io.modelcontextprotocol.client.transport.StdioClientTransport;<br><span class="hljs-keyword">import</span> io.modelcontextprotocol.json.McpJsonDefaults;<br><span class="hljs-keyword">import</span> io.modelcontextprotocol.spec.McpSchema.ListToolsResult;<br><span class="hljs-keyword">import</span> org.slf4j.Logger;<br><span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ClientStdio</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">log</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(ClientStdio.class);<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br><br>        <span class="hljs-type">var</span> <span class="hljs-variable">stdioParams</span> <span class="hljs-operator">=</span> ServerParameters.builder(<span class="hljs-string">&quot;java&quot;</span>)<br>                .args(<span class="hljs-string">&quot;-jar&quot;</span>,<br>                        <span class="hljs-string">&quot;D:\\IdeaProjects\\McpDemo\\target\\McpDemo-0.0.1-SNAPSHOT.jar&quot;</span>)<br>                .build();<br><br>        <span class="hljs-type">var</span> <span class="hljs-variable">transport</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StdioClientTransport</span>(stdioParams, McpJsonDefaults.getMapper());<br>        <span class="hljs-type">var</span> <span class="hljs-variable">client</span> <span class="hljs-operator">=</span> McpClient.sync(transport).build();<br><br>        client.initialize();<br><br>        <span class="hljs-comment">// List and demonstrate tools</span><br>        <span class="hljs-type">ListToolsResult</span> <span class="hljs-variable">toolsList</span> <span class="hljs-operator">=</span> client.listTools();<br>        log.info(<span class="hljs-string">&quot;Available Tools = &quot;</span> + toolsList);<br>        <br>        client.closeGracefully();<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>通过maven将项目进行package，获得文件McpDemo-0.0.1-SNAPSHOT.jar</p></li><li><p>运行ClientStdio客户端，可以看到在控制台打印出了工具列表：</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/image-20260323110608682-7cce6092cba1.png" class="image-20260323110608682"></li><li><p>在Codex Desktop的 设置-&gt;MCP服务器 中添加服务器：</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/image-20260322184409796-6f6bbe411928.png" class="image-20260322184409796"><p>对于Codex基于STDIO的MCP，有以下几个主要参数可以配置：</p><ul><li>command：启动命令，必填，是启动MCP服务器的命令</li><li>args：参数，可选，是启动命令的参数</li><li>env：环境变灵，可选，用于配置MCP服务器需要的环境变量</li><li>env_vars：环境变量传递，可选，将系统中的环境变量传递给MCP服务</li><li>cwd：工作目录，可选，限制MCP服务在该目录下工作</li></ul></li><li><p>在Codex中测试：</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/image-20260323112633882-519bfb816473.png" class="image-20260323112633882"></li></ol><h2 id="远程MCP服务"><a href="#远程MCP服务" class="headerlink" title="远程MCP服务"></a>远程MCP服务</h2><p>在这一部分，我们会实现一个可供Codex远程调用的MCP服务。</p><ol><li><p>创建一个SpringBoot项目：McpDemo：</p><p>引入pom依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>4.0.4<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>McpDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>McpDemo<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>McpDemo<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>/&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">licenses</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">license</span>/&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">licenses</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">developers</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">developer</span>/&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">developers</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">scm</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">connection</span>/&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">developerConnection</span>/&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">tag</span>/&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">url</span>/&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">scm</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-ai.version</span>&gt;</span>2.0.0-M3<span class="hljs-tag">&lt;/<span class="hljs-name">spring-ai.version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.ai<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-ai-starter-mcp-server-webmvc<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.ai<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-ai-bom<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-ai.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br><br></code></pre></td></tr></table></figure></li><li><p>编写application.yml配置文件：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">McpDemo</span><br>  <span class="hljs-attr">ai:</span><br>    <span class="hljs-attr">mcp:</span><br>      <span class="hljs-attr">server:</span><br>        <span class="hljs-attr">name:</span> <span class="hljs-string">remote-mcp-server</span> <span class="hljs-comment"># MCP服务名称</span><br>        <span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span> <span class="hljs-comment"># 版本</span><br>        <span class="hljs-attr">type:</span> <span class="hljs-string">SYNC</span> <span class="hljs-comment"># 同步模式，客户端会阻塞等待服务器的返回</span><br>        <span class="hljs-attr">instructions:</span> <span class="hljs-string">这是一个可以和你打招呼的远程MCP服务。</span> <span class="hljs-comment"># MCP 服务的描述</span><br>        <span class="hljs-attr">annotation-scanner:</span><br>          <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 启动时自动扫描项目中带 MCP 注解的 Bean 并注册为 MCP 工具，不需要手动注册每个工具</span><br>        <span class="hljs-attr">protocol:</span> <span class="hljs-string">streamable</span> <span class="hljs-comment"># 基于 Streamable 的 MCP服务</span><br></code></pre></td></tr></table></figure></li><li><p>编写要提供的工具：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.ai.mcp.annotation.McpTool;<br><span class="hljs-keyword">import</span> org.springframework.ai.mcp.annotation.McpToolParam;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component;<br><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">McpTools</span> &#123;<br><br>    <span class="hljs-meta">@McpTool(name = &quot;ping&quot;, description = &quot;检查远程 MCP 服务是否可用&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">ping</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;pong&quot;</span>;<br>    &#125;<br><br>    <span class="hljs-meta">@McpTool(name = &quot;hello&quot;, description = &quot;打个招呼&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">hello</span><span class="hljs-params">(</span><br><span class="hljs-params">            <span class="hljs-meta">@McpToolParam(description = &quot;你的名字&quot;)</span> String name</span><br><span class="hljs-params">    )</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;你好 &quot;</span> + name + <span class="hljs-string">&quot;,我是 远程MCP服务。&quot;</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>启动这个项目</p></li><li><p>在Codex Desktop中配置MCP：</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/image-20260323124300939-30d863886605.png" class="image-20260323124300939"><p>对于Codex基于Streamable HTTP的MCP，有以下几个主要参数可以配置：</p><ul><li>url：必填，MCP服务器的地址</li><li>bearer_token_env_var：Bearer令牌环境变量，可选，用于MCP服务认证授权的Token</li><li>http_headers：表头，可选，添加在http请求头里的键值对</li><li>env_http_headers：来自环境变量的标头，可选，添加在http请求头里的环境变量键值对</li></ul></li><li><p>在Codex中测试：</p><img src="/2026/03/23/MCP%E5%85%A5%E9%97%A8/image-20260323124350189-1b1cc96c91c7.png" class="image-20260323124350189"></li></ol>]]>
    </content>
    <id>https://www.wananhome.site/2026/03/23/MCP%E5%85%A5%E9%97%A8/</id>
    <link href="https://www.wananhome.site/2026/03/23/MCP%E5%85%A5%E9%97%A8/"/>
    <published>2026-03-23T04:52:23.000Z</published>
    <summary>本文适合作为MCP入门级教程，浅显易懂的讲解了MCP概念，并通过简单的两个实践项目来走入MCP服务开发的大门。</summary>
    <title>MCP入门</title>
    <updated>2026-03-23T04:54:36.811Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="JUC" scheme="https://www.wananhome.site/tags/JUC/"/>
    <content>
      <![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p><strong>JUC</strong>（java.util.concurrent），Java并发包，可以让Java更好地处理多线程并发问题。</p><h2 id="传统线程创建方式"><a href="#传统线程创建方式" class="headerlink" title="传统线程创建方式"></a>传统线程创建方式</h2><h3 id="Thread"><a href="#Thread" class="headerlink" title="Thread"></a>Thread</h3><p>继承<strong>Thread类</strong>，重写其中的<em>run</em>方法，在run方法中编写任务代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">ThreadA</span> <span class="hljs-variable">threadA</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadA</span>();<br>        threadA.start();<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ThreadA</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Thread</span>&#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;通过继承Thread的方式创建一个线程：ThreadA&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><em>start方法</em>才是启动线程的方法，<em>run方法</em>是执行任务的方法，所以需要通过start方法来启动线程，线程会自动执行任务。</p><p>因为需要在Thread的run方法中编写任务代码，也就造成了线程与任务的耦合，一个线程与一个任务捆绑，不够灵活。</p><h3 id="Runnable"><a href="#Runnable" class="headerlink" title="Runnable"></a>Runnable</h3><p>我们是通过重写在<em>run方法</em>来提供任务的，如果我们看看<em>Thread类</em>就会发现，Thread类中的run方法也是重写的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br></code></pre></td></tr></table></figure><p>Thread类实现了<strong>Runnable接口</strong>，重写了Runnable接口中的<em>run方法</em>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Runnable</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * Runs this operation.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>也就是说，其实run方法和Thread类是可以分开的，不捆绑在一起。</p><p>如果我们不重写Thread类的run方法，执行的就是Thread类自己的run方法逻辑：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-type">Runnable</span> <span class="hljs-variable">task</span> <span class="hljs-operator">=</span> holder.task;  <span class="hljs-comment">// 获取到task</span><br>    <span class="hljs-keyword">if</span> (task != <span class="hljs-literal">null</span>) &#123;<br>        <span class="hljs-type">Object</span> <span class="hljs-variable">bindings</span> <span class="hljs-operator">=</span> scopedValueBindings(); <span class="hljs-comment">// 获取线程上下文</span><br>        runWith(bindings, task); <span class="hljs-comment">// 执行</span><br>    &#125;<br>&#125;<br><br><span class="hljs-meta">@Hidden</span><br><span class="hljs-meta">@ForceInline</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">runWith</span><span class="hljs-params">(Object bindings, Runnable op)</span> &#123;<br>    ensureMaterializedForStackWalk(bindings);<br>    op.run();<br>    Reference.reachabilityFence(bindings);<br>&#125;<br></code></pre></td></tr></table></figure><p>可以看到，此时，run方法需要先获取到一个<em>Runnable对象</em>，然后才能在<em>runWith方法</em>中调用Runnable对象的run方法来执行任务。</p><p>上面我们是通过无参构造函数来创建Thread对象的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">ThreadA</span> <span class="hljs-variable">threadA</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadA</span>();<br></code></pre></td></tr></table></figure><p>而无参构造函数旁边刚好有一个可以传入Runnable对象的构造函数：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-title function_">Thread</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-built_in">this</span>(<span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">null</span>);<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">Thread</span><span class="hljs-params">(Runnable task)</span> &#123;<br>    <span class="hljs-built_in">this</span>(<span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">0</span>, task, <span class="hljs-number">0</span>, <span class="hljs-literal">null</span>);<br>&#125;<br></code></pre></td></tr></table></figure><p>那么，我们只需要自己实现Runnable接口，重写run方法，就可以将任务与线程拆开啦：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">ThreadB</span> <span class="hljs-variable">threadB</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadB</span>();<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">threadB1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(threadB);<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">threadB2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(threadB);<br>        threadB1.start();<br>        threadB2.start();<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ThreadB</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Runnable</span>&#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;通过实现Runnable接口的方式创建一个线程：ThreadB&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>但是，这种创建线程的方式还有两个问题：</p><ol><li><p>拿不到返回值，start方法的返回值是void。</p></li><li><p>线程内部异常不能被外层 try-catch 直接捕获：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">ThreadB</span> <span class="hljs-variable">threadB</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadB</span>();<br><br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">threadB1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(threadB);<br>        <span class="hljs-keyword">try</span> &#123;<br>            threadB1.start();<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            System.out.println(<span class="hljs-string">&quot;能捕捉到异常吗？&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ThreadB</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Runnable</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-comment">// 构建异常</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span> / <span class="hljs-number">0</span>;<br>        System.out.println(<span class="hljs-string">&quot;通过实现Runnable接口的方式创建一个线程：ThreadB&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>执行时，线程threadB1内部发生的异常，如果线程threadB1内部不处理的话，外部线程无法捕捉到该异常并处理。</p></li></ol><h2 id="FutureTask"><a href="#FutureTask" class="headerlink" title="FutureTask"></a>FutureTask</h2><p><em>JUC</em>提供了一个<strong>FutureTask类</strong>，可以用来获取线程执行结果。</p><p><em>FutureTask类</em>实现了<em>RunnableFuture接口</em>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">FutureTask</span>&lt;V&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title class_">RunnableFuture</span>&lt;V&gt; &#123;<br></code></pre></td></tr></table></figure><p><em>RunnableFuture接口</em>也继承了<em>Runnable接口</em>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">RunnableFuture</span>&lt;V&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Runnable</span>, Future&lt;V&gt; &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>所以，FutureTask对象也可以用来构建一个Thread对象。那么，FutureTask类的<em>run方法</em>是如何实现的呢？</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-keyword">if</span> (state != NEW ||<br>        !RUNNER.compareAndSet(<span class="hljs-built_in">this</span>, <span class="hljs-literal">null</span>, Thread.currentThread()))<br>        <span class="hljs-keyword">return</span>;<br>    <span class="hljs-keyword">try</span> &#123;<br>        Callable&lt;V&gt; c = callable;<br>        <span class="hljs-keyword">if</span> (c != <span class="hljs-literal">null</span> &amp;&amp; state == NEW) &#123;<br>            V result;<br>            <span class="hljs-type">boolean</span> ran;<br>            <span class="hljs-keyword">try</span> &#123;<br>                result = c.call(); <span class="hljs-comment">// 调用了Callable对象的call方法，并且获取到了结果result</span><br>                ran = <span class="hljs-literal">true</span>;<br>            &#125; <span class="hljs-keyword">catch</span> (Throwable ex) &#123;<br>                result = <span class="hljs-literal">null</span>;<br>                ran = <span class="hljs-literal">false</span>;<br>                setException(ex);<br>            &#125;<br>            <span class="hljs-keyword">if</span> (ran)<br>                set(result);<br>        &#125;<br>    &#125; <span class="hljs-keyword">finally</span> &#123;<br>        <span class="hljs-comment">// runner must be non-null until state is settled to</span><br>        <span class="hljs-comment">// prevent concurrent calls to run()</span><br>        runner = <span class="hljs-literal">null</span>;<br>        <span class="hljs-comment">// state must be re-read after nulling runner to prevent</span><br>        <span class="hljs-comment">// leaked interrupts</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">s</span> <span class="hljs-operator">=</span> state;<br>        <span class="hljs-keyword">if</span> (s &gt;= INTERRUPTING)<br>            handlePossibleCancellationInterrupt(s);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>除了对线程状态的判断外（保证任务只执行一次、避免并发重复执行），关键就是调用了<strong>Callable对象</strong>的<em>call方法</em>，并且获取到了结果result。</p><p>而FutureTask类里也刚好有一个构造函数可以传递Callable对象：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-title function_">FutureTask</span><span class="hljs-params">(Callable&lt;V&gt; callable)</span> &#123;<br>     <span class="hljs-keyword">if</span> (callable == <span class="hljs-literal">null</span>)<br>         <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NullPointerException</span>();<br>     <span class="hljs-built_in">this</span>.callable = callable;<br>     <span class="hljs-built_in">this</span>.state = NEW;       <span class="hljs-comment">// ensure visibility of callable</span><br> &#125;<br></code></pre></td></tr></table></figure><p>也就是说，我们需要在Callable对象中编写具体的任务。看看<em>Callable接口</em>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Callable</span>&lt;V&gt; &#123;<br>    V <span class="hljs-title function_">call</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception;<br>&#125;<br></code></pre></td></tr></table></figure><p>有一个需要实现的<em>call方法</em>，并且是有返回值的！</p><p>那么，清晰了，我们需要先实现Callable接口，并重写call方法，然后传递给FutureTask对象，再将FutureTask对象传递给Thread对象，就可以啦！既可以拿到返回值，又可以捕捉到线程内部的异常：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        FutureTask&lt;Integer&gt; futureTask = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FutureTask</span>&lt;&gt;(<span class="hljs-keyword">new</span> <span class="hljs-title class_">TaskC</span>());<br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(futureTask).start();<br>        <span class="hljs-keyword">try</span> &#123;<br>            System.out.println(futureTask.get());<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            System.out.println(<span class="hljs-string">&quot;捕捉到异常&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">TaskC</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Callable</span>&lt;Integer&gt; &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> Integer <span class="hljs-title function_">call</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="CompletableFuture"><a href="#CompletableFuture" class="headerlink" title="CompletableFuture"></a>CompletableFuture</h2><p>如果有多个线程需要灵活合作的话，<em>FutureTask</em>用起来就不方便了。</p><p><em>JUC</em>提供了<strong>CompletableFuture类</strong>，可以让我们灵活编排多个线程：</p><h3 id="顺序执行"><a href="#顺序执行" class="headerlink" title="顺序执行"></a>顺序执行</h3><p><em>thenApplyAsync方法</em>可以拿到前一个阶段的结果，然后开始执行当前任务：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future1 ok! &quot;</span>);<br>        CompletableFuture&lt;String&gt; future2 = future1.thenApplyAsync((result) -&gt; result+<span class="hljs-string">&quot; future2 ok! &quot;</span>);<br>        CompletableFuture&lt;String&gt; future3 = future2.thenApplyAsync((result) -&gt; result+<span class="hljs-string">&quot; future3 ok!&quot;</span>);<br>        System.out.println(future3.join());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="阻塞等待"><a href="#阻塞等待" class="headerlink" title="阻塞等待"></a>阻塞等待</h3><p><em>allOf方法</em>可以组合多个任务，配合<em>join&#x2F;get方法</em>阻塞等待所有的任务都执行完成：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future1 ok! &quot;</span>);<br>        CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future2 ok! &quot;</span>);<br>        CompletableFuture&lt;String&gt; future3 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future3 ok! &quot;</span>);<br>        <span class="hljs-comment">// 阻塞等待所有任务完成</span><br>        CompletableFuture&lt;Void&gt; all = CompletableFuture.allOf(future1, future2, future3);<br>        <span class="hljs-comment">// 统一处理</span><br>        all.thenRun(() -&gt; &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">result1</span> <span class="hljs-operator">=</span> future1.join();<br>            <span class="hljs-type">String</span> <span class="hljs-variable">result2</span> <span class="hljs-operator">=</span> future2.join();<br>            <span class="hljs-type">String</span> <span class="hljs-variable">result3</span> <span class="hljs-operator">=</span> future3.join();<br>            System.out.println(result1);<br>            System.out.println(result2);<br>            System.out.println(result3);<br>        &#125;).join(); <span class="hljs-comment">// 阻塞等待处理完成</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><em>anyOf方法</em>可以组合多个任务，配合<em>join&#x2F;get方法</em>阻塞等待任意一个任务先执行完成：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future1 ok! &quot;</span>);<br>        CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future2 ok! &quot;</span>);<br>        CompletableFuture&lt;String&gt; future3 = CompletableFuture.supplyAsync(() -&gt; <span class="hljs-string">&quot; future3 ok! &quot;</span>);<br>        <span class="hljs-comment">// 阻塞等待一个任务完成</span><br>        CompletableFuture&lt;Object&gt; any = CompletableFuture.anyOf(future1, future2, future3);<br>        any.thenAccept(result -&gt; System.out.println(<span class="hljs-string">&quot;First one：&quot;</span> + result)).join();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><p><em>CompletableFuture.supplyAsync方法</em>还可以传入一个线程池，否则使用的就是公共线程池。使用公共线程池的话，可能会因为线程资源不足，影响公共线程池中的其他任务执行，所以推荐自定义一个线程池。</p><h3 id="Executors"><a href="#Executors" class="headerlink" title="Executors"></a>Executors</h3><p><em>JUC</em>提供了一个<strong>Executors类</strong>，可以帮助我们快捷创建线程池。</p><p>常用的有三个方法，分别创建一种常用的线程池：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 创建只有一个线程的线程池  </span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ExecutorService <span class="hljs-title function_">newSingleThreadExecutor</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FinalizableDelegatedExecutorService</span><br>            (<span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadPoolExecutor</span>(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>,<br>                                    <span class="hljs-number">0L</span>, TimeUnit.MILLISECONDS,<br>                                    <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;()));<br>    &#125;<br><br><span class="hljs-comment">// 创建有nThreads个线程的线程池  </span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ExecutorService <span class="hljs-title function_">newFixedThreadPool</span><span class="hljs-params">(<span class="hljs-type">int</span> nThreads)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadPoolExecutor</span>(nThreads, nThreads,<br>                                      <span class="hljs-number">0L</span>, TimeUnit.MILLISECONDS,<br>                                      <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;());<br>    &#125;<br><br><span class="hljs-comment">// 创建可以动态扩容到Integer.MAX_VALUE个线程的线程池 </span><br>  <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ExecutorService <span class="hljs-title function_">newCachedThreadPool</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadPoolExecutor</span>(<span class="hljs-number">0</span>, Integer.MAX_VALUE,<br>                                      <span class="hljs-number">60L</span>, TimeUnit.SECONDS,<br>                                      <span class="hljs-keyword">new</span> <span class="hljs-title class_">SynchronousQueue</span>&lt;Runnable&gt;());<br>    &#125;<br></code></pre></td></tr></table></figure><p>众所周知，阿里巴巴Java开发手册禁止通过Executors类来创建线程池，因为：</p><ol><li>SingleThreadExecutor和FixedThreadPool配置的等待队列为LinkedBlockingQueue<Runnable>()，默认的长度是Integer.MAX_VALUE。允许大量等待任务，容易导致内存溢出（OOM）。</li><li>CachedThreadPool配置的最大线程数量为Integer.MAX_VALUE，允许创建大量的线程，也容易导致内存溢出（OOM）。</li></ol><h3 id="ThreadPoolExecutor"><a href="#ThreadPoolExecutor" class="headerlink" title="ThreadPoolExecutor"></a>ThreadPoolExecutor</h3><p>可以看到，<em>Executors类</em>里创建线程池的方法，其实是通过<strong>ThreadPoolExecutor类</strong>创建的，不过是帮我们配置了一些参数而已。如果要避开Executors类创建线程池的弊端，我们就需要通过ThreadPoolExecutor类来创建，并且自己配置参数。</p><p>ThreadPoolExecutor类的构造函数，有下面这几个参数：</p><ul><li><em>int corePoolSize</em>：核心线程数，即使处于空闲状态也不会被销毁的线程</li><li><em>int maximumPoolSize</em>：线程池的最大线程数</li><li><em>long keepAliveTime</em>：当核心线程用完、任务等待队列用完、最大线程数未用完时，会创建新线程来处理任务，这些线程在空闲时的存活时间</li><li><em>TimeUnit unit</em>：keepAliveTime的单位</li><li><em>BlockingQueue<Runnable> workQueue</em>：任务等待队列，核心线程用完时，任务会在等待队列进行等待</li><li><em>ThreadFactory threadFactory</em>：创建线程时使用的工厂</li><li><em>RejectedExecutionHandler handler</em>：当核心线程用完、任务等待队列用完、最大线程数用完时，执行的任务拒绝策略</li></ul><h2 id="辅助工具"><a href="#辅助工具" class="headerlink" title="辅助工具"></a>辅助工具</h2><p>关于控制多个线程间的行为，<em>JUC</em>还提供了三个工具来辅助我们：</p><h3 id="Semaphore"><a href="#Semaphore" class="headerlink" title="Semaphore"></a>Semaphore</h3><p>可以将<strong>Semaphore</strong>理解为一个信号量，可以设置持有一定数量的许可证，许可证被分发给某个线程后，只有该线程释放许可证后，其他线程才可获取：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-comment">// 三个许可证</span><br>        <span class="hljs-type">Semaphore</span> <span class="hljs-variable">semaphore</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Semaphore</span>(<span class="hljs-number">3</span>);<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">6</span>; i++) &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">temp</span> <span class="hljs-operator">=</span> i;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-type">boolean</span> <span class="hljs-variable">tried</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-comment">// 等待获取一个许可证</span><br>                    tried = semaphore.tryAcquire();<br>                    <span class="hljs-keyword">if</span> (tried) &#123;<br>                        <span class="hljs-comment">// 模拟任务处理耗时</span><br>                        Thread.sleep(<span class="hljs-number">1000</span>);<br>                        System.out.println(temp + <span class="hljs-string">&quot;号：占用一个许可证&quot;</span>);<br>                    &#125;<br>                &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                    System.out.println(<span class="hljs-string">&quot;线程被中断&quot;</span>);<br>                &#125; <span class="hljs-keyword">finally</span> &#123;<br>                    <span class="hljs-comment">// 释放一个许可证</span><br>                    <span class="hljs-keyword">if</span> (tried) &#123;<br>                        semaphore.release();<br>                        System.out.println(temp + <span class="hljs-string">&quot;号：释放一个许可证&quot;</span>);<br>                    &#125;<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>结果：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs">0号：占用一个许可证<br>2号：占用一个许可证<br>1号：占用一个许可证<br>0号：释放一个许可证<br>2号：释放一个许可证<br>1号：释放一个许可证<br>3号：占用一个许可证<br>3号：释放一个许可证<br>5号：占用一个许可证<br>4号：占用一个许可证<br>4号：释放一个许可证<br>5号：释放一个许可证<br></code></pre></td></tr></table></figure><p>可以看到，线程会阻塞等待获取许可证。</p><p>也就是说，可以用<em>Semaphore</em>来控制大量线程获取有限的资源。</p><h3 id="CyclicBarrier"><a href="#CyclicBarrier" class="headerlink" title="CyclicBarrier"></a>CyclicBarrier</h3><p>可以将<strong>CyclicBarrier</strong>理解为一个屏障，可以在任务中设置一个屏障，线程必须要等所有线程均抵达屏障处，才可继续向下执行任务：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">CyclicBarrier</span> <span class="hljs-variable">cyclicBarrier</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CyclicBarrier</span>(<span class="hljs-number">4</span>, () -&gt; System.out.println(<span class="hljs-string">&quot;均抵达屏障处&quot;</span>));<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">4</span>; i++) &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">temp</span> <span class="hljs-operator">=</span> i;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-comment">// 模拟任务耗时</span><br>                    Thread.sleep(<span class="hljs-number">1000</span>);<br>                    System.out.println(temp + <span class="hljs-string">&quot;号已抵达屏障处&quot;</span>);<br>                    <span class="hljs-comment">// 等待其它线程抵达屏障处</span><br>                    cyclicBarrier.await();<br>                    <span class="hljs-comment">// 继续任务</span><br>                    System.out.println(temp + <span class="hljs-string">&quot;号继续任务&quot;</span>);<br>                &#125; <span class="hljs-keyword">catch</span> (InterruptedException | BrokenBarrierException e) &#123;<br>                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>结果：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs">2号已抵达屏障处<br>3号已抵达屏障处<br>1号已抵达屏障处<br>0号已抵达屏障处<br>均抵达屏障处<br>0号继续任务<br>2号继续任务<br>3号继续任务<br>1号继续任务<br></code></pre></td></tr></table></figure><p>可以看到，每个线程都会等待其它线程抵达屏障处，然后才会继续执行任务。</p><p>也就是说，可以用<em>CyclicBarrier</em>来控制多个线程间互相等待。</p><h3 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h3><p>可以将<strong>CountDownLatch</strong>理解为一个计数器，可以控制一个线程必须阻塞等待计数器归零，才能继续执行任务：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">CountDownLatch</span> <span class="hljs-variable">countDownLatch</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CountDownLatch</span>(<span class="hljs-number">4</span>);<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">4</span>; i++) &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">temp</span> <span class="hljs-operator">=</span> i;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-comment">// 模拟任务耗时</span><br>                    Thread.sleep(<span class="hljs-number">1000</span>);<br>                    System.out.println(temp + <span class="hljs-string">&quot;号执行完成&quot;</span>);<br>                &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                &#125; <span class="hljs-keyword">finally</span> &#123;<br>                    <span class="hljs-comment">// 减少计数</span><br>                    countDownLatch.countDown();<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 阻塞等待计数器归零</span><br>            countDownLatch.await();<br>            System.out.println(<span class="hljs-string">&quot;全部执行完成&quot;</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>结果：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs">2号执行完成<br>1号执行完成<br>3号执行完成<br>0号执行完成<br>全部执行完成<br></code></pre></td></tr></table></figure><p>可以看到，我们控制了main线程等待其他线程减少计数器，然后main线程才能继续执行任务。</p><p>也就是说，<em>CountDownLatch</em>可以用来控制一个线程等待其它线程。</p><h2 id="原子类"><a href="#原子类" class="headerlink" title="原子类"></a>原子类</h2><p>前面我们介绍了<em>JUC</em>提供的很多工具，可以帮助我们很方便地构建多线程。但是，还无法解决多线程访问共享数据的并发安全问题。JUC在atomic包下提供了一些<strong>原子类</strong>，原子类封装了一些并发安全的方法。</p><p>常用的原子类有：<code>AtomicBoolean</code>、<code>AtomicInteger</code>、<code>AtomicLong</code>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-type">AtomicInteger</span> <span class="hljs-variable">atomicInteger</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-type">int</span> <span class="hljs-variable">get</span> <span class="hljs-operator">=</span> atomicInteger.incrementAndGet();<br>                System.out.println(get);<br>            &#125;).start();<br>        &#125;<br>        <span class="hljs-comment">// 等待线程执行完成</span><br>        Thread.sleep(<span class="hljs-number">1000</span>);<br>        System.out.println(<span class="hljs-string">&quot;最终:&quot;</span> + atomicInteger.get());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><em>原子类</em>内部对值添加了<strong>volatile</strong>关键字：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> <span class="hljs-type">int</span> value;<br></code></pre></td></tr></table></figure><p>这样保证了值的可见性，一个线程修改后，其它线程立刻能看见。</p><p>此外，对值的修改使用了<em>Unsafe类</em>下的<em>CAS</em>方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Unsafe</span> <span class="hljs-variable">U</span> <span class="hljs-operator">=</span> Unsafe.getUnsafe();  <br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">compareAndSet</span><span class="hljs-params">(<span class="hljs-type">int</span> expectedValue, <span class="hljs-type">int</span> newValue)</span> &#123;<br>        <span class="hljs-keyword">return</span> U.compareAndSetInt(<span class="hljs-built_in">this</span>, VALUE, expectedValue, newValue);<br>    &#125;<br></code></pre></td></tr></table></figure><p>CAS方法是利用JVM调用CPU级原子指令来保证并发安全性的。</p><p>所以，原子类只能保证单次方法调用是并发安全的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-type">AtomicInteger</span> <span class="hljs-variable">atomicInteger</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-comment">// 一个原子类的两次操作就不是并发安全的了</span><br>                <span class="hljs-keyword">if</span> (atomicInteger.get() == <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        Thread.sleep(<span class="hljs-number">1000</span>);<br>                        atomicInteger.incrementAndGet();<br>                    &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                    &#125;<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>        <span class="hljs-comment">// 等待线程执行完成</span><br>        Thread.sleep(<span class="hljs-number">2000</span>);<br>        System.out.println(<span class="hljs-string">&quot;最终:&quot;</span> + atomicInteger.get());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="集合类"><a href="#集合类" class="headerlink" title="集合类"></a>集合类</h2><p><em>JUC</em>还提供了一些并发安全的集合类，常用的有<code>ConcurrentHashMap</code>、<code>CopyOnWriteArrayList</code>、<code>CopyOnWriteArraySet</code>、<code>ArrayBlockingQueue</code>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        ConcurrentHashMap&lt;String, Integer&gt; map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>&lt;&gt;();<br>        map.put(<span class="hljs-string">&quot;a&quot;</span>,<span class="hljs-number">0</span>);<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(()-&gt; map.compute(<span class="hljs-string">&quot;a&quot;</span>,(k, v)-&gt;v+<span class="hljs-number">1</span>)).start();<br>        &#125;<br>        Thread.sleep(<span class="hljs-number">1000</span>);<br>        System.out.println(map.get(<span class="hljs-string">&quot;a&quot;</span>));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>JUC提供的集合类，并不都采用同一种机制来实现并发安全性。</p><p><em>ConcurrentHashMap</em>是通过CAS结合synchronized来实现，详情参考本人另一篇文章：<a href="https://blog.csdn.net/Uncommen/article/details/144174135%E3%80%82">https://blog.csdn.net/Uncommen/article/details/144174135。</a></p><p><em>CopyOnWriteArrayList</em>在写操作时会先加synchronized锁，然后将原数组复制一份，在新数组上修改，再将新数组赋给元素组。这样，读操作即使不加锁，结合volatile就可以保证要么读到旧数组，要么读到新数组，绝不会读到中间脏数据：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">transient</span> <span class="hljs-keyword">volatile</span> Object[] array;<br><br><span class="hljs-keyword">public</span> E <span class="hljs-title function_">get</span><span class="hljs-params">(<span class="hljs-type">int</span> index)</span> &#123;<br>        <span class="hljs-keyword">return</span> elementAt(getArray(), index);<br>    &#125;<br><br><span class="hljs-keyword">public</span> E <span class="hljs-title function_">set</span><span class="hljs-params">(<span class="hljs-type">int</span> index, E element)</span> &#123;<br>        <span class="hljs-keyword">synchronized</span> (lock) &#123;<br>            Object[] es = getArray();<br>            <span class="hljs-type">E</span> <span class="hljs-variable">oldValue</span> <span class="hljs-operator">=</span> elementAt(es, index);<br><br>            <span class="hljs-keyword">if</span> (oldValue != element) &#123;<br>                es = es.clone();<br>                es[index] = element;<br>            &#125;<br>            <span class="hljs-comment">// Ensure volatile write semantics even when oldvalue == element</span><br>            setArray(es);<br>            <span class="hljs-keyword">return</span> oldValue;<br>        &#125;<br>    &#125;<br></code></pre></td></tr></table></figure><p><em>CopyOnWriteArraySet</em>跟<em>CopyOnWriteArrayList</em>差不多，只是需要判断重复值。会先不加锁判断重复情况，提高效率，如果没有重复值，再加锁去进一步操作，加了锁后还需要再判断一次重复值，避免被其它线程趁方法调用间隙插入了重复值：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">addIfAbsent</span><span class="hljs-params">(E e)</span> &#123;<br>    Object[] snapshot = getArray();<br>    <span class="hljs-keyword">return</span> indexOfRange(e, snapshot, <span class="hljs-number">0</span>, snapshot.length) &lt; <span class="hljs-number">0</span><br>        &amp;&amp; addIfAbsent(e, snapshot);<br>&#125;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * A version of addIfAbsent using the strong hint that given</span><br><span class="hljs-comment"> * recent snapshot does not contain e.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">addIfAbsent</span><span class="hljs-params">(E e, Object[] snapshot)</span> &#123;<br>    <span class="hljs-keyword">synchronized</span> (lock) &#123;<br>        Object[] current = getArray();<br>        <span class="hljs-type">int</span> <span class="hljs-variable">len</span> <span class="hljs-operator">=</span> current.length;<br>        <span class="hljs-keyword">if</span> (snapshot != current) &#123;<br>            <span class="hljs-comment">// Optimize for lost race to another addXXX operation</span><br>            <span class="hljs-type">int</span> <span class="hljs-variable">common</span> <span class="hljs-operator">=</span> Math.min(snapshot.length, len);<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; common; i++)<br>                <span class="hljs-keyword">if</span> (current[i] != snapshot[i]<br>                    &amp;&amp; Objects.equals(e, current[i]))<br>                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">if</span> (indexOfRange(e, current, common, len) &gt;= <span class="hljs-number">0</span>)<br>                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>        &#125;<br>        Object[] newElements = Arrays.copyOf(current, len + <span class="hljs-number">1</span>);<br>        newElements[len] = e;<br>        setArray(newElements);<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><em>ArrayBlockingQueue</em>是对写操作加ReentrantLock锁实现并发安全性，并且通Condition来实现等待队列空余：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">put</span><span class="hljs-params">(E e)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        Objects.requireNonNull(e);<br>        <span class="hljs-keyword">final</span> <span class="hljs-type">ReentrantLock</span> <span class="hljs-variable">lock</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.lock;<br>        lock.lockInterruptibly();<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">while</span> (count == items.length)<br>                notFull.await();<br>            enqueue(e);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            lock.unlock();<br>        &#125;<br>    &#125;<br></code></pre></td></tr></table></figure><h2 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h2><h3 id="Synchronized"><a href="#Synchronized" class="headerlink" title="Synchronized"></a>Synchronized</h3><p>既然是通过加<em>synchronized</em>锁来保证并发安全性的，那我们完全可以自己操作synchronized锁来保证一段操作的并发安全性：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Object</span> <span class="hljs-variable">LOCK</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>();<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-keyword">synchronized</span> (LOCK) &#123;<br>                    <span class="hljs-keyword">if</span> (count == <span class="hljs-number">0</span>) &#123;<br>                        <span class="hljs-keyword">try</span> &#123;<br>                            Thread.sleep(<span class="hljs-number">500</span>);<br>                            count++;<br>                        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                        &#125;<br>                    &#125;<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>        Thread.sleep(<span class="hljs-number">1000</span>);<br>        System.out.println(count);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p>关于synchronized锁的详细讲解，可以阅读本人的另一篇文章：<a href="https://blog.csdn.net/Uncommen/article/details/152095400">https://blog.csdn.net/Uncommen/article/details/152095400</a></p></blockquote><h3 id="Lock"><a href="#Lock" class="headerlink" title="Lock"></a>Lock</h3><p><em>synchronized</em>锁的用法非常简单，可读性强，不需要我们手动解锁，但同时也缺乏了灵活性。如果我们想更灵活的控制加锁解锁，<em>JUC</em>为我们提供了<strong>Lock</strong>锁。</p><p><em>Lock</em>是一个接口，代表一类锁，常用的是<em>ReentrantLock</em>可重入锁：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ReentrantLock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Lock</span>, java.io.Serializable &#123;<br></code></pre></td></tr></table></figure><p>简单的使用，手动加锁解锁，需要在<code>finally</code>中保证锁的释放，避免死锁：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Lock</span> <span class="hljs-variable">LOCK</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReentrantLock</span>();<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    LOCK.lock();<br>                    <span class="hljs-keyword">if</span> (count == <span class="hljs-number">0</span>) &#123;<br>                        <span class="hljs-keyword">try</span> &#123;<br>                            Thread.sleep(<span class="hljs-number">500</span>);<br>                            count++;<br>                        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                        &#125;<br>                    &#125;<br>                &#125; <span class="hljs-keyword">finally</span> &#123;<br>                    LOCK.unlock();<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>        Thread.sleep(<span class="hljs-number">1000</span>);<br>        System.out.println(count);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>可以在规定时间内尝试获取锁，而不是一直等着：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Lock</span> <span class="hljs-variable">LOCK</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReentrantLock</span>();<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>                <span class="hljs-type">boolean</span> <span class="hljs-variable">tried</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-comment">// 尝试获取锁，超过3秒没获取到锁时返回false</span><br>                    tried = LOCK.tryLock(<span class="hljs-number">3</span>, TimeUnit.SECONDS);<br>                    <span class="hljs-keyword">if</span> (tried &amp;&amp; count == <span class="hljs-number">0</span>) &#123;<br>                        Thread.sleep(<span class="hljs-number">500</span>);<br>                        count++;<br>                    &#125;<br>                &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                &#125; <span class="hljs-keyword">finally</span> &#123;<br>                    <span class="hljs-keyword">if</span> (tried) &#123;<br>                        LOCK.unlock();<br>                    &#125;<br>                &#125;<br>            &#125;).start();<br>        &#125;<br>        Thread.sleep(<span class="hljs-number">5000</span>);<br>        System.out.println(count);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><em>ReentrantLock</em>默认是非公平锁，可以传参设置为公平锁（讲究先来后到）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-title function_">ReentrantLock</span><span class="hljs-params">(<span class="hljs-type">boolean</span> fair)</span> &#123;<br>        sync = fair ? <span class="hljs-keyword">new</span> <span class="hljs-title class_">FairSync</span>() : <span class="hljs-keyword">new</span> <span class="hljs-title class_">NonfairSync</span>();<br>    &#125;<br></code></pre></td></tr></table></figure><p>如果我们想要控制两个线程交替执行，形成类似<em>生产者消费者模型</em>的话，对于<em>synchronized</em>锁，由于是利用<em>Object</em>类提供的<em>wait方法</em>和<em>notifyAll方法</em>，所以生产者和消费者相当于共用同一个等待队列，notifyAll方法会将所有等待线程唤醒：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Object</span> <span class="hljs-variable">LOCK</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>();<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>                <span class="hljs-keyword">synchronized</span> (LOCK) &#123;<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        <span class="hljs-keyword">while</span> (count &gt; <span class="hljs-number">0</span>) &#123;<br>                            LOCK.wait();<br>                        &#125;<br>                        count++;<br>                        System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot;:&quot;</span> + count);<br>                        LOCK.notifyAll();<br>                    &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125;, <span class="hljs-string">&quot;生产者&quot;</span>).start();<br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>                <span class="hljs-keyword">synchronized</span> (LOCK) &#123;<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        <span class="hljs-keyword">while</span> (count &lt; <span class="hljs-number">1</span>) &#123;<br>                            LOCK.wait();<br>                        &#125;<br>                        count--;<br>                        System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot;:&quot;</span> + count);<br>                        LOCK.notifyAll();<br>                    &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125;, <span class="hljs-string">&quot;消费者&quot;</span>).start();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>但是，<em>JUC</em>为我们提供了<strong>Condition</strong>，可以让我们为生产者和消费者提供不同的等待队列：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Pc</span> <span class="hljs-variable">pc</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Pc</span>();<br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>                pc.publish();<br>            &#125;<br>        &#125;, <span class="hljs-string">&quot;生产者&quot;</span>).start();<br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>                pc.consume();<br>            &#125;<br>        &#125;, <span class="hljs-string">&quot;消费者&quot;</span>).start();<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Pc</span> &#123;<br>    <span class="hljs-type">Lock</span> <span class="hljs-variable">lock</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReentrantLock</span>();<br>    <span class="hljs-type">Condition</span> <span class="hljs-variable">publisher</span> <span class="hljs-operator">=</span> lock.newCondition();<br>    <span class="hljs-type">Condition</span> <span class="hljs-variable">consumer</span> <span class="hljs-operator">=</span> lock.newCondition();<br>    <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">publish</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            lock.lock();<br>            <span class="hljs-keyword">while</span> (count &gt; <span class="hljs-number">0</span>) &#123;<br>                publisher.await();<br>            &#125;<br>            System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot;:&quot;</span> + ++count);<br>            consumer.signalAll();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            e.printStackTrace();<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            lock.unlock();<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">consume</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            lock.lock();<br>            <span class="hljs-keyword">while</span> (count &lt; <span class="hljs-number">1</span>) &#123;<br>                consumer.await();<br>            &#125;<br>            System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot;:&quot;</span> + --count);<br>            publisher.signalAll();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            e.printStackTrace();<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            lock.unlock();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>但是，上面的所有情况，都是无论读数据还是写数据，都用同一把锁，多个读操作间仍然需要竞争锁，这是不合理的，读操作完全可以单独使用一把<em>共享锁</em>，而不是与写操作共用一把<em>互斥锁</em>。</p><p>没关系，<em>JUC</em>为我们提供了一个<strong>ReentrantReadWriteLock</strong>可重入读写锁：</p><p>可以将共享的读锁与互斥的写锁分开：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Pc</span> <span class="hljs-variable">pc</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Pc</span>();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(pc::read).start();<br>        &#125;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>            <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(pc::write).start();<br>        &#125;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Pc</span> &#123;<br>    <span class="hljs-type">ReentrantReadWriteLock</span> <span class="hljs-variable">lock</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReentrantReadWriteLock</span>();<br>    <span class="hljs-type">Lock</span> <span class="hljs-variable">readLock</span> <span class="hljs-operator">=</span> lock.readLock();<br>    <span class="hljs-type">Lock</span> <span class="hljs-variable">writeLock</span> <span class="hljs-operator">=</span> lock.writeLock();<br>    <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">read</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            readLock.lock();<br>            System.out.println(<span class="hljs-string">&quot;获取到读锁&quot;</span>);<br>            Thread.sleep(<span class="hljs-number">1000</span>);<br>            System.out.println(<span class="hljs-string">&quot;读到：&quot;</span> + count);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            e.printStackTrace();<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            System.out.println(<span class="hljs-string">&quot;释放读锁&quot;</span>);<br>            readLock.unlock();<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">write</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            writeLock.lock();<br>            System.out.println(<span class="hljs-string">&quot;获取到写锁&quot;</span>);<br>            System.out.println(<span class="hljs-string">&quot;写入:&quot;</span> + ++count);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            System.out.println(<span class="hljs-string">&quot;释放写锁&quot;</span>);<br>            writeLock.unlock();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>读读间共享，读写、写写间互斥。</p>]]>
    </content>
    <id>https://www.wananhome.site/2026/03/10/JUC%E8%AF%A6%E8%A7%A3/</id>
    <link href="https://www.wananhome.site/2026/03/10/JUC%E8%AF%A6%E8%A7%A3/"/>
    <published>2026-03-09T16:00:00.000Z</published>
    <summary>JUC（java.util.concurrent），Java并发包，可以让Java更好地处理多线程并发问题。</summary>
    <title>JUC详解</title>
    <updated>2026-03-21T09:45:20.309Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="JMM" scheme="https://www.wananhome.site/tags/JMM/"/>
    <content>
      <![CDATA[<h2 id="引子"><a href="#引子" class="headerlink" title="引子"></a>引子</h2><p>众所周知，在计算机中，数据主要存储在硬盘中，但是，硬盘的读写速度相对于CPU执行指令的速度太慢太慢了，于是，我们会添加读写速度更快的内存（因为内存读写速度比较快，所以也比较贵，如果直接用内存取代磁盘的话，大部分人都承担不起这个价钱）来作为硬盘的缓存。数据先从磁盘读取到内存中，内存可以较高的效率与CPU交互，这样也就提高了计算机的效率。</p><p>当内存中的数据更新时，需要写回到磁盘中，这需要时间，也就导致了在这段时间内，磁盘与内存间数据的不一致现象。</p><p>当有多个线程时，有的现场从内存中读值，有的线程从磁盘中读值，或者多个线程同时修改内存中的值，结果导致操作被覆盖，等等。此外，现代编译器为了提高程序运行效率，会对代码进行重排序，现代CPU为了提高指令运行效率，也会对指令进行重排序。这些情况都可能导致多线程共享数据数据的不一致现象（重排序只保证单线程安全）。</p><p>这就需要一个规范（称之为“内存模型”）来解决这些混乱的情况，不同操作系统会有不同的规范。Java因为要保证跨平台性，所以不能直接使用某个操作系统现成的规范，需要有自己的规范，由JVM负责适配各操作系统。Java的这套规范，就叫做<strong>JMM（Java Memory Model ：Java内存模型）</strong>。</p><h2 id="JMM"><a href="#JMM" class="headerlink" title="JMM"></a>JMM</h2><p><strong>JMM</strong>是用来解决Java并发编程问题的一个规范。</p><p>JMM不是具体的技术实现，不同版本的Java可以用不同的方式实现这个规范。</p><p>JMM也利用了缓存机制来提效，所有共享变量都存在主内存中（可能是用操作系统的内存来实现），线程需要将共享变量读取到自己独立的工作内存中（可能是用操作系统的CPU高速缓存来实现），操作完成后再写回主内存中。</p><p>想要保证并发编程的安全性，就要解决以下问题：</p><ul><li><p><em>一个线程对共享变量的操作是个不可分割的整体，不可中断</em>：</p><p>错误情况：</p><figure class="highlight xl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs xl">初始 i = <span class="hljs-number">0</span><br>线程A: 读 <span class="hljs-function"><span class="hljs-title">i</span> -&gt;</span> <span class="hljs-number">0</span><br>线程B: 读 <span class="hljs-function"><span class="hljs-title">i</span> -&gt;</span> <span class="hljs-number">0</span><br>线程A: <span class="hljs-function"><span class="hljs-title">i</span>+1 -&gt;</span> <span class="hljs-number">1</span><br>线程B: <span class="hljs-function"><span class="hljs-title">i</span>+1 -&gt;</span> <span class="hljs-number">1</span><br>线程A: 写回 i=<span class="hljs-number">1</span><br>线程B: 写回 i=<span class="hljs-number">1</span><br>最终 i=<span class="hljs-number">1</span> （本该是<span class="hljs-number">2</span>）<br></code></pre></td></tr></table></figure><p>正确情况：</p><figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs coq">            初始 i = <span class="hljs-number">0</span><br> ---------------<br>           | <span class="hljs-type">线程A</span>: 读 i -&gt; <span class="hljs-number">0</span>  <br>不可分割的整体| <span class="hljs-type">线程A</span>: i+<span class="hljs-number">1</span> -&gt; <span class="hljs-number">1</span><br>          | <span class="hljs-type">线程A</span>: 写回 i=<span class="hljs-number">1</span><br> ---------------      <br>          | <span class="hljs-type">线程B</span>: 读 i -&gt; <span class="hljs-number">1</span><br>不可分割的整体| <span class="hljs-type">线程B</span>: i+<span class="hljs-number">1</span> -&gt; <span class="hljs-number">2</span><br>          | <span class="hljs-type">线程B</span>: 写回 i=<span class="hljs-number">2</span><br> ---------------  <br>            最终 i=<span class="hljs-number">2</span><br></code></pre></td></tr></table></figure><p>因为线程A对i的操作是个不可分割的整体，所以线程B不能穿插进去。</p></li><li><p><em>一个线程对共享数据的操作结果可以立刻被其它线程看见</em>：</p><p>对于上面的情况，可能线程A写回i&#x3D;1到主存中，但是，</p><p>可能主存也有多级缓存，线程A写回到2级主存中，但线程B却从1级主存中读值；</p><p>可能主存虽然拿到线程A写回的新值了，但是还没来得及更新旧值；</p><p>可能线程B之前缓存过旧值，这次没从主存中读取，而是从自己的缓存中读取。</p><p>所以，必须要保证线程A写回的值，线程B能够立即看见。</p></li><li><p><em>一个线程对共享数据的操作的重排序不能影响其他线程</em>：</p><p>试想这种情况：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs routeros">初始 i = 0<br>线程A: 读 i -&gt; 0  <br>线程A: <span class="hljs-attribute">i</span>=1<br>线程A: <span class="hljs-attribute">i</span>=2<br>线程A: 写回 <span class="hljs-attribute">i</span>=2<br>线程B: 读 i -&gt; 2<br>线程B: i+1 -&gt; 3<br>线程B: 写回 <span class="hljs-attribute">i</span>=3<br>最终 <span class="hljs-attribute">i</span>=3<br></code></pre></td></tr></table></figure><p>如果发生指令重排序，将<code>线程A: i=1</code>和<code>线程A: i=2</code>的顺序反过来执行，那么最终结果就是2。</p></li></ul><p>总结一下，如果想要保证并发编程的安全性，就需要保证三大特性：</p><ul><li><p><strong>原子性</strong>：一系列操作组成不可分割的整体，不可被中断。</p></li><li><p><strong>可见性</strong>：一个线程对共享变量的修改，其它线程可以立即看见。</p></li><li><p><strong>有序性</strong>：指令重排序不能影响多线程的结果。</p></li></ul><p>那么，Java通常是如何实现这三大特性的呢?</p><h3 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h3><p>在Java中，可以通过<em>synchronized</em>或<em>Lock</em>加锁来实现原子性：</p><p>加锁的代码块，在同一时刻只有一个线程能访问。其他线程必须等待锁被释放后，并且尝试获取到锁后，才能访问该代码块。也就不会被其它线程穿插进来，中断执行。</p><p>也可以通过原子类来实现原子性：</p><p>原子类（如：AtomicInteger、AtomicLong）利用CPU提供的原子级指令*CAS（Compare And Swap）*来实现原子性，CPU会保证不会有其它指令穿插进来。</p><h3 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h3><p>用<em>volatile</em>修饰共享变量，可以保证被修改的共享变量立刻写回主存，读取时只从主存中读取。</p><p>因为原子类内部也是用volatile来修饰变量，所以原子类也可以保证可见性。</p><p>但是，volatile只能修饰单个变量，所以也只能保证单个变量的可见性。如果要组合操作多个共享变量，需要使用<em>synchronized</em>或<em>Lock</em>加锁，他们也可以保证被修改的共享变量立刻写回主存，读取时只从主存中读取。</p><h3 id="有序性"><a href="#有序性" class="headerlink" title="有序性"></a>有序性</h3><p>JMM有一个<strong>happens-before</strong>原则，要求某个操作必须发生在某个操作之前，从而限制了指令重排序，保证了有序性。</p><p>在Java中，<em>volatile</em>、<em>synchronized</em>、<em>Lock</em>都实现了<strong>happens-before</strong>原则，会在操作前后加入内存屏障，不允许内存屏障前的指令和内存屏障后的乱序，从而保证有序性。</p>]]>
    </content>
    <id>https://www.wananhome.site/2026/03/07/JMM%E8%AF%A6%E8%A7%A3/</id>
    <link href="https://www.wananhome.site/2026/03/07/JMM%E8%AF%A6%E8%A7%A3/"/>
    <published>2026-03-06T16:00:00.000Z</published>
    <summary>详细解读Java内存模型</summary>
    <title>JMM详解</title>
    <updated>2026-03-21T09:44:17.429Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="SpringCloudAlibaba" scheme="https://www.wananhome.site/tags/SpringCloudAlibaba/"/>
    <content>
      <![CDATA[<blockquote><p>文章由本人阅读<a href="https://sca.aliyun.com/">Spring Cloud Alibaba官方文档</a>时总结而成，一切以官方描述为准。</p><p>项目源码：<a href="https://github.com/WanAnUncommon/SpringCloudAlibabaDemo">https://github.com/WanAnUncommon/SpringCloudAlibabaDemo</a></p></blockquote><h2 id="Spring-Cloud-是什么？"><a href="#Spring-Cloud-是什么？" class="headerlink" title="Spring Cloud 是什么？"></a>Spring Cloud 是什么？</h2><p>我们知道，如果要实现一个分布式系统，需要引入各种组件，来实现网关、服务发现、配置中心、限流等功能。如果每一个组件都需要我们手动下载、编写配置文件、适配原生API，那就太麻烦了。既然一个单体项目可以通过Spring Boot整合各种组件实现快速开发，那么，自然可以有一个框架整合网关、服务发现、配置中心、限流等组件来实现分布式项目的快速开发。<strong>Spring Cloud</strong>应运而生。</p><p><strong>Spring Cloud</strong>是一套规范，主流的微服务组件都遵循了这套规范，我们就可以便捷的通过Spring Cloud来统筹一个分布式系统所需要的网关、服务发现、配置中心、限流等组件。</p><h2 id="Spring-Cloud-Alibaba-是什么？"><a href="#Spring-Cloud-Alibaba-是什么？" class="headerlink" title="Spring Cloud Alibaba 是什么？"></a>Spring Cloud Alibaba 是什么？</h2><p>Spring Cloud致力于统筹所有的微服务组件，想要为所有的微服务组件都提供同一个开发规范，但是，实际项目开发中，往往需要具体问题具体分析，针对特定的场景，对某些组件进行优化。</p><p>Netflix开发了<strong>Spring Cloud Netflix</strong>，主要针对Eureka、Ribbon、Feign、Hystrix等组件进行了优化。但是慢慢的，Netflix不再维护Spring Cloud Netflix了。</p><p>Alibaba开发了<strong>Spring Cloud Alibaba</strong>，主要针对Nacos、Sentinel、Seata等组件进行了优化。Spring Cloud Alibaba慢慢的取代了Spring Cloud Netflix，成为目前主流的分布系统开发框架。</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/spring-cloud-alibaba-img-f4ef56bcee58.png" class="spring-cloud"><!--图片源自-->[Spring Cloud Alibaba](https://sca.aliyun.com/docs/2023/overview/what-is-sca/?spm=5176.29160081.0.0.74805c72Cn8wbR)<p><strong>Spring Cloud Alibaba</strong>也是国内首个进入Spring社区的开源项目。2018年7月，Spring Cloud Alibaba正式开源，并进入Spring Cloud孵化器中孵化；2019年7月，Spring Cloud官方宣布Spring Cloud Alibaba毕业，并将仓库迁移到Alibaba Github OSS下。</p><h2 id="服务发现与配置中心：Nacos"><a href="#服务发现与配置中心：Nacos" class="headerlink" title="服务发现与配置中心：Nacos"></a>服务发现与配置中心：Nacos</h2><h3 id="“服务发现”是什么？"><a href="#“服务发现”是什么？" class="headerlink" title="“服务发现”是什么？"></a>“服务发现”是什么？</h3><p>对于一个单体项目，一个方法想要使用另一个方法，只需要正常的方法调用就行。但是，对于一个分布式系统，两个方法可能分布在不同的服务器，那么一个方法就需要先知道另一个方法的IP、端口等基本信息才能进行调用。如果直接将其他服务器上方法的基本信息记录在自己的服务器上，那么一个分布式系统的模块间的耦合度就太高了，不便于维护。</p><p>所以，需要有一个统一的服务，不同模块将自己需要暴露出去的接口信息注册到这个服务中，那么，一个模块中的方法想要调用其他模块中的方法时，只需要到这个服务中去查询一下，就可以得到IP、端口等信息，然后就可以去调用方法了。这个服务就叫“<strong>服务发现</strong>”，或者“服务注册与发现”。</p><p>Spring Cloud Alibaba通过<strong>Nacos</strong>实现服务发现。</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/service-discovery-415a38066790.png" class="服务注册与发现模型"><!--图片源自-->[Spring Cloud Alibaba](https://sca.aliyun.com/docs/2025.x/user-guide/nacos/overview/?spm=5176.29160081.0.0.74805c72Cn8wbR)<p>如上图所示，所有的微服务应用在启动过程中会将自身包含服务名称、主机IP地址和端口号等信息发送到注册中心中，然后服务调用方根据服务名称到注册中心中查找对应服务的所有实例IP地址和端口号来进行服务调用。从而让分散的微服务系统之间能像一个整体一样进行交互。</p><h3 id="“配置中心”是什么？"><a href="#“配置中心”是什么？" class="headerlink" title="“配置中心”是什么？"></a>“配置中心”是什么？</h3><p>一个项目中，会有很多配置信息，例如数据库连接字符串、日志级别等，如果想要更换数据库，就需要修改配置文件，然后重新打包、部署项目。对于单体项目，还可以忍受，但是对于一个分布式项目，可能涉及多个模块，每个模块都需要重新打包、部署，这就太不利于维护了。</p><p>所以，需要有一个统一的服务，将项目中主要的配置信息交给这个服务来维护，项目动态地从该服务中拉取配置信息。这个服务就叫“<strong>配置中心</strong>”。</p><p>Spring Cloud Alibaba通过<strong>Nacos</strong>实现配置中心。</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/spring-cloud-config-7336fbcdedcb.png" class="service-config"><!--图片源自-->[Spring Cloud Alibaba](https://sca.aliyun.com/docs/2025.x/user-guide/nacos/overview/?spm=5176.29160081.0.0.74805c72Cn8wbR)<p>配置中心在分布式场景中可以帮助解决以下问题：</p><ol><li>管理应用程序配置：当有大量应用程序需要管理时，手动维护配置文件会变得非常困难。分布式配置中心提供了一个集中管理和发布配置信息的解决方案。</li><li>环境隔离：在开发、测试和生产等不同环境中，应用程序的配置信息往往都会有不同。使用分布式配置中心，可以轻松的管理和分发不同环境下的配置信息。</li><li>提高程序安全性：将配置信息存储在代码库或应用程序文件中可能会导致安全风险，因为这些信息可能会被意外地泄露或被恶意攻击者利用。使用分布式配置，可以将配置信息加密和保护，并且可以进行访问权限控制。</li><li>动态更新配置：在应用程序运行时，可能需要动态地更新配置信息，以便应用程序可以及时响应变化。使用分布式配置中心，可以在运行时动态更新配置信息，而无需重新启动应用程序。</li></ol><h3 id="Nacos实战"><a href="#Nacos实战" class="headerlink" title="Nacos实战"></a>Nacos实战</h3><p><a href="https://nacos.io/zh-cn/?spm=5176.29160081.0.0.74805c72Cn8wbR"><strong>Nacos</strong></a> &#x2F;nɑ:k əʊs&#x2F; （Dynamic Naming and Configuration Service）是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。</p><h4 id="本地安装Nacos"><a href="#本地安装Nacos" class="headerlink" title="本地安装Nacos"></a>本地安装Nacos</h4><ol><li><p>在Nacos<a href="https://nacos.io/download/release-history/?spm=5238cd80.cff869d.0.0.237f7e84lYfO6f">官网下载</a>稳定版本的二进制包（这里下载的是Spring Cloud Alibaba 2022.x推荐</p><p>的2.2.3版本）：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260212175240614-67334063f2ac.png" class="image-20260212175240614"></li><li><p>下载完成后，解压缩对应的nacos-server-2.2.3.zip压缩包</p></li><li><p>在解压缩后的nacos&#x2F;conf&#x2F;application.properties文件中配置数据源及鉴权信息：</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs properties"><span class="hljs-comment">#*************** Config Module Related Configurations ***************#</span><br><span class="hljs-comment">### If use MySQL as datasource:</span><br><span class="hljs-comment">### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced.</span><br><span class="hljs-comment"># spring.datasource.platform=mysql</span><br><span class="hljs-attr">spring.sql.init.platform</span>=<span class="hljs-string">mysql</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### Count of DB:</span><br><span class="hljs-attr">db.num</span>=<span class="hljs-string">1</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### Connect URL of DB:</span><br><span class="hljs-attr">db.url.0</span>=<span class="hljs-string">jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&amp;connectTimeout=1000&amp;socketTimeout=3000&amp;autoReconnect=true&amp;useUnicode=true&amp;useSSL=false&amp;serverTimezone=UTC</span><br><span class="hljs-attr">db.user.0</span>=<span class="hljs-string">root</span><br><span class="hljs-attr">db.password.0</span>=<span class="hljs-string">2469</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### Connection pool configuration: hikariCP</span><br><span class="hljs-attr">db.pool.config.connectionTimeout</span>=<span class="hljs-string">30000</span><br><span class="hljs-attr">db.pool.config.validationTimeout</span>=<span class="hljs-string">10000</span><br><span class="hljs-attr">db.pool.config.maximumPoolSize</span>=<span class="hljs-string">20</span><br><span class="hljs-attr">db.pool.config.minimumIdle</span>=<span class="hljs-string">2</span><br></code></pre></td></tr></table></figure><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs properties"><span class="hljs-comment">### The auth system to use, currently only &#x27;nacos&#x27; and &#x27;ldap&#x27; is supported:</span><br><span class="hljs-attr">nacos.core.auth.system.type</span>=<span class="hljs-string">nacos</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### If turn on auth system:</span><br><span class="hljs-attr">nacos.core.auth.enabled</span>=<span class="hljs-string">true</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay.</span><br><span class="hljs-attr">nacos.core.auth.caching.enabled</span>=<span class="hljs-string">true</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### Since 1.4.1, Turn on/off white auth for user-agent: nacos-server, only for upgrade from old version.</span><br><span class="hljs-attr">nacos.core.auth.enable.userAgentAuthWhite</span>=<span class="hljs-string">false</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false.</span><br><span class="hljs-comment">### The two properties is the white list for auth and used by identity the request from other server.</span><br><span class="hljs-attr">nacos.core.auth.server.identity.key</span>=<span class="hljs-string">nacos</span><br><span class="hljs-attr">nacos.core.auth.server.identity.value</span>=<span class="hljs-string">nacos</span><br><span class="hljs-comment"></span><br><span class="hljs-comment">### worked when nacos.core.auth.system.type=nacos</span><br><span class="hljs-comment">### The token expiration in seconds:</span><br><span class="hljs-attr">nacos.core.auth.plugin.nacos.token.cache.enable</span>=<span class="hljs-string">false</span><br><span class="hljs-attr">nacos.core.auth.plugin.nacos.token.expire.seconds</span>=<span class="hljs-string">18000</span><br><span class="hljs-comment">### The default token (Base64 String):</span><br><span class="hljs-attr">nacos.core.auth.plugin.nacos.token.secret.key</span>=<span class="hljs-string">SecretKey012345678901234567890123456789012345678901234567890123456789</span><br></code></pre></td></tr></table></figure></li><li><p>在连接的MySQL数据库中创建<em>nacos</em>库，并且在该库内执行nacos&#x2F;conf&#x2F;mysql-schema.sql文件，进行数据库初始化</p></li><li><p>然后在nacos&#x2F;bin目录下通过cmd命令行运行<code>startup.cmd -m standalone</code>启动Nacos单机模式</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260213123106536-899fe3f42f48.png" class="image-20260213123106536"></li><li><p>运行成功后，可以在浏览器访问Nacos控制台：<a href="http://127.0.0.1:8848/">http://127.0.0.1:8848</a></p><p>默认账号：nacos  密码：nacos</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260213123210986-d5ec83150bf7.png" class="image-20260213123210986"></li></ol><h4 id="项目接入Nacos-Config"><a href="#项目接入Nacos-Config" class="headerlink" title="项目接入Nacos Config"></a>项目接入Nacos Config</h4><ol><li><p>新建一个Spring Boot项目：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260212155107682-86b432a42871.png" class="image-20260212155107682"><blockquote><p>我的idea最低支持SpringBoot3.5.10，但是Spring Cloud Alibaba最多支持SpringBoot3.0.2，所以在pom.xml文件中改为3.0.2版本</p></blockquote></li><li><p>引入Spring Cloud Alibaba依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">packaging</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">packaging</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-boot.version</span>&gt;</span>3.0.2<span class="hljs-tag">&lt;/<span class="hljs-name">spring-boot.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud-alibaba.version</span>&gt;</span>2022.0.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud-alibaba.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud.version</span>&gt;</span>2022.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>20<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modules</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>NacosConfig<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">modules</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-boot.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-cloud.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-alibaba-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-cloud-alibaba.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">pluginManagement</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-boot.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">pluginManagement</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br><br></code></pre></td></tr></table></figure><p>删除src目录。</p><p>创建子模块NacosConfig。</p><p>在NacosConfig模块的pom.xml文件中添加nacos-config的依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>&gt;</span>../pom.xml<span class="hljs-tag">&lt;/<span class="hljs-name">relativePath</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>NacosConfig<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-config<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br><br></code></pre></td></tr></table></figure><p>在resources&#x2F;application.yaml中配置nacos-config：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">NacosConfig</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">serverAddr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">import:</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-string">nacos:nacos-config-example.properties?refreshEnabled=true</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18084</span><br><span class="hljs-attr">management:</span><br>  <span class="hljs-attr">endpoints:</span><br>    <span class="hljs-attr">web:</span><br>      <span class="hljs-attr">exposure:</span><br>        <span class="hljs-attr">include:</span> <span class="hljs-string">&quot;*&quot;</span><br></code></pre></td></tr></table></figure></li><li><p>在Nacos控制台中添加配置信息：<code>nacos-config-example.properties</code></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-string">spring.cloud.nacos.config.serveraddr=127.0.0.1:8848</span><br><span class="hljs-string">spring.cloud.nacos.config.prefix=PREFIX</span><br><span class="hljs-string">spring.cloud.nacos.config.group=GROUP</span><br><span class="hljs-string">spring.cloud.nacos.config.namespace=NAMESPACE</span><br></code></pre></td></tr></table></figure><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260212181055806-624a571c9a60.png" class="image-20260212181055806"></li><li><p>运行项目后可见，配置加载成功：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260213143746596-92d32b1edcc7.png" class="image-20260213143746596"><p>在Nacos控制台中修改nacos-config-example.properties的配置信息后，项目动态识别更新：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260213143850230-91cf653cbe25.png" class="image-20260213143850230"></li></ol><h4 id="项目接入Nacos-Discovery"><a href="#项目接入Nacos-Discovery" class="headerlink" title="项目接入Nacos Discovery"></a>项目接入Nacos Discovery</h4><ol><li><p>新建一个NacosProvider子模块，在pom.xml文件中添加依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>NacosProvider<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure></li><li><p>在NacosProvider模块的application.yml文件中添加配置：</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs properties"><span class="hljs-attr">spring</span>:<span class="hljs-string"></span><br>  <span class="hljs-attr">application</span>:<span class="hljs-string"></span><br>    <span class="hljs-attr">name</span>: <span class="hljs-string">NacosProvider</span><br>  <span class="hljs-attr">cloud</span>:<span class="hljs-string"></span><br>    <span class="hljs-attr">nacos</span>:<span class="hljs-string"></span><br>      <span class="hljs-attr">discovery</span>:<span class="hljs-string"></span><br>        <span class="hljs-attr">server-addr</span>: <span class="hljs-string">127.0.0.1:8848</span><br>        <span class="hljs-attr">username</span>: <span class="hljs-string">nacos</span><br>        <span class="hljs-attr">password</span>: <span class="hljs-string">nacos</span><br><span class="hljs-attr">server</span>:<span class="hljs-string"></span><br>  <span class="hljs-attr">port</span>: <span class="hljs-string">18085</span><br></code></pre></td></tr></table></figure></li><li><p>在NacosProvider模块的启动类上添加注解<code>@EnableDiscoveryClient</code>，开启Nacos服务发现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.cloud.client.discovery.EnableDiscoveryClient;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-meta">@EnableDiscoveryClient</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NacosProviderApplication</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(NacosProviderApplication.class, args);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>添加这个注解后，这个项目就成为了服务的提供者。</p></li><li><p>在NacosProvider模块提供一个接口，便于后面消费者调用：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example.controller;<br><br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping(&quot;/provider&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Provider</span> &#123;<br>    <span class="hljs-meta">@GetMapping(&quot;/hello&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">hello</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;hello nacos&quot;</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>启动NacosProvider项目后，可以在Nacos控制台看见服务已被注册：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260213151005621-364de8c43755.png" class="image-20260213151005621"></li><li><p>我们新建一个NacosConsumer子模块，在pom.xml中添加依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>NacosConsumer<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-loadbalancer<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>   <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-openfeign<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在application.yml文件中添加配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">NacosConsumer</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>        <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>        <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">loadbalancer:</span><br>      <span class="hljs-attr">nacos:</span><br>        <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18086</span><br></code></pre></td></tr></table></figure></li><li><p>在NacosConsumer模块的启动类上添加注解<code>@EnableDiscoveryClient</code>，开启Nacos服务发现，</p><p>添加注解<code>@EnableFeignClients</code>开启Feign：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> example;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.cloud.client.discovery.EnableDiscoveryClient;<br><span class="hljs-keyword">import</span> org.springframework.cloud.openfeign.EnableFeignClients;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-meta">@EnableDiscoveryClient</span><br><span class="hljs-meta">@EnableFeignClients</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NacosConsumerApplication</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(NacosConsumerApplication.class, args);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在NacosConsumer模块配置一个 FeignClient：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> example.api;<br><br><span class="hljs-keyword">import</span> org.springframework.cloud.openfeign.FeignClient;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><br><span class="hljs-comment">// value 对应 Nacos 中的服务名</span><br><span class="hljs-meta">@FeignClient(value = &quot;NacosProvider&quot;, path = &quot;/provider&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserClient</span> &#123;<br><br>    <span class="hljs-meta">@GetMapping(&quot;/hello&quot;)</span><br>    String <span class="hljs-title function_">hello</span><span class="hljs-params">()</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>就可以正常使用这个接口了：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> example.controller;<br><br><span class="hljs-keyword">import</span> example.api.UserClient;<br><span class="hljs-keyword">import</span> jakarta.annotation.Resource;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping(&quot;/consumer&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Consumer</span> &#123;<br><br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">private</span> UserClient userClient;<br><br>    <span class="hljs-meta">@GetMapping(&quot;/hello&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">hello</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> userClient.hello();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>启动NacosProvider和NacosConsumer，在浏览器访问<a href="http://127.0.0.1:18086/consumer/hello%EF%BC%8C%E6%AD%A3%E7%A1%AE%E8%BF%94%E5%9B%9E%E4%BA%86%60hello">http://127.0.0.1:18086/consumer/hello，正确返回了`hello</a> nacos&#96;字符串。</p></li></ol><h2 id="分布式事务：Seata"><a href="#分布式事务：Seata" class="headerlink" title="分布式事务：Seata"></a>分布式事务：Seata</h2><h3 id="分布式事务是什么？"><a href="#分布式事务是什么？" class="headerlink" title="分布式事务是什么？"></a>分布式事务是什么？</h3><p>我们知道，事务具有四大特性：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）。系统需要通过对原子性、隔离性以及持久性的权衡，来保障数据的一致性。</p><p>区别于单体项目，分布式事务的难点在于，一个事务涉及的数据需要在多个模块、多台服务器上进行操作。数据传输的不确定性，造成了分布式事务的复杂性。</p><p><strong>Seata</strong>应运而生。</p><h3 id="Seata实战"><a href="#Seata实战" class="headerlink" title="Seata实战"></a>Seata实战</h3><h4 id="启动Seata"><a href="#启动Seata" class="headerlink" title="启动Seata"></a>启动Seata</h4><ol><li><p>在Seata官网下载Spring Cloud Alibaba对应版本的<a href="https://seata.apache.org/zh-cn/release-history/seata-server">Seata 1.7.0</a>二进制压缩包：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260215130040739-dd205f96fb80.png" class="image-20260215130040739"></li><li><p>修改Seata配置文件。</p><p><code>seata/conf/application.example.yml</code>是参考模版，需要在<code>seata/conf/application.yml</code>提供自己的配置信息。我这里已经改好了，可以参考注解修改：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">7091</span> <span class="hljs-comment"># 默认端口</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">seata-server</span> <span class="hljs-comment"># 默认服务名称</span><br><br><span class="hljs-attr">logging:</span><br>  <span class="hljs-attr">config:</span> <span class="hljs-string">classpath:logback-spring.xml</span> <span class="hljs-comment"># 日志配置</span><br>  <span class="hljs-attr">file:</span><br>    <span class="hljs-attr">path:</span> <span class="hljs-string">$&#123;user.home&#125;/logs/seata</span><br>  <span class="hljs-attr">extend:</span><br>    <span class="hljs-attr">logstash-appender:</span><br>      <span class="hljs-attr">destination:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:4560</span><br>    <span class="hljs-attr">kafka-appender:</span><br>      <span class="hljs-attr">bootstrap-servers:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:9092</span><br>      <span class="hljs-attr">topic:</span> <span class="hljs-string">logback_to_logstash</span><br><br><span class="hljs-attr">console:</span> <span class="hljs-comment"># 控制台的用户名密码</span><br>  <span class="hljs-attr">user:</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">seata</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">seata</span><br><br><span class="hljs-attr">seata:</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-comment"># support: nacos 、 consul 、 apollo 、 zk  、 etcd3</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span>  <span class="hljs-comment"># 指定nacos作为配置中心</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span> <span class="hljs-comment"># nacos的地址</span><br>      <span class="hljs-attr">namespace:</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span>  <span class="hljs-comment"># nacos控制台的用户名密码</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-comment">##if use MSE Nacos with auth, mutex with username/password attribute </span><br>      <span class="hljs-comment">#access-key: &quot;&quot;</span><br>      <span class="hljs-comment">#secret-key: &quot;&quot;</span><br>      <span class="hljs-attr">data-id:</span> <span class="hljs-string">seataServer.properties</span><br>  <span class="hljs-attr">registry:</span><br>    <span class="hljs-comment"># support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span>  <span class="hljs-comment"># 指定nacos作为注册中心</span><br>    <span class="hljs-attr">preferred-networks:</span> <span class="hljs-number">30.240</span><span class="hljs-string">.*</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br>      <span class="hljs-attr">namespace:</span><br>      <span class="hljs-attr">cluster:</span> <span class="hljs-string">default</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-comment">##if use MSE Nacos with auth, mutex with username/password attribute</span><br>      <span class="hljs-comment">#access-key: &quot;&quot;</span><br>      <span class="hljs-comment">#secret-key: &quot;&quot;</span><br><br>  <span class="hljs-attr">server:</span><br>    <span class="hljs-attr">service-port:</span> <span class="hljs-number">8091</span> <span class="hljs-comment">#If not configured, the default is &#x27;$&#123;server.port&#125; + 1000&#x27; seata服务的配置 我这边都用的模版的默认配置</span><br>    <span class="hljs-attr">max-commit-retry-timeout:</span> <span class="hljs-number">-1</span><br>    <span class="hljs-attr">max-rollback-retry-timeout:</span> <span class="hljs-number">-1</span><br>    <span class="hljs-attr">rollback-retry-timeout-unlock-enable:</span> <span class="hljs-literal">false</span><br>    <span class="hljs-attr">enable-check-auth:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-attr">enable-parallel-request-handle:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-attr">retry-dead-threshold:</span> <span class="hljs-number">130000</span><br>    <span class="hljs-attr">xaer-nota-retry-timeout:</span> <span class="hljs-number">60000</span><br>    <span class="hljs-attr">vgroup-mapping:</span><br>      <span class="hljs-attr">fsp_tx_group:</span> <span class="hljs-string">default</span><br>    <span class="hljs-attr">recovery:</span><br>      <span class="hljs-attr">handle-all-session-period:</span> <span class="hljs-number">1000</span><br>    <span class="hljs-attr">undo:</span><br>      <span class="hljs-attr">log-save-days:</span> <span class="hljs-number">7</span><br>      <span class="hljs-attr">log-delete-period:</span> <span class="hljs-number">86400000</span><br>    <span class="hljs-attr">session:</span><br>      <span class="hljs-attr">branch-async-queue-size:</span> <span class="hljs-number">5000</span> <span class="hljs-comment">#branch async remove queue size</span><br>      <span class="hljs-attr">enable-branch-async-remove:</span> <span class="hljs-literal">false</span> <span class="hljs-comment">#enable to asynchronous remove branchSession</span><br>  <span class="hljs-attr">store:</span><br>    <span class="hljs-comment"># support: file 、 db 、 redis seata数据的保存方式</span><br>    <span class="hljs-attr">mode:</span> <span class="hljs-string">db</span>  <span class="hljs-comment"># 这边后续考虑高可用 选择使用数据库保存 默认是file文件</span><br>    <span class="hljs-attr">db:</span><br>      <span class="hljs-attr">datasource:</span> <span class="hljs-string">druid</span><br>      <span class="hljs-attr">db-type:</span> <span class="hljs-string">mysql</span><br>      <span class="hljs-attr">driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br>      <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:mysql://127.0.0.1:3308/seata?rewriteBatchedStatements=true</span>  <span class="hljs-comment"># 自己的MySQL地址，及用户名密码</span><br>      <span class="hljs-attr">user:</span> <span class="hljs-string">root</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-number">2469</span><br>      <span class="hljs-attr">min-conn:</span> <span class="hljs-number">5</span><br>      <span class="hljs-attr">max-conn:</span> <span class="hljs-number">100</span><br>      <span class="hljs-attr">global-table:</span> <span class="hljs-string">global_table</span><br>      <span class="hljs-attr">branch-table:</span> <span class="hljs-string">branch_table</span><br>      <span class="hljs-attr">lock-table:</span> <span class="hljs-string">lock_table</span><br>      <span class="hljs-attr">distributed-lock-table:</span> <span class="hljs-string">distributed_lock</span><br>      <span class="hljs-attr">query-limit:</span> <span class="hljs-number">100</span><br>      <span class="hljs-attr">max-wait:</span> <span class="hljs-number">5000</span><br>  <span class="hljs-attr">security:</span><br>    <span class="hljs-attr">secretKey:</span> <span class="hljs-string">SeataSecretKey0c382ef121d778043159209298fd40bf3850a017</span><br>    <span class="hljs-attr">tokenValidityInMilliseconds:</span> <span class="hljs-number">1800000</span><br>    <span class="hljs-attr">ignore:</span><br>      <span class="hljs-attr">urls:</span> <span class="hljs-string">/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login</span><br></code></pre></td></tr></table></figure></li><li><p>在<code>seata/lib/jdbc</code>下，会有多个mySQL驱动版本，留下自己的版本，删除其它：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260215132412645-46ebe58691fc.png" class="image-20260215132412645"></li><li><p>在MySQL中创建数据库<code>seata</code>，执行<code>seata/script/server/db/mysql.sql</code>文件，创建4张表：global_table、branch_table、lock_table、distributed_lock。</p><p>执行下面的sql，创建 undo_log 表：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">CREATE TABLE</span> `undo_log` (<br>  `id` <span class="hljs-type">bigint</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT NULL</span> AUTO_INCREMENT,<br>  `branch_id` <span class="hljs-type">bigint</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT NULL</span>,<br>  `xid` <span class="hljs-type">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT NULL</span>,<br>  `context` <span class="hljs-type">varchar</span>(<span class="hljs-number">128</span>) <span class="hljs-keyword">NOT NULL</span>,<br>  `rollback_info` longblob <span class="hljs-keyword">NOT NULL</span>,<br>  `log_status` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT NULL</span>,<br>  `log_created` datetime <span class="hljs-keyword">NOT NULL</span>,<br>  `log_modified` datetime <span class="hljs-keyword">NOT NULL</span>,<br>  `ext` <span class="hljs-type">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span>,<br>  <span class="hljs-keyword">PRIMARY KEY</span> (`id`),<br>  <span class="hljs-keyword">UNIQUE</span> KEY `ux_undo_log` (`xid`,`branch_id`)<br>) ENGINE<span class="hljs-operator">=</span>InnoDB AUTO_INCREMENT<span class="hljs-operator">=</span><span class="hljs-number">1</span> <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8;<br></code></pre></td></tr></table></figure></li><li><p>在nacos&#x2F;bin目录下通过cmd命令行运行<code>startup.cmd -m standalone</code>启动Nacos单机模式。</p><p>然后运行<code>seata/bin/seata-server.bat</code>文件，启动Seata Server：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260215133726148-197e2f88f992.png" class="image-20260215133726148"><p>成功访问<a href="http://127.0.0.1:7091/%EF%BC%8C%E8%B4%A6%E5%8F%B7/%E5%AF%86%E7%A0%81%EF%BC%9Aseata/seata">http://127.0.0.1:7091/，账号/密码：seata/seata</a></p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260215133927207-b36ab7983582.png" class="image-20260215133927207"><p>访问<a href="http://localhost:8848/nacos/">Nacos控制台</a>可以看见seata-server已成功注册：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260215134249850-7a8dd552aada.png" class="image-20260215134249850"></li></ol><h4 id="创建示例项目"><a href="#创建示例项目" class="headerlink" title="创建示例项目"></a>创建示例项目</h4><ol><li><p>在<code>seata</code>数据库中创建示例项目需要的表：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-comment">-- -------------- Create the database tables needed by the business in the example ----------------</span><br><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">EXISTS</span> `storage_tbl`;<br><span class="hljs-keyword">CREATE TABLE</span> `storage_tbl` (<br>                               `id` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT NULL</span> AUTO_INCREMENT,<br>                               `commodity_code` <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span>,<br>                               `count` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span>,<br>                               <span class="hljs-keyword">PRIMARY KEY</span> (`id`),<br>                               <span class="hljs-keyword">UNIQUE</span> KEY (`commodity_code`)<br>) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8;<br><br><br><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">EXISTS</span> `order_tbl`;<br><span class="hljs-keyword">CREATE TABLE</span> `order_tbl` (<br>                             `id` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT NULL</span> AUTO_INCREMENT,<br>                             `user_id` <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span>,<br>                             `commodity_code` <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span>,<br>                             `count` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span>,<br>                             `money` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span>,<br>                             <span class="hljs-keyword">PRIMARY KEY</span> (`id`)<br>) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8;<br><br><br><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">EXISTS</span> `account_tbl`;<br><span class="hljs-keyword">CREATE TABLE</span> `account_tbl` (<br>                               `id` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT NULL</span> AUTO_INCREMENT,<br>                               `user_id` <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span>,<br>                               `money` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span>,<br>                               <span class="hljs-keyword">PRIMARY KEY</span> (`id`)<br>) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8;<br></code></pre></td></tr></table></figure></li><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>SeataStorage</code></p><p>在SeataStorage中引入依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SeataStorage<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">exclusions</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">exclusion</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>dubbo-filter-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.dubbo.extensions<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">exclusion</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">exclusions</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>log4j<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>log4j<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.2.17<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>添加<code>application.yml</code>配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">base:</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">mdb:</span><br>      <span class="hljs-attr">hostname:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> <span class="hljs-comment">#your mysql server ip address</span><br>      <span class="hljs-attr">dbname:</span> <span class="hljs-string">seata</span> <span class="hljs-comment">#your database name for test</span><br>      <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span> <span class="hljs-comment">#your mysql server listening port</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;root&#x27;</span> <span class="hljs-comment">#your mysql server username</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;2469&#x27;</span> <span class="hljs-comment">#your mysql server password</span><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18092</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>        <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>        <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">storage-service</span><br>  <span class="hljs-attr">main:</span><br>    <span class="hljs-attr">allow-bean-definition-overriding:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">datasource:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">storageDataSource</span><br>    <span class="hljs-attr">driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:mysql://$&#123;base.config.mdb.hostname&#125;:$&#123;base.config.mdb.port&#125;/$&#123;base.config.mdb.dbname&#125;?useSSL=false&amp;serverTimezone=UTC</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">$&#123;base.config.mdb.username&#125;</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">$&#123;base.config.mdb.password&#125;</span><br><br><span class="hljs-attr">seata:</span><br>  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">application-id:</span> <span class="hljs-string">$&#123;spring.application.name&#125;</span><br>  <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">$&#123;spring.application.name&#125;-tx-group</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">serverAddr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">dataId:</span> <span class="hljs-string">&quot;seata.properties&quot;</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br>  <span class="hljs-attr">registry:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br></code></pre></td></tr></table></figure><p>在<code>example/config/DatabaseConfiguration.java</code>中编写jdbc配置：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example.config;<br><br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;<br><br><span class="hljs-keyword">import</span> javax.sql.DataSource;<br><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DatabaseConfiguration</span> &#123;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> JdbcTemplate <span class="hljs-title function_">jdbcTemplate</span><span class="hljs-params">(DataSource dataSource)</span> &#123;<br><br>        <span class="hljs-type">JdbcTemplate</span> <span class="hljs-variable">jdbcTemplate</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JdbcTemplate</span>(dataSource);<br><br>        jdbcTemplate.update(<span class="hljs-string">&quot;delete from storage_tbl where commodity_code = &#x27;C00321&#x27;&quot;</span>);<br>        jdbcTemplate.update(<br>                <span class="hljs-string">&quot;insert into storage_tbl(commodity_code, count) values (&#x27;C00321&#x27;, 100)&quot;</span>);<br><br>        <span class="hljs-keyword">return</span> jdbcTemplate;<br><br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>添加移除商品库存的接口<code>example/controller/StorageController.java</code>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example.controller;<br><br><span class="hljs-keyword">import</span> io.seata.core.context.RootContext;<br><span class="hljs-keyword">import</span> org.slf4j.Logger;<br><span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PathVariable;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">StorageController</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">LOGGER</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(StorageController.class);<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">SUCCESS</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;SUCCESS&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">FAIL</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;FAIL&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">StorageController</span><span class="hljs-params">(JdbcTemplate jdbcTemplate)</span> &#123;<br>        <span class="hljs-built_in">this</span>.jdbcTemplate = jdbcTemplate;<br>    &#125;<br><br>    <span class="hljs-meta">@GetMapping(value = &quot;/storage/&#123;commodityCode&#125;/&#123;count&#125;&quot;, produces = &quot;application/json&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">echo</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> String commodityCode, <span class="hljs-meta">@PathVariable</span> <span class="hljs-type">int</span> count)</span> &#123;<br>        LOGGER.info(<span class="hljs-string">&quot;Storage Service Begin ... xid: &quot;</span> + RootContext.getXID());<br>        <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> jdbcTemplate.update(<br>                <span class="hljs-string">&quot;update storage_tbl set count = count - ? where commodity_code = ?&quot;</span>,<br>                count, commodityCode);<br>        LOGGER.info(<span class="hljs-string">&quot;Storage Service End ... &quot;</span>);<br>        <span class="hljs-keyword">if</span> (result == <span class="hljs-number">1</span>) &#123;<br>            <span class="hljs-keyword">return</span> SUCCESS;<br>        &#125;<br>        <span class="hljs-keyword">return</span> FAIL;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>SeataOrder</code></p><p>在子模块SeataOrder中添加依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SeataOrder<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">exclusions</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">exclusion</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>dubbo-filter-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.dubbo.extensions<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">exclusion</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">exclusions</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>log4j<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>log4j<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.2.17<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在<code>resources/application.yml</code>中添加配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">base:</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">mdb:</span><br>      <span class="hljs-attr">hostname:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> <span class="hljs-comment">#your mysql server ip address</span><br>      <span class="hljs-attr">dbname:</span> <span class="hljs-string">seata</span> <span class="hljs-comment">#your database name for test</span><br>      <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span> <span class="hljs-comment">#your mysql server listening port</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;root&#x27;</span> <span class="hljs-comment">#your mysql server username</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;2469&#x27;</span> <span class="hljs-comment">#your mysql server password</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18093</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>        <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>        <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">order-service</span><br>  <span class="hljs-attr">main:</span><br>    <span class="hljs-attr">allow-bean-definition-overriding:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">datasource:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">storageDataSource</span><br>    <span class="hljs-attr">driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:mysql://$&#123;base.config.mdb.hostname&#125;:$&#123;base.config.mdb.port&#125;/$&#123;base.config.mdb.dbname&#125;?useSSL=false&amp;serverTimezone=UTC</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">$&#123;base.config.mdb.username&#125;</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">$&#123;base.config.mdb.password&#125;</span><br><br><span class="hljs-attr">seata:</span><br>  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">application-id:</span> <span class="hljs-string">$&#123;spring.application.name&#125;</span><br>  <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">$&#123;spring.application.name&#125;-tx-group</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">dataId:</span> <span class="hljs-string">&quot;seata.properties&quot;</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br>  <span class="hljs-attr">registry:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br></code></pre></td></tr></table></figure><p>在<code>example/config/DatabaseConfiguration.java</code>中编写jdbc配置：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example.config;<br><br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;<br><br><span class="hljs-keyword">import</span> javax.sql.DataSource;<br><br><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DatabaseConfiguration</span> &#123;<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> JdbcTemplate <span class="hljs-title function_">jdbcTemplate</span><span class="hljs-params">(DataSource dataSource)</span> &#123;<br>        <span class="hljs-type">JdbcTemplate</span> <span class="hljs-variable">jdbcTemplate</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JdbcTemplate</span>(dataSource);<br><br>        jdbcTemplate.execute(<span class="hljs-string">&quot;TRUNCATE TABLE order_tbl&quot;</span>);<br><br>        <span class="hljs-keyword">return</span> jdbcTemplate;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>example/Order.java</code>中编写订单实体类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> java.io.Serializable;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Order</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Serializable</span> &#123;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * id.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">long</span> id;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * user id.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> String userId;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * commodity code.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> String commodityCode;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * count.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> count;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * money.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> money;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;Order&#123;&quot;</span> + <span class="hljs-string">&quot;id=&quot;</span> + id + <span class="hljs-string">&quot;, userId=&#x27;&quot;</span> + userId + <span class="hljs-string">&#x27;\&#x27;&#x27;</span> + <span class="hljs-string">&quot;, commodityCode=&#x27;&quot;</span><br>                + commodityCode + <span class="hljs-string">&#x27;\&#x27;&#x27;</span> + <span class="hljs-string">&quot;, count=&quot;</span> + count + <span class="hljs-string">&quot;, money=&quot;</span> + money + <span class="hljs-string">&#x27;&#125;&#x27;</span>;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>example/SeataOrderApplication.java</code>中提供<code>RestTemplate</code>的Bean：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.web.client.RestTemplate;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SeataOrderApplication</span> &#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(SeataOrderApplication.class, args);<br>    &#125;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> RestTemplate <span class="hljs-title function_">restTemplate</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RestTemplate</span>();<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>example/OrderController.java</code>中添加创建订单的接口，并且调用账号模块提供的接口来扣除账号下的金额：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> io.seata.core.context.RootContext;<br><span class="hljs-keyword">import</span> org.slf4j.Logger;<br><span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;<br><span class="hljs-keyword">import</span> org.springframework.http.HttpEntity;<br><span class="hljs-keyword">import</span> org.springframework.http.HttpHeaders;<br><span class="hljs-keyword">import</span> org.springframework.http.MediaType;<br><span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.support.GeneratedKeyHolder;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.support.KeyHolder;<br><span class="hljs-keyword">import</span> org.springframework.util.LinkedMultiValueMap;<br><span class="hljs-keyword">import</span> org.springframework.util.MultiValueMap;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PostMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><span class="hljs-keyword">import</span> org.springframework.web.client.RestTemplate;<br><br><span class="hljs-keyword">import</span> java.sql.PreparedStatement;<br><span class="hljs-keyword">import</span> java.util.Random;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderController</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">LOGGER</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(OrderController.class);<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">SUCCESS</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;SUCCESS&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">FAIL</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;FAIL&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">USER_ID</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;U100001&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">COMMODITY_CODE</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;C00321&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> RestTemplate restTemplate;<br><br>    <span class="hljs-keyword">private</span> Random random;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">OrderController</span><span class="hljs-params">(JdbcTemplate jdbcTemplate, RestTemplate restTemplate)</span> &#123;<br>        <span class="hljs-built_in">this</span>.jdbcTemplate = jdbcTemplate;<br>        <span class="hljs-built_in">this</span>.restTemplate = restTemplate;<br>        <span class="hljs-built_in">this</span>.random = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Random</span>();<br>    &#125;<br><br>    <span class="hljs-meta">@PostMapping(value = &quot;/order&quot;, produces = &quot;application/json&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">order</span><span class="hljs-params">(String userId, String commodityCode, <span class="hljs-type">int</span> orderCount)</span> &#123;<br>        LOGGER.info(<span class="hljs-string">&quot;Order Service Begin ... xid: &quot;</span> + RootContext.getXID());<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">orderMoney</span> <span class="hljs-operator">=</span> calculate(commodityCode, orderCount);<br><br>        invokerAccountService(orderMoney);<br><br>        <span class="hljs-keyword">final</span> <span class="hljs-type">Order</span> <span class="hljs-variable">order</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Order</span>();<br>        order.userId = userId;<br>        order.commodityCode = commodityCode;<br>        order.count = orderCount;<br>        order.money = orderMoney;<br><br>        <span class="hljs-type">KeyHolder</span> <span class="hljs-variable">keyHolder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">GeneratedKeyHolder</span>();<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> jdbcTemplate.update(con -&gt; &#123;<br>            <span class="hljs-type">PreparedStatement</span> <span class="hljs-variable">pst</span> <span class="hljs-operator">=</span> con.prepareStatement(<br>                    <span class="hljs-string">&quot;insert into order_tbl (user_id, commodity_code, count, money) values (?, ?, ?, ?)&quot;</span>,<br>                    PreparedStatement.RETURN_GENERATED_KEYS);<br>            pst.setObject(<span class="hljs-number">1</span>, order.userId);<br>            pst.setObject(<span class="hljs-number">2</span>, order.commodityCode);<br>            pst.setObject(<span class="hljs-number">3</span>, order.count);<br>            pst.setObject(<span class="hljs-number">4</span>, order.money);<br>            <span class="hljs-keyword">return</span> pst;<br>        &#125;, keyHolder);<br><br>        order.id = keyHolder.getKey().longValue();<br><br>        <span class="hljs-comment">// 50%概率抛出异常，模拟订单创建异常，测试事务回滚</span><br>        <span class="hljs-keyword">if</span> (random.nextBoolean()) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">&quot;this is a mock Exception&quot;</span>);<br>        &#125;<br><br>        LOGGER.info(<span class="hljs-string">&quot;Order Service End ... Created &quot;</span> + order);<br><br>        <span class="hljs-keyword">if</span> (result == <span class="hljs-number">1</span>) &#123;<br>            <span class="hljs-keyword">return</span> SUCCESS;<br>        &#125;<br>        <span class="hljs-keyword">return</span> FAIL;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> <span class="hljs-title function_">calculate</span><span class="hljs-params">(String commodityId, <span class="hljs-type">int</span> orderCount)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * orderCount;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">invokerAccountService</span><span class="hljs-params">(<span class="hljs-type">int</span> orderMoney)</span> &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;http://127.0.0.1:18094/account&quot;</span>;<br>        <span class="hljs-type">HttpHeaders</span> <span class="hljs-variable">headers</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HttpHeaders</span>();<br>        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);<br><br>        MultiValueMap&lt;String, String&gt; map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedMultiValueMap</span>&lt;String, String&gt;();<br><br>        map.add(<span class="hljs-string">&quot;userId&quot;</span>, USER_ID);<br>        map.add(<span class="hljs-string">&quot;money&quot;</span>, orderMoney + <span class="hljs-string">&quot;&quot;</span>);<br><br>        HttpEntity&lt;MultiValueMap&lt;String, String&gt;&gt; request = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HttpEntity</span>&lt;MultiValueMap&lt;String, String&gt;&gt;(<br>                map, headers);<br><br>        ResponseEntity&lt;String&gt; response = restTemplate.postForEntity(url, request,<br>                String.class);<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>SeataBusiness</code></p><p>在子模块SeataBusiness中添加依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SeataBusiness<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">exclusions</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">exclusion</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>dubbo-filter-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.dubbo.extensions<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">exclusion</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">exclusions</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-openfeign<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-loadbalancer<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-tx<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在<code>resources/application.yml</code>中添加配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18091</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>        <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>        <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>    <span class="hljs-attr">loadbalancer:</span><br>      <span class="hljs-attr">ribbon:</span><br>        <span class="hljs-string">enabled:true</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">business-service</span><br><br><span class="hljs-attr">seata:</span><br>  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">application-id:</span> <span class="hljs-string">$&#123;spring.application.name&#125;</span><br><br>  <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">$&#123;spring.application.name&#125;-tx-group</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">dataId:</span> <span class="hljs-string">&quot;seata.properties&quot;</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br>  <span class="hljs-attr">registry:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br><br><span class="hljs-attr">feign:</span><br>  <span class="hljs-attr">client:</span><br>    <span class="hljs-attr">config:</span><br>      <span class="hljs-attr">default:</span><br>        <span class="hljs-attr">connectTimeout:</span> <span class="hljs-number">10000</span><br>        <span class="hljs-attr">readTimeout:</span> <span class="hljs-number">10000</span><br><span class="hljs-attr">logging:</span><br>  <span class="hljs-attr">level:</span><br>    <span class="hljs-attr">io:</span><br>      <span class="hljs-attr">seata:</span> <span class="hljs-string">debug</span><br></code></pre></td></tr></table></figure><p>在<code>example/SeataBusinessApplication.java</code>文件中引入其他模块的接口：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.cloud.client.discovery.EnableDiscoveryClient;<br><span class="hljs-keyword">import</span> org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;<br><span class="hljs-keyword">import</span> org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;<br><span class="hljs-keyword">import</span> org.springframework.cloud.openfeign.EnableFeignClients;<br><span class="hljs-keyword">import</span> org.springframework.cloud.openfeign.FeignClient;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PathVariable;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PostMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestParam;<br><span class="hljs-keyword">import</span> org.springframework.web.client.RestTemplate;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-meta">@EnableFeignClients</span><br><span class="hljs-meta">@EnableDiscoveryClient(autoRegister = false)</span><br><span class="hljs-meta">@LoadBalancerClients(&#123;</span><br><span class="hljs-meta">        @LoadBalancerClient(&quot;storage-service&quot;),</span><br><span class="hljs-meta">        @LoadBalancerClient(&quot;order-service&quot;)</span><br><span class="hljs-meta">&#125;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SeataBusinessApplication</span> &#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(SeataBusinessApplication.class, args);<br>    &#125;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> RestTemplate <span class="hljs-title function_">restTemplate</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RestTemplate</span>();<br>    &#125;<br><br>    <span class="hljs-meta">@FeignClient(&quot;storage-service&quot;)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">StorageService</span> &#123;<br><br>        <span class="hljs-meta">@GetMapping(path = &quot;/storage/&#123;commodityCode&#125;/&#123;count&#125;&quot;)</span><br>        String <span class="hljs-title function_">storage</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;commodityCode&quot;)</span> String commodityCode,</span><br><span class="hljs-params">                       <span class="hljs-meta">@PathVariable(&quot;count&quot;)</span> <span class="hljs-type">int</span> count)</span>;<br><br>    &#125;<br><br>    <span class="hljs-meta">@FeignClient(&quot;order-service&quot;)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">OrderService</span> &#123;<br><br>        <span class="hljs-meta">@PostMapping(path = &quot;/order&quot;)</span><br>        String <span class="hljs-title function_">order</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam(&quot;userId&quot;)</span> String userId,</span><br><span class="hljs-params">                     <span class="hljs-meta">@RequestParam(&quot;commodityCode&quot;)</span> String commodityCode,</span><br><span class="hljs-params">                     <span class="hljs-meta">@RequestParam(&quot;orderCount&quot;)</span> <span class="hljs-type">int</span> orderCount)</span>;<br><br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>example/Order.java</code>中添加订单实体类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> java.io.Serializable;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Order</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Serializable</span> &#123;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * order id.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">long</span> id;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * user id.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> String userId;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * commodity code.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> String commodityCode;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * count.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">int</span> count;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * money.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">int</span> money;<br><br><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-string">&quot;Order&#123;&quot;</span> + <span class="hljs-string">&quot;id=&quot;</span> + id + <span class="hljs-string">&quot;, userId=&#x27;&quot;</span> + userId + <span class="hljs-string">&#x27;\&#x27;&#x27;</span> + <span class="hljs-string">&quot;, commodityCode=&#x27;&quot;</span><br>+ commodityCode + <span class="hljs-string">&#x27;\&#x27;&#x27;</span> + <span class="hljs-string">&quot;, count=&quot;</span> + count + <span class="hljs-string">&quot;, money=&quot;</span> + money + <span class="hljs-string">&#x27;&#125;&#x27;</span>;<br>&#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>example/OrderController.java</code>中，rest方法用RestTemplate直接通过URL调用其它模块；feign方法结合Nacos服务发现，通过FeignClient调用其它模块。测试两种方式下的Seata全局事务：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> com.example.SeataBusinessApplication.OrderService;<br><span class="hljs-keyword">import</span> com.example.SeataBusinessApplication.StorageService;<br><span class="hljs-keyword">import</span> io.seata.spring.annotation.GlobalTransactional;<br><span class="hljs-keyword">import</span> org.slf4j.Logger;<br><span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;<br><span class="hljs-keyword">import</span> org.springframework.http.HttpEntity;<br><span class="hljs-keyword">import</span> org.springframework.http.HttpHeaders;<br><span class="hljs-keyword">import</span> org.springframework.http.MediaType;<br><span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;<br><span class="hljs-keyword">import</span> org.springframework.util.LinkedMultiValueMap;<br><span class="hljs-keyword">import</span> org.springframework.util.MultiValueMap;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><span class="hljs-keyword">import</span> org.springframework.web.client.RestTemplate;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HomeController</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">LOGGER</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(HomeController.class);<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">SUCCESS</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;SUCCESS&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">FAIL</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;FAIL&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">USER_ID</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;U100001&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">COMMODITY_CODE</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;C00321&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-variable">ORDER_COUNT</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> RestTemplate restTemplate;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> OrderService orderService;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> StorageService storageService;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">HomeController</span><span class="hljs-params">(RestTemplate restTemplate, OrderService orderService,</span><br><span class="hljs-params">                          StorageService storageService)</span> &#123;<br>        <span class="hljs-built_in">this</span>.restTemplate = restTemplate;<br>        <span class="hljs-built_in">this</span>.orderService = orderService;<br>        <span class="hljs-built_in">this</span>.storageService = storageService;<br>    &#125;<br><br>    <span class="hljs-meta">@GlobalTransactional(timeoutMills = 300000, name = &quot;spring-cloud-demo-tx&quot;)</span><br>    <span class="hljs-meta">@GetMapping(value = &quot;/seata/rest&quot;, produces = &quot;application/json&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">rest</span><span class="hljs-params">()</span> &#123;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> restTemplate.getForObject(<br>                <span class="hljs-string">&quot;http://127.0.0.1:18092/storage/&quot;</span> + COMMODITY_CODE + <span class="hljs-string">&quot;/&quot;</span> + ORDER_COUNT,<br>                String.class);<br><br>        <span class="hljs-keyword">if</span> (!SUCCESS.equals(result)) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>();<br>        &#125;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;http://127.0.0.1:18093/order&quot;</span>;<br>        <span class="hljs-type">HttpHeaders</span> <span class="hljs-variable">headers</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HttpHeaders</span>();<br>        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);<br><br>        MultiValueMap&lt;String, String&gt; map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedMultiValueMap</span>&lt;String, String&gt;();<br>        map.add(<span class="hljs-string">&quot;userId&quot;</span>, USER_ID);<br>        map.add(<span class="hljs-string">&quot;commodityCode&quot;</span>, COMMODITY_CODE);<br>        map.add(<span class="hljs-string">&quot;orderCount&quot;</span>, ORDER_COUNT + <span class="hljs-string">&quot;&quot;</span>);<br><br>        HttpEntity&lt;MultiValueMap&lt;String, String&gt;&gt; request = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HttpEntity</span>&lt;MultiValueMap&lt;String, String&gt;&gt;(<br>                map, headers);<br><br>        ResponseEntity&lt;String&gt; response;<br>        <span class="hljs-keyword">try</span> &#123;<br>            response = restTemplate.postForEntity(url, request, String.class);<br>        &#125; <span class="hljs-keyword">catch</span> (Exception exx) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">&quot;mock error&quot;</span>);<br>        &#125;<br>        result = response.getBody();<br>        <span class="hljs-keyword">if</span> (!SUCCESS.equals(result)) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>();<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> SUCCESS;<br>    &#125;<br><br>    <span class="hljs-meta">@GlobalTransactional(timeoutMills = 300000, name = &quot;spring-cloud-demo-tx&quot;)</span><br>    <span class="hljs-meta">@GetMapping(value = &quot;/seata/feign&quot;, produces = &quot;application/json&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">feign</span><span class="hljs-params">()</span> &#123;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> storageService.storage(COMMODITY_CODE, ORDER_COUNT);<br><br>        <span class="hljs-keyword">if</span> (!SUCCESS.equals(result)) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>();<br>        &#125;<br><br>        result = orderService.order(USER_ID, COMMODITY_CODE, ORDER_COUNT);<br><br>        <span class="hljs-keyword">if</span> (!SUCCESS.equals(result)) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>();<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> SUCCESS;<br><br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>SeataAccount</code></p><p>在子模块SeataAccount中添加依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SeataAccount<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">exclusions</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">exclusion</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>dubbo-filter-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.dubbo.extensions<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">exclusion</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">exclusions</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>log4j<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>log4j<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.2.17<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在<code>resources/application.yml</code>中添加配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">base:</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">mdb:</span><br>      <span class="hljs-attr">hostname:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> <span class="hljs-comment">#your mysql server ip address</span><br>      <span class="hljs-attr">dbname:</span> <span class="hljs-string">seata</span> <span class="hljs-comment">#your database name for test</span><br>      <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span> <span class="hljs-comment">#your mysql server listening port</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;root&#x27;</span> <span class="hljs-comment">#your mysql server username</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;2469&#x27;</span> <span class="hljs-comment">#your mysql server password</span><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18094</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>        <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>        <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">account-service</span><br>  <span class="hljs-attr">main:</span><br>    <span class="hljs-attr">allow-bean-definition-overriding:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">datasource:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">storageDataSource</span><br>    <span class="hljs-attr">driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:mysql://$&#123;base.config.mdb.hostname&#125;:$&#123;base.config.mdb.port&#125;/$&#123;base.config.mdb.dbname&#125;?useSSL=false&amp;serverTimezone=UTC</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">$&#123;base.config.mdb.username&#125;</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">$&#123;base.config.mdb.password&#125;</span><br><br><span class="hljs-attr">seata:</span><br>  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">application-id:</span> <span class="hljs-string">$&#123;spring.application.name&#125;</span><br><br>  <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">$&#123;spring.application.name&#125;-tx-group</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">serverAddr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">dataId:</span> <span class="hljs-string">&quot;seata.properties&quot;</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br>  <span class="hljs-attr">registry:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">&#x27;nacos&#x27;</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">SEATA_GROUP</span><br></code></pre></td></tr></table></figure><p>在<code>example/config/DatabaseConfiguration.java</code>中配置jdbc：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example.config;<br><br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;<br><br><span class="hljs-keyword">import</span> javax.sql.DataSource;<br><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DatabaseConfiguration</span> &#123;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> JdbcTemplate <span class="hljs-title function_">jdbcTemplate</span><span class="hljs-params">(DataSource dataSource)</span> &#123;<br>        <span class="hljs-type">JdbcTemplate</span> <span class="hljs-variable">jdbcTemplate</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JdbcTemplate</span>(dataSource);<br><br>        jdbcTemplate.update(<span class="hljs-string">&quot;delete from account_tbl where user_id = &#x27;U100001&#x27;&quot;</span>);<br>        jdbcTemplate.update(<br>                <span class="hljs-string">&quot;insert into account_tbl(user_id, money) values (&#x27;U100001&#x27;, 10000)&quot;</span>);<br><br>        <span class="hljs-keyword">return</span> jdbcTemplate;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>example/AccountController.java</code>中编写接口，删除对应账号中的金额：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> io.seata.core.context.RootContext;<br><span class="hljs-keyword">import</span> org.slf4j.Logger;<br><span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;<br><span class="hljs-keyword">import</span> org.springframework.jdbc.core.JdbcTemplate;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PostMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><span class="hljs-keyword">import</span> java.util.Random;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AccountController</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">LOGGER</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(AccountController.class);<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">SUCCESS</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;SUCCESS&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">FAIL</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;FAIL&quot;</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-keyword">private</span> Random random;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">AccountController</span><span class="hljs-params">(JdbcTemplate jdbcTemplate)</span> &#123;<br>        <span class="hljs-built_in">this</span>.jdbcTemplate = jdbcTemplate;<br>        <span class="hljs-built_in">this</span>.random = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Random</span>();<br>    &#125;<br><br>    <span class="hljs-meta">@PostMapping(value = &quot;/account&quot;, produces = &quot;application/json&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">account</span><span class="hljs-params">(String userId, <span class="hljs-type">int</span> money)</span> &#123;<br>        LOGGER.info(<span class="hljs-string">&quot;Account Service ... xid: &quot;</span> + RootContext.getXID());<br><br>        <span class="hljs-keyword">if</span> (random.nextBoolean()) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">&quot;this is a mock Exception&quot;</span>);<br>        &#125;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> jdbcTemplate.update(<br>                <span class="hljs-string">&quot;update account_tbl set money = money - ? where user_id = ?&quot;</span>,<br>                money, userId);<br>        LOGGER.info(<span class="hljs-string">&quot;Account Service End ... &quot;</span>);<br>        <span class="hljs-keyword">if</span> (result == <span class="hljs-number">1</span>) &#123;<br>            <span class="hljs-keyword">return</span> SUCCESS;<br>        &#125;<br>        <span class="hljs-keyword">return</span> FAIL;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure></li></ol><h4 id="测试Seata事务"><a href="#测试Seata事务" class="headerlink" title="测试Seata事务"></a>测试Seata事务</h4><ol><li><p>在nacos&#x2F;bin目录下通过cmd命令行运行<code>startup.cmd -m standalone</code>启动Nacos单机模式</p><p>然后运行<code>seata/bin/seata-server.bat</code>文件，启动Seata Server</p></li><li><p>在Nacos控制台创建配置信息：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260217145302334-e0f126389912.png" class="image-20260217145302334"></li><li><p>运行<code>SeataAccountApplication.java</code>,<code>SeataOrderApplication.java</code>,<code>SeataStorageApplication.java</code>，最后运行<code>SeataBusinessApplication.java</code></p></li><li><p>浏览器访问<a href="http://127.0.0.1:18091/seata/feign%E6%88%96%E8%80%85http://127.0.0.1:18091/seata/rest%EF%BC%8C%E9%AA%8C%E8%AF%81%EF%BC%9A">http://127.0.0.1:18091/seata/feign或者http://127.0.0.1:18091/seata/rest，验证：</a></p><ul><li>account-server、order-service 和 storage-service 三个服务打印的xid都相同，说明在一个事务中</li><li>数据库中账户金额、货物数量、订单数据，数据前后一致</li><li>当报错时，所有服务都回滚，数据库数据不变</li></ul></li></ol><h2 id="限流降级：Sentinel"><a href="#限流降级：Sentinel" class="headerlink" title="限流降级：Sentinel"></a>限流降级：Sentinel</h2><h3 id="限流降级是什么？"><a href="#限流降级是什么？" class="headerlink" title="限流降级是什么？"></a>限流降级是什么？</h3><p>在一个微服务调用链中，A调用B，B调用C，C调用D：<em>A-&gt;B-&gt;C-&gt;D</em></p><p>如果服务D出现问题，变得卡顿迟缓，就会造成服务C对服务D的请求大量堆积，迟迟得不到返回值。服务C的大量资源耗费在苦苦等待服务D上面，就没有足够的资源处理服务B的请求，造成服务A对服务B的请求大量堆积，……</p><p>一个调用链上的某个服务出现问题，就可能会导致整个调用链上的所有服务出现问题，造成雪崩现象。</p><p>如果我们能够在服务D出问题时，一方面控制服务D不再接收过多的请求，保留有限的资源去自我修复；一方面限制服务C不再发送过多的请求给服务D，避免服务C的资源浪费。那么就可以在一定程度上缓解雪崩问题。这种方法我们称之为<strong>限流</strong>。</p><p>如果我们能够在服务D出问题时，让服务D不再对每个请求都正常处理（或者让服务C不再请求服务D），而是对部分甚至全部请求只进行简单处理，就快速返回（例如：直接返回一个“网络卡顿，请稍候重试”），那么就不会造成服务C对服务D的请求大量堆积，导致服务C的资源浪费，还能够让服务D保存有限的资源用于更重要的任务。这种方法我们称之为<strong>降级</strong>。</p><p>Spring Cloud Alibaba通过<strong>Sentinel</strong>来实现限流降级，其以流量为切入点，从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。</p><h3 id="Sentinel实战"><a href="#Sentinel实战" class="headerlink" title="Sentinel实战"></a>Sentinel实战</h3><ol><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>SentinelDemo</code></p><p>为子模块SentinelDemo添加依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SentinelDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-sentinel<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>配置<code>resources/application.yml</code>：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">sentinel-demo</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">18097</span><br></code></pre></td></tr></table></figure></li><li><p>编写<code>example/SentinelConfig.java</code>，配置Sentinel限流规则：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> com.alibaba.csp.sentinel.slots.block.RuleConstant;<br><span class="hljs-keyword">import</span> com.alibaba.csp.sentinel.slots.block.flow.FlowRule;<br><span class="hljs-keyword">import</span> com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;<br><span class="hljs-keyword">import</span> jakarta.annotation.PostConstruct;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;<br><br><span class="hljs-keyword">import</span> java.util.ArrayList;<br><span class="hljs-keyword">import</span> java.util.List;<br><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SentinelConfig</span> &#123;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initFlowRules</span><span class="hljs-params">()</span> &#123;<br>        List&lt;FlowRule&gt; rules = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>        <span class="hljs-type">FlowRule</span> <span class="hljs-variable">rule</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FlowRule</span>();<br>        rule.setResource(<span class="hljs-string">&quot;HelloWorld&quot;</span>);  <span class="hljs-comment">// 与 @SentinelResource 的 value 一致</span><br>        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);<br>        rule.setCount(<span class="hljs-number">1</span>);  <span class="hljs-comment">// QPS 阈值为 1</span><br>        rules.add(rule);<br>        FlowRuleManager.loadRules(rules);<br>        System.out.println(<span class="hljs-string">&quot;Sentinel 限流规则已加载&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>编写<code>example/HelloService.java</code>，配置Sentinel资源：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> com.alibaba.csp.sentinel.annotation.SentinelResource;<br><span class="hljs-keyword">import</span> com.alibaba.csp.sentinel.slots.block.BlockException;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Service;<br><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HelloService</span> &#123;<br><br>    <span class="hljs-comment">// 将方法标记为Sentinel的资源，起名为&quot;HelloWorld&quot;，提供限流降级处理方法handleBlock</span><br>    <span class="hljs-meta">@SentinelResource(value = &quot;HelloWorld&quot;, blockHandler = &quot;handleBlock&quot;)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">helloWorld</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;hello world - &quot;</span> + System.currentTimeMillis());<br>    &#125;<br><br>    <span class="hljs-comment">// 限流后的处理方法（必须与原方法签名一致，加上 BlockException 参数）</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">handleBlock</span><span class="hljs-params">(BlockException ex)</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;被限流了！&quot;</span> + ex.getMessage());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>编写<code>example/TestController.java</code>，提供测试接口：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> jakarta.annotation.Resource;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TestController</span> &#123;<br><br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">private</span> HelloService helloService;<br><br>    <span class="hljs-meta">@GetMapping(&quot;/hello&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">hello</span><span class="hljs-params">()</span> &#123;<br>        helloService.helloWorld();<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;success&quot;</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>启动程序后，浏览器多次访问<a href="http://localhost:18097/hello">http://localhost:18097/hello</a></p><p>可以看到Sentinel起作用了：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260218130602483-44dac07cd3b9.png" class="image-20260218130602483"></li></ol><h2 id="分布式消息：RocketMQ"><a href="#分布式消息：RocketMQ" class="headerlink" title="分布式消息：RocketMQ"></a>分布式消息：RocketMQ</h2><h3 id="分布式消息是什么？"><a href="#分布式消息是什么？" class="headerlink" title="分布式消息是什么？"></a>分布式消息是什么？</h3><p>在一个分布式系统中，多个微服务之间可能需要复杂的数据交互，数据有可能在传输过程中丢失、延迟、乱序等问题。如果每个微服务都需要自己处理数据传输的问题，还要与接收方统一数据格式，那么，重复性的代码就会过多，而且微服务间的耦合性也会变高。完全可以有一个独立的中间件，提供发送与接收数据的功能。</p><p>**<a href="https://rocketmq.apache.org/">RocketMQ</a>**就是这样一个工具，他是一款开源的分布式消息系统。可以保证严格的消息顺序，提供丰富的消息拉取模式，高效的订阅者水平扩展能力，实时的消息订阅机制，亿级消息堆积能力。</p><h3 id="RocketMQ实战"><a href="#RocketMQ实战" class="headerlink" title="RocketMQ实战"></a>RocketMQ实战</h3><h4 id="安装RocketMQ"><a href="#安装RocketMQ" class="headerlink" title="安装RocketMQ"></a>安装RocketMQ</h4><ol><li><p>在<a href="https://rocketmq.apache.org/download">RocketMQ官网</a>下载RocketMQ 5.4.0版本二进制压缩包：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260306144335695-4c5db1fcc7dc.png" class="image-20260306144335695"></li><li><p>推荐在JDK&gt;17下运行RocketMQ</p></li><li><p>在rocketmq-all-5.4.0-bin-release安装目录下，通过cmd命令<code> .\bin\mqnamesrv.cmd</code>启动Name Server：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260306144422042-ecb1e05bb9e5.png" class="image-20260306144422042"></li><li><p>在rocketmq-all-5.4.0-bin-release安装目录下，通过cmd命令<code> .\bin\mqbroker.cmd -n localhost:9876</code>启动Broker：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260306133740518-e6ac2fbb63a6.png" class="image-20260306133740518"></li><li><p>在rocketmq-all-5.4.0-bin-release安装目录下，通过cmd命令<code> .\bin\mqadmin.cmd updateTopic -n localhost:9876 -c DefaultCluster -t broadcast</code>创建一个主题：</p><img src="/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/image-20260306144844830-7c4e7de9df49.png" class="image-20260306144844830"></li></ol><h4 id="广播消费"><a href="#广播消费" class="headerlink" title="广播消费"></a>广播消费</h4><ol><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>RocketMQProducer</code></p><p>在RocketMQProducer中引入依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>RocketMQProducer<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-stream-rocketmq<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在<code>resources/application.yml</code>中配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">28085</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">rocketmq-producer</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">stream:</span><br>      <span class="hljs-attr">rocketmq:</span><br>        <span class="hljs-attr">binder:</span><br>          <span class="hljs-attr">name-server:</span> <span class="hljs-string">localhost:9876</span><br>        <span class="hljs-attr">bindings:</span><br>          <span class="hljs-attr">producer-out-0:</span><br>            <span class="hljs-attr">producer:</span><br>              <span class="hljs-attr">group:</span> <span class="hljs-string">output_1</span><br>      <span class="hljs-attr">bindings:</span><br>        <span class="hljs-attr">producer-out-0:</span><br>          <span class="hljs-attr">destination:</span> <span class="hljs-string">broadcast</span><br></code></pre></td></tr></table></figure></li><li><p>在<code>com/example/RocketMQProducerApplication.java</code>中发送100条消息：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.apache.rocketmq.common.message.MessageConst;<br><span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;<br><span class="hljs-keyword">import</span> org.springframework.boot.ApplicationRunner;<br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.cloud.stream.function.StreamBridge;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.messaging.Message;<br><span class="hljs-keyword">import</span> org.springframework.messaging.support.GenericMessage;<br><br><span class="hljs-keyword">import</span> java.util.HashMap;<br><span class="hljs-keyword">import</span> java.util.Map;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RocketMQProducerApplication</span> &#123;<br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> StreamBridge streamBridge;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(RocketMQProducerApplication.class, args);<br>    &#125;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> ApplicationRunner <span class="hljs-title function_">producer</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> args -&gt; &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">100</span>; i++) &#123;<br>                <span class="hljs-type">String</span> <span class="hljs-variable">key</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;KEY&quot;</span> + i;<br>                Map&lt;String, Object&gt; headers = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();<br>                headers.put(MessageConst.PROPERTY_KEYS, key);<br>                headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);<br>                Message&lt;String&gt; msg = <span class="hljs-keyword">new</span> <span class="hljs-title class_">GenericMessage</span>&lt;&gt;(<span class="hljs-string">&quot;Hello RocketMQ &quot;</span> + i, headers);<br>                streamBridge.send(<span class="hljs-string">&quot;producer-out-0&quot;</span>, msg);<br>            &#125;<br>        &#125;;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>RocketMQConsumer1</code></p><p>在RocketMQConsumer1中引入依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>RocketMQConsumer1<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-stream-rocketmq<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在<code>resources/application.yml</code>中配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">28084</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">rocketmq-consumer1</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">stream:</span><br>      <span class="hljs-attr">function:</span><br>        <span class="hljs-attr">definition:</span> <span class="hljs-string">consumer</span><br>      <span class="hljs-attr">rocketmq:</span><br>        <span class="hljs-attr">binder:</span><br>          <span class="hljs-attr">name-server:</span> <span class="hljs-string">localhost:9876</span><br>        <span class="hljs-attr">bindings:</span><br>          <span class="hljs-attr">consumer-in-0:</span><br>            <span class="hljs-attr">consumer:</span><br>              <span class="hljs-attr">messageModel:</span> <span class="hljs-string">BROADCASTING</span><br>      <span class="hljs-attr">bindings:</span><br>        <span class="hljs-attr">consumer-in-0:</span><br>          <span class="hljs-attr">destination:</span> <span class="hljs-string">broadcast</span><br>          <span class="hljs-attr">group:</span> <span class="hljs-string">broadcast-consumer</span><br></code></pre></td></tr></table></figure></li><li><p>在<code>com/example/RocketMQConsumer1Application.java</code>中发送接收消息:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.messaging.Message;<br><br><span class="hljs-keyword">import</span> java.util.function.Consumer;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RocketMQConsumer1Application</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(RocketMQConsumer1Application.class, args);<br>    &#125;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> Consumer&lt;Message&lt;String&gt;&gt; <span class="hljs-title function_">consumer</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> msg -&gt; System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot; Consumer1 Receive New Messages: &quot;</span> + msg.getPayload());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在父项目<code>SpringCloudAlibabaDemo</code>下新建子模块<code>RocketMQConsumer2</code></p><p>在RocketMQConsumer2中引入依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>SpringCloudAlibabaDemo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>RocketMQConsumer2<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-stream-rocketmq<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><p>在<code>resources/application.yml</code>中配置：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">28083</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">rocketmq-consumer2</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">stream:</span><br>      <span class="hljs-attr">function:</span><br>        <span class="hljs-attr">definition:</span> <span class="hljs-string">consumer</span><br>      <span class="hljs-attr">rocketmq:</span><br>        <span class="hljs-attr">binder:</span><br>          <span class="hljs-attr">name-server:</span> <span class="hljs-string">localhost:9876</span><br>        <span class="hljs-attr">bindings:</span><br>          <span class="hljs-attr">consumer-in-0:</span><br>            <span class="hljs-attr">consumer:</span><br>              <span class="hljs-attr">messageModel:</span> <span class="hljs-string">BROADCASTING</span><br>      <span class="hljs-attr">bindings:</span><br>        <span class="hljs-attr">consumer-in-0:</span><br>          <span class="hljs-attr">destination:</span> <span class="hljs-string">broadcast</span><br>          <span class="hljs-attr">group:</span> <span class="hljs-string">broadcast-consumer</span><br></code></pre></td></tr></table></figure></li><li><p>在<code>com/example/RocketMQConsumer1Application.java</code>中发送接收消息:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.example;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.messaging.Message;<br><br><span class="hljs-keyword">import</span> java.util.function.Consumer;<br><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RocketMQConsumer2Application</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(RocketMQConsumer2Application.class, args);<br>    &#125;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> Consumer&lt;Message&lt;String&gt;&gt; <span class="hljs-title function_">consumer</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> msg -&gt; System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot; Consumer2 Receive New Messages: &quot;</span> + msg.getPayload());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>先启动RocketMQCosumer1和RocketMQCosumer2，再启动RocketMQProducer</p><p>可以看到两个Consumer的控制台中都打印出了消息。</p></li></ol>]]>
    </content>
    <id>https://www.wananhome.site/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/</id>
    <link href="https://www.wananhome.site/2026/03/06/Spring-Cloud-Alibaba-%E5%85%A5%E9%97%A8%E7%BA%A7%E5%AE%9E%E8%B7%B5/"/>
    <published>2026-03-05T16:00:00.000Z</published>
    <summary>文章由本人阅读Spring Cloud Alibaba官方文档时总结而成，一切以官方描述为准。</summary>
    <title>Spring Cloud Alibaba 入门级实践</title>
    <updated>2026-03-21T09:49:15.196Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="经济" scheme="https://www.wananhome.site/categories/%E7%BB%8F%E6%B5%8E/"/>
    <category term="经济" scheme="https://www.wananhome.site/tags/%E7%BB%8F%E6%B5%8E/"/>
    <content>
      <![CDATA[<blockquote><p>参考视频：<a href="https://youtu.be/rFV7wdEX-Mo?si=Y__akUYgDimlGh1j">YouTube-经济机器是怎样运行的</a> 作者: 瑞·达利奥 (Ray Dalio)</p><p>本文观点只是我个人的简单想法，请辩证看待，欢迎指正！</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>曾经我在QQ空间里看见了某位朋友发的一条<em>说说</em>：<strong>谁能告诉我在钱数不变的情况下，经济是怎么发展起来的？</strong></p><p>问题可以简化为：<strong>经济发展意味着市面上的钱变多了，那钱是如何变多的呢？</strong></p><p>想要钱变多，那直接疯狂印钱就行了，但是不合理的印钱只会让通货膨胀，导致经济崩溃，所以，一定有个内在<strong>规则</strong>，在合适的时候，也就是市场需要更多的钱的时候再去印适量的钱，才能促进经济发展。</p><p>那么这个<strong>规则</strong>是什么呢？让我们来浅谈一下。</p><h2 id="信贷"><a href="#信贷" class="headerlink" title="信贷"></a>信贷</h2><p>试想这样一种情况：<em>小王</em>现在手里只有1000元，可是小王想为女朋友买一个价值2000元的口红，如果非买不可的话，能让小王合法的，快速的得到1000元的方法就只有<strong>借贷</strong>（向亲人索要，变卖财产等特殊方法没有一般意义）。</p><p>何为<strong>借贷</strong>？</p><p>什么情况下你会借给别人（除了亲朋好友）钱？当你相信他会按约还钱的时候，往往还有附加的利息。这里，你因为相信别人会按约还钱，所以借钱给了他，这其实也是一笔<strong>交易</strong>。别人用<strong>信用</strong>向你换了<strong>钱</strong>，我们称之为<strong>借贷</strong>，通过借贷就产生了<strong>信贷</strong>。</p><p>注意，<strong>借贷</strong>是动词，指<strong>借钱</strong>的动作，借贷的结果是，借款方获得了一笔钱，被借款方获得了<strong>信贷</strong>，信贷是名词，如果以后借款方会按约还钱并支付<em>利息</em>，<strong>信贷</strong>就代表的是那部分<em>利息</em>的价值。</p><h2 id="银行"><a href="#银行" class="headerlink" title="银行"></a>银行</h2><p>现在我们知道，<strong>信贷</strong>是具有<em>价值</em>，有价值就可以用来交易，可问题是：你因为某些原因相信借款方会按约还钱并支付利息，但是其他人不相信啊！这样，你与借款方所产生的<strong>信贷</strong>就只在你二人间具有价值，在其他人那里不具备价值。</p><p>如何让<strong>信贷</strong>可以广泛的交易？</p><p>问题关键在于如何让大家都相信这个<strong>信贷</strong>具备价值，而这个信贷的价值所代表的是借款方未来会按约支付的<strong>利息</strong>，也就是说，需要让大家都相信这个借款方能够在未来按约还钱并支付利息。</p><p>可是，让所有人都认识一下这个借款方，显然不可能，我们就需要一个**“中间人”<strong>，大家都信任这个“中间人”，并且都只向这个“中间人”借贷，那么借贷所产生的</strong>信贷**的价值，就会被所有人认可，信贷就可以在市场上流通并交易了，也就变成了“钱”，市场上的钱也就变多了。</p><p>不难想到，<strong>银行</strong>天生就适合做这个**”中间人“<strong>，银行具有充足的钱出借，并且一般会与政府合作，得到较高</strong>公信力<strong>的同时颇具</strong>“执行力”**。</p><h2 id="杠杆"><a href="#杠杆" class="headerlink" title="杠杆"></a>杠杆</h2><p>回到一开始的情况，<strong>小王</strong>向银行借了1000元并且约定一个月后偿还包含利息的1100元，小王凑够2000元后给女朋友送了她心爱的口红，女朋友很开心，晚上女朋友狠狠地“奖励”了小王，小王也很开心。可是小王一个月只能赚1000元，第二个月赚的钱全部偿还给银行后还差100元，小王便想再借100元来“拆东墙补西墙”，可这时因为小王违反了与银行借贷时签订的条约，银行不再信任小王了，小王无法再借贷100元了。最终小王受到了法律的制裁！</p><p>而<strong>小李</strong>不一样，小李虽然月薪也只有1000元，但小李积极进取，想要提升自己，于是小李也向银行借了1000元并且约定一个月后偿还包含利息的1100元。但小李并没有女朋友，于是将凑够的2000元全部用来给自己买书、升级设备、锻炼身体等等，最后小李因为自身实力的提升，将月薪提到了3000元！</p><p>后面，<strong>小李</strong>因为按时偿还债务，并且提高了月薪，所以银行提升了小李的信用等级，小李可以借更多的钱，用更多的钱来提升自己的实力，然后可以赚到更多的钱，……..，形成一个正向循环。</p><p>通过较少的钱来撬动较多的钱就要用到<strong>杠杆</strong>，向<strong>银行借贷</strong>只是杠杆的一种方式，更显而易见的是<strong>炒股</strong>，企业许诺你现在购买它们的股份，以后会返还你更多的钱，本质上也是<strong>企业</strong>在向你<strong>借贷</strong>。</p><p>放眼整个社会，想要经济发展，就一定会通过各种方法进行<strong>杠杆</strong>，理想情况下，会像<strong>小李</strong>一样稳中向好，迎来经济的大发展。但往往事与愿违，如果通过<strong>杠杆</strong>撬动了太多的钱，就会像<strong>小王</strong>一样，陷入经济衰败的怪圈：</p><p>大家赚到的大部分钱都要用来偿还用杠杆所撬动的大量的钱，大家只有少部分的钱可以用来购物，导致企业能赚到的也钱越来越少，企业就会降低工资，同时裁员，导致大家赚到的钱更加少…….</p><p>同时，社会的公信力大幅下降，银行不相信大家能偿还更多的债务，人们借不到钱，只能变卖家产，经济形势不好，导致人们必须要低价贱卖家产才能卖出去。人们发现自己拥有的大部分钱财都要用来偿还因为杠杆所撬动的大量资金，就好像自己的大部分钱都是虚假的，如同泡沫一般！其实这种情况，就是被称之为<strong>经济泡沫</strong>。</p><h2 id="去杠杆"><a href="#去杠杆" class="headerlink" title="去杠杆"></a>去杠杆</h2><p>社会的经济发展不可能永远像<strong>小李</strong>一样好，当经济开始像<strong>小王</strong>一样变坏时，就需要采取行动来解决，也就是<strong>去杠杆</strong>。</p><p>不难发现，<strong>经济泡沫</strong>的产生，就是因为通过<strong>杠杆</strong>产生了大量的<strong>信贷</strong>，又因为信贷具有<strong>价值</strong>，所以让社会上多了很多**”钱“<strong>，看起来经济发展很好，一旦</strong>杠杆过大**就会产生经济泡沫，爆发金融危机。</p><p>既然问题在于<strong>信贷</strong>过多，大家无力偿还，那就很容易想到解决办法了（往往是多个方法结合使用）：</p><h3 id="1-消除债务"><a href="#1-消除债务" class="headerlink" title="1.消除债务"></a>1.消除债务</h3><p>欠钱太多？无力偿还？那就…….不还了把！</p><p>这种方法当然不是破罐子破摔，而是在合理的范围内适当的取消部分债务，让本就比较有钱的银行吃点亏，也不是不可以。</p><p>好处是，可以极速的去杠杆，同时还能坑一波富人，降低贫富差距。</p><p>坏处是，不利于市场的健康发展，毕竟这种“掀桌子”的事，伤敌一千，自损八百。</p><h3 id="2-资源再分配"><a href="#2-资源再分配" class="headerlink" title="2.资源再分配"></a>2.资源再分配</h3><p>这个方法比第一个更狠，虽然说经济形式不好，但是富人因为基础比较大，所以富人还是比一般人要有钱的多，所以只需要将富人的钱分出来一部分给大家就好了。</p><p>好处是，可以快速的去杠杆，同时还能坑一波富人，降低贫富差距。</p><p>坏处是，难度太大，不利于国家的稳定。</p><h3 id="3-国债"><a href="#3-国债" class="headerlink" title="3.国债"></a>3.国债</h3><p>想要让经济从**“小王”<strong>变成</strong>“小李”<strong>，就要让小王能够借到钱，并且把钱用在提高自身上。可是，银行已经不相信小王了，银行也不愿意承担这份会破产的风险，这时，就需要一个愿意相信小王，又能被银行相信的</strong>“第三者”**来向银行借钱，然后让小王“正确”的使用。</p><p>显而易见，这个**“第三者”<strong>就是</strong>国家政府**，<strong>政府</strong>不能不相信自己的公民，不然就“玩完”了。政府所具备的极高的公信力可以让银行（或者说是<strong>中央银行</strong>）愿意借钱（一般也没得选），形式一般是：</p><p>政府会发行<strong>国债</strong>，银行进行购买（一般需要先印钱），政府也就得（借）到了大量的钱，然后政府会通过社会福利、基建、补贴等方式让这笔钱流入市场。大家手里的钱变多了，就可以开始从“小王”变成“小李”了！</p><p>好处是，可以较稳定的去杠杆。</p><p>坏处是，钱太多了容易造成通货膨胀，不好把握。</p>]]>
    </content>
    <id>https://www.wananhome.site/2026/02/01/%E6%B5%85%E8%B0%88%E2%80%9C%E7%BB%8F%E6%B5%8E%E6%9C%BA%E5%99%A8%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84%EF%BC%9F%E2%80%9D/</id>
    <link href="https://www.wananhome.site/2026/02/01/%E6%B5%85%E8%B0%88%E2%80%9C%E7%BB%8F%E6%B5%8E%E6%9C%BA%E5%99%A8%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84%EF%BC%9F%E2%80%9D/"/>
    <published>2026-01-31T16:00:00.000Z</published>
    <summary>在钱数不变的情况下，经济是怎么发展起来的？</summary>
    <title>浅谈“经济机器是怎样运行的？”</title>
    <updated>2026-03-21T09:16:15.505Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="RocketMQ" scheme="https://www.wananhome.site/categories/RocketMQ/"/>
    <category term="RocketMQ" scheme="https://www.wananhome.site/tags/RocketMQ/"/>
    <content>
      <![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p><a href="https://rocketmq.apache.org/">RocketMQ</a>是阿里巴巴开源，后来捐赠给Apache基金会的一款<strong>分布式消息中间件</strong>。</p><p>因其架构简单、业务功能丰富、具备极强可扩展性等特点被众多企业开发者以及云厂商广泛采用。历经十余年的大规模场景打磨，RocketMQ 已经成为业内共识的金融级可靠业务消息首选方案，被广泛应用于互联网、大数据、移动互联网、物联网等领域的业务场景。</p><h2 id="领域模型"><a href="#领域模型" class="headerlink" title="领域模型"></a>领域模型</h2><p>RocketMQ使用异步通信方式，以及发布订阅的消息传输模型。</p><p>模型图（源自官网）：</p><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQ-3f7970c7e582.png" class="alt text"><p>如上图所示，RocketMQ的领域模型主要分为三部分：消息生产、消息存储、消息消费。</p><p>生产者将消息发送到RocketMQ的服务端，消息被存储在服务端的主题中，消费者通过订阅主题来消费消息。</p><h3 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h3><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>主题是RocketMQ中消息传输和存储的最外层容器（顶层容器），用于区分不同类业务逻辑的消息。主要作用：</p><ul><li>隔离不同类数据：将不同类的数据发送到不同的主题中，就可以实现存储的隔离性、订阅的隔离性。</li><li>标识数据的身份及权限：消息本身是匿名的，可以将不同身份的消息发送到特定的主题中，就可以区分出不同消息的身份以进行权限管理。</li></ul><p>主题实际是一个逻辑概念，每个主题内可以由多个队列，消息的存储是由队列来实际完成的。</p><h4 id="内部属性"><a href="#内部属性" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li><p>主题名称：由用户创建主题时定义，用于标识主题，集群内全局唯一。</p></li><li><p>队列列表：创建主题时可以定义队列的数量，系统会根据队列数量给主题分配队列，一个主题至少包含一个队列。</p></li><li><p>消息类型：一个主题只能接收一类消息：</p><p>Normal：普通消息，没有特殊用途，无需特殊处理的消息。</p><p>FIFO：顺序消息，可以保证消息的投递顺序严格按照消息的发送顺序（消费顺序应由消费者自己保证）。</p><p>Delay：定时&#x2F;延时消息，消息发送后并不立即投递，在一定时间后才投递。</p><p>Transaction：事务消息，用于保障分布式事务一致性。</p></li></ul><h3 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h3><h4 id="定义-1"><a href="#定义-1" class="headerlink" title="定义"></a>定义</h4><p>队列是主题内的次级容器，是消息的最小存储单元，也是消息传输和存储的实际物理容器。主要作用：</p><ul><li>顺序性：同一队列的消息天然按照其存入的顺序保存。</li><li>流式操作：队列可以通过索引访问任意位置的消息，可以实现聚合读取、回溯读取等操作。</li></ul><h4 id="内部属性-1"><a href="#内部属性-1" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li>读写权限：可以控制队列是否可以读写数据（读写、只读、只写、不可读写）。</li></ul><h3 id="消息"><a href="#消息" class="headerlink" title="消息"></a>消息</h3><h4 id="定义-2"><a href="#定义-2" class="headerlink" title="定义"></a>定义</h4><p>消息是RocketMQ中的最小数据传输单元。主要特点：</p><ul><li>不可变性：消息构建后，不可被修改。</li><li>持久化：RocketMQ默认会对消息进行持久化，以保证可回溯性和可恢复性。</li></ul><h4 id="内部属性-2"><a href="#内部属性-2" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li>主题名称：该消息所属的主题的名称，主题名称集群内全局唯一。</li><li>消息类型：当前消息的类型（Normal、FIFO、Delay、Transaction）。</li><li>消息队列：该消息所属的主题内的队列。</li><li>消息位点：该消息在队列中的位置（0~Long.Max）。</li><li>消息ID：消息的集群内唯一标识，系统自动生成的由数字和大写字母组成的32位字符串。</li><li>索引Key（可选）：可以给消息设置不同的Key，用于区分不同消息，以及快速查找消息。</li><li>标签Tag（可选）：消费者可以通过标签Tag来过滤消息，一条消息最多一个Tag。</li><li>定时&#x2F;延时时间（可选）：若为定时&#x2F;延时消息，则需要为消息设置定时&#x2F;延时时间，最大为40天。</li><li>消息发送时间：生产者客户端系统自动填充的系统本地毫秒级时间戳。</li><li>消息保存时间：服务端完成消息存储时，服务端系统自动填充的系统本地毫秒级时间戳。</li><li>消费重试次数：消息消费失败后，服务端系统会自动记录消息的重新投递次数。</li><li>自定义属性：生产者可以自定义字符串键值对属性。</li><li>消息负载：消息的实际业务数据，生产者负责编码，会按照二进制字节传输。</li></ul><h4 id="约束"><a href="#约束" class="headerlink" title="约束"></a>约束</h4><p>消息必须为对应主题指定的消息类型。</p><p>不得超过消息类型的最大限制：Normal&#x2F;FIFO:4MB ，Delay&#x2F;Transaction:64KB</p><h3 id="生产者"><a href="#生产者" class="headerlink" title="生产者"></a>生产者</h3><h4 id="定义-3"><a href="#定义-3" class="headerlink" title="定义"></a>定义</h4><p>生产者是构建消息并将消息发送到服务端的运行实体。</p><p>生产者和主题是多对多的关系，可以据此实现生产者的水平扩展和容灾。</p><h4 id="内部属性-3"><a href="#内部属性-3" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li><p>客户端ID：由RocketMQ自动生成，用于区分不同的生产者，集群内全局唯一，无法修改。</p></li><li><p>通信参数：</p><p>接入点信息（必填）：要连接的服务端的地址。</p><p>身份认证信息（可选）：若服务端开启身份识别和认证，需要传输用于身份认证的凭证信息。</p><p>请求超时时间（可选）：网络请求调用的超时时间。</p></li><li><p>预绑定主题列表：对于Transaction消息，必须要预绑定主题列表，因为生产者在故障、重启恢复时，需要检查事务消息的主题中是否有未提交的事务消息。对于非Transaction消息，可以根据后续发消息的目标主题动态变更。</p></li><li><p>事务检查器：在事务消息机制中，生产者需要提供事务检查器，用于返回业务事务是否成功。</p></li><li><p>发送重试策略：生产者在消息发送失败时的重试策略。</p></li></ul><h3 id="消费者分组"><a href="#消费者分组" class="headerlink" title="消费者分组"></a>消费者分组</h3><h4 id="定义-4"><a href="#定义-4" class="headerlink" title="定义"></a>定义</h4><p>消费者分组是承载多个消费行为一致的消费者的负载均衡分组。</p><p>消费者分组不是一个运行实体，而是一个逻辑资源。通过消费者分组内初始化多个消费者实现消费能力的水平扩展和高可用容灾。</p><p>同一消费者分组下的消费者安装统一的消费行为和负载均衡策略消费消息。</p><h4 id="内部属性-4"><a href="#内部属性-4" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li>消费者分组名称：消费者分组的名称，用于区分不同的消费者分组，集群内全局唯一。</li><li>投递顺序：RocketMQ向消费者客户端投递消息的顺序：并发投递（默认）、顺序投递。</li><li>消费重试策略：消费者消费失败时，系统会根据重试策略（最大重试次数、重试间隔）将消息重新投递给消费者。</li><li>订阅关系：该消费者分组的订阅关系集合。包括订阅的主题、消息过滤规则等。订阅关系由消费者动态注册到消费者分组中，服务端会持久化订阅关系并匹配消息的消费进度。</li></ul><h4 id="约束-1"><a href="#约束-1" class="headerlink" title="约束"></a>约束</h4><p>同一消费者分组下的消费者必须具有一致的消息投递顺序，以及消费重试策略。</p><h3 id="消费者"><a href="#消费者" class="headerlink" title="消费者"></a>消费者</h3><h4 id="定义-5"><a href="#定义-5" class="headerlink" title="定义"></a>定义</h4><p>消费者是RocketMQ中用来接收并处理数据的运行实体，通常被集成在业务系统中。可以定义行为：</p><ul><li>消费者身份：消费者必须关联一个指定的消费者分组，以获取分组内统一定义的行为配置和消费状态。</li><li>消费者类型：多种消费者类型：PushConsumer、SimpleConsumer、PullConsumer。</li><li>消费者本地运行配置：控制消费者客户端本地的运行配置：线程数、并发度等。</li></ul><h4 id="内部属性-5"><a href="#内部属性-5" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li><p>消费者分组名称：消费者关联的消费者分组名称，消费者必须关联一个指定的消费者分组。</p></li><li><p>客户端ID：由RocketMQ自动生成，用于区分不同的消费者，集群内全局唯一，不可修改。</p></li><li><p>通信参数：</p><p>接入点信息（必填）：要连接的服务端地址。</p><p>身份认证信息（可选）：若服务端开启身份识别和认证，需要传输用于身份认证的凭证信息。</p><p>请求超时时间（可选）：网络请求调用的超时时间。</p></li><li><p>预绑定订阅关系列表：消费者订阅的主题列表，可以动态变更。</p></li><li><p>消费监听器：服务端将消息推送给消费者后，监听器会调用消费逻辑。</p></li></ul><h3 id="订阅关系"><a href="#订阅关系" class="headerlink" title="订阅关系"></a>订阅关系</h3><h4 id="定义-6"><a href="#定义-6" class="headerlink" title="定义"></a>定义</h4><p>订阅关系是消费者获取消息、处理消息的规则和状态配置。</p><p>订阅关系由消费者分组动态注册到服务端，并在后续的消息传输中按照订阅关系定义的过滤规则进行消息匹配和消费进度维护。</p><p>订阅关系可以控制如下传输行为：</p><ul><li>消息过滤规则：控制消费者对主题内的哪些消息进行消费。</li><li>消费状态：RocketMQ默认提供订阅关系持久化功能，当消费者离线后再次上线，可以获取到离线前的消费进度并再次消费。</li></ul><h4 id="订阅关系判断原则"><a href="#订阅关系判断原则" class="headerlink" title="订阅关系判断原则"></a>订阅关系判断原则</h4><p>RocketMQ的订阅关系以消费者分组和主题为单位进行设计，一个订阅关系指的是某个消费者分组对某个主题的订阅。</p><p>不同消费者对同一主题的订阅相互独立：</p><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQSubscription1-7e7894518956.png" class="RocketMQSubscription1"><p>同一消费者分组对不同主题的订阅相互独立：</p><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQSubscription2-b54a6e15df86.png" class="RocketMQSubscription2"><h4 id="内部属性-6"><a href="#内部属性-6" class="headerlink" title="内部属性"></a>内部属性</h4><ul><li><p>过滤类型：消息过滤方式的类型：</p><ul><li>TAG过滤：通过Tag标签进行过滤。</li><li>SQL92过滤：通过SQL语法进行过滤。</li></ul></li><li><p>过滤表达式：过滤规则的表达式。</p></li></ul><h2 id="领域模型-1"><a href="#领域模型-1" class="headerlink" title="领域模型"></a>领域模型</h2><h3 id="顺序消息"><a href="#顺序消息" class="headerlink" title="顺序消息"></a>顺序消息</h3><p>顺序消息强调多条消息间的先后顺序关系，可以支持消费者按照发送消息的先后顺序获取消息。</p><p>发送顺序消息时需要为每条消息指定一个消息组，同一消息组内的消息才可以保证先进先出的顺序关系。</p><p>消息生产顺序性的保证：</p><ul><li>单一生产者：消息生产的顺序性仅支持单一生产者，不同生产者间的消息即使在同一消息组也无法保证顺序性。</li><li>串行发送：如果生产者使用多线程并行发送，则不同线程产生的消息无法保证顺序性。</li></ul><p>保证了消息生产的顺序性后，RocketMQ会将同一消息组内的消息按照发送顺序存储在同一个队列中。不同消息组的消息可以存储在同一个队列中，但不保证消息的连续性。</p><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQFIFO-390a0cf2a117.png" class="RocketMQFIFO"><p>消费的顺序性应当由消费者自己保证。</p><p>顺序消息的重试次数应当在合理的范围内，不应长时间阻塞后续消息的消费。</p><h3 id="事务消息"><a href="#事务消息" class="headerlink" title="事务消息"></a>事务消息</h3><p>事务消息通过<strong>二阶段提交</strong>的方式保证了分布式事务的最终一致性。</p><p>事务消息处理流程：</p><ol><li><p>生产者将事务消息发送给RocketMQ服务端。</p></li><li><p>服务端收到消息并持久化存储后返回给生产者ack确认，此时事务消息还暂时不能发送给消费者，这种状态下的事务消息即为半事务消息。</p></li><li><p>生产者开始执行本地事务。</p></li><li><p>生产者根据本地事务执行结果，选择向服务端提交二次确认结果（Commit&#x2F;Rollback）：</p><ul><li>Commit：服务端将半事务消息投递给消费者。</li><li>Rollback：服务端将半事务消息回滚，不发送给消费者。</li></ul></li><li><p>在网络异常等情况下，服务端可能长时间未收到生产者的二次确认结果，或则收到的二次确认结果异常，服务端将会更具设置的回查间隔时间和最大回查次数，对生产者发送回查消息。</p></li><li><p>生产者收到回查消息后，需要检查对应的本地事务执行结果，并向服务端提交二次确认（Commit&#x2F;Rollback）。</p></li></ol><h3 id="消费者分类"><a href="#消费者分类" class="headerlink" title="消费者分类"></a>消费者分类</h3><p>RocketMQ提供了不同的消费者类型，用来满足不同业务场景下的消费需求。</p><blockquote><p>同一消费者分组内，应只包含同一种消费者类型</p></blockquote><h4 id="PushConsumer"><a href="#PushConsumer" class="headerlink" title="PushConsumer"></a>PushConsumer</h4><p>PushConsumer是一种高度封装的消费者类型，只需要在消费者初始化时注册一个监听器，并在监听器内部实现消息处理逻辑，就可以自动化实现消息的获取、触发监听器以及消息重试处理。</p><p>PushConsumer的消费者监听器要返回执行结果：</p><ul><li>ConsumeResult.SUCCESS：代表执行成功，服务端更新消费进度。</li><li>ConsumeResult.FAILURE：代表执行失败，根据消费重试逻辑进行消费重试。</li><li>其它：例如抛出异常、消费超时等情况，按照执行失败的逻辑处理，根据消费重试逻辑进行消费重试。</li></ul><p>PushConsumer的消息实时处理能力是基于Reactor线程模型实现的。通过一个轮询线程将消息异步拉取到缓存队列中，再分别提交到消费线程中，触发监听器调用本地消费逻辑：</p><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQPushConsumer-a468fbab54bb.png" class="RocketMQPushConsumer"><h4 id="SimpleConsumer"><a href="#SimpleConsumer" class="headerlink" title="SimpleConsumer"></a>SimpleConsumer</h4><p>SimpleConsumer是一种自由度较高的消费者类型，需要自定义业务逻辑来调用接口获取消息，然后分发给业务线程处理消息，最后调用提交接口将处理结果返回给服务端。</p><p>相较于PushConsumer：</p><ul><li>SimpleConsumer可以在消费消息时灵活自定义消息的预计处理时长。</li><li>SimpleConsumer内部没有复杂的线程封装，可以自定义实现异步分发、批量消费等。</li><li>SimpleConsumer由业务主动调取接口获取消息，因此可以自定义消息获取频率、消费频率。</li></ul><h3 id="消息过滤"><a href="#消息过滤" class="headerlink" title="消息过滤"></a>消息过滤</h3><p>消费者在订阅了某个主题后，RocketMQ会将该主题中的所有消息都投递给该消费者，消费者可以设置消息过滤规则来避免接收到大量无效消息。</p><p>消息过滤关键流程：</p><ul><li>生产者在初始化消息时预先为消息设置一些属性和标签。</li><li>消费者通过调用订阅关系注册接口，上报自己的过滤规则。</li><li>服务端会根据消费者上报的过滤规则筛选合适的消息发送给消费者。</li></ul><p>消息过滤分类为：</p><ul><li><p>Tag标签过滤：</p><p>生产者在发送消息时，最多为每个消息设置一个Tag标签（建议长度不超过128字符）。<br>消费者在设置订阅关系时，可以配置需要的Tag标签。</p></li><li><p>SQL属性过滤：</p><p>生产者在发送消息时，可以为每个消息设置多个属性（Key-Value）。<br>Tag标签是一种系统属性，所以SQL过滤方式也兼容Tag过滤。</p></li></ul><blockquote><p>具体过滤语法请参考<a href="https://rocketmq.apache.org/zh/docs/featureBehavior/07messagefilter">官方文档</a></p></blockquote><h3 id="消费者负载均衡"><a href="#消费者负载均衡" class="headerlink" title="消费者负载均衡"></a>消费者负载均衡</h3><p>因为RocketMQ支持同一个主题被不同消费者分组订阅，因此，可以根据消费者分组和消费者的不同组合，实现两种消费效果：</p><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQLoadBalancer-83962a676665.png" class="RocketMQLoadBalancer"><ul><li>消费组间广播消费：每个消费者分组只包含一个消费者，每个消费者得到消费者分组内的所有消息，实现广播的效果。</li><li>消费组内共享消费：每个消费者分组内包含多个消费者，多个消费者共同分担消费者分组内的所有消息，实现水平拆分和负载均衡的效果。</li></ul><p>在共享消费模式下，同一个消费者分组内的多个消费者，共同分担消费者分组内的所有消息，但是哪条消息分给哪个消费者，就涉及到了消费者负载均衡的问题。</p><p>消费者负载均衡可以分为两种模式：消息粒度负载均衡，队列粒度负载均衡。</p><h4 id="消息粒度负载均衡"><a href="#消息粒度负载均衡" class="headerlink" title="消息粒度负载均衡"></a>消息粒度负载均衡</h4><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQMessageBalance-368566148ed0.png" class=""><p>如上图所示，在消息粒度负载均衡中，同一消费者分组中的消费者平分主题中的所有消息，主题中的消息将随机平均分配给消费者分组内的所有消费者。</p><p>当消费者获取到某条消息后，RocketMQ服务端会对该条消息加锁，保证该条消息对其他消费者不可见，直到该条消息消费成功或消费超时。</p><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQFIFOBalance-c5f8337d88d3.png" class="RocketMQFIFOBalance"><p>如上图所示，对于同一个消息组内的顺序消息，RocketMQ服务端保证按照服务端存储的先后顺序将消息发送给消费者。当前置消息未消费完成时，将会锁定后置消息，保证同一消费组内的消息被串行消费。</p><p>基于消息粒度的负载均衡，可以保证每个消费者都可以平均分配到消息，而不会出先部分消费者空闲的情况。</p><h4 id="队列粒度负载均衡"><a href="#队列粒度负载均衡" class="headerlink" title="队列粒度负载均衡"></a>队列粒度负载均衡</h4><blockquote><p>图片源自官网</p></blockquote><img src="/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/RocketMQQueueBalance-d4684a72651e.png" class="RocketMQQueueBalance"><p>如上图所示，在队列粒度负载均衡中，主题中的队列会根据统一的算法，分配给消费者分组中特定的消费者。为了避免消息被重复消费，每个队列仅能被一个消费者消费。</p><p>当队列数量大于消费者数量时，会出现部分消费者空闲的情况，因此没有消息粒度负载均衡策略灵活。但是队列粒度负载均衡，在流式处理场景下具有优势，因为可以保证同一队列中的消息被同一个消费者消费，对批量处理，聚合处理更友好。</p><h3 id="消费进度管理"><a href="#消费进度管理" class="headerlink" title="消费进度管理"></a>消费进度管理</h3><p>每条消息在队列中都有一个唯一的Long类型的坐标（0~Long.Max），这个坐标被定义为<strong>消息位点</strong>。</p><p>RocketMQ定义队列中的最早的一条消息位点为MinOffset，最新一条消息的位点为MaxOffset。</p><p>队列在理论上是可以无限延长的，但受限于物理机的空间，RocketMQ会滚动删除队列中最早的消息。因此MinOffset和MaxOffset会一直滚动递增。</p><p>如果RocketMQ在消费者消费完消息后，就删除该消息，那么其它订阅了该主题的消费者将无法消费该消息。因此，消息被消费过后并不是被立即删除，RocketMQ会为每个消费者分组维护一份消费记录，记录消费者分组消费主题中某一队列时消费过的最新一条消息的位点，也就是该消费者分组的<strong>消费位点</strong>。</p><p>当消费者分组重新上线后，就可以根据消费位点继续消费了。</p><p><strong>重置消费位点</strong>：</p><ul><li>如果业务需要消费历史消息，可以将消费位点重置到指定时刻，服务端会自动匹配最近的消费位点。</li><li>如果消息大量堆积，可以更新消费位点以绕过部分消息，减少下游处理的压力。</li><li>如果出现消费错误等异常，导致需要重新消费历史消息，可以将消费位点更新到历史位点，实现消费回溯。</li></ul>]]>
    </content>
    <id>https://www.wananhome.site/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</id>
    <link href="https://www.wananhome.site/2025/10/16/RocketMQ%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    <published>2025-10-15T16:00:00.000Z</published>
    <summary>详细解读RocketMQ入门需要知道的基础知识</summary>
    <title>RocketMQ基础知识</title>
    <updated>2026-03-21T10:10:42.255Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="Java" scheme="https://www.wananhome.site/tags/Java/"/>
    <content>
      <![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Synchronized锁是用来解决多线程并发访问共享数据的安全性问题的，可以保证对共享数据访问的原子性和可见性。</p><p><strong>原子性</strong>：Synchronized可以保证只有同时只有一个线程能够访问共享数据，也就是一个互斥锁，通过这种互斥机制，保证的操作的原子性。</p><p><strong>可见性</strong> ：一个线程在获取到锁时，或强制将线程工作内存中的变量失效，从主内存中重新读取该变量。当线程释放锁时，会将工作内存中的变量刷新回主内存，然后删除工作内存中的变量。可以理解为，Synchronized锁会在操作前重新从主存中读取数据，保证数据是最新的，操作完成后立即刷新回主存中，保证其它线程读取到最新数据，这样就保证了操作的可见性。</p><h2 id="锁的基本使用"><a href="#锁的基本使用" class="headerlink" title="锁的基本使用"></a>锁的基本使用</h2><p>Synchronized可以用于修饰<strong>实例方法</strong>、<strong>静态方法</strong>、<strong>代码块</strong>。</p><h3 id="修饰实例方法"><a href="#修饰实例方法" class="headerlink" title="修饰实例方法"></a>修饰实例方法</h3><p>Synchronized修饰实例方法时，线程进入该方法前，需要获取到对应实例的锁。</p><p>示例：</p><p>给User类的实例方法getName加Synchronized锁：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">User</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> <span class="hljs-number">18</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">(String name)</span> &#123;<br>        System.out.println(name + <span class="hljs-string">&quot; 已成功获取到锁&quot;</span>);<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 睡眠3秒，模拟业务处理</span><br>            Thread.sleep(<span class="hljs-number">3000</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br>        System.out.println(name + <span class="hljs-string">&quot; 业务处理完成，释放锁&quot;</span>);<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getAge</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> age;<br>    &#125;<br></code></pre></td></tr></table></figure><p>构造两个线程竞争同一个示例user1中的方法getName：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user1.getName(<span class="hljs-string">&quot;thread1&quot;</span>);<br>        &#125;);<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user1.getName(<span class="hljs-string">&quot;thread2&quot;</span>);<br>        &#125;);<br>        thread1.start();<br>        thread2.start();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>运行结果显示，只有先抢到锁的thread1释放锁后，thread2才能获得锁去访问getName方法：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">thread1</span> 已成功获取到锁<br>thread1 业务处理完成，释放锁<br>thread2 已成功获取到锁<br>thread2 业务处理完成，释放锁<br></code></pre></td></tr></table></figure><p>如果thread2访问的是另一个实例的getName方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user1.getName(<span class="hljs-string">&quot;thread1&quot;</span>);<br>        &#125;);<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user2.getName(<span class="hljs-string">&quot;thread2&quot;</span>);<br>        &#125;);<br>        thread1.start();<br>        thread2.start();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>运行结果显示，thread2不会受到thread1的影响，thread2可以在thread1还未释放锁时就获取到锁，因为user1和user2属于两个实例，二者的实例锁相互独立：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">thread1</span> 已成功获取到锁<br>thread2 已成功获取到锁<br>thread2 业务处理完成，释放锁<br>thread1 业务处理完成，释放锁<br></code></pre></td></tr></table></figure><h3 id="修饰静态方法"><a href="#修饰静态方法" class="headerlink" title="修饰静态方法"></a>修饰静态方法</h3><p>Synchronized修饰静态方法时，线程进入该方法前，需要获取到对应类的锁</p><p>我们让getName方法变成静态方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">(String name)</span> &#123;<br>        System.out.println(name + <span class="hljs-string">&quot; 已成功获取到锁&quot;</span>);<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 睡眠3秒，模拟业务处理</span><br>            Thread.sleep(<span class="hljs-number">3000</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br>        System.out.println(name + <span class="hljs-string">&quot; 业务处理完成，释放锁&quot;</span>);<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br></code></pre></td></tr></table></figure><p>thread1和thread2访问不同实例的getName方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user1.getName(<span class="hljs-string">&quot;thread1&quot;</span>);<br>        &#125;);<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user2.getName(<span class="hljs-string">&quot;thread2&quot;</span>);<br>        &#125;);<br>        thread1.start();<br>        thread2.start();<br>    &#125;<br></code></pre></td></tr></table></figure><p>结果显示，只有先抢到锁的thread1释放锁后，thread2才能获得锁去访问getName方法，因为user1和user2属于同一个类，他们的对象锁是同一个：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">thread1</span> 已成功获取到锁<br>thread1 业务处理完成，释放锁<br>thread2 已成功获取到锁<br>thread2 业务处理完成，释放锁<br></code></pre></td></tr></table></figure><p>此时对于User类中的实例方法getAge：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">User</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> <span class="hljs-number">18</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">(String name)</span> &#123;<br>        System.out.println(name + <span class="hljs-string">&quot; 已成功获取到锁&quot;</span>);<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 睡眠3秒，模拟业务处理</span><br>            Thread.sleep(<span class="hljs-number">3000</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br>        System.out.println(name + <span class="hljs-string">&quot; 业务处理完成，释放锁&quot;</span>);<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getAge</span><span class="hljs-params">(String name)</span> &#123;<br>        System.out.println(name + <span class="hljs-string">&quot; 成功获取到age&quot;</span>);<br>        <span class="hljs-keyword">return</span> age;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>如果thread2访问任一实例的getAge方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user1.getName(<span class="hljs-string">&quot;thread1&quot;</span>);<br>        &#125;);<br>        <span class="hljs-type">Thread</span> <span class="hljs-variable">thread2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>            user2.getAge(<span class="hljs-string">&quot;thread2&quot;</span>);<br>        &#125;);<br>        thread1.start();<br>        <span class="hljs-keyword">try</span> &#123;<br>            Thread.sleep(<span class="hljs-number">1000</span>); <span class="hljs-comment">// 确保thread1先获取锁</span><br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br>        thread2.start();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>结果显示，thread2访问实例方法getAge并不受thread1影响，因为访问getAge方法不需要获取锁：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">thread1</span> 已成功获取到锁<br>thread2 成功获取到age<br>thread1 业务处理完成，释放锁<br></code></pre></td></tr></table></figure><p>可以推理出，不需要锁的方法，不会受锁的影响。</p><p>静态方法的对象锁和实例方法的实例锁也是互不影响的。</p><p>当然，如果方法getName和getAge都是静态方法，且都被Synchronized修饰，那么两个方法是同属于一个对象，则受制于同一个锁。</p><h3 id="修饰代码块"><a href="#修饰代码块" class="headerlink" title="修饰代码块"></a>修饰代码块</h3><p>当锁的是实例时，结果是和修饰实例方法是一样的，本质上是进入该代码块前，需要先获取到实例的锁：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-keyword">synchronized</span> (<span class="hljs-built_in">this</span>) &#123;<br>            System.out.println(name + <span class="hljs-string">&quot; 已成功获取到锁&quot;</span>);<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-comment">// 睡眠3秒，模拟业务处理</span><br>                Thread.sleep(<span class="hljs-number">3000</span>);<br>            &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>            &#125;<br>            System.out.println(name + <span class="hljs-string">&quot; 业务处理完成，释放锁&quot;</span>);<br>        &#125;<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>       <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>       <span class="hljs-type">User</span> <span class="hljs-variable">user2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>       <span class="hljs-type">Thread</span> <span class="hljs-variable">thread1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>           user1.getName(<span class="hljs-string">&quot;thread1&quot;</span>);<br>       &#125;);<br>       <span class="hljs-type">Thread</span> <span class="hljs-variable">thread2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>           user1.getName(<span class="hljs-string">&quot;thread2&quot;</span>);<br>       &#125;);<br>       thread1.start();<br>       <span class="hljs-keyword">try</span> &#123;<br>           Thread.sleep(<span class="hljs-number">1000</span>); <span class="hljs-comment">// 确保thread1先获取锁</span><br>       &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>       &#125;<br>       thread2.start();<br>   &#125;<br></code></pre></td></tr></table></figure><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">thread1</span> 已成功获取到锁<br>thread1 业务处理完成，释放锁<br>thread2 已成功获取到锁<br>thread2 业务处理完成，释放锁<br></code></pre></td></tr></table></figure><p>当锁的是对象时，结果是和修饰静态方法是一样的，本质上是进入该代码块前，需要先获取到对象的锁：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-keyword">synchronized</span> (User.class) &#123;<br>            System.out.println(name + <span class="hljs-string">&quot; 已成功获取到锁&quot;</span>);<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-comment">// 睡眠3秒，模拟业务处理</span><br>                Thread.sleep(<span class="hljs-number">3000</span>);<br>            &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>            &#125;<br>            System.out.println(name + <span class="hljs-string">&quot; 业务处理完成，释放锁&quot;</span>);<br>        &#125;<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>       <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>       <span class="hljs-type">User</span> <span class="hljs-variable">user2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>       <span class="hljs-type">Thread</span> <span class="hljs-variable">thread1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>           user1.getName(<span class="hljs-string">&quot;thread1&quot;</span>);<br>       &#125;);<br>       <span class="hljs-type">Thread</span> <span class="hljs-variable">thread2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; &#123;<br>           user2.getName(<span class="hljs-string">&quot;thread2&quot;</span>);<br>       &#125;);<br>       thread1.start();<br>       <span class="hljs-keyword">try</span> &#123;<br>           Thread.sleep(<span class="hljs-number">1000</span>); <span class="hljs-comment">// 确保thread1先获取锁</span><br>       &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>       &#125;<br>       thread2.start();<br>   &#125;<br></code></pre></td></tr></table></figure><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">thread1</span> 已成功获取到锁<br>thread1 业务处理完成，释放锁<br>thread2 已成功获取到锁<br>thread2 业务处理完成，释放锁<br></code></pre></td></tr></table></figure><h2 id="锁的升级"><a href="#锁的升级" class="headerlink" title="锁的升级"></a>锁的升级</h2><p>由于Synchronized锁的获取和释放需要调用操作系统的方法将线程挂起和阻塞，就涉及到用户态和内核态的转换，会消耗大量的cpu资源，效率低下，被成为重量级锁，所以在JDK6，引入了锁升级机制来优化：偏向锁-&gt;轻量级锁-&gt;重量级锁。</p><ol><li>偏向锁</li></ol><p>JVM启动后，会启动偏向锁，此时有线程来访问共享资源时，就会进入偏向锁状态（在启动偏向锁之前会有一个延迟时间，在此时间内访问共享资源，会直接进入轻量级锁状态）。此时，会将共享资源对象的Markword的锁信息标志为偏向锁，偏向锁ID存储为当前线程的ID，当该线程再次访问该共享资源对象时，比对偏向锁ID和线程ID，如果一致，就可以直接访问。相当于在共享资源对象里记录一下线程ID，表示当前“偏向”与这个线程，当这个线程再来访问的话，就直接访问，相当于无并发竞争的单线程情况了。在竞争很弱的情况下，偏向锁效率较高。</p><blockquote><p>值得注意的是，在 JDK15 中，偏向锁被默认关闭（仍然可以使用 -XX:+UseBiasedLocking 启用偏向锁），在 JDK18 中，偏向锁已经被彻底废弃。</p><p>因为偏向锁只在无并发竞争的情况下才高效，但是现代程序越来越多的有多线程并发情况，偏向锁的应用情况较少。但是JVM还需要维护偏向锁及其升级逻辑，带来的性能提升难以弥补其开销。</p></blockquote><ol><li>轻量级锁</li></ol><p>当有第二个线程试图访问共享资源对象时，如果第一个线程已经释放了偏向锁，第二个线程就持有该偏向锁，此时锁继续是偏向锁。如果第一个线程没有释放偏向锁，也就是第二个线程竞争访问共享资源对象失败，此时，偏向锁升级为轻量级锁。此时，JVM会通过CAS（Compare And Swap）操作尝试：将共享资源对象的Markword拷贝到线程栈帧的LockRecord区域中，在Markword中存储指向LockRecord的指针，将LockRecord中的owner指针指向Markword。此时是通过CAS尝试获取锁，线程并没有被挂起，没有涉及内核状态的转变，所以相对“轻量”，在竞争不强的情况效率高。如果尝试CAS多次（自旋）失败，或者有太多线程同时尝试的话，锁就升级为重量级锁。</p><ol start="3"><li>重量级锁</li></ol><p>重量级锁是通过底层操作系统的服务来实现的，会为共享资源对象分配一个Monitor，在线程的LockRecord中存储一个指向Monitor的指针，代表持有该锁。操作系统会通过Monitor来维护线程与锁的状态（维护持有者线程，递归计数，等待队列等数据结构）。需要通过操作系统内核态的操作来控制锁，所以相对“重量”。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/09/25/Synchronized%E9%94%81%E7%9A%84%E7%94%A8%E6%B3%95%E5%8F%8A%E5%85%B6%E5%8D%87%E7%BA%A7%E5%8E%9F%E7%90%86/</id>
    <link href="https://www.wananhome.site/2025/09/25/Synchronized%E9%94%81%E7%9A%84%E7%94%A8%E6%B3%95%E5%8F%8A%E5%85%B6%E5%8D%87%E7%BA%A7%E5%8E%9F%E7%90%86/"/>
    <published>2025-09-24T16:00:00.000Z</published>
    <summary>详细解读Synchronized锁的用法及其升级原理</summary>
    <title>Synchronized锁的用法及其升级原理</title>
    <updated>2026-03-21T09:56:38.975Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="IOC" scheme="https://www.wananhome.site/tags/IOC/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>Spring根据IOC控制反转的思想，将Bean（对象）的生命周期交由Spring IOC容器来管理，当你需要使用一个对象的时候，Spring IOC容器会自动帮你创建该对象的实例并初始化，大大降低的代码的耦合性。</p><p>我们知道，如果对象A引用了对象B，对象B又引用了对象A，那么在创建对象A的时候，就会去创建对象B，创建对象B的时候又会去创建对象A，………….如此便产生了对象的循环依赖的问题。Spring IOC是如何解决的呢？</p><h2 id="三级缓存"><a href="#三级缓存" class="headerlink" title="三级缓存"></a>三级缓存</h2><p>Spring IOC是通过三级缓存机制来解决循环依赖问题的，每级缓存都是一个Map：</p><ul><li>一级缓存：<strong>singletonObjects</strong>，key为BeanName，value为Bean，是放置完全体Bean的地方，所有的Bean最终都要放在一级缓存里，获取Bean也先从一级缓存开始获取</li><li>二级缓存：<strong>earlySingletonObjects</strong>，key为BeanName，value为Bean，是放置已经实例化完成，但还没有填充属性值的Bean的地方</li><li>三级缓存：<strong>singletonFactories</strong>，key为BeanName，value为对象工厂（ObjectFactory），Spring IOC在实例化一个Bean时，为这个Bean创建一个对象工厂，并将对象工厂放置在第三缓存中</li></ul><h3 id="解决循环依赖"><a href="#解决循环依赖" class="headerlink" title="解决循环依赖"></a>解决循环依赖</h3><ol><li>当我们在程序中使用到了对象A，Spring IOC容器就会先创建对象A：<ul><li>在对对象A实例化的过程中，还会为对象A创建一个对象工厂，并将对象工厂放置在第三缓存中：<ul><li>对象工厂用于在进行代理等操作的时候，可以通过第三级缓存获取对象的包装对象。</li></ul></li><li>在实例化对象A的过程中，会发现引用了对象B：<ul><li>Spring IOC容器并未在缓存中找到对象B，就会走对象B的创建流程：<ul><li>Spring IOC容器在实例化对象B的时候，又发现了对象B引用了对象A，此时，Spring IOC在第二级缓存中找到了已经实例化但没有初始化的对象A，</li><li>将第二级缓存中的对象A赋值给对象B，便可以继续完成对象B的实例化，并将实例化完的对象B放到第二级缓存中</li></ul></li></ul></li><li>此时，Spring IOC容器就可以在第二级缓存中找到对象B，将其赋值给对象A，完成对象A的实例化</li></ul></li><li>实例化完成的对象A，会被放到第二级缓存中，此时，对象A还没有初始化属性赋值</li><li>Spring IOC容器将对象A的属性值初始化完成后，便会将对象A放到第一级缓存中，提供给程序使用。</li></ol><p>可以看到，Spring IOC通过第二级缓存，将实例化完成，还未初始化属性值的对象，提前暴露给其它对象，解决了循环依赖的问题。</p><h3 id="不足"><a href="#不足" class="headerlink" title="不足"></a>不足</h3><p>由于放在缓存中的额对象都是同一个对象，每次获取的都是同一个对象，也就是<strong>单例模式</strong>，在这种单例模式下，才可以通过缓存来解决循环依赖的问题。</p><p>对于<strong>多例模式</strong>，每次获取的都是不同的对象，也就无法通过缓存的方式来解决循环依赖问题。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/03/21/Spring-IOC%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E7%9A%84%E9%97%AE%E9%A2%98%EF%BC%9F/</id>
    <link href="https://www.wananhome.site/2025/03/21/Spring-IOC%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E7%9A%84%E9%97%AE%E9%A2%98%EF%BC%9F/"/>
    <published>2025-03-20T16:00:00.000Z</published>
    <summary>如果对象A引用了对象B，对象B又引用了对象A，那么在创建对象A的时候，就会去创建对象B，创建对象B的时候又会去创建对象A，.............如此便产生了对象的循环依赖的问题。Spring IOC是如何解决的呢？</summary>
    <title>Spring IOC如何解决循环依赖的问题？</title>
    <updated>2026-03-21T09:51:36.181Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Redis" scheme="https://www.wananhome.site/categories/Redis/"/>
    <category term="Redis" scheme="https://www.wananhome.site/tags/Redis/"/>
    <content>
      <![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在分布式系统的开发中，我们往往需要解决多进程间的并发安全性问题，此时就需要实现一个分布式锁。</p><p>想要解决分布式系统的锁问题，我们就需要让<em>锁</em> 不单依赖于某个系统，需要将<em>锁</em> 给提取出来，独立的为所有系统服务。</p><p>在一个分布式系统中，缓存往往是必不可少的，故而我们常用的Redis就很好的接过了这个任务！</p><h2 id="SETNX-EXPIRE"><a href="#SETNX-EXPIRE" class="headerlink" title="SETNX + EXPIRE"></a>SETNX + EXPIRE</h2><p>在Key-Value形式的Redis数据结构中，Key具有唯一性，我就可以用一个Key来唯一标识一个锁，</p><p>当一个服务需要加锁时，就在Redis中尝试获取这个锁的唯一Key，如果这个Key存在（获取到了Key），就代表锁已经被其它服务持有，加锁失败。</p><p>如果这个Key不存在（没有获取到Key），就代表锁还未被持有，该服务就可以在Redis中设置一个Key，代表已经持有锁，成功加上锁了。</p><p>其中，<em>尝试获取锁的Key，若Key已存在，则加锁失败，否则加锁成功</em> 的流程，在Redis中提供了一个命令：<strong>SETNX</strong>，可以一步完成：</p><p><strong>SETNX</strong>在设置Key之前会先判断Key是否已经存在，若已存在则返回设置失败，否则就成功设置Key。</p><p>如果要释放锁，直接删除这个Key即可。</p><p>此时，我们已经利用Redis的唯一Key性质简单实现了分布式锁！</p><p>但是！</p><p>当服务成功加上锁后，必须要服务在Redis中删除这个锁才能释放掉锁，让其它服务继续获取锁。</p><p>如果服务在成功加上锁后，在释放锁之前宕机了，或者释放锁失败了！锁就永远存在Redis中了，其它服务永远也获取不到锁了！</p><p>其实在Redis中，我们可以通过<strong>EXPIRE</strong>命令，给某个Key加上过期时间，达到过时间，Key就会自动删除，也就是锁会自动释放。这样就解决了上面的问题。</p><h3 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h3><p>但是！</p><p><strong>SETNX</strong>与<strong>EXPIRE</strong>命令是两个命令，它们在一起不具备<em>原子性</em> ，也就是说：</p><p><strong>SETNX</strong>加锁成功后，<strong>EXPIRE</strong>给锁设置过期时间可能会失败，这样还是会产生上面的问题！</p><h3 id="可重入性"><a href="#可重入性" class="headerlink" title="可重入性"></a>可重入性</h3><p>其实还有一个问题：</p><p>每个服务都可以访问Redis，那么，就有可能把其他服务加的锁，给删除了！</p><p>此时，我们可以在设置锁的Key时，加入一个<strong>唯一标识</strong>，用以标识这个锁是哪个服务持有得到，</p><p>当其它在操作这个锁之前，要先比较一下唯一标识是否是自己的，如果不是，就不能操作，</p><p>否则，就知道这个锁是自己的，可以操作。</p><p>但是，这个方法“防君子，不防小人”，其它服务还是可以不管不顾，直接把锁给删了！</p><p>此外，如果想要给锁提供<strong>可重入性</strong>，</p><p>可以在锁的Key中设置一个整数，该数用以表明锁被重入的次数，当释放锁时，先对该数值减一，如果还是大于零，就代表还有本服务的其它线程持有该锁，还不能直接删除。</p><p>这样就简单实现了锁的重入，但是可用性较差。</p><h3 id="发布订阅"><a href="#发布订阅" class="headerlink" title="发布订阅"></a>发布订阅</h3><p>此外，我们还可以利用上Redis的<strong>发布订阅</strong>功能：</p><p>当一个服务成功加上锁后，会发布一个主题，其它加锁失败的服务，会订阅这个主题。</p><p>当服务释放锁后，会在主题上广播这个信息，其它服务收到信息后就可以重新尝试获取锁了！</p><h3 id="自动续约"><a href="#自动续约" class="headerlink" title="自动续约"></a>自动续约</h3><p>此外，当服务成功获取锁，设置好过期时间后，如果程序执行时间太长，锁要失效了怎么办？</p><p>如果锁失效了，而程序还没有执行完，其它服务获取到锁后也来执行，就会出现并发问题。</p><p>我们除了给锁设置一个合理的过期时间外，还可以提供一个<strong>自动续约</strong>的机制：</p><p>设置合适的周期（要小于锁的过期时间），循环检测程序是否执行完成，若没有执行完成，则给锁刷新过期时间。</p><h3 id="RedLock"><a href="#RedLock" class="headerlink" title="RedLock"></a>RedLock</h3><p>此外，如果Redis崩了，分布式锁就无法服务了，我们就需要部署Redis集群以提高可用性。</p><p>对于Redis集群，加锁的时候，是单节点加锁，还是部分节点加锁，还是全部节点加锁？</p><p>如果是单节点加锁，加锁的节点如果宕机了，锁的信息就会丢失了。</p><p>如果是全部节点加锁，效率太低，绝大多数情况下，大部分节点是不会同时宕机的，所以只需要在大部分节点上加锁就行了，也就是部分节点加锁。</p><p><strong>RedLock</strong>选择在<strong>N&#x2F;2+1</strong>个节点加锁，只有在N&#x2F;2+1个节点上都加锁成功了，才算最终加锁成功。</p><p>在<strong>RedLock</strong>下，服务需要依次尝试在集群中的节点上加锁，需要设置一个尝试超时时间，超时还未加锁成功的话，就换下一个节点尝试加锁，直到N&#x2F;2+1个节点加锁成功，并且，加锁耗时小于锁的超时时间（也就是尝试一圈加完锁后，锁还没有过期），才算最终加锁成功。</p><p>如果加锁耗时超过锁的超时时间，必须要主动释放所有已经加上的节点锁。</p><h2 id="Lua"><a href="#Lua" class="headerlink" title="Lua"></a>Lua</h2><p>在Redis中可以使用Lua脚本，这是一个原子性且高效的操作，</p><p>通过使用<strong>Lua脚本</strong>来设置Key和过期时间，可以解决<strong>SETNX</strong>与<strong>EXPIRE</strong>非原子性的问题。</p><h2 id="Redisson"><a href="#Redisson" class="headerlink" title="Redisson"></a>Redisson</h2><p>上面提到的所有技术方案，如果都要让我们自己来实现，未免有些太过麻烦！</p><p><strong>Redisson</strong>为我们提供了更高效，更高可用的实现，我们可以直接使用：</p><p>结合<strong>Lua</strong>可以原子性的加锁和设置过期时间；</p><p>通过给Key中添加服务的唯一标识，以及设置整数值，实现锁的<strong>可重入性</strong>；</p><p>通过<strong>看门狗</strong>机制，定期检测程序如果没有执行完，就自动续约；</p><p>结合Redis的<strong>发布订阅</strong>机制来唤醒其它服务重试获取锁；</p><p>结合RedLock算法，提供<strong>RedissonRedLock</strong>解决单点失败问题。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/03/15/%E6%B5%85%E6%9E%90Redis%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95/</id>
    <link href="https://www.wananhome.site/2025/03/15/%E6%B5%85%E6%9E%90Redis%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95/"/>
    <published>2025-03-14T16:00:00.000Z</published>
    <summary>入门级分析Redis分布式锁</summary>
    <title>浅析Redis分布式锁的实现方法</title>
    <updated>2026-03-21T10:04:20.346Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Redis" scheme="https://www.wananhome.site/categories/Redis/"/>
    <category term="MySQL" scheme="https://www.wananhome.site/tags/MySQL/"/>
    <category term="Redis" scheme="https://www.wananhome.site/tags/Redis/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在学习中，为了提高数据的读取效率，我们往往会使用Redis来作为MySQL数据的缓存，那么，自然就产生了二者间数据的一致性问题。</p><p>想要对MySQL和Redis进行数据处理，自然会产生以下问题：</p><ul><li>MySQL与Redis操作的时序问题<ul><li>更新与删除的选择及时序问题</li></ul></li></ul><p>下面我们来一一分析：</p><h2 id="先操作MySQL"><a href="#先操作MySQL" class="headerlink" title="先操作MySQL"></a>先操作MySQL</h2><h3 id="先删除MySQL中数据"><a href="#先删除MySQL中数据" class="headerlink" title="先删除MySQL中数据"></a>先删除MySQL中数据</h3><p>这种情况下，当我们选择先将MySQL中的数据删除时，如果后续写入新数据失败，新数据就很有可能会丢失，我们就完全失去了这条数据，这是难以接受的。所以我们还是来看看先更新MySQL的情况吧：</p><h3 id="先更新MySQL中数据"><a href="#先更新MySQL中数据" class="headerlink" title="先更新MySQL中数据"></a>先更新MySQL中数据</h3><p>这种情况下，当客户端修改一个数据时，我们先将MySQL中数据更新为最新状态，然后再操作Redis（如果第一步更新MySQL数据失败，就不会继续操作Redis了，此时MySQL和Redis中的数据都还是老数据，也是处于一致状态，可以接受）：</p><h4 id="再更新Redis中数据"><a href="#再更新Redis中数据" class="headerlink" title="再更新Redis中数据"></a>再更新Redis中数据</h4><p>试想这样一种情况：</p><p>线程A和线程B按以下顺序执行：</p><ol><li>线程A缓存未命中，然后从MySQL中读到数据c&#x3D;1</li><li>线程B想将数据c&#x3D;1修改为c&#x3D;2</li><li>线程B先更新MySQL成功，再更新Redis数据c&#x3D;2（不论更新Redis成功与否）</li><li>线程A写回Redis数据c&#x3D;1</li></ol><p>可以看到，此时MySQL中是新数据c&#x3D;2，Redis中却是老数据c&#x3D;1，处于不一致状态。在Redis中该数据自动过期，或者再次更新该数据之前，客户端都会读到Redis中的老数据（脏数据）。</p><p>不过读操作，往往比写操作更快速，也就是说大多数情况下，线程A等线程B操作完了再写回Redis的情况不会出现。</p><p>但是，对于并发更新的情况，可能会出现多个线程并发更新Redis数据，导致老数据覆盖新数据的情况，也会造成不一致状态。</p><h4 id="再删除Redis中数据"><a href="#再删除Redis中数据" class="headerlink" title="再删除Redis中数据"></a>再删除Redis中数据</h4><p>与上面“再更新Redis中数据”的情况类似，可能会出现线程B删除完Redis数据后，线程A又写回老数据的情况。</p><p>但是，对于并发更新的情况，就算多个线程并发删除Redis数据，也能够保证删除老数据，不会造成不一致状态。</p><p>当然，如果删除Redis失败了，Redis中还是会留下老数据，造成不一致状态。</p><h2 id="先操作Redis"><a href="#先操作Redis" class="headerlink" title="先操作Redis"></a>先操作Redis</h2><h3 id="先删除Redis中数据"><a href="#先删除Redis中数据" class="headerlink" title="先删除Redis中数据"></a>先删除Redis中数据</h3><h4 id="再更新MySQL中数据"><a href="#再更新MySQL中数据" class="headerlink" title="再更新MySQL中数据"></a>再更新MySQL中数据</h4><p>试想这样一种情况：</p><p>线程A和线程B按以下顺序执行：</p><ol><li>线程B想将数据c&#x3D;1修改为c&#x3D;2</li><li>线程B先删除Redis中数据成功</li><li>线程A缓存未命中，然后从MySQL中读到数据c&#x3D;1</li><li>线程B更新MySQL中数据c&#x3D;2</li><li>线程A写回Redis数据c&#x3D;1</li></ol><p>可以看到，此时MySQL中是新数据c&#x3D;2，Redis中却是老数据c&#x3D;1，处于不一致状态。在Redis中该数据自动过期，或者再次更新该数据之前，客户端都会读到Redis中的老数据（脏数据）。</p><p>此外，对于并发更新的情况，可能会出现多个线程并发更新MySQL数据，导致老数据覆盖新数据的情况，也会造成不一致状态。</p><h4 id="再删除MySQL中数据"><a href="#再删除MySQL中数据" class="headerlink" title="再删除MySQL中数据"></a>再删除MySQL中数据</h4><p>这种情况下，当我们选择将MySQL中的数据删除时，如果后续Redis中的新数据丢失（磁盘存储相对内存存储的可靠性更高），我们就完全失去了这条数据，这是难以接受的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><h3 id="Cache-Aside-旁路缓存"><a href="#Cache-Aside-旁路缓存" class="headerlink" title="Cache-Aside (旁路缓存)"></a>Cache-Aside (旁路缓存)</h3><p>综上所述，我们往往会选择 <strong>先更新MySQL中数据-再删除Redis中数据</strong> 的方案。</p><p>以上所述所有的方案，有一个统称：<strong>Cache-Aside (旁路缓存)</strong>。</p><h3 id="直读-与-同步-异步直写"><a href="#直读-与-同步-异步直写" class="headerlink" title="直读 与 同步&#x2F;异步直写"></a>直读 与 同步&#x2F;异步直写</h3><p>此外，如果我们将缓存的业务直接从其它业务代码中抽取出来，给其它业务提供一个缓存抽象层，将缓存的操作全部放在这个缓存抽象层中独立存在，也就是缓存的解耦。</p><p>这样，主体业务就只需要与抽出来的缓存层进行交互，无需再关心数据的一致性，直接读&#x2F;写缓存层，也就是<strong>直读</strong>与<strong>直写</strong>。</p><h4 id="直读（Write-Through）"><a href="#直读（Write-Through）" class="headerlink" title="直读（Write-Through）"></a>直读（Write-Through）</h4><p>客户端（也可以是其他业务层）直接读取Redis缓存，如果缓存未命中，从MySQL中读取数据后，再写回Redis，然后再返回给客户端。</p><h4 id="直写（Write-Through）"><a href="#直写（Write-Through）" class="headerlink" title="直写（Write-Through）"></a>直写（Write-Through）</h4><p>客户端（也可以是其他业务层）直接先更新Redis缓存，然后再更新MySQL，然后再返回数据给客户端。</p><p>上面的是<strong>同步直写</strong>，如果在更新MySQL的同时，异步将数据返回给客户端，那么就叫<strong>异步直写（Write-Behind）</strong>。</p><h4 id="canal"><a href="#canal" class="headerlink" title="canal"></a>canal</h4><p>此外，为了提高系统的可用性，我们可以配合一些成熟的中间件，例如：</p><p>使用Canal监控MySQL的binlog日志，自动通知缓存业务MySQL中的数据进行了什么修改，可以让我们的系统迅速知道MySQL的数据改动，延迟极低。</p><p>在缓存业务中，我们往往还可以引入<strong>消息队列</strong>，提高数据传输的可靠性，例如：</p><p>当Canal监控到MysQL数据改动后，将监控数据发送到RabbitMQ，由订阅了相关Topic的缓存业务消费监控数据并进行处理，可以有效避免数据传输中失败、丢失等问题。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/03/13/MySQL%E4%B8%8ERedis%E7%9A%84%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/</id>
    <link href="https://www.wananhome.site/2025/03/13/MySQL%E4%B8%8ERedis%E7%9A%84%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/"/>
    <published>2025-03-12T16:00:00.000Z</published>
    <summary>详细分析MySQL与Redis的缓存一致性问题的各种解决策略</summary>
    <title>MySQL与Redis的缓存一致性问题</title>
    <updated>2026-03-21T10:05:53.943Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="RabbitMQ" scheme="https://www.wananhome.site/categories/RabbitMQ/"/>
    <category term="RabbitMQ" scheme="https://www.wananhome.site/tags/RabbitMQ/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在实际业务中，往往会遇到例如：订单10分钟后过期、会议1小时后开始、商品3天后下架等等需求。</p><p>对于普通的消息队列，消息一旦被发送到队列内，消费者就可以直接消费，无法实现诸如<em>过期订单</em>的需求。</p><p>那么就需要特殊的技术来实现，<strong>RabbitMQ</strong>中有两种实现方式：<strong>TTL</strong>+<strong>死信队列</strong>、<strong>插件</strong>。</p><h2 id="TTL-死信队列"><a href="#TTL-死信队列" class="headerlink" title="TTL+死信队列"></a>TTL+死信队列</h2><p><strong>TTL</strong>就是<strong>Time To Live</strong>，也就是<strong>消息存活时间</strong>。</p><p>可以选择为一个<strong>消息队列</strong>设置TTL，或者为某条<strong>消息</strong>设置TTL，区别在于：</p><p>如果为<strong>消息队列</strong>设置了TTL，那么当该消息队列中的消息存在时长达到了TTL，也就是该消息过期了，消息队列就会<strong>立刻</strong>将该消息丢弃。</p><p>如果为某条<strong>消息</strong>设置了TTL，消息队列只会在消息将被消费时才会进行TTL检查，如果检查发现该条消息过期了，才会将消息丢弃。也就是说，这种情况下，如果消息还没有要被消费，就算消息早已经过期很久了，也不会被丢弃。</p><p>被<strong>丢弃</strong>的消息，并不是被销毁了，而是被转移到<strong>死信交换机</strong>里了，再由死信交换机<em>路由</em>到<strong>死信队列</strong>，顾名思义，死信队列就是死信的消息队列。</p><p>当消息被检测到<strong>TTL</strong>过期时，会被转移到死信交换机，再由死信交换机从多个<strong>死信队列</strong>中选一个死信队列，将过期的消息，也就是<strong>死信</strong>，路由到该死信队列，消费者再从<strong>死信队列</strong>中消费消息，也就实现了延迟消息（或者称为<strong>延迟队列</strong>）。</p><p>在<strong>TTL</strong>的两种设置方式中，</p><p>如果为<strong>消息队列</strong>设置TTL，就会导致该消息队列内的每条消息都有固定的一样的TTL，如果想要提供更多的TTL选择，就需要提供更多的消息队列，灵活性不高。</p><p>如果为<strong>消息</strong>设置TTL，就会导致由于消息堵塞，过期消息并未即时转移到死信队列的情况，可用性不高。</p><p>两种<strong>TTL</strong>设置方式需具体业务具体分析。</p><h2 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h2><p>RabbitMQ<strong>官方</strong>提供了一个插件：<strong>rabbitmq_delayed_message_exchange</strong>，可以用来快速实现<strong>延迟消息</strong>。</p><p>该插件可以让交换机本身具备延迟发送消息的功能，也就是<strong>延迟交换机</strong>。</p><p>通过<strong>延迟交换机</strong>，可以灵活的为每条延迟消息设置一个延迟时间，延迟交换机会根据消息的延迟时间进行发送。</p><p>使用<strong>插件</strong>的方法，实现较为简易，延迟时间的灵活性较高，</p><p>但<strong>TTL</strong>+<strong>死信队列</strong>的方法，由于是通过原生特性实现的，所以性能较高。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/02/05/%E7%AE%80%E8%BF%B0RabbitMQ%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E7%9A%84%E4%B8%A4%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/</id>
    <link href="https://www.wananhome.site/2025/02/05/%E7%AE%80%E8%BF%B0RabbitMQ%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E7%9A%84%E4%B8%A4%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/"/>
    <published>2025-02-04T16:00:00.000Z</published>
    <summary>RabbitMQ两种迟消息实现方式：TTL+死信队列、插件。</summary>
    <title>简述RabbitMQ延迟消息的两种实现方式</title>
    <updated>2026-03-21T10:07:21.679Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="MySQL" scheme="https://www.wananhome.site/categories/MySQL/"/>
    <category term="MySQL" scheme="https://www.wananhome.site/tags/MySQL/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><em>MySQL5.6</em>引入了一项查询优化技术：<strong>索引下推（Index Condition Pushdown，简称 ICP）</strong>。</p><p>目前只有<strong>MyISAM</strong>和<strong>InnoDB</strong>支持了<strong>ICP</strong>。</p><p>下面就来简单讲讲到底什么是<strong>ICP</strong>吧。</p><h2 id="MySQL服务层与存储引擎层"><a href="#MySQL服务层与存储引擎层" class="headerlink" title="MySQL服务层与存储引擎层"></a>MySQL服务层与存储引擎层</h2><p>在MySQL初期设计时，由于当时的局限性，以及为了对<strong>MySQL服务层</strong>和<strong>存储引擎层</strong>进行职责划分，所以<strong>存储引擎层</strong>的主要职责是快速借助索引定位数据，然后将数据返回给<strong>MySQL服务层</strong>，服务层可以更好更快速的对数据进行过滤处理。</p><p>所以<strong>存储引擎层</strong>为了快速地定位数据，只会进行简单的数据过滤。也就是说，如果查询语句的<em>where</em>后通过<em>and</em>连接了多个过滤条件，<strong>存储引擎层</strong>一般只会执行部分简单的条件。</p><p>例如：<code>SELECT * FROM users WHERE age &gt; 30 AND name LIKE &#39;A%&#39;;</code> 就算有联合索引<code>（age,name）</code> ，存储引擎也只会执行<code>age&gt;30</code> ，将符合该条件的数据全部返回给<strong>MySQL服务层</strong>，再由服务层对数据进行<code>name LIKE &#39;A%&#39;</code> 条件过滤。</p><h2 id="ICP"><a href="#ICP" class="headerlink" title="ICP"></a>ICP</h2><p>通过前面的讲解，我们知道了，由于初期MySQL设计的局限性，<strong>存储引擎层</strong>没有充分的用到索引，导致返回了大量多余数据给<strong>MySQL服务层</strong>，此外存储引擎层在数据查询时也容易产生多余的<em>回表</em>操作。</p><p>MySQL完全可以在<strong>存储引擎层</strong>利用联合索引<code>（age,name）</code> ，执行完<strong>被索引覆盖</strong>的全部的<code> age &gt; 30 AND name LIKE &#39;A%&#39;</code> 过滤条件，就可以返回较少的数据给<strong>MySQL服务层</strong>。这个技术就是<strong>ICP</strong>。</p><p>可以看到，<strong>ICP</strong>就是将<strong>MySQL服务层</strong>需要执行的部分复杂条件过滤<strong>下推</strong>到<strong>存储引擎层</strong>执行，有利于减少存储引擎层与MySQL服务层之间的<strong>数据传输开销</strong>，也有利于减少<strong>回表</strong>，提高MySQL查询效率。</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p><strong>ICP</strong>在查询条件能够被索引覆盖的情况下才有意义，所以<strong>联合索引</strong>、<strong>覆盖索引</strong>与<strong>ICP</strong>以及其它优化查询的技术之间往往是<strong>相辅相成</strong>的。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/02/04/MySQL-%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8/</id>
    <link href="https://www.wananhome.site/2025/02/04/MySQL-%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8/"/>
    <published>2025-02-03T16:00:00.000Z</published>
    <summary>简单讲讲到底什么是Index Condition Pushdown</summary>
    <title>MySQL-索引下推</title>
    <updated>2026-03-21T09:58:20.268Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="认知与思想" scheme="https://www.wananhome.site/categories/%E8%AE%A4%E7%9F%A5%E4%B8%8E%E6%80%9D%E6%83%B3/"/>
    <category term="《认知觉醒》" scheme="https://www.wananhome.site/tags/%E3%80%8A%E8%AE%A4%E7%9F%A5%E8%A7%89%E9%86%92%E3%80%8B/"/>
    <content>
      <![CDATA[<blockquote><p>推荐书籍：<a href="https://book.douban.com/subject/35193035/">《认知觉醒：开启自我改变的原动力》</a> 作者：周岭</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>假期闲来无事，偶然间发现**《认知觉醒》**这本书，便开始尝试读一读，渐渐被它所吸引，其中的一些说法让我颇为赞同。</p><p>本着 <em>“读书是为了有所改变 ”</em> 的观念，我决定把自己的一些感想写下来，也有利于促进我持久的付诸行动。</p><h2 id="“三脑”理论"><a href="#“三脑”理论" class="headerlink" title="“三脑”理论"></a>“三脑”理论</h2><p>书中讲解的**“三脑”理论<strong>让我印象深刻，将人的大脑抽象成</strong>本能脑**、<strong>情绪脑</strong>、<strong>理性脑</strong>，用这三种大脑来解释了人性的种种，包括<em>懒惰</em>、<em>趋易避难</em>、<em>贪图享乐</em>等等并非人的错误，而是人类数千年进化而来的基因所决定的，我们应该正视它们，寻找合适的方法循循善诱，而不是一味地依靠所谓的“毅力”。</p><p><strong>本能脑</strong>是人类最早期的脑，那时人类为了在原始危险的环境下生存下来，必须依靠动物的<strong>本能</strong>来帮助自己<em>呼吸</em>、<em>奔跑</em>、<em>寻找食物</em>等等。本能脑就是为了帮助人类降低能耗，基本的存活下去。</p><p><strong>情绪脑</strong>是在<strong>本能脑</strong>之后进化出来的，它通过<em>恐惧</em>与<em>快乐</em>等情绪帮助人类<em>趋利避害</em>，构建<em>原始社会</em>等等。在危险的原始世界，<em>趋利避害</em>、<em>趋易避难</em>等等特性是有利于人类持久存活下去的。</p><p><strong>理性脑</strong>是在前两个脑之后很久才进化出来的，它让人类更加注重<em>思考</em>，更加<em>理性</em>，理性脑对于处于文明社会的人类来说是非常重要的。</p><p>但是由于<strong>理性脑</strong>发展的时间太短了，很容易被<strong>本能脑</strong>与<strong>情绪脑</strong>所占领，导致人类很多时候往往会<em>失去理性</em>，或者<em>智商下降</em>，因为<em>理性脑</em>在<em>本能脑</em>与<em>情绪脑</em>面前就像一个发育不完全的婴儿一样。</p><p>有些时候，我们表现得<em>好吃懒做</em>、<em>贪图享乐</em>、<em>不思进取</em>等等，并不是因为我们有问题，而是人类天性如此，这是数千年进化的结构，人类进入文明社会的时间太短了，<strong>理性脑</strong>还没有发育到可以控制<strong>本能脑</strong>与<strong>情绪脑</strong>的地步，反而常常被后者所控制。</p><p>但是作为一个<strong>有理性</strong>的人，肯定会想办法帮助<strong>理性脑</strong>来<em>说服</em> <strong>本能脑</strong>与<strong>情绪脑</strong>，很多人试图通过自己所谓的强大的<strong>毅力</strong>来与本能脑和情绪脑对抗，结果往往不尽如人意。我们应该通过<em>学一会玩一会</em>、<em>学一会休息一会</em>等建立<strong>奖赏机制</strong>来<strong>利用</strong>本能脑与情绪脑，与其通过<em>毅力</em>对抗本能脑与情绪脑，不如将二者为我所用，才是长久之道。</p><blockquote><p>具体方法在书中学习</p></blockquote><h2 id="“伸展区”理论"><a href="#“伸展区”理论" class="headerlink" title="“伸展区”理论"></a>“伸展区”理论</h2><p>书中特别提到了一种练习的分级方法，将事务分在<strong>舒适区</strong>、<strong>伸展区</strong>、<strong>困难区</strong>，三个等级。</p><p>人类<em>趋易避难</em>的<strong>本能</strong>会让我们选择待在<strong>舒适区</strong>，具有一定<em>理性</em>的人又容易被<strong>情绪脑</strong>所俘获，而选择了<strong>困难区</strong>，妄想<em>一口吃成个胖子</em>。</p><p>特别是在如今社会节奏越来越快的情况下，会让人有种*“来不及了…..，时间不多了…..”* 的感觉，大家都想要<strong>速成</strong>，最终却只能在<strong>困难区</strong>不断碰壁。</p><p>合理的方法是，我们应该选择那些有点难度，蹦一蹦就能碰到的区域挑战，也就是<strong>伸展区</strong>，伸展区的挑战能够让我们在感到有些难度的情况下，还能感到挑战的乐趣，不至于感到<em>无从下手</em>的无力感。</p><p>在<strong>伸展区</strong>不断的挑战，渐渐地，就会发现，原本的<strong>困难区</strong>已经变成了现在的<strong>伸展区</strong>。</p><h2 id="知与行"><a href="#知与行" class="headerlink" title="知与行"></a>知与行</h2><p>对于读书来说，很多人都喜欢*“读了就是会了”* ，往往贪图读书的<em>量</em>，好像每年读了多少多少本书就能让自己很有成就感，但是，读书应该是为了改变什么，是为了让自己有所改变，有所行动。</p><p>如果你读书只是“看”完了书中的字，那读书是没有任何意义的，你已经落入了<em>情绪脑</em> 的欺骗中。</p><p>读书不求能够读懂书中的全部理论，不求明白贤者的所思所想，只要书中的某一点让你产生了触动，并且你为之付诸了行动，那么，你读了这本书就是有意义的。</p><p>而如何知道书中哪一点让自己产生了触动呢？就是<strong>写作</strong>，在读完书一段时间后，回想书中让你现在还印象较为深刻的点，写下来，能够用自己的话写下来，那么，这本书就改变了你。</p><p>对于读书以外的万事万物都是一样的，真正的成长不仅在于知道，更在于行动。只有将知识转化为实践，才能真正改变自己，影响世界。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/02/03/%E8%AF%BB%E3%80%8A%E8%AE%A4%E7%9F%A5%E8%A7%89%E9%86%92%E3%80%8B%E5%90%8E%E6%84%9F/</id>
    <link href="https://www.wananhome.site/2025/02/03/%E8%AF%BB%E3%80%8A%E8%AE%A4%E7%9F%A5%E8%A7%89%E9%86%92%E3%80%8B%E5%90%8E%E6%84%9F/"/>
    <published>2025-02-02T16:00:00.000Z</published>
    <summary>本书是我读完《认知觉醒》后的主观所感，与原书内容可能有出入</summary>
    <title>读《认知觉醒》后感</title>
    <updated>2026-03-21T07:33:59.292Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="MySQL" scheme="https://www.wananhome.site/categories/MySQL/"/>
    <category term="MySQL" scheme="https://www.wananhome.site/tags/MySQL/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>当一个索引包含了查询所需的全部字段时，就可以提高查询效率，这样的索引又被称之为<strong>覆盖索引</strong>。</p><p>以MySQL常见的三种存储引擎为例：<strong>InnoDB</strong>、<strong>MyISAM</strong>、<strong>Memory</strong>，对于覆盖索引提高查询效率的方式均不同，</p><p>下面让我们分别讲讲：</p><h2 id="InnoDB"><a href="#InnoDB" class="headerlink" title="InnoDB"></a>InnoDB</h2><p>在<strong>InnoDB</strong>中，<strong>主键索引</strong>的叶子节点存储完整的数据行，称为<strong>聚簇索引</strong>，而<strong>唯一索引</strong>、<strong>普通索引</strong>、<strong>联合索引</strong>的叶子节点只存储索引字段和主键值，称之为<strong>二级索引</strong>。</p><p>当一条查询sql用到的索引只包含部分需要的字段时，就需要先在二级索引中查到相应数据的主键字段，然后根据主键字段在主键索引中查到全部数据。<em>回到主键索引中查询数据</em>，这个就叫做<strong>回表</strong>，因为一次索引查询还查不到全部数据，还需要<strong>回表</strong>一次才能查到全部数据。</p><p>但是，如果sql查询所需的字段全部包含在用到的索引中，就可以在二级索引中直接查询到所需的全部数据，不需要再回表了，这种包含所需的全部字段的索引，就叫做<strong>覆盖索引</strong>。</p><p>在<strong>InnoDB</strong>中，<strong>覆盖索引</strong>可以减少<strong>回表</strong>的次数，提高查询效率。</p><h2 id="MyISAM"><a href="#MyISAM" class="headerlink" title="MyISAM"></a>MyISAM</h2><p>在<strong>MyISAM</strong>中，索引保存的是数据值，以及指向数据在磁盘中位置的<strong>指针</strong>。</p><p>当一条sql查询没有用到索引时，就需要直接到磁盘中的数据文件进行搜索。</p><p>当一条sql查询用到索引的部分字段时，会先在索引中查到部分字段的指针，然后再到磁盘中根据指针查询到对应行，再在行中查询全部所需数据。</p><p>但是，如果sql查询用到的索引包含全部所需的字段时，也就是用到<strong>覆盖索引</strong>时，就可以直接在索引中查到全部所需字段的值，不需要再进行磁盘IO。</p><p>在<strong>MyISAM</strong>中，<strong>覆盖索引</strong>可以减少<strong>磁盘IO</strong>的次数，提高查询效率。</p><h2 id="Memory"><a href="#Memory" class="headerlink" title="Memory"></a>Memory</h2><p>在<strong>Memory</strong>中，与在<strong>MyISAM</strong>中逻辑相似，不过<strong>Memory</strong>是将数据存储在内存中，虽然<strong>内存IO</strong>比<strong>磁盘IO</strong>快很多，但使用<strong>覆盖索引</strong>仍有利于减少<strong>内存IO</strong>，提高查询效率。</p><blockquote><p>需要注意的是，本文并未特别考虑同时使用其它索引的情况，不使用覆盖索引而使用其它索引仍然可能达到同样的效果。</p></blockquote>]]>
    </content>
    <id>https://www.wananhome.site/2025/02/02/MySQL%E7%9A%84%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95/</id>
    <link href="https://www.wananhome.site/2025/02/02/MySQL%E7%9A%84%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95/"/>
    <published>2025-02-01T16:00:00.000Z</published>
    <summary>MySQL的覆盖索引</summary>
    <title>MySQL的覆盖索引</title>
    <updated>2026-03-21T10:00:02.467Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="AI" scheme="https://www.wananhome.site/categories/AI/"/>
    <category term="Cursor" scheme="https://www.wananhome.site/tags/Cursor/"/>
    <content>
      <![CDATA[<h2 id="序言"><a href="#序言" class="headerlink" title="序言"></a>序言</h2><p>近期，在CSDN、微信公众号、X、Youtube等平台上看到了一些关于**<a href="https://www.cursor.com/">Cursor</a><strong>的文章，让我意识到Cursor作为一个</strong>“ The AI Code Editor ”**，已经在尝试提出新的编程方式。</p><p>作为一个程序员（也可能是作为一个懒惰的菜鸟程序员），我决定去尝试一下Cursor。</p><img src="/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/image-20250127194144477-78484583c73f.png" class="image-20250127194144477"><h2 id="项目开发过程"><a href="#项目开发过程" class="headerlink" title="项目开发过程"></a>项目开发过程</h2><blockquote><p>Cursor的安装配置较为简单，这里就不多言，可以在网上自行学习。</p></blockquote><h3 id="项目简介"><a href="#项目简介" class="headerlink" title="项目简介"></a>项目简介</h3><h4 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h4><p>近期寒假在家，帮助我奶奶解决了几次用智能手机接打电话的问题后，引发了我的一个想法，这个想法以前也有过，只是出于对相关技术的不了解，以及没有太大的动力，就一直没有去实现它，这次通过测试Cursor，就顺便实现了这个想法。</p><blockquote><p>项目源码（完全使用Cursor编写）：<a href="https://github.com/WanAnUncommon/PhoneApp">https://github.com/WanAnUncommon/PhoneApp</a></p></blockquote><h4 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h4><ol><li>首页展示联系人大头像以及姓名</li><li>单击头像即可进行拨号</li><li>长按头像可以编辑联系人信息</li><li>单击右下角的<em>加号</em>，可以添加联系人</li><li>单击右上角的<em>更多</em>，可以修改头像的尺寸以及文字的大小</li></ol><h3 id="Cursor开发项目"><a href="#Cursor开发项目" class="headerlink" title="Cursor开发项目"></a>Cursor开发项目</h3><p>在Cursor中开发项目，只需要新建一个空白文件夹，使用<code>Ctrl+L</code>打开<strong>CHAT</strong>对话框，然后像和市面上的AI对话工具对话一样即可。</p><p>本次在Cursor中使用<strong>claude-3.5-sonnet</strong>模型，可以在对话框左下角修改：</p><img src="/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/image-20250127195914864-9eef49a2b356.png" class="image-20250127195914864"><p>先给AI详细描述项目的需求，它就会给你初步的代码实现以及后续的功能建议：</p><img src="/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/image-20250127200036216-7f02043052b4.png" class="image-20250127200036216"><p>当AI给出代码实现后，需要你选择是否<strong>accept</strong>，accept它的代码后，它就会直接在左侧项目区域生成文件目录以及具体代码：（这里是后期截的图，所以有全部的目录结构，仅供参考）</p><img src="/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/image-20250127200511888-70306ff671d1.png" class="image-20250127200511888"><p>因为我并不会Android程序开发，所以我都是一路<em>accept</em>过来，不论AI提出什么修改，我都接受。</p><blockquote><p>目前AI的编码水平还无法完全取代一个普通的本科程序员，在使用时最好对AI写的代码进行基本的审查。</p><p>需警惕AI生成代码的‘黑箱’特性，可能存在未知漏洞或依赖风险。</p></blockquote><p><strong>Cursor</strong>有一点不好的是，目前还只主要是一个AI代码生成器，远没有一般IDE提供的编程&#x2F;调试功能方便，所以在开发本项目时，我还专们去下载了开发Android项目的IDE：<a href="https://developer.android.google.cn/studio?hl=zh-cn">Android Studio</a></p><p>在<strong>Cursor</strong>与<strong>Android Studio</strong>中同时打开这个项目，在Cursor中用AI生成代码，然后到Android Studio中运行和调试，这样的开发方式确实有一些不便。（更是对我轻薄本带来了巨大的挑战！！）</p><blockquote><p>需要注意的是，Cursor中生成完代码后，在Android Studio中可能还没有立即更新，需要稍等一会儿，或者刷新一下项目。</p></blockquote><p>当然，AI生成的代码很难一步到位，用<strong>Cursor</strong>写完初代项目代码后，<strong>Android Studio</strong>却运行不起来，这时直接将报错丢给Cursor：</p><img src="/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/image-20250127202054628-d3588fee4c05.png" class="image-20250127202054628"><p>在经历数次的 <em>报错-让Cursor修改</em> 循环后，项目总算是能运行起来了。</p><p>但是，又遇到某些功能无法使用，某些功能实现的不够好之类的问题，还是直接将需求告诉AI，让它修改，然后选择是否 <em><strong>accept</strong></em> 。</p><p>最终，在经过了约<strong>5小时</strong>的开发后，我得到了一个符合我预期的产品！</p><h2 id="感言"><a href="#感言" class="headerlink" title="感言"></a>感言</h2><p>作为一个只上过几节二本大学Android水课的Java菜鸟来说，从无到有，只用约5个小时就开发出一款可用的简单Android软件，当时确实让我感到兴奋！</p><p>兴奋之后，又很感慨。</p><p>在不久的将来（或许就是明年），AI的编程能力或许可以让没接触过编程的普通人借助像 <em>Cursor</em> 这样的 <strong>The AI Code Editor</strong> 写出自己的程序，实现真正的**“Hello World”** ，就像 <em>可视化操作系统</em> 将计算机带给”家庭妇女“，AI会将计算机技术带给普罗大众。</p><p>或许现在很难通过 <em>AI Code Editor</em> 从零开始开发出大型项目，但随着<strong>软件工程</strong>的持续发展，大型项目都开始讲究模块化，每个大项目都由一个个小项目组成，相信在未来，AI也会逐渐提高它在大型项目中的戏份。</p><p>可以预见的是，AI一定会取代部分程序员。</p><p>这不禁让人思考，就像 <em>自媒体</em> 时代的到来一样，我们或许会迎来 <em><strong>自编程</strong></em> 时代，借助AI，每个人都可以进行编程，程序员逐渐由 <em>编码</em> 转向 <em>需求</em> ，由 <em>实现</em> 转向 <em>设计</em> 。</p><p>AI的发展一定会让越来越多的人从 <em>“拧螺丝”</em> 的岗位上脱离出来，不在拘泥于传统的企业结构，毕竟传统的企业一般是 <em>“老板一句话，员工跑断腿”</em> ，而AI可以作为你的 <em>“员工”</em> ，你成为自己的 <em>“老板”</em> ，你提出想法，借助AI来实现，以此来实现 <em>个人价值的商业化</em> ，树立个人IP，打造 <em>一人企业</em> ，成为 <strong>超级个体</strong> 。</p>]]>
    </content>
    <id>https://www.wananhome.site/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/</id>
    <link href="https://www.wananhome.site/2025/01/27/Cursor%E4%BD%BF%E7%94%A8%E5%90%8E%E6%9C%89%E6%84%9F/"/>
    <published>2025-01-26T16:00:00.000Z</published>
    <summary>初试Cursor后，感触颇深，写下此文</summary>
    <title>Cursor使用后有感</title>
    <updated>2026-03-21T07:35:59.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="MySQL" scheme="https://www.wananhome.site/categories/MySQL/"/>
    <category term="MySQL" scheme="https://www.wananhome.site/tags/MySQL/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>创建索引有利于提高数据库的查询效率，当我们需要对多个列进行查询时，可以考虑建立一个联合索引。</p><p>但是有些时候，当我们对三个列a、b、c建立一个联合索引（a,b,c）后，按理说，列b和c的索引也包含在联合索引(a,b,c)中了，</p><p>但是当我们执行sql语句<code>select * from table where b=&#39;1&#39; and c=&#39;2&#39;</code>时，发现却没有用到索引，查询效率并没有得到提升，</p><p>执行sql语句<code>select * from table where a=&#39;1&#39; and c=&#39;2&#39;</code>时，却用到了索引，查询效率得到了提升。</p><p>这是为什么呢？</p><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p>联合索引是通过B+树构建的，对于只在单列上构建的非联合索引，在查询时只需要根据B+树顺序查询即可，</p><p>但是对于一个有多个列的联合索引，如果想将多个列的索引都构建在一颗B+树内，也就是通过一个排序方法对多个列进行排序，那么就必须有一个优先级：</p><p>表明优先按照哪个列进行排序，当该列值相等时再更具次优先级的列进行排序，以此类推。</p><p>联合索引便是这样的，当我们在创建联合索引时，就通过列的顺序指定了列在构建B+树时的优先级：联合索引（a,b,c）越靠左侧，优先级越高，a&gt;b&gt;c。</p><p>执行sql语句<code>select * from table where b=&#39;1&#39; and c=&#39;2&#39;</code>时，因为并没有用到列a，对于以列a为第一优先级构建的B+树来说，没有a，单看b和c都是无序的，自然就无法使用该B+树，所以联合索引(a,b,c)没有生效。</p><p>对于：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">table</span> <span class="hljs-keyword">where</span> a<span class="hljs-operator">=</span><span class="hljs-string">&#x27;1&#x27;</span> <span class="hljs-keyword">and</span> b<span class="hljs-operator">=</span><span class="hljs-string">&#x27;3&#x27;</span> <span class="hljs-keyword">and</span> c<span class="hljs-operator">=</span><span class="hljs-string">&#x27;2&#x27;</span>;<br><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">table</span> <span class="hljs-keyword">where</span> a<span class="hljs-operator">=</span><span class="hljs-string">&#x27;1&#x27;</span> <span class="hljs-keyword">and</span> b<span class="hljs-operator">=</span><span class="hljs-string">&#x27;3&#x27;</span>;<br><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">table</span> <span class="hljs-keyword">where</span> a<span class="hljs-operator">=</span><span class="hljs-string">&#x27;1&#x27;</span> <span class="hljs-keyword">and</span> c<span class="hljs-operator">=</span><span class="hljs-string">&#x27;2&#x27;</span>;<br></code></pre></td></tr></table></figure><p>由于都用到了列a，所以可以部分或全部用到联合索引（a,b,c），因为列a在B+树中的顺序不依赖其它列。</p><h2 id="特殊情况"><a href="#特殊情况" class="headerlink" title="特殊情况"></a>特殊情况</h2><h3 id="优化器"><a href="#优化器" class="headerlink" title="优化器"></a>优化器</h3><p>此外像<code>select * from table where c=&#39;1&#39; and a=&#39;3&#39;;</code>的，先用其它列，后用列a的sql语句，按理说也无法用到联合索引（a,b,c），</p><p>但是，MySQL在执行最终sql语句之前，会通过优化器进行sql优化，</p><p>将<code>select * from table where c=&#39;1&#39; and a=&#39;3&#39;;</code>优化为可以使用联合索引的sql语句！</p><h3 id="范围值"><a href="#范围值" class="headerlink" title="范围值"></a>范围值</h3><p>对于范围查询&gt;、&lt;、between，</p><p>例如<code>select * from table where a=&#39;1&#39; and b&gt;100 and c=&#39;2&#39;</code>，a&#x3D;’1’可以用到索引，b&gt;100也可以用到索引，但是b&gt;100查出的是一个范围值，由于只有在b相等时，c才有序，所以一个范围值b，对于c的有序性是没有意义的，c就无法用到索引了。</p><p>当遇到&gt;、&lt;、between时，该条件列还可以使用索引，右边的列就无法使用索引了！</p><h3 id="模糊查询"><a href="#模糊查询" class="headerlink" title="模糊查询"></a>模糊查询</h3><p>对于like模糊匹配，</p><p>例如<code>select * from table where a=&#39;1&#39; and b like &#39;李%&#39; and c=&#39;2&#39;</code>，对于like ‘李%’ 这样的前缀匹配，与上面的范围值是同样的道理，该条件列还可以使用索引，右边的列就无法使用索引了！</p><p>但是对于like ‘%李’ 这样的后缀匹配，因为 ‘李’ 前面可以有任意值，就没有顺序的意义了，该条件列及右边的列都无法使用索引了！</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>由于联合索引的B+树是根据列顺序来先后在原基础上构建的，只有前列相等时后列才有序，所以在使用时，也必须要保证前列所查出的结果为单一值，后列才能使用索引。</p>]]>
    </content>
    <id>https://www.wananhome.site/2024/12/22/MySQL%E7%B4%A2%E5%BC%95%E7%9A%84%E6%9C%80%E5%B7%A6%E5%89%8D%E7%BC%80%E5%8E%9F%E5%88%99/</id>
    <link href="https://www.wananhome.site/2024/12/22/MySQL%E7%B4%A2%E5%BC%95%E7%9A%84%E6%9C%80%E5%B7%A6%E5%89%8D%E7%BC%80%E5%8E%9F%E5%88%99/"/>
    <published>2024-12-21T16:00:00.000Z</published>
    <summary>MySQL索引的最左前缀原则</summary>
    <title>MySQL索引的最左前缀原则</title>
    <updated>2026-03-21T10:00:57.757Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="HashMap" scheme="https://www.wananhome.site/tags/HashMap/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>HashMap是Java程序员使用频率最高的一种映射（&lt;Key,Value&gt;键值对）数据结构，它继承自AbstractMap，又实现了Map类。</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241130192859341-4ae641adddf3.png" class="image-20241130192859341"><p>本文将深入源码解析一下HashMap的底层原理。</p><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p>HashMap底层通过维护一个table数组来实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">transient</span> Node&lt;K,V&gt;[] table;<br></code></pre></td></tr></table></figure><p>table数组里面的元素是一个Node类，用于存储每个键值对。</p><p>Node类是HashMap的一个静态内部类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Node</span>&lt;K,V&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Map</span>.Entry&lt;K,V&gt; &#123;<br>    <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> hash;<br>    <span class="hljs-keyword">final</span> K key;<br>    V value;<br>    Node&lt;K,V&gt; next;<br><br>    Node(<span class="hljs-type">int</span> hash, K key, V value, Node&lt;K,V&gt; next) &#123;<br>        <span class="hljs-built_in">this</span>.hash = hash;<br>        <span class="hljs-built_in">this</span>.key = key;<br>        <span class="hljs-built_in">this</span>.value = value;<br>        <span class="hljs-built_in">this</span>.next = next;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> K <span class="hljs-title function_">getKey</span><span class="hljs-params">()</span>        &#123; <span class="hljs-keyword">return</span> key; &#125;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> V <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span>      &#123; <span class="hljs-keyword">return</span> value; &#125;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> &#123; <span class="hljs-keyword">return</span> key + <span class="hljs-string">&quot;=&quot;</span> + value; &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-title function_">hashCode</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> Objects.hashCode(key) ^ Objects.hashCode(value);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> V <span class="hljs-title function_">setValue</span><span class="hljs-params">(V newValue)</span> &#123;<br>        <span class="hljs-type">V</span> <span class="hljs-variable">oldValue</span> <span class="hljs-operator">=</span> value;<br>        value = newValue;<br>        <span class="hljs-keyword">return</span> oldValue;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">equals</span><span class="hljs-params">(Object o)</span> &#123;<br>        <span class="hljs-keyword">if</span> (o == <span class="hljs-built_in">this</span>)<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        <span class="hljs-keyword">if</span> (o <span class="hljs-keyword">instanceof</span> Map.Entry) &#123;<br>            Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;<br>            <span class="hljs-keyword">if</span> (Objects.equals(key, e.getKey()) &amp;&amp;<br>                Objects.equals(value, e.getValue()))<br>                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>可以看到，在Node类中维护了每个键值对的哈希值：hash，Key值：key，Value值：value，以及用于维护链表或者红黑树（JDK1.8）的下一个Node：next。</p><p>在理想情况下（没有哈希冲突）HashMap的操作时间复杂度可以达到O(1)。为了解决哈希冲突（也就是哈希值重复）的问题，HashMap在table数组中将哈希冲突的Node结点维护成链表或者红黑树。在JDK1.8前（不含JDK1.8），HashMap的数据结构是数组+链表，在JDK1.8及以后优化为数组+链表+红黑树。</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241130201458030-bcc97693821e.png" class="image-20241130201458030"><blockquote><p>图中红黑树并不规范，仅作参考。</p></blockquote><p>在JDK1.7及之前，对于哈希冲突的不同键值对结点，仅将它们维护成一个链表，而对于一个链表的操作，时间复杂度是O(n)，大大影响了HashMap理想情况下O(1)的时间复杂度。因此，在JDK1.8及之后，增加的红黑树来优化，红黑树是一种平衡二叉搜索树，操作的时间复杂度为$O(log_n)$，在元素多的情况下，远优于O(n)：</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241208162115285-066d1bf01fca.png" class="image-20241208162115285"><p>可以看到，在ptuVal方法中，如果链表结点数量超过7（因为一般情况下，链表节点数超过7，性能就会大幅下降），就会树化，调用treeifyBin函数：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">treeifyBin</span><span class="hljs-params">(Node&lt;K,V&gt;[] tab, <span class="hljs-type">int</span> hash)</span> &#123;<br>        <span class="hljs-type">int</span> n, index; Node&lt;K,V&gt; e;<br>        <span class="hljs-keyword">if</span> (tab == <span class="hljs-literal">null</span> || (n = tab.length) &lt; MIN_TREEIFY_CAPACITY)<br>            resize();<br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((e = tab[index = (n - <span class="hljs-number">1</span>) &amp; hash]) != <span class="hljs-literal">null</span>) &#123;<br>            TreeNode&lt;K,V&gt; hd = <span class="hljs-literal">null</span>, tl = <span class="hljs-literal">null</span>;<br>            <span class="hljs-keyword">do</span> &#123;<br>                TreeNode&lt;K,V&gt; p = replacementTreeNode(e, <span class="hljs-literal">null</span>);<br>                <span class="hljs-keyword">if</span> (tl == <span class="hljs-literal">null</span>)<br>                    hd = p;<br>                <span class="hljs-keyword">else</span> &#123;<br>                    p.prev = tl;<br>                    tl.next = p;<br>                &#125;<br>                tl = p;<br>            &#125; <span class="hljs-keyword">while</span> ((e = e.next) != <span class="hljs-literal">null</span>);<br>            <span class="hljs-keyword">if</span> ((tab[index] = hd) != <span class="hljs-literal">null</span>)<br>                hd.treeify(tab);<br>        &#125;<br>    &#125;<br></code></pre></td></tr></table></figure><p>treeifyBin函数会将链表树化，但是，在这之前：</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241208162707432-1df5e5c10120.png" class="image-20241208162707432"><p>可以看到，会先监测数组长度，当数组长度小于64时并不会数化，而是调用resize方法进行扩容！为什么呢？</p><p>这是由于当数组长度较小时，哈希冲突率较高，会产生较多的长链表，那么也就会频繁的执行数化操作，此外树结构会比链表占用更多的空间，而且，对于短数组，大概率会在不久的将来执行扩容操作，又会将树进行拆分，故而，选择在数组长度较小时执行扩容操作。</p><h2 id="Hash方法"><a href="#Hash方法" class="headerlink" title="Hash方法"></a>Hash方法</h2><p>当我们使用put方法向HashMap中加入一个键值对时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> V <span class="hljs-title function_">put</span><span class="hljs-params">(K key, V value)</span> &#123;<br>        <span class="hljs-keyword">return</span> putVal(hash(key), key, value, <span class="hljs-literal">false</span>, <span class="hljs-literal">true</span>);<br>    &#125;<br></code></pre></td></tr></table></figure><p>在put方法内调用了putVal方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> V <span class="hljs-title function_">putVal</span><span class="hljs-params">(<span class="hljs-type">int</span> hash, K key, V value, <span class="hljs-type">boolean</span> onlyIfAbsent,</span><br><span class="hljs-params">               <span class="hljs-type">boolean</span> evict)</span> &#123;<br>    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; <span class="hljs-type">int</span> n, i;<br>    <span class="hljs-keyword">if</span> ((tab = table) == <span class="hljs-literal">null</span> || (n = tab.length) == <span class="hljs-number">0</span>)<br>        n = (tab = resize()).length;<br>    <span class="hljs-keyword">if</span> ((p = tab[i = (n - <span class="hljs-number">1</span>) &amp; hash]) == <span class="hljs-literal">null</span>)<br>        tab[i] = newNode(hash, key, value, <span class="hljs-literal">null</span>);<br>    <span class="hljs-keyword">else</span> &#123;<br>        Node&lt;K,V&gt; e; K k;<br>        <span class="hljs-keyword">if</span> (p.hash == hash &amp;&amp;<br>            ((k = p.key) == key || (key != <span class="hljs-literal">null</span> &amp;&amp; key.equals(k))))<br>            e = p;<br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (p <span class="hljs-keyword">instanceof</span> TreeNode)<br>            e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(<span class="hljs-built_in">this</span>, tab, hash, key, value);<br>        <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">binCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; ; ++binCount) &#123;<br>                <span class="hljs-keyword">if</span> ((e = p.next) == <span class="hljs-literal">null</span>) &#123;<br>                    p.next = newNode(hash, key, value, <span class="hljs-literal">null</span>);<br>                    <span class="hljs-keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD - <span class="hljs-number">1</span>) <span class="hljs-comment">// -1 for 1st</span><br>                        treeifyBin(tab, hash);<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>                <span class="hljs-keyword">if</span> (e.hash == hash &amp;&amp;<br>                    ((k = e.key) == key || (key != <span class="hljs-literal">null</span> &amp;&amp; key.equals(k))))<br>                    <span class="hljs-keyword">break</span>;<br>                p = e;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">if</span> (e != <span class="hljs-literal">null</span>) &#123; <span class="hljs-comment">// existing mapping for key</span><br>            <span class="hljs-type">V</span> <span class="hljs-variable">oldValue</span> <span class="hljs-operator">=</span> e.value;<br>            <span class="hljs-keyword">if</span> (!onlyIfAbsent || oldValue == <span class="hljs-literal">null</span>)<br>                e.value = value;<br>            afterNodeAccess(e);<br>            <span class="hljs-keyword">return</span> oldValue;<br>        &#125;<br>    &#125;<br>    ++modCount;<br>    <span class="hljs-keyword">if</span> (++size &gt; threshold)<br>        resize();<br>    afterNodeInsertion(evict);<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>&#125;<br></code></pre></td></tr></table></figure><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241130202729239-bb5ea9f93a85.png" class="image-20241130202729239"><p>可以看到，在putVal方法中会根据<em>hash</em>(key)生成的hash值来计算Node在table数组中的位置索引。</p><p>也就是通过(n - 1) &amp; hash来计算Node的位置索引，hash就是通过hash方法算出的哈希值，n就是table数组的长度(元素个数)。</p><p>那么为什么通过(n - 1) &amp; hash来计算Node的索引呢？</p><p>我们知道，对于一个数集[0,1,2,…,k]，如果想要让一个非负数数据集B内的数据全部映射到A内，可以通过取余运算来完成：B[i] % (k+1)</p><p>此时，如果（k+1）为2的n次方，那么B[i] % (k+1)&#x3D;B[i] &amp; k。</p><p>也就是说，如果table的长度n为2的某个次方，那么(n - 1) &amp; hash等价于hash%n，又因为对于二进制的计算机来说，位运算是比取余运算更快的，所以这里巧妙的采用(n - 1) &amp; hash来计算Node的索引。</p><p>我们知道了hash值的作用，是用来确定Node在table数组中的位置的，接下来让我们看看hash方法是怎么算出hash值的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-title function_">hash</span><span class="hljs-params">(Object key)</span> &#123;<br>       <span class="hljs-type">int</span> h;<br>       <span class="hljs-keyword">return</span> (key == <span class="hljs-literal">null</span>) ? <span class="hljs-number">0</span> : (h = key.hashCode()) ^ (h &gt;&gt;&gt; <span class="hljs-number">16</span>);<br>   &#125;<br></code></pre></td></tr></table></figure><p>可以看到，如果key值不为空的话，将会通过key的哈希码和key的哈希码逻辑右移16位后的值进行异或运算：(h &#x3D; key.hashCode()) ^ (h &gt;&gt;&gt; 16)</p><p>哈希码是通过key对象本身的Object.hashCode()方法得到的。</p><p>对于将key的哈希码逻辑右移16位，我们可以看到h也就是key的哈希码是int类型的，占32位，逻辑右移16位后得到其高16位。也就是将key哈希码的高16位与低16位进行异或运算，作用是使hash值更均匀分散，降低哈希冲突率。</p><p>此外，当两个数据哈希冲突时，会根据equals方法来判断，如果相等，则直接覆盖原数据，如果不等，则维护成链表或红黑树（JDK1.8及以后）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">equals</span><span class="hljs-params">(Object o)</span> &#123;<br>          <span class="hljs-keyword">if</span> (o == <span class="hljs-built_in">this</span>)<br>              <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>          <span class="hljs-keyword">if</span> (o <span class="hljs-keyword">instanceof</span> Map.Entry) &#123;<br>              Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;<br>              <span class="hljs-keyword">if</span> (Objects.equals(key, e.getKey()) &amp;&amp;<br>                  Objects.equals(value, e.getValue()))<br>                  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>          &#125;<br>          <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>      &#125;<br>  &#125;<br></code></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>我们在构建HashMap时，可以通过构造函数传参来自定义初始容量和负载因子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-title function_">HashMap</span><span class="hljs-params">(<span class="hljs-type">int</span> initialCapacity, <span class="hljs-type">float</span> loadFactor)</span>&#123;...&#125;<br></code></pre></td></tr></table></figure><p>HashMap的最大容量是2的30次方：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-variable">MAXIMUM_CAPACITY</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">30</span>;<br></code></pre></td></tr></table></figure><p>HashMap给的默认初始容量是16：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-variable">DEFAULT_INITIAL_CAPACITY</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">4</span>;<br></code></pre></td></tr></table></figure><p>那么，什么时候需要扩容呢？</p><p>HashMap会在容量大于阈值时触发扩容机制，这个阈值是由容量与负载因子的乘积求得的，负载因子loadFactor的默认值为0.75：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">float</span> <span class="hljs-variable">DEFAULT_LOAD_FACTOR</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.75f</span><br></code></pre></td></tr></table></figure><p>那么，为什么加载因子是0.75呢？</p><p>加载因子是用来表示HashMap中数据的填满程度的，也就是说，加载因子&#x3D;HashMap中数据的个数&#x2F;HashMap的容量，根据这个公式，可得：</p><ul><li>加载因子越大，空间利用率就越高，但是哈希冲突率也会越高</li><li>加载因子越小，哈希冲突率就越低，但是空间利用率也会越低</li></ul><p>鱼与熊掌不可兼得！这就必须要求我们取一个中庸之值，至于如何取得0.75这个值，这里面涉及了泊松分布和指数分布等统计与概率学的知识，大家感兴趣自行研究！</p><p>如果HashMap的数据量超过了阈值，就会调用resize()方法进行扩容：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> Node&lt;K,V&gt;[] resize() &#123;<br>        Node&lt;K,V&gt;[] oldTab = table;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">oldCap</span> <span class="hljs-operator">=</span> (oldTab == <span class="hljs-literal">null</span>) ? <span class="hljs-number">0</span> : oldTab.length;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">oldThr</span> <span class="hljs-operator">=</span> threshold;<br>        <span class="hljs-type">int</span> newCap, newThr = <span class="hljs-number">0</span>;<br>        <span class="hljs-keyword">if</span> (oldCap &gt; <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-keyword">if</span> (oldCap &gt;= MAXIMUM_CAPACITY) &#123;<br>                threshold = Integer.MAX_VALUE;<br>                <span class="hljs-keyword">return</span> oldTab;<br>            &#125;<br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((newCap = oldCap &lt;&lt; <span class="hljs-number">1</span>) &lt; MAXIMUM_CAPACITY &amp;&amp;<br>                     oldCap &gt;= DEFAULT_INITIAL_CAPACITY)<br>                newThr = oldThr &lt;&lt; <span class="hljs-number">1</span>; <span class="hljs-comment">// double threshold</span><br>        &#125;<br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (oldThr &gt; <span class="hljs-number">0</span>) <span class="hljs-comment">// initial capacity was placed in threshold</span><br>            newCap = oldThr;<br>        <span class="hljs-keyword">else</span> &#123;               <span class="hljs-comment">// zero initial threshold signifies using defaults</span><br>            newCap = DEFAULT_INITIAL_CAPACITY;<br>            newThr = (<span class="hljs-type">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);<br>        &#125;<br>        <span class="hljs-keyword">if</span> (newThr == <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-type">float</span> <span class="hljs-variable">ft</span> <span class="hljs-operator">=</span> (<span class="hljs-type">float</span>)newCap * loadFactor;<br>            newThr = (newCap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; (<span class="hljs-type">float</span>)MAXIMUM_CAPACITY ?<br>                      (<span class="hljs-type">int</span>)ft : Integer.MAX_VALUE);<br>        &#125;<br>        threshold = newThr;<br>        <span class="hljs-meta">@SuppressWarnings(&#123;&quot;rawtypes&quot;,&quot;unchecked&quot;&#125;)</span><br>        Node&lt;K,V&gt;[] newTab = (Node&lt;K,V&gt;[])<span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>[newCap];<br>        table = newTab;<br>        <span class="hljs-keyword">if</span> (oldTab != <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; j &lt; oldCap; ++j) &#123;<br>                Node&lt;K,V&gt; e;<br>                <span class="hljs-keyword">if</span> ((e = oldTab[j]) != <span class="hljs-literal">null</span>) &#123;<br>                    oldTab[j] = <span class="hljs-literal">null</span>;<br>                    <span class="hljs-keyword">if</span> (e.next == <span class="hljs-literal">null</span>)<br>                        newTab[e.hash &amp; (newCap - <span class="hljs-number">1</span>)] = e;<br>                    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (e <span class="hljs-keyword">instanceof</span> TreeNode)<br>                        ((TreeNode&lt;K,V&gt;)e).split(<span class="hljs-built_in">this</span>, newTab, j, oldCap);<br>                    <span class="hljs-keyword">else</span> &#123; <span class="hljs-comment">// preserve order</span><br>                        Node&lt;K,V&gt; loHead = <span class="hljs-literal">null</span>, loTail = <span class="hljs-literal">null</span>;<br>                        Node&lt;K,V&gt; hiHead = <span class="hljs-literal">null</span>, hiTail = <span class="hljs-literal">null</span>;<br>                        Node&lt;K,V&gt; next;<br>                        <span class="hljs-keyword">do</span> &#123;<br>                            next = e.next;<br>                            <span class="hljs-keyword">if</span> ((e.hash &amp; oldCap) == <span class="hljs-number">0</span>) &#123;<br>                                <span class="hljs-keyword">if</span> (loTail == <span class="hljs-literal">null</span>)<br>                                    loHead = e;<br>                                <span class="hljs-keyword">else</span><br>                                    loTail.next = e;<br>                                loTail = e;<br>                            &#125;<br>                            <span class="hljs-keyword">else</span> &#123;<br>                                <span class="hljs-keyword">if</span> (hiTail == <span class="hljs-literal">null</span>)<br>                                    hiHead = e;<br>                                <span class="hljs-keyword">else</span><br>                                    hiTail.next = e;<br>                                hiTail = e;<br>                            &#125;<br>                        &#125; <span class="hljs-keyword">while</span> ((e = next) != <span class="hljs-literal">null</span>);<br>                        <span class="hljs-keyword">if</span> (loTail != <span class="hljs-literal">null</span>) &#123;<br>                            loTail.next = <span class="hljs-literal">null</span>;<br>                            newTab[j] = loHead;<br>                        &#125;<br>                        <span class="hljs-keyword">if</span> (hiTail != <span class="hljs-literal">null</span>) &#123;<br>                            hiTail.next = <span class="hljs-literal">null</span>;<br>                            newTab[j + oldCap] = hiHead;<br>                        &#125;<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> newTab;<br>    &#125;<br></code></pre></td></tr></table></figure><p>扩容机制会有以下几个主要流程：</p><ul><li><p>如果已经超过了HashMap最大容量<em>MAXIMUM_CAPACITY</em>，就不再扩容，将阈值设为Integer.<em>MAX_VALUE</em>（2的31次方减1），直接返回。</p></li><li><p>将数组table容量扩大两倍，由于Java没有动态数组，需要新创建一个两倍大小的数组，然后再将原数组中的数据都移至新数组。</p></li><li><p>将数据从原数组移至新数组时，在JDK1.8之前需要重新计算hash值，重新确定数组索引位置；在JDK1.8及以后，为了使数据更分散，对于链表数据的索引做了优化：我们通过(n - 1) &amp; hash来计算索引位置，由于数组长度n是2倍扩展，所以新的（n-1）比旧的（n-1）只是高位多了一个1，结果就是：如果hash不变的情况下，</p><ul><li>若hash多出来的高位是1，那么计算出来的新索引刚好就是原索引值+原数组长度</li><li>若hash多出来的高位是0，那么索引不变。</li></ul><p>优化以后，转移链表数据至新数组时，就不用再重新计算hash值，提高了效率。</p></li><li><p>对于红黑树，会通过split方法，将其拆分为新树或链表：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (e <span class="hljs-keyword">instanceof</span> TreeNode)<br>   ((TreeNode&lt;K,V&gt;)e).split(<span class="hljs-built_in">this</span>, newTab, j, oldCap);<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">split</span><span class="hljs-params">(HashMap&lt;K,V&gt; map, Node&lt;K,V&gt;[] tab, <span class="hljs-type">int</span> index, <span class="hljs-type">int</span> bit)</span> &#123;<br>            TreeNode&lt;K,V&gt; b = <span class="hljs-built_in">this</span>;<br>            <span class="hljs-comment">// Relink into lo and hi lists, preserving order</span><br>            TreeNode&lt;K,V&gt; loHead = <span class="hljs-literal">null</span>, loTail = <span class="hljs-literal">null</span>;<br>            TreeNode&lt;K,V&gt; hiHead = <span class="hljs-literal">null</span>, hiTail = <span class="hljs-literal">null</span>;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">lc</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>, hc = <span class="hljs-number">0</span>;<br>            <span class="hljs-keyword">for</span> (TreeNode&lt;K,V&gt; e = b, next; e != <span class="hljs-literal">null</span>; e = next) &#123;<br>                next = (TreeNode&lt;K,V&gt;)e.next;<br>                e.next = <span class="hljs-literal">null</span>;<br>                <span class="hljs-keyword">if</span> ((e.hash &amp; bit) == <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-keyword">if</span> ((e.prev = loTail) == <span class="hljs-literal">null</span>)<br>                        loHead = e;<br>                    <span class="hljs-keyword">else</span><br>                        loTail.next = e;<br>                    loTail = e;<br>                    ++lc;<br>                &#125;<br>                <span class="hljs-keyword">else</span> &#123;<br>                    <span class="hljs-keyword">if</span> ((e.prev = hiTail) == <span class="hljs-literal">null</span>)<br>                        hiHead = e;<br>                    <span class="hljs-keyword">else</span><br>                        hiTail.next = e;<br>                    hiTail = e;<br>                    ++hc;<br>                &#125;<br>            &#125;<br><br>            <span class="hljs-keyword">if</span> (loHead != <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-keyword">if</span> (lc &lt;= UNTREEIFY_THRESHOLD)<br>                    tab[index] = loHead.untreeify(map);<br>                <span class="hljs-keyword">else</span> &#123;<br>                    tab[index] = loHead;<br>                    <span class="hljs-keyword">if</span> (hiHead != <span class="hljs-literal">null</span>) <span class="hljs-comment">// (else is already treeified)</span><br>                        loHead.treeify(tab);<br>                &#125;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (hiHead != <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-keyword">if</span> (hc &lt;= UNTREEIFY_THRESHOLD)<br>                    tab[index + bit] = hiHead.untreeify(map);<br>                <span class="hljs-keyword">else</span> &#123;<br>                    tab[index + bit] = hiHead;<br>                    <span class="hljs-keyword">if</span> (loHead != <span class="hljs-literal">null</span>)<br>                        hiHead.treeify(tab);<br>                &#125;<br>            &#125;<br>        &#125;<br></code></pre></td></tr></table></figure></li></ul><p>​<img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241208164202700-04938aa0116b.png" class="image-20241208164202700"></p><p>可以看到，调用split方法重构树时，如果树的结点数小于等于6，就会调用untreeify方法进行链表化，这是因为，当结点数小于等于6时，树提供的操作效率已经无法弥补它占用的大量空间，此时需要对时间与空间进行妥协，将树转为链表。那么为什么是小于等于6，而不是小于等于7呢？</p><p>这是为了留个缓冲余地，避免结点数在7、8之间频繁切换导致链表与树的频繁转化，导致性能的浪费。</p><h2 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h2><p>JDK1.7及其之前，多线程的情况下，在扩容时，链表在从旧数组转移到新数组时可能会产生死循环的问题（转移链表时采用的是单指针头插法）：</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241201210108068-a3688b42cd41.png" class="image-20241201210108068"><p>对于链表A-&gt;B-&gt;C：</p><p>线程P1刚要开始迁移，让指针p指向A，此时，线程P1的时间片用完，CPU调度线程P2开始执行。</p><p>线程P2完整执行了整个链表迁移工作，使链表变成：</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241201210057626-b45a3e9c1769.png" class="image-20241201210057626"><p>此时，CPU调度线程P1继续执行:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">p.next = newTable[i];  <span class="hljs-comment">// A.next = C</span><br>newTable[i] = p;   <br>p = next;  <br></code></pre></td></tr></table></figure><p>由于新链表头结点已经由null转变成了C，导致A与C结点构成了循环：</p><img src="/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/image-20241201205909992-77a00d986d21.png" class="image-20241201205909992"><p>JDK1.8及其之后，虽然通过双指针尾插法消除了这个问题，但依然存在其它线程安全问题，在多线程的环境下，我们应该使用线程安全的ConcurrentHashMap类。</p>]]>
    </content>
    <id>https://www.wananhome.site/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/</id>
    <link href="https://www.wananhome.site/2024/12/08/Java%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3HashMap/"/>
    <published>2024-12-07T16:00:00.000Z</published>
    <summary>通过阅读源码，详细理解HashMap</summary>
    <title>Java之深入理解HashMap</title>
    <updated>2026-03-21T09:35:29.890Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="Java" scheme="https://www.wananhome.site/categories/Java/"/>
    <category term="ConcurrentHashMap" scheme="https://www.wananhome.site/tags/ConcurrentHashMap/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>对于Java程序员来说，HashMap是他们用到最多的映射（键值对）数据结构，但是HashMap是线程不安全的，在多线程的环境下，建议使用线程安全的ConcurrentHashMap。</p><h2 id="锁机制"><a href="#锁机制" class="headerlink" title="锁机制"></a>锁机制</h2><p>在JDK1.7及其之前，ConcurrentHashMap采用的是分段锁，也就是先将ConcurrentHashMap分成一定数量的段Segment，构成一个Segment数组，数组中的元素才是HashMap的键值对Node结点。可以理解为维护了一个Segment数组，数组中的元素是HashMap。</p><p>如此，在加锁时只需要对每个Segment段进行加锁即可，无需对整个ConcurrentHashMap加锁，提高了并发效率。</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201184619140-27ff68569169.png" class="image-20241201184619140"><p>但是，对Segment段加锁，会导致统一段内的其它Node结点也无法被其它线程访问。</p><p>在JDK1.8及其之后，取消了分段机制，保留原有HashMap的数据结构，单独只对某一个Node结点进行加锁，不会影响其它结点，大大提高了锁粒度，提高了并发效率。</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201185321379-39f92baadd12.png" class="image-20241201185321379"><p>在JDK1.8及其之后，使用synchronized关键字以及CAS自旋锁保证多线程安全性。</p><p>当我们使用put方法新增键值对时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> V <span class="hljs-title function_">put</span><span class="hljs-params">(K key, V value)</span> &#123;<br>        <span class="hljs-keyword">return</span> putVal(key, value, <span class="hljs-literal">false</span>);<br>    &#125;<br></code></pre></td></tr></table></figure><p>会调用putVal方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> V <span class="hljs-title function_">putVal</span><span class="hljs-params">(K key, V value, <span class="hljs-type">boolean</span> onlyIfAbsent)</span> &#123;<br>        <span class="hljs-keyword">if</span> (key == <span class="hljs-literal">null</span> || value == <span class="hljs-literal">null</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NullPointerException</span>();<br>        <span class="hljs-type">int</span> <span class="hljs-variable">hash</span> <span class="hljs-operator">=</span> spread(key.hashCode());<br>        <span class="hljs-type">int</span> <span class="hljs-variable">binCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>        <span class="hljs-keyword">for</span> (Node&lt;K,V&gt;[] tab = table;;) &#123;<br>            Node&lt;K,V&gt; f; <span class="hljs-type">int</span> n, i, fh;<br>            <span class="hljs-keyword">if</span> (tab == <span class="hljs-literal">null</span> || (n = tab.length) == <span class="hljs-number">0</span>)<br>                tab = initTable();<br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((f = tabAt(tab, i = (n - <span class="hljs-number">1</span>) &amp; hash)) == <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-keyword">if</span> (casTabAt(tab, i, <span class="hljs-literal">null</span>,<br>                             <span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>&lt;K,V&gt;(hash, key, value, <span class="hljs-literal">null</span>)))<br>                    <span class="hljs-keyword">break</span>;                   <span class="hljs-comment">// no lock when adding to empty bin</span><br>            &#125;<br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((fh = f.hash) == MOVED)<br>                tab = helpTransfer(tab, f);<br>            <span class="hljs-keyword">else</span> &#123;<br>                <span class="hljs-type">V</span> <span class="hljs-variable">oldVal</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>                <span class="hljs-keyword">synchronized</span> (f) &#123;<br>                    <span class="hljs-keyword">if</span> (tabAt(tab, i) == f) &#123;<br>                        <span class="hljs-keyword">if</span> (fh &gt;= <span class="hljs-number">0</span>) &#123;<br>                            binCount = <span class="hljs-number">1</span>;<br>                            <span class="hljs-keyword">for</span> (Node&lt;K,V&gt; e = f;; ++binCount) &#123;<br>                                K ek;<br>                                <span class="hljs-keyword">if</span> (e.hash == hash &amp;&amp;<br>                                    ((ek = e.key) == key ||<br>                                     (ek != <span class="hljs-literal">null</span> &amp;&amp; key.equals(ek)))) &#123;<br>                                    oldVal = e.val;<br>                                    <span class="hljs-keyword">if</span> (!onlyIfAbsent)<br>                                        e.val = value;<br>                                    <span class="hljs-keyword">break</span>;<br>                                &#125;<br>                                Node&lt;K,V&gt; pred = e;<br>                                <span class="hljs-keyword">if</span> ((e = e.next) == <span class="hljs-literal">null</span>) &#123;<br>                                    pred.next = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>&lt;K,V&gt;(hash, key,<br>                                                              value, <span class="hljs-literal">null</span>);<br>                                    <span class="hljs-keyword">break</span>;<br>                                &#125;<br>                            &#125;<br>                        &#125;<br>                        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (f <span class="hljs-keyword">instanceof</span> TreeBin) &#123;<br>                            Node&lt;K,V&gt; p;<br>                            binCount = <span class="hljs-number">2</span>;<br>                            <span class="hljs-keyword">if</span> ((p = ((TreeBin&lt;K,V&gt;)f).putTreeVal(hash, key,<br>                                                           value)) != <span class="hljs-literal">null</span>) &#123;<br>                                oldVal = p.val;<br>                                <span class="hljs-keyword">if</span> (!onlyIfAbsent)<br>                                    p.val = value;<br>                            &#125;<br>                        &#125;<br>                    &#125;<br>                &#125;<br>                <span class="hljs-keyword">if</span> (binCount != <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD)<br>                        treeifyBin(tab, i);<br>                    <span class="hljs-keyword">if</span> (oldVal != <span class="hljs-literal">null</span>)<br>                        <span class="hljs-keyword">return</span> oldVal;<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>            &#125;<br>        &#125;<br>        addCount(<span class="hljs-number">1L</span>, binCount);<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>    &#125;<br></code></pre></td></tr></table></figure><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201190043657-9b668417399d.png" class="image-20241201190043657"><p>我们可以看到，当通过 (f &#x3D; <em>tabAt</em>(tab, i &#x3D; (n - 1) &amp; hash)) &#x3D;&#x3D; null 判断数组索引位置上没有数据时，就会通过casTabAt方法将Node结点直接加进去：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> &lt;K,V&gt; Node&lt;K,V&gt; <span class="hljs-title function_">tabAt</span><span class="hljs-params">(Node&lt;K,V&gt;[] tab, <span class="hljs-type">int</span> i)</span> &#123;<br>        <span class="hljs-keyword">return</span> (Node&lt;K,V&gt;)U.getObjectVolatile(tab, ((<span class="hljs-type">long</span>)i &lt;&lt; ASHIFT) + ABASE);<br>    &#125;<br></code></pre></td></tr></table></figure><p>tabAt方法会调用Unsafe类内的getObjectVolatile方法，该方法通过直接从内存中取值，确保数据是其它线程修改后上传的最新值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> &lt;K,V&gt; <span class="hljs-type">boolean</span> <span class="hljs-title function_">casTabAt</span><span class="hljs-params">(Node&lt;K,V&gt;[] tab, <span class="hljs-type">int</span> i,</span><br><span class="hljs-params">                                        Node&lt;K,V&gt; c, Node&lt;K,V&gt; v)</span> &#123;<br>        <span class="hljs-keyword">return</span> U.compareAndSwapObject(tab, ((<span class="hljs-type">long</span>)i &lt;&lt; ASHIFT) + ABASE, c, v);<br>    &#125;<br></code></pre></td></tr></table></figure><p>casTabAt方法会调用Unsafe类内的compareAndSwapObject方法，该方法通过CAS的方式保证将数据安全的修改。</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201191123534-9b74fee4c903.png" class="image-20241201191123534"><p>可以看到，如果数组索引位上已经有结点数据的情况下，会使用synchronized锁住该结点，如果结点后面是链表或者红黑树，也可以保证只有当前线程可以访问（因为锁住了根结点，其它线程拿不到根结点，就无法向后遍历）。</p><h2 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h2><p>在JDK1.7及其之前，Segment数组无法进行扩容，如果某个Segment内的Node数组达到了阈值，就单独对该Node数组进行扩容。这样，扩容的次数越多，每个Segment内的数组就越长，锁的灵活性就越差。</p><p>在JDK1.8及其之后，与原HashMap扩容机制基本一致，不过加入了多线程协助扩容机制：</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201192329427-213b75b5bd53.png" class="image-20241201192329427"><p>可以看到，如果结点的hash值为MOVED也就是-1，就认为当前数组正在扩容，就会调用helpTransfer方法协助扩容：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> Node&lt;K,V&gt;[] helpTransfer(Node&lt;K,V&gt;[] tab, Node&lt;K,V&gt; f) &#123;<br>        Node&lt;K,V&gt;[] nextTab; <span class="hljs-type">int</span> sc;<br>        <span class="hljs-keyword">if</span> (tab != <span class="hljs-literal">null</span> &amp;&amp; (f <span class="hljs-keyword">instanceof</span> ForwardingNode) &amp;&amp;<br>            (nextTab = ((ForwardingNode&lt;K,V&gt;)f).nextTable) != <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">rs</span> <span class="hljs-operator">=</span> resizeStamp(tab.length) &lt;&lt; RESIZE_STAMP_SHIFT;<br>            <span class="hljs-keyword">while</span> (nextTab == nextTable &amp;&amp; table == tab &amp;&amp;<br>                   (sc = sizeCtl) &lt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-keyword">if</span> (sc == rs + MAX_RESIZERS || sc == rs + <span class="hljs-number">1</span> ||<br>                    transferIndex &lt;= <span class="hljs-number">0</span>)<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">if</span> (U.compareAndSwapInt(<span class="hljs-built_in">this</span>, SIZECTL, sc, sc + <span class="hljs-number">1</span>)) &#123;<br>                    transfer(tab, nextTab);<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>            &#125;<br>            <span class="hljs-keyword">return</span> nextTab;<br>        &#125;<br>        <span class="hljs-keyword">return</span> table;<br>    &#125;<br></code></pre></td></tr></table></figure><h2 id="总数"><a href="#总数" class="headerlink" title="总数"></a>总数</h2><p>多线程环境下，如何保证size方法获取到的结点总数量是正确的？</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">size</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-type">long</span> <span class="hljs-variable">n</span> <span class="hljs-operator">=</span> sumCount();<br>    <span class="hljs-keyword">return</span> ((n &lt; <span class="hljs-number">0L</span>) ? <span class="hljs-number">0</span> :<br>            (n &gt; (<span class="hljs-type">long</span>)Integer.MAX_VALUE) ? Integer.MAX_VALUE :<br>            (<span class="hljs-type">int</span>)n);<br>&#125;<br><br></code></pre></td></tr></table></figure><p>size方法调用了sumCount方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> <span class="hljs-type">long</span> <span class="hljs-title function_">sumCount</span><span class="hljs-params">()</span> &#123;<br>        CounterCell[] as = counterCells; CounterCell a;<br>        <span class="hljs-type">long</span> <span class="hljs-variable">sum</span> <span class="hljs-operator">=</span> baseCount;<br>        <span class="hljs-keyword">if</span> (as != <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; as.length; ++i) &#123;<br>                <span class="hljs-keyword">if</span> ((a = as[i]) != <span class="hljs-literal">null</span>)<br>                    sum += a.value;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> sum;<br>    &#125;<br></code></pre></td></tr></table></figure><p>可以看到，除了基础的总数baseCount外，ConcurrentHashMap还维护了一个CounterCell数组，通过将baseCount与CounterCell数组中的每个数相加得到最终总数。</p><p>在putVal方法的最后调用了addCount方法：</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201193314927-8e82c6e3a6ca.png" class="image-20241201193314927"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addCount</span><span class="hljs-params">(<span class="hljs-type">long</span> x, <span class="hljs-type">int</span> check)</span> &#123;<br>        CounterCell[] as; <span class="hljs-type">long</span> b, s;<br>        <span class="hljs-keyword">if</span> ((as = counterCells) != <span class="hljs-literal">null</span> ||<br>            !U.compareAndSwapLong(<span class="hljs-built_in">this</span>, BASECOUNT, b = baseCount, s = b + x)) &#123;<br>            CounterCell a; <span class="hljs-type">long</span> v; <span class="hljs-type">int</span> m;<br>            <span class="hljs-type">boolean</span> <span class="hljs-variable">uncontended</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br>            <span class="hljs-keyword">if</span> (as == <span class="hljs-literal">null</span> || (m = as.length - <span class="hljs-number">1</span>) &lt; <span class="hljs-number">0</span> ||<br>                (a = as[ThreadLocalRandom.getProbe() &amp; m]) == <span class="hljs-literal">null</span> ||<br>                !(uncontended =<br>                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) &#123;<br>                fullAddCount(x, uncontended);<br>                <span class="hljs-keyword">return</span>;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (check &lt;= <span class="hljs-number">1</span>)<br>                <span class="hljs-keyword">return</span>;<br>            s = sumCount();<br>        &#125;<br>        <span class="hljs-keyword">if</span> (check &gt;= <span class="hljs-number">0</span>) &#123;<br>            Node&lt;K,V&gt;[] tab, nt; <span class="hljs-type">int</span> n, sc;<br>            <span class="hljs-keyword">while</span> (s &gt;= (<span class="hljs-type">long</span>)(sc = sizeCtl) &amp;&amp; (tab = table) != <span class="hljs-literal">null</span> &amp;&amp;<br>                   (n = tab.length) &lt; MAXIMUM_CAPACITY) &#123;<br>                <span class="hljs-type">int</span> <span class="hljs-variable">rs</span> <span class="hljs-operator">=</span> resizeStamp(n) &lt;&lt; RESIZE_STAMP_SHIFT;<br>                <span class="hljs-keyword">if</span> (sc &lt; <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-keyword">if</span> (sc == rs + MAX_RESIZERS || sc == rs + <span class="hljs-number">1</span> ||<br>                        (nt = nextTable) == <span class="hljs-literal">null</span> || transferIndex &lt;= <span class="hljs-number">0</span>)<br>                        <span class="hljs-keyword">break</span>;<br>                    <span class="hljs-keyword">if</span> (U.compareAndSwapInt(<span class="hljs-built_in">this</span>, SIZECTL, sc, sc + <span class="hljs-number">1</span>))<br>                        transfer(tab, nt);<br>                &#125;<br>                <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (U.compareAndSwapInt(<span class="hljs-built_in">this</span>, SIZECTL, sc, rs + <span class="hljs-number">2</span>))<br>                    transfer(tab, <span class="hljs-literal">null</span>);<br>                s = sumCount();<br>            &#125;<br>        &#125;<br>    &#125;<br></code></pre></td></tr></table></figure><p>addCount会先通过CAS尝试直接增加baseCount：</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201193739162-12b4d09f3e8d.png" class="image-20241201193739162"><p>如果失败，则随机在CounterCell数组内增加：</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201193932755-5cb519fe4a41.png" class="image-20241201193932755"><p>增加完总数后，还要判断一下是否需要扩容：</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201194108145-65c98ef0a238.png" class="image-20241201194108145"><h2 id="CAS"><a href="#CAS" class="headerlink" title="CAS"></a>CAS</h2><p>CAS全称CompareAndSwap，也就是自旋锁。</p><p>其本质上并不是锁，只是通过循环比较来不断尝试：</p><p>CAS会拿到一个要修改的旧值A，想将其改为新值B，然后访问内存中的当前值C，</p><p>判断如果内存值C与自己的旧值A不相等，则认为当前有其它线程在访问该数据，自己就先放弃修改，</p><p>然后循环再次比较C与A，如果一直不相等就一直循环直至相等，</p><p>此时如果内存值C与自己的旧值A相等，那么认为当前没有其它线程访问该数据，将该数据该为新值B，结束CAS。</p><p>CAS会直接从内存中读取数据，并且可以保证读写操作的原子性。</p><p>需要注意的是，CAS并不能保证数据的可见性，它无法保证从内存中读取的数据C是最新的值，也无法保证修改后的值B可以被其他线程同步。我们应该结合可以保证数据可见性的关键字volatile使用：</p><img src="/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/image-20241201200256587-6eeb7df25cfc.png" class="image-20241201200256587">]]>
    </content>
    <id>https://www.wananhome.site/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/</id>
    <link href="https://www.wananhome.site/2024/12/01/Java%E4%B9%8BConcurrentHashMap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%8E%9F%E7%90%86/"/>
    <published>2024-11-30T16:00:00.000Z</published>
    <summary>通过阅读源码，详细理解ConcurrentHashMap</summary>
    <title>Java之ConcurrentHashMap线程安全原理</title>
    <updated>2026-03-21T09:37:14.575Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="数据结构与算法" scheme="https://www.wananhome.site/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"/>
    <category term="数据结构" scheme="https://www.wananhome.site/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>我们知道，在计算机中，数字以0和1组成的二进制序列来表示。但是，对于非常大的数字以及非常接近0的数字，简单的存储方式往往会造成精度的丢失。</p><p>为了解决这个问题，提供更高效的浮点数表示方法，各大厂商纷纷推出自己的浮点数表示标准。大约在1985年，IEEE（电气和电子工程师协会）推出了745标准，从此，江湖一统。</p><h2 id="IEEE754标准浮点格式"><a href="#IEEE754标准浮点格式" class="headerlink" title="IEEE754标准浮点格式"></a>IEEE754标准浮点格式</h2><p>IEEE754标准浮点格式由三部分组成：$(-1)^S<em>M</em>2^E$</p><ul><li>S：符号，表示浮点数是正数（s&#x3D;0）还是负数（s&#x3D;1）</li><li>E：阶码，对浮点数进行加权。阶码被解释为以偏置形式表示的有符号整数，偏置值（Bias）为$2^{(n-1)}-1$，n为e所占的位数（Bias单精度为$2^{(8-1)}-1&#x3D;127_{10}&#x3D;0111 1111_2$，双精度为$2^{(11-1)}-1&#x3D;1023$）。</li><li>M：尾数，用于表示小数</li></ul><p>如下图所示，IEEE754标准浮点格式下的单精度浮点数占32位：符号占1位，阶码占8位，尾数占23位；双精度浮点数占64位。</p><img src="/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/image-20241123201454656-068ae2673990.png" class="image-20241123201454656"><h3 id="规格化的值"><a href="#规格化的值" class="headerlink" title="规格化的值"></a>规格化的值</h3><p>当阶码位既不全为0也不全为1时，是规格化浮点数。此时E&#x3D;e-Bias，M&gt;&#x3D;1。</p><img src="/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/image-20241123205914917-1901baae793f.png" class="image-20241123205914917"><p>例子：十进制的$(-0.75)_{10}$，将其转化为IEEE754格式：</p><ul><li>转化为二进制：$(-0.75)<em>{10}&#x3D;(-3&#x2F;4)</em>{10}&#x3D;-(2^{-1}+2^{-2})_{10}&#x3D;(-0.11)_2$</li><li>写为$(-1)^S<em>M</em>2^E$格式：$(-1)^1<em>1.1_2</em>2^{-1_2}$</li><li>S&#x3D;1，E&#x3D;-1，M&#x3D;1.1，$e&#x3D;E+Bias&#x3D;-1_{10}+127_{10}&#x3D;-0000 0001_2 + 0111 1111_2 &#x3D; 0111 1110_2$，将M去除包含的最高位1只留小数部分得到$m&#x3D;2^{(-1)_2}&#x3D;100 0000 0000 0000 0000 0000_2$</li><li>组合S、e、m即得IEEE754格式：$1 011 1111 0100 0000 0000 0000 0000 0000$</li></ul><h3 id="非规格化的值"><a href="#非规格化的值" class="headerlink" title="非规格化的值"></a>非规格化的值</h3><p>当阶码位全为0时，是非规格化浮点数。此时E&#x3D;1-Bias。</p><img src="/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/image-20241123205959482-9248a535d644.png" class="image-20241123205959482"><p>因为M不需要&gt;&#x3D;1，所以提供了表示非常接近0值的方式。当尾数位也全为0时，表示0。</p><h3 id="特殊值"><a href="#特殊值" class="headerlink" title="特殊值"></a>特殊值</h3><p>当阶码位全为1时，是特殊浮点数。</p><p>此时，如果尾数位也全为1，当阶码位为1时代表负无穷，当阶码位为0时代表正无穷；</p><img src="/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/image-20241123210156935-b7da03a4f7fb.png" class="image-20241123210156935"><p>当尾数位不全为1时，代表NaN，（Not a Number）不是一个数。</p><img src="/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/image-20241123210213826-ef878e31106b.png" class="image-20241123210213826">]]>
    </content>
    <id>https://www.wananhome.site/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/</id>
    <link href="https://www.wananhome.site/2024/11/23/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%9A%84%E8%A1%A8%E7%A4%BA%E2%80%94IEEE754%E6%A0%87%E5%87%86/"/>
    <published>2024-11-22T16:00:00.000Z</published>
    <summary>入门级详细解读IEEE754标准</summary>
    <title>浮点数的表示—IEEE754标准</title>
    <updated>2026-03-21T10:09:15.333Z</updated>
  </entry>
  <entry>
    <author>
      <name>WanAn</name>
    </author>
    <category term="计算机" scheme="https://www.wananhome.site/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA/"/>
    <category term="计算机" scheme="https://www.wananhome.site/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>对于一位计算机专业的学生来说，Ta的C语言课程往往会以下面这个经典的程序开始（后面我们称之为<em>hello程序</em>）：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;stdio.h&gt;</span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>&#123;<br>    <span class="hljs-built_in">printf</span>(<span class="hljs-string">&quot;Hello World\n&quot;</span>);<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>经过简单学习的人都知道，这段程序会在控制台打印一个字符串“Hello World”并换行。很多人都知道hello程序的作用，但却从未想过从我们在代码编辑器上写出这段程序，到执行打印出“Hello World”，hello程序在计算机中到底经历了什么？</p><p>今天，我们以不一样的视角来简单看看这段hello程序在计算机中的旅途。</p><blockquote><p>一个简单hello程序的运行也需要复杂的软硬件系统逻辑，本文只是简单粗略的描述，至于更详细的解释，敬请期待！</p></blockquote><h2 id="编译系统"><a href="#编译系统" class="headerlink" title="编译系统"></a>编译系统</h2><p>我们在编辑器上写出hello程序，然后点击编辑器自带的运行按钮，即可运行我们刚刚些好的hello程序。好像很简单的样子，这是因为我们使用的编辑器帮助我们在背后完成了一系列的操作，又对我们隐藏了具体细节，这种编辑器我们称之为<em>集成开发环境（IDE，Integrated Development Environment ）</em>。</p><p>hello程序从编写到运行，需要经历一套编译系统：</p><img src="/2024/11/20/%E6%B5%85%E8%B0%88%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E6%97%85%E9%80%94/image-20241120195618377-9a66ed13e802.png" class="image-20241120195618377"><p>hello源程序在预处理阶段会识别<code>#include &lt;stdio.h&gt;</code>这段代码，读取系统头文件<em>stdio.h</em>的内容并将其加在hello程序内。然后经过编译器、汇编器的处理后，链接器识别到程序中用到了<em>printf</em>函数，会在预提供的函数库中找到<em>printf.o</em>文件与<em>hello.o</em>链接。最后输出hello可执行目标程序，运行该可执行目标程序即可打印出<em>Hello World</em>。</p><h2 id="硬件结构"><a href="#硬件结构" class="headerlink" title="硬件结构"></a>硬件结构</h2><p>此时，hello可执行目标程序会被存储到磁盘，需要将其从磁盘中读取到处理器中才能执行该程序：</p><img src="/2024/11/20/%E6%B5%85%E8%B0%88%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E6%97%85%E9%80%94/image-20241120202616124-54f3dad33c56.png" class="image-20241120202616124"><ol><li>总线：贯穿整个系统，在各个部件间传输数据</li><li>CPU：中央处理单元，简称处理器，用于执行指令</li><li>PC：程序计数器，指向处理器需要执行的指令，即记录指令地址</li><li>寄存器：为处理器运行提供快速数据存取服务</li><li>ALU：运算单元，进行数学运算</li><li>主存：临时存储设备</li><li>磁盘：提供持久化存储服务</li><li>控制器&#x2F;适配器：接入I&#x2F;O设备</li></ol><p>系统会从磁盘中读取hello程序到主存储器中，处理器再根据PC指定的指令地址，从主存中读取一条指令，将相应数据存在寄存器中，并通过ALU完成数据运算。当执行完一条指令后，再次读取PC指向的下一条指令并处理，知道执行完hello程序，将结果输出到显示器中。</p><h2 id="存储结构"><a href="#存储结构" class="headerlink" title="存储结构"></a>存储结构</h2><p>至此，hello程序在计算机中的旅途便简单的走完了。在过程中，我们看到hello程序从磁盘走到主存，在从主存走到寄存器，为什么不能直接一步到位呢？</p><p>这就不得不讲到，在物理硬件世界，对于不同的存储器材料，相应的成本可是天差地别！虽然磁盘的容量可能必主存大1000倍，但是主存的读取速度却可以比磁盘快1000万倍！如果要造一个足够大的主存来取代磁盘，其带来的经济成本是难以承受的。</p><p>我们虽然不能用一个大的主存来取代磁盘，但却可以用一个小的更快的存储器在两者中间提供一层缓存，也可以起到加速的作用！就如同我们在图书馆里，无法将全部的书都拿走，但却可以先将最近要读的书拿到书桌上，这样就不用每次换新书都去书架上找，再将那一本书拿到书桌上了，这样就大大提高了我们读书的效率。</p><p>常见存储结构：</p><img src="/2024/11/20/%E6%B5%85%E8%B0%88%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E6%97%85%E9%80%94/image-20241120210041038-94e1e1939141.png" class="image-20241120210041038">]]>
    </content>
    <id>https://www.wananhome.site/2024/11/20/%E6%B5%85%E8%B0%88%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E6%97%85%E9%80%94/</id>
    <link href="https://www.wananhome.site/2024/11/20/%E6%B5%85%E8%B0%88%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E6%97%85%E9%80%94/"/>
    <published>2024-11-19T16:00:00.000Z</published>
    <summary>入门级解读一个程序的生命周期</summary>
    <title>浅谈一个程序在计算机中的旅途</title>
    <updated>2026-03-21T10:12:24.074Z</updated>
  </entry>
</feed>
