<?xml version="1.0" encoding="utf-8"?>
<search> 
  
  
    
    <entry>
      <title></title>
      <link href="/2023/06/13/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E9%A3%8E%E6%8E%A7%E7%B3%BB%E7%BB%9F-2%E3%80%81%E9%A3%8E%E6%8E%A7%E4%B8%AD%E5%8F%B0/"/>
      <url>/2023/06/13/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E9%A3%8E%E6%8E%A7%E7%B3%BB%E7%BB%9F-2%E3%80%81%E9%A3%8E%E6%8E%A7%E4%B8%AD%E5%8F%B0/</url>
      
        <content type="html"><![CDATA[]]></content>
      
      
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-15、链路追踪</title>
      <link href="/2023/06/13/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-15%E3%80%81%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA/"/>
      <url>/2023/06/13/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-15%E3%80%81%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Open-Tracing"><a href="#1-Open-Tracing" class="headerlink" title="1. Open Tracing"></a>1. Open Tracing</h1><h1 id="2-Skywalking"><a href="#2-Skywalking" class="headerlink" title="2. Skywalking"></a>2. Skywalking</h1><h2 id="2-1-ELK-Skywalking"><a href="#2-1-ELK-Skywalking" class="headerlink" title="2.1. ELK+Skywalking"></a>2.1. ELK+Skywalking</h2><p><a href="https://juejin.cn/post/7012877514787799071#comment">https://juejin.cn/post/7012877514787799071#comment</a></p><p><a href="https://bbs.huaweicloud.com/blogs/273943">https://bbs.huaweicloud.com/blogs/273943</a></p><p><a href="https://zhengjianfeng.cn/?p=578">https://zhengjianfeng.cn/?p=578</a></p><h2 id="2-2-skywalking-VS-Sleuth-ZipKin"><a href="#2-2-skywalking-VS-Sleuth-ZipKin" class="headerlink" title="2.2. skywalking VS Sleuth+ZipKin"></a>2.2. skywalking VS Sleuth+ZipKin</h2><p><a href="https://www.cnblogs.com/cbvlog/p/15770726.html">https://www.cnblogs.com/cbvlog/p/15770726.html</a></p><h1 id="3-Zipkin"><a href="#3-Zipkin" class="headerlink" title="3. Zipkin"></a>3. Zipkin</h1><p>官方网址： <a href="https://zipkin.io/pages/quickstart.html">https://zipkin.io/pages/quickstart.html</a></p><p>参考项目： <a href="https://www.cnblogs.com/lori/p/11113665.html">https://www.cnblogs.com/lori/p/11113665.html</a> 对应 13</p><p>​                   <a href="https://segmentfault.com/a/1190000020946622?utm_source=tag-newest">https://segmentfault.com/a/1190000020946622?utm_source=tag-newest</a></p><p>本地目录：D:\StudyWorkSpace\SpringCloudLearning\13</p><p>原文链接：<a href="https://blog.csdn.net/liuminglei1987/article/details/104004884">https://blog.csdn.net/liuminglei1987/article/details/104004884</a></p><h2 id="3-1-故事"><a href="#3-1-故事" class="headerlink" title="3.1. 故事"></a>3.1. 故事</h2><blockquote><p>在微服务架构中，必须实现分布式链路追踪，来记录一个请求到底有哪些服务参与，顺序又是怎样，达到每个请求的细节都清晰可见，出了问题，快速定位。</p><p>Google 开源了 Dapper 链路追踪组件，并在 2010 年发表了论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》，这篇文章是业内实现链路追踪的标杆和理论基础，具有非常大的参考价值。</p><p>目前，链路追踪组件有 Google 的 Dapper，Twitter 的 Zipkin，以及阿里的 Eagleeye （鹰眼）等，都是非常优秀的链路追踪开源组件。</p><p>其中，Twitter 的 Zipkin 是基于 google 的分布式监控系统 Dapper（论文）的开源实现，zipkin 用于追踪分布式服务之间的应用数据链路，分析处理延时，帮助我们改进系统的性能和定位故障。</p></blockquote><h2 id="3-2-安装"><a href="#3-2-安装" class="headerlink" title="3.2. 安装"></a>3.2. 安装</h2><p>Zipkin Server</p><p><strong>注意事项</strong>：Spring Cloud 从 F 版本开始，已经无需开发者自行构建 Zipkin Server 了，只需下载 jar 运行即可。下载地址为：</p><p><a href="https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/%E3%80%82">https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/。</a></p><p>Docker 方式安装</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">docker run -d -p 9411:9411 openzipkin/zipkin --network microintegration<br></code></pre></td></tr></table></figure><p>验证 test.sxd.com:9411&#x2F;zipkin&#x2F;</p><h2 id="3-3-工程配置"><a href="#3-3-工程配置" class="headerlink" title="3.3. 工程配置"></a>3.3. 工程配置</h2><figure class="highlight plaintext"><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 plaintext">&lt;!--链路追踪start--&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-cloud-starter-zipkin&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-cloud-starter-sleuth&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br>&lt;!--链路追踪end--&gt;<br></code></pre></td></tr></table></figure><h2 id="3-4-增加配置"><a href="#3-4-增加配置" class="headerlink" title="3.4. 增加配置"></a>3.4. 增加配置</h2><figure class="highlight plaintext"><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 plaintext">spring:<br>  zipkin:<br>    base-url: http://192.168.1.212:9411/<br>  sleuth:<br>    sampler:<br>      probability: 1.0<br></code></pre></td></tr></table></figure><h2 id="3-5-Imarket-实施"><a href="#3-5-Imarket-实施" class="headerlink" title="3.5. Imarket 实施"></a>3.5. Imarket 实施</h2><p>链路搜索</p><p><img src="/upload/image-20200716175011876.png" alt="image-20200716175011876"></p><p>链路结构</p><p><img src="/upload/image-20200716174947715.png" alt="image-20200716174947715"></p><h1 id="4-Cat"><a href="#4-Cat" class="headerlink" title="4. Cat"></a>4. Cat</h1><p>有代码侵入性</p><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><h2 id="5-1-黑马"><a href="#5-1-黑马" class="headerlink" title="5.1. 黑马"></a>5.1. 黑马</h2><h3 id="5-1-1-视频"><a href="#5-1-1-视频" class="headerlink" title="5.1.1. 视频"></a>5.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1154y1r7Mn/?p=3&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1154y1r7Mn/?p=3&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="5-1-2-资料"><a href="#5-1-2-资料" class="headerlink" title="5.1.2. 资料"></a>5.1.2. 资料</h3><p>[[skywalking讲义.pdf]]</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">/Users/Enterprise/<span class="hljs-number">0003</span>-Architecture/<span class="hljs-number">005</span>-分布式专题/分布式链路追踪skywalking<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>资源导航</title>
      <link href="/2023/06/13/013-%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/%E8%B5%84%E6%BA%90%E5%AF%BC%E8%88%AA/"/>
      <url>/2023/06/13/013-%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/%E8%B5%84%E6%BA%90%E5%AF%BC%E8%88%AA/</url>
      
        <content type="html"><![CDATA[<hr><p>1.JavaGuide: 一份涵盖大部分 Java 程序员所需要掌握的核心知识。<br>2.CS-Notes: 技术面试必备基础知识<br>3.advanced-java: 涵盖高并发、分布式、高可用、微服务<br>4.miaosha:秒杀系统设计与实现<br>5.architect-awesome:后端架构师技术图谱<br>6.toBeTopJavaer:Java工程师成神之路<br>7.technology-talk:Java生态圈常用技术<br>8.JavaFamily：互联网一线大厂面试指南<br>9.JCSprout：处于萌芽的Java核心知识库<br>10.fullstack-tutorial:全栈学习<br>11.JGrowing:Java 成长路线，但学到不仅仅是 Java<br>12.3y:从Java基础、JavaWeb基础到常用的框架再到面试题都有完整的教程，几乎涵盖了Java后端必备的知识点<br>13.interview_internal_reference:2019年最新总结，阿里，腾讯，百度，美团，头条等技术面试题目，以及答案，专家出题人分析汇总。<br>14.effective-java-3rd-chinese:Effective Java中文版（第3版），Java 四大名著之一，本书一共包含90个条目，每个条目讨论Java程序设计中的一条规则。这些规则反映了最有经验的优秀程序员在实践中常用的一些有益的做法。<br>15.《OnJava8》：又名《Java编程思想》第5版， Java 四大名著之一。  </p><p>《Java工程师面试突击第一季》：链接:<a href="https://pan.baidu.com/s/1wQNloOiviOLc6jlXGBtSjA">https://pan.baidu.com/s/1wQNloOiviOLc6jlXGBtSjA</a> 密码:srgn</p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>算法-0、汇总</title>
      <link href="/2023/06/13/014-AI%E7%AE%97%E6%B3%95%E4%B8%93%E9%A2%98/%E7%AE%97%E6%B3%95-0%E3%80%81%E6%B1%87%E6%80%BB/"/>
      <url>/2023/06/13/014-AI%E7%AE%97%E6%B3%95%E4%B8%93%E9%A2%98/%E7%AE%97%E6%B3%95-0%E3%80%81%E6%B1%87%E6%80%BB/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-算法分析"><a href="#1-算法分析" class="headerlink" title="1. 算法分析"></a>1. 算法分析</h1><p>研究算法的最终目的就是如何花更少的时间，如何占用更少的内存去完成相同的需求，并且<br>也通过案例演示了不同算法之间时间耗费和空间耗费上的差异，但我们并不能将时间占用和空间占用量化，因此，接下来我们要学习有关算法时间耗费和算法空间耗费的描述和分析。有关算法时间耗费分析，我们称之为算法的时间复杂度分析，有关算法的空间耗费分析，我们称之为算法的空间复杂度分析。</p><h2 id="1-1-时间复杂度分析-大O记法"><a href="#1-1-时间复杂度分析-大O记法" class="headerlink" title="1.1. 时间复杂度分析-大O记法"></a>1.1. 时间复杂度分析-大O记法</h2><h3 id="1-1-1-定义"><a href="#1-1-1-定义" class="headerlink" title="1.1.1. 定义"></a>1.1.1. 定义</h3><p>在进行算法分析时，语句总的执行次数T(n)是关于问题规模n的函数，进而分析T(n)随着n的变化情况并确定T(n)的量级。算法的时间复杂度，就是算法的时间量度，记作:T(n)&#x3D;O(f(n))。它表示随着问题规模n的增大，算法执行时间的增长率和f(n)的增长率相同，称作算法的渐近时间复杂度，简称时间复杂度，其中f(n)是问题规模n的某个函数。<br>在这里，我们需要明确一个事情：执行次数&#x3D;执行时间用大写O()来体现算法时间复杂度的记法，我们称之为大O记法。一般情况下，随着输入规模n的增大，T(n)增长最慢的算法为最优算法。</p><h3 id="1-1-2-常见的大O阶"><a href="#1-1-2-常见的大O阶" class="headerlink" title="1.1.2. 常见的大O阶"></a>1.1.2. 常见的大O阶</h3><p>常数阶：O(1)<br>对数阶：O(logn)<br>线性阶：O(n)<br>线性对数阶: O(nlogn)<br>平方阶：O(n<sup>2</sup>)<br>立方阶：O(n<sup>3</sup>)<br>k次方阶：O(n<sup>k</sup>)<br>指数阶：O(2<sup>n</sup>)</p><p>他们的复杂程度从低到高依次为：<br>O(1)&lt;O(logn)&lt;O(n)&lt;O(nlogn)&lt;O(n<sup>2</sup>)&lt;O(n<sup>3</sup>)&lt;O(n<sup>k</sup>)&lt;O(2<sup>n</sup>)</p><h2 id="1-2-空间复杂度分析"><a href="#1-2-空间复杂度分析" class="headerlink" title="1.2. 空间复杂度分析"></a>1.2. 空间复杂度分析</h2><p>1.基本数据类型内存占用情况：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220927094340.png"></p><p>2.计算机访问内存的方式都是一次一个字节<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220927094410.png"><br>3.一个引用（机器地址）需要8个字节表示：<br>例如： Date date &#x3D; new Date(),则date这个变量需要占用8个字节来表示<br>4.创建一个对象，比如new Date()，除了Date对象内部存储的数据(例如年月日等信息)占用的内存，该对象本身也有内存开销，<span style="background-color:#00ff00">每个对象的自身开销是16个字节，用来保存对象的头信息</span>。<br>5.一般内存的使用，如果不够8个字节，都会被自动填充为8字节的倍数字节数：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220927094459.png"></p><p>6.java中数组被被限定为对象，他们一般都会因为记录长度而需要额外的内存，一个原始数据类型的数组一般需要：24字节的头信息(16个自己的对象开销，4字节用于保存长度以及4个填充字节)，再加上保存值所需的内存。</p><p>了解了java的内存最基本的机制，就能够有效帮助我们估计大量程序的内存使用情况。</p><p>算法的空间复杂度计算公式记作：S(n)&#x3D;O(f(n)),其中n为输入规模，f(n)为语句关于n所占存储空间的函数。</p><h1 id="2-算法题"><a href="#2-算法题" class="headerlink" title="2. 算法题"></a>2. 算法题</h1><p>[[Java基础-算法题解析]]</p><h1 id="3-算法汇总"><a href="#3-算法汇总" class="headerlink" title="3. 算法汇总"></a>3. 算法汇总</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220924130123.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/imgimage-20211016144158004.png" alt="image-20211016144158004"></p><h1 id="4-排序算法"><a href="#4-排序算法" class="headerlink" title="4. 排序算法"></a>4. 排序算法</h1><h2 id="4-1-算法分类"><a href="#4-1-算法分类" class="headerlink" title="4.1. 算法分类"></a>4.1. 算法分类</h2><p><img src="https://tva1.sinaimg.cn/large/006tNbRwly1gbhyjsntg3j30za0f0790.jpg" alt="image-20200202124614464"></p><p>内部排序和外部排序。<br>内部排序：整个排序过程不需要借助于外部存储器（如磁盘等），所有排序操作都在内存中完成。<br>外部排序：参与排序的数据非常多，数据量非常大，计算机无法把整个排序过程放在内存中完成，必须借助于外部存储器（如磁盘）。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。</p><h2 id="4-2-算法比较"><a href="#4-2-算法比较" class="headerlink" title="4.2. 算法比较"></a>4.2. 算法比较</h2><p><img src="https://tva1.sinaimg.cn/large/006tNbRwly1gbhyk8czcpj31ao0u04qp.jpg" alt="image-20200202124639588"></p><h1 id="5-查找算法"><a href="#5-查找算法" class="headerlink" title="5. 查找算法"></a>5. 查找算法</h1><h2 id="5-1-BM"><a href="#5-1-BM" class="headerlink" title="5.1. BM"></a>5.1. BM</h2><p><a href="https://blog.csdn.net/DBC_121/article/details/105569440">https://blog.csdn.net/DBC_121/article/details/105569440</a><br><a href="https://www.zhihu.com/question/21923021">https://www.zhihu.com/question/21923021</a></p><h1 id="6-其他算法"><a href="#6-其他算法" class="headerlink" title="6. 其他算法"></a>6. 其他算法</h1><p><a href="https://www.cnblogs.com/hongdada/p/10406902.html">https://www.cnblogs.com/hongdada/p/10406902.html</a><br><a href="https://leetcode.cn/problems/lru-cache/">https://leetcode.cn/problems/lru-cache/</a><br>[[20221011-LRU和LFU的区别 - stardsd - 博客园]]</p><h2 id="6-1-LRU"><a href="#6-1-LRU" class="headerlink" title="6.1. LRU"></a>6.1. LRU</h2><h2 id="6-2-LFU"><a href="#6-2-LFU" class="headerlink" title="6.2. LFU"></a>6.2. LFU</h2><h2 id="6-3-FIFO"><a href="#6-3-FIFO" class="headerlink" title="6.3. FIFO"></a>6.3. FIFO</h2><h1 id="7-参考"><a href="#7-参考" class="headerlink" title="7. 参考"></a>7. 参考</h1><h2 id="7-1-左程云算法"><a href="#7-1-左程云算法" class="headerlink" title="7.1. 左程云算法"></a>7.1. 左程云算法</h2><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;001-基础知识专题&#x2F;000-数据结构与算法&#x2F;左神算法资料</p><h2 id="7-2-吴师兄学算法"><a href="#7-2-吴师兄学算法" class="headerlink" title="7.2. 吴师兄学算法"></a>7.2. 吴师兄学算法</h2><p><a href="https://blog.algomooc.com/">https://blog.algomooc.com/</a><br>动图：<a href="https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg">https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg</a><br><a href="https://github.com/GarenXie1/LeetCodeAnimation">https://github.com/GarenXie1/LeetCodeAnimation</a></p><h2 id="7-3-CodeSheep"><a href="#7-3-CodeSheep" class="headerlink" title="7.3. CodeSheep"></a>7.3. CodeSheep</h2><p><a href="https://mp.weixin.qq.com/s/ekGdneZrMa23ALxt5mvKpQ">https://mp.weixin.qq.com/s/ekGdneZrMa23ALxt5mvKpQ</a></p><h2 id="7-4-抖码课堂"><a href="#7-4-抖码课堂" class="headerlink" title="7.4. 抖码课堂"></a>7.4. 抖码课堂</h2><p><a href="https://www.bilibili.com/video/BV1qL411M7iB/?p=103&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1qL411M7iB/?p=103&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>代码已下载<br>&#x2F;Users&#x2F;weileluo&#x2F;obsidian_repo&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;algo_interview_bilibili-master</p><h2 id="7-5-黑马程序员"><a href="#7-5-黑马程序员" class="headerlink" title="7.5. 黑马程序员"></a>7.5. 黑马程序员</h2><p><a href="https://www.bilibili.com/video/BV1iJ411E7xW?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1iJ411E7xW?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>代码已下载</p><h2 id="7-6-图灵学院"><a href="#7-6-图灵学院" class="headerlink" title="7.6. 图灵学院"></a>7.6. 图灵学院</h2><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;001-基础知识专题&#x2F;000-数据结构与算法&#x2F;算法资料&#x2F;leetcode算法资料.pdf</p><p><a href="https://www.jianshu.com/p/33cffa1ce613">https://www.jianshu.com/p/33cffa1ce613</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>算法-1、题目解析</title>
      <link href="/2023/06/13/014-AI%E7%AE%97%E6%B3%95%E4%B8%93%E9%A2%98/%E7%AE%97%E6%B3%95-1%E3%80%81%E9%A2%98%E7%9B%AE%E8%A7%A3%E6%9E%90/"/>
      <url>/2023/06/13/014-AI%E7%AE%97%E6%B3%95%E4%B8%93%E9%A2%98/%E7%AE%97%E6%B3%95-1%E3%80%81%E9%A2%98%E7%9B%AE%E8%A7%A3%E6%9E%90/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-数组"><a href="#1-数组" class="headerlink" title="1. 数组"></a>1. 数组</h1><h2 id="1-1-不用中间变量交换两个数"><a href="#1-1-不用中间变量交换两个数" class="headerlink" title="1.1. 不用中间变量交换两个数"></a>1.1. 不用中间变量交换两个数</h2><h2 id="1-2-找到出现奇数次的数"><a href="#1-2-找到出现奇数次的数" class="headerlink" title="1.2. 找到出现奇数次的数"></a>1.2. 找到出现奇数次的数</h2><p><a href="https://www.bilibili.com/video/BV1zu411X744?p=8&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zu411X744?p=8&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221023080100.png"></p><h2 id="1-3-找到-2-个出现奇数次的数"><a href="#1-3-找到-2-个出现奇数次的数" class="headerlink" title="1.3. 找到 2 个出现奇数次的数"></a>1.3. 找到 2 个出现奇数次的数</h2><figure class="highlight plaintext"><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 plaintext">public static int bit1counts(int N) &#123;  <br>   int count = 0;  <br>   //   011011010000  <br>   //   000000010000     1      //   011011000000  <br>   //     <br>while(N != 0) &#123;  <br>      int rightOne = N &amp; ((~N) + 1);  <br>      count++;  <br>      N ^= rightOne;  <br>      // N -= rightOne  <br>   &#125;  <br>     <br>   return count;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="1-4-数字中-1-的个数"><a href="#1-4-数字中-1-的个数" class="headerlink" title="1.4. 数字中 1 的个数"></a>1.4. 数字中 1 的个数</h2><figure class="highlight plaintext"><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 plaintext">public static int bit1counts(int N) &#123;  <br>   int count = 0;  <br>   //   011011010000  <br>   //   000000010000     1      //   011011000000  <br>   //   <br>   while(N != 0) &#123;  <br>      int rightOne = N &amp; ((~N) + 1);  <br>      count++;  <br>      N ^= rightOne;  <br>      // N -= rightOne  <br>   &#125;  <br>  <br>   return count;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="1-5-合并两个有序数组-L88"><a href="#1-5-合并两个有序数组-L88" class="headerlink" title="1.5. 合并两个有序数组 -L88"></a>1.5. 合并两个有序数组 -L88</h2><p><a href="https://www.bilibili.com/video/BV1ya4y1H7gH/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ya4y1H7gH/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="1-5-1-双指针倒序复制"><a href="#1-5-1-双指针倒序复制" class="headerlink" title="1.5.1. 双指针倒序复制"></a>1.5.1. 双指针倒序复制</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221014135111.png" alt="image-20221014135111713"></p><h3 id="1-5-2-有剩余情况"><a href="#1-5-2-有剩余情况" class="headerlink" title="1.5.2. 有剩余情况"></a>1.5.2. 有剩余情况</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221014141451.gif" alt="Oct-14-2022 14-14-16"></p><h3 id="1-5-3-无剩余情况"><a href="#1-5-3-无剩余情况" class="headerlink" title="1.5.3. 无剩余情况"></a>1.5.3. 无剩余情况</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221014140607.gif" alt="Oct-14-2022 14-05-22"></p><p>动图中有个小错误，在最后一步，p1 指针应该没动，只有 p 和 p2 向左移动了一步，p 和 p1 都指向 1 所在的位置。p2 越界了跳出 while 循环，此时 p2&#x3D;-1，走到下面一句：System.arraycopy(nums2,0,num1,0,0)，相当于没有变化，即 nums2 已经全部转移到了 nums1 中，最后的扫尾动作相当于没有变化。</p><h3 id="1-5-4-代码实现"><a href="#1-5-4-代码实现" class="headerlink" title="1.5.4. 代码实现"></a>1.5.4. 代码实现</h3><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">void</span> <span class="hljs-title function_">merge</span><span class="hljs-params">(<span class="hljs-type">int</span>[] nums1, <span class="hljs-type">int</span> m, <span class="hljs-type">int</span>[] nums2, <span class="hljs-type">int</span> n)</span> &#123;<br>  <span class="hljs-type">int</span> <span class="hljs-variable">p1</span> <span class="hljs-operator">=</span> m - <span class="hljs-number">1</span>;<br>  <span class="hljs-type">int</span> <span class="hljs-variable">p2</span> <span class="hljs-operator">=</span> n - <span class="hljs-number">1</span>;<br>  <span class="hljs-type">int</span> <span class="hljs-variable">p</span> <span class="hljs-operator">=</span> m + n - <span class="hljs-number">1</span>;<br><br>  <span class="hljs-keyword">while</span> ((p1 &gt;= <span class="hljs-number">0</span>) &amp;&amp; (p2 &gt;= <span class="hljs-number">0</span>))<br>    nums1[p--] = (nums1[p1] &lt; nums2[p2]) ? nums2[p2--] : nums1[p1--];<br><br>  System.arraycopy(nums2, <span class="hljs-number">0</span>, nums1, <span class="hljs-number">0</span>, p2 + <span class="hljs-number">1</span>);<br><br>&#125;<br></code></pre></td></tr></table></figure><h1 id="2-链表"><a href="#2-链表" class="headerlink" title="2. 链表"></a>2. 链表</h1><h2 id="2-1-反转链表"><a href="#2-1-反转链表" class="headerlink" title="2.1. 反转链表"></a>2.1. 反转链表</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221014124457.png"></p><h1 id="3-树"><a href="#3-树" class="headerlink" title="3. 树"></a>3. 树</h1><h2 id="3-1-层序遍历"><a href="#3-1-层序遍历" class="headerlink" title="3.1. 层序遍历"></a>3.1. 层序遍历</h2><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">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">levelOrder</span><span class="hljs-params">(TreeNode root, <span class="hljs-type">int</span> index, List result)</span> &#123;  <br>    <span class="hljs-keyword">if</span> (root == <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span>;  <br>  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">length</span> <span class="hljs-operator">=</span> result.size();  <br>    <span class="hljs-keyword">if</span> (length &lt;= index) &#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;= index - length; j++) &#123;  <br>            result.add(length + j, <span class="hljs-literal">null</span>);  <br>        &#125;  <br>    &#125;  <br>  <br>    result.set(index, root.val);  <br>    levelOrder(root.left, <span class="hljs-number">2</span>*index, result);  <br>    levelOrder(root.right, <span class="hljs-number">2</span>*index + <span class="hljs-number">1</span>, result);  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-2-树的最大宽度"><a href="#3-2-树的最大宽度" class="headerlink" title="3.2. 树的最大宽度"></a>3.2. 树的最大宽度</h2><h3 id="3-2-1-使用-map"><a href="#3-2-1-使用-map" class="headerlink" title="3.2.1. 使用 map"></a>3.2.1. 使用 map</h3><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></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-type">int</span> <span class="hljs-title function_">maxWidthUseMap</span><span class="hljs-params">(Node head)</span> &#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;  <br>   &#125;  <br>   Queue&lt;Node&gt; queue = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedList</span>&lt;&gt;();  <br>   queue.add(head);  <br>   <span class="hljs-comment">// key 在 哪一层，value  </span><br>   HashMap&lt;Node, Integer&gt; levelMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();  <br>   levelMap.put(head, <span class="hljs-number">1</span>);  <br>   <span class="hljs-type">int</span> <span class="hljs-variable">curLevel</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; <span class="hljs-comment">// 当前你正在统计哪一层的宽度  </span><br>   <span class="hljs-type">int</span> <span class="hljs-variable">curLevelNodes</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; <span class="hljs-comment">// 当前层curLevel层，宽度目前是多少  </span><br>   <span class="hljs-type">int</span> <span class="hljs-variable">max</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;  <br>   <span class="hljs-keyword">while</span> (!queue.isEmpty()) &#123;  <br>      <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> queue.poll();  <br>      <span class="hljs-type">int</span> <span class="hljs-variable">curNodeLevel</span> <span class="hljs-operator">=</span> levelMap.get(cur);  <br>      <span class="hljs-keyword">if</span> (cur.left != <span class="hljs-literal">null</span>) &#123;  <br>         levelMap.put(cur.left, curNodeLevel + <span class="hljs-number">1</span>);  <br>         queue.add(cur.left);  <br>      &#125;  <br>      <span class="hljs-keyword">if</span> (cur.right != <span class="hljs-literal">null</span>) &#123;  <br>         levelMap.put(cur.right, curNodeLevel + <span class="hljs-number">1</span>);  <br>         queue.add(cur.right);  <br>      &#125;  <br>      <span class="hljs-keyword">if</span> (curNodeLevel == curLevel) &#123;  <br>         curLevelNodes++;  <br>      &#125; <span class="hljs-keyword">else</span> &#123;  <br>         max = Math.max(max, curLevelNodes);  <br>         curLevel++;  <br>         curLevelNodes = <span class="hljs-number">1</span>;  <br>      &#125;  <br>   &#125;  <br>   max = Math.max(max, curLevelNodes);  <br>   <span class="hljs-keyword">return</span> max;  <br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-2-2-不用-map"><a href="#3-2-2-不用-map" class="headerlink" title="3.2.2. 不用 map"></a>3.2.2. 不用 map</h3><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">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">maxWidthNoMap</span><span class="hljs-params">(Node head)</span> &#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;  <br>   &#125;  <br>   Queue&lt;Node&gt; queue = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedList</span>&lt;&gt;();  <br>   queue.add(head);  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">curEnd</span> <span class="hljs-operator">=</span> head; <span class="hljs-comment">// 当前层，最右节点是谁  </span><br>   <span class="hljs-type">Node</span> <span class="hljs-variable">nextEnd</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>; <span class="hljs-comment">// 下一层，最右节点是谁  </span><br>   <span class="hljs-type">int</span> <span class="hljs-variable">max</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;  <br>   <span class="hljs-type">int</span> <span class="hljs-variable">curLevelNodes</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; <span class="hljs-comment">// 当前层的节点数  </span><br>   <span class="hljs-keyword">while</span> (!queue.isEmpty()) &#123;  <br>      <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> queue.poll();  <br>      <span class="hljs-keyword">if</span> (cur.left != <span class="hljs-literal">null</span>) &#123;  <br>         queue.add(cur.left);  <br>         nextEnd = cur.left;  <br>      &#125;  <br>      <span class="hljs-keyword">if</span> (cur.right != <span class="hljs-literal">null</span>) &#123;  <br>         queue.add(cur.right);  <br>         nextEnd = cur.right;  <br>      &#125;  <br>      curLevelNodes++;  <br>      <span class="hljs-keyword">if</span> (cur == curEnd) &#123;  <br>         max = Math.max(max, curLevelNodes);  <br>         curLevelNodes = <span class="hljs-number">0</span>;  <br>         curEnd = nextEnd;  <br>      &#125;  <br>   &#125;  <br>   <span class="hljs-keyword">return</span> max;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-3-序列化"><a href="#3-3-序列化" class="headerlink" title="3.3. 序列化"></a>3.3. 序列化</h2><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">public</span> <span class="hljs-keyword">static</span> Queue&lt;String&gt; <span class="hljs-title function_">preSerial</span><span class="hljs-params">(Node head)</span> &#123;  <br>   Queue&lt;String&gt; ans = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedList</span>&lt;&gt;();  <br>   pres(head, ans);  <br>   <span class="hljs-keyword">return</span> ans;  <br>&#125;  <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_">pres</span><span class="hljs-params">(Node head, Queue&lt;String&gt; ans)</span> &#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      ans.add(<span class="hljs-literal">null</span>);  <br>   &#125; <span class="hljs-keyword">else</span> &#123;  <br>      ans.add(String.valueOf(head.value));  <br>      pres(head.left, ans);  <br>      pres(head.right, ans);  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="4-图"><a href="#4-图" class="headerlink" title="4. 图"></a>4. 图</h1><h1 id="5-ForkJoin"><a href="#5-ForkJoin" class="headerlink" title="5. ForkJoin"></a>5. ForkJoin</h1><p>分而治之思想<br><a href="https://www.bilibili.com/video/BV11A411H7Xh?p=7&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV11A411H7Xh?p=7&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221012193941.png"></p><h1 id="6-等概率生成器"><a href="#6-等概率生成器" class="headerlink" title="6. 等概率生成器"></a>6. 等概率生成器</h1><p><a href="https://www.bilibili.com/video/BV1qd4y1B7XC?p=12&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1qd4y1B7XC?p=12&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="7-快慢指针"><a href="#7-快慢指针" class="headerlink" title="7. 快慢指针"></a>7. 快慢指针</h1><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;001- 基础知识专题&#x2F;000- 数据结构与算法&#x2F;黑马 - 数据结构与算法资料&#x2F;文档&#x2F;04_ 线性表.pdf</p><h1 id="8-从后往前节省空间"><a href="#8-从后往前节省空间" class="headerlink" title="8. 从后往前节省空间"></a>8. 从后往前节省空间</h1><p><a href="https://www.bilibili.com/video/BV1iE411s7Gi/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1iE411s7Gi/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="9-参考"><a href="#9-参考" class="headerlink" title="9. 参考"></a>9. 参考</h1><p><a href="https://space.bilibili.com/390775036">https://space.bilibili.com/390775036</a></p><h2 id="9-1-左程云算法"><a href="#9-1-左程云算法" class="headerlink" title="9.1. 左程云算法"></a>9.1. 左程云算法</h2><p><a href="https://www.bilibili.com/video/BV1qd4y1B7XC/?p=13&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1qd4y1B7XC/?p=13&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;001- 基础知识专题&#x2F;000- 数据结构与算法&#x2F;左神算法资料</p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Kafka</title>
      <link href="/2023/06/13/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/Kafka/"/>
      <url>/2023/06/13/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/Kafka/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Kafka-知识总结"><a href="#1-Kafka-知识总结" class="headerlink" title="1. Kafka 知识总结"></a>1. <strong>Kafka 知识总结</strong></h1><h2 id="1-1-一、讲讲-acks-参数对消息持久化的影响"><a href="#1-1-一、讲讲-acks-参数对消息持久化的影响" class="headerlink" title="1.1. 一、讲讲 acks 参数对消息持久化的影响"></a>1.1. <strong>一、讲讲 acks 参数对消息持久化的影响</strong></h2><h3 id="1-1-1-目录"><a href="#1-1-1-目录" class="headerlink" title="1.1.1. 目录"></a>1.1.1. <strong>目录</strong></h3><ol><li>写在前面</li><li>如何保证宕机时数据不丢失？</li><li>多副本之间数据如何同步？</li><li>ISR 到底指的是什么东西？</li><li>acks 参数的含义？</li><li>最后的思考</li></ol><h3 id="1-1-2-1-写在前面"><a href="#1-1-2-1-写在前面" class="headerlink" title="1.1.2. 1.写在前面"></a>1.1.2. <strong>1.写在前面</strong></h3><p>面试大厂时，一旦简历上写了 Kafka，几乎必然会被问到一个问题：说说 acks 参数对消息持久化的影响？</p><p>这个 acks 参数在 kafka 的使用中，是非常核心以及关键的一个参数，决定了很多东西。</p><p>所以无论是为了面试还是实际项目使用，大家都值得看一下这篇文章对 Kafka 的 acks 参数的分析，以及背后的原理。</p><h3 id="1-1-3-2-如何保证宕机的时候数据不丢失？（或者-kafka-如何保证高可用、或者-Kafka-如何保证高可用）"><a href="#1-1-3-2-如何保证宕机的时候数据不丢失？（或者-kafka-如何保证高可用、或者-Kafka-如何保证高可用）" class="headerlink" title="1.1.3. 2.如何保证宕机的时候数据不丢失？（或者 kafka 如何保证高可用、或者 Kafka 如何保证高可用）"></a>1.1.3. <strong>2.如何保证宕机的时候数据不丢失？（或者 kafka 如何保证高可用、或者 Kafka 如何保证高可用）</strong></h3><ul><li><p>Kafka 一个最基本的架构认识：由多个 broker 组成，每个 broker 是一个节点；创建一个 topic，这个 topic 可以划分为多个 partition，每个 partition 可以存在于不同的 broker 上，每个 partition 就放一部分数据。</p><p>这就是 <strong>天然的分布式消息队列</strong>，就是说一个 topic 的数据，是 <strong>分散放在多个机器上的，每个机器就放一部分数据</strong>。</p></li><li><p>而且 Kafka 还提供 replica<strong>副本机制</strong>，每个 partition 的数据都会同步到其他机器上，形成自己的多个 replica 副本。所有 replica 会选举出来一个 leader 出来，那么 <strong>生产和消费都跟这个 leader 打交道</strong>，然后其他 replica 就是 follower。写的时候，leader 会负责把数据同步到所有 follower 上去，读的时候就直接读 leader 上的数据即可。</p></li></ul><p>如果某个 broker 宕机了，那个 broker 上的 partition 在其他机器上都有副本。如果这个宕机的 broker 上面有某个 partition 的 leader，那么从 follower 中重新选举一个新的 leader 出来，然后继续读写新的 leader 即可，这就是所谓的高可用。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230514115527.jpg" alt="11"></p><h3 id="1-1-4-3-多副本之间数据如何保证同步"><a href="#1-1-4-3-多副本之间数据如何保证同步" class="headerlink" title="1.1.4. 3.多副本之间数据如何保证同步"></a>1.1.4. 3.<strong>多副本之间数据如何保证同步</strong></h3><p>其实任何一个 Partition，只有 Leader 是对外提供读写服务的，也就是说，如果有一个客户端往一个 Partition 写入数据，此时一般就是写入这个 Partition 的 Leader 副本。</p><p>然后 Leader 副本接收到数据之后，Follower 副本会不停的给他发送请求尝试去拉取最新的数据，拉取到自己本地后，写入磁盘中。如下图所示：</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/2.jpg"></p><h3 id="1-1-5-4-ISR-到底指的是什么东西？"><a href="#1-1-5-4-ISR-到底指的是什么东西？" class="headerlink" title="1.1.5. 4.ISR 到底指的是什么东西？"></a>1.1.5. <strong>4.ISR 到底指的是什么东西？</strong></h3><p>ISR 全称是“In-Sync Replicas”，也就是 <strong>保持同步的副本</strong>，他的含义就是，跟 Leader 始终保持同步的 Follower 有哪些。</p><p>大家可以想一下 ，如果说某个 Follower 所在的 Broker 因为 JVM FullGC 之类的问题，导致自己卡顿了，无法及时从 Leader 拉取同步数据，那么是不是会导致 Follower 的数据比 Leader 要落后很多？</p><p>所以这个时候，就意味着 Follower 已经跟 Leader 不再处于同步的关系了。但是只要 Follower 一直及时从 Leader 同步数据，就可以保证他们是处于同步的关系的。</p><p>所以每个 Partition 都有一个 ISR，这个 ISR 里一定会有 Leader 自己，因为 Leader 肯定数据是最新的，然后就是那些跟 Leader 保持同步的 Follower，也会在 ISR 里。</p><h3 id="1-1-6-5-acks-参数的含义"><a href="#1-1-6-5-acks-参数的含义" class="headerlink" title="1.1.6. 5.acks 参数的含义"></a>1.1.6. <strong>5.acks 参数的含义</strong></h3><p>首先这个 acks 参数，是在 KafkaProducer，也就是生产者客户端里设置的</p><p>也就是说，你往 kafka 写数据的时候，就可以来设置这个 acks 参数。然后这个参数实际上有三种常见的值可以设置，分别是：<strong>0、1 和 all</strong>。</p><p><strong>第一种选择是把 acks 参数设置为 0</strong>，意思就是我的 KafkaProducer 在客户端，只要把消息发送出去，不管那条数据有没有在哪怕 Partition Leader 上落到磁盘，我就不管他了，直接就认为这个消息发送成功了。</p><p>如果你采用这种设置的话，那么你必须注意的一点是，可能你发送出去的消息还在半路。结果呢，Partition Leader 所在 Broker 就直接挂了，然后结果你的客户端还认为消息发送成功了，此时就会 <strong>导致这条消息就丢失了</strong>。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/3.jpg"></p><p><strong>第二种选择是设置 acks &#x3D; 1</strong>，意思就是说只要 Partition Leader 接收到消息而且写入本地磁盘了，就认为成功了，不管他其他的 Follower 有没有同步过去这条消息了。</p><p>这种设置其实是 <strong>kafka 默认的设置</strong></p><p>也就是说，默认情况下，你要是不管 acks 这个参数，只要 Partition Leader 写成功就算成功。</p><p>但是这里有一个问题，万一 Partition Leader 刚刚接收到消息，Follower 还没来得及同步过去，结果 Leader 所在的 broker 宕机了，此时也会导致这条消息丢失，因为人家客户端已经认为发送成功了。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/4.jpg"></p><p><strong>最后一种情况，就是设置 acks&#x3D;all</strong>，这个意思就是说，<strong>Partition Leader 接收到消息之后，还必须要求 ISR 列表里跟 Leader 保持同步的那些 Follower 都要把消息同步过去</strong>，才能认为这条消息是写入成功了。</p><p>如果说 Partition Leader 刚接收到了消息，但是结果 Follower 没有收到消息，此时 Leader 宕机了，那么客户端会感知到这个消息没发送成功，他会重试再次发送消息过去。</p><p>此时可能 Partition 2 的 Follower 变成 Leader 了，此时 ISR 列表里只有最新的这个 Follower 转变成的 Leader 了，那么只要这个新的 Leader 接收消息就算成功了。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/5.jpg"></p><h3 id="1-1-7-6-最后的思考"><a href="#1-1-7-6-最后的思考" class="headerlink" title="1.1.7. 6.最后的思考"></a>1.1.7. <strong>6.最后的思考</strong></h3><p>acks&#x3D;all 就可以代表数据一定不会丢失了吗？</p><p>当然不是，如果你的 Partition 只有一个副本，也就是一个 Leader，任何 Follower 都没有，你认为 acks&#x3D;all 有用吗？</p><p>当然没用了，因为 ISR 里就一个 Leader，他接收完消息后宕机，也会导致数据丢失。</p><p>所以说，<strong>这个 acks&#x3D;all，必须跟 ISR 列表里至少有 2 个以上的副本配合使用</strong>，起码是有一个 Leader 和一个 Follower 才可以。</p><p>这样才能保证说写一条数据过去，一定是 2 个以上的副本都收到了才算是成功，此时任何一个副本宕机，不会导致数据丢失。</p><p><strong>参考</strong>：<a href="https://mp.weixin.qq.com/s/IxS46JAr7D9sBtCDr8pd7A">https://mp.weixin.qq.com/s/IxS46JAr7D9sBtCDr8pd7A</a></p><h2 id="1-2-二、Kafka-参数调优实战"><a href="#1-2-二、Kafka-参数调优实战" class="headerlink" title="1.2. 二、Kafka 参数调优实战"></a>1.2. 二、Kafka 参数调优实战</h2><h3 id="1-2-1-目录"><a href="#1-2-1-目录" class="headerlink" title="1.2.1. 目录"></a>1.2.1. 目录</h3><ol><li>背景引入：很多同学看不懂的 Kafka 参数</li><li>一段 Kafka 生产端的示例代码</li><li>内存缓冲的大小</li><li>多少数据打包为一个 Batch 合适？</li><li>要是一个 Batch 迟迟无法凑满怎么办？</li><li>最大请求大小</li><li>重试机制</li><li>持久化机制</li></ol><h4 id="1-2-1-1-1、背景引入：很多同学看不懂的-kafka-参数"><a href="#1-2-1-1-1、背景引入：很多同学看不懂的-kafka-参数" class="headerlink" title="1.2.1.1. 1、背景引入：很多同学看不懂的 kafka 参数"></a>1.2.1.1. 1、背景引入：很多同学看不懂的 kafka 参数</h4><p>在使用 Kafka 的客户端编写代码与服务器交互的时候，是需要对客户端设置很多的参数的。</p><h4 id="1-2-1-2-2、一段-Kafka-生产端的示例代码"><a href="#1-2-1-2-2、一段-Kafka-生产端的示例代码" class="headerlink" title="1.2.1.2. 2、一段 Kafka 生产端的示例代码"></a>1.2.1.2. 2、一段 Kafka 生产端的示例代码</h4><figure class="highlight scala"><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 scala"><span class="hljs-type">Properties</span> props = <span class="hljs-keyword">new</span> <span class="hljs-type">Properties</span>();<br>props.put(<span class="hljs-string">&quot;bootstrap.servers&quot;</span>, <span class="hljs-string">&quot;localhost:9092&quot;</span>); <br>props.put(<span class="hljs-string">&quot;key.serializer&quot;</span>, <span class="hljs-string">&quot;org.apache.kafka.common.serialization.StringSerializer&quot;</span>);<br>props.put(<span class="hljs-string">&quot;value.serializer&quot;</span>, <span class="hljs-string">&quot;org.apache.kafka.common.serialization.StringSerializer&quot;</span>);<br>props.put(<span class="hljs-string">&quot;buffer.memory&quot;</span>, <span class="hljs-number">67108864</span>); <br>props.put(<span class="hljs-string">&quot;batch.size&quot;</span>, <span class="hljs-number">131072</span>); <br>props.put(<span class="hljs-string">&quot;linger.ms&quot;</span>, <span class="hljs-number">100</span>); <br>props.put(<span class="hljs-string">&quot;max.request.size&quot;</span>, <span class="hljs-number">10485760</span>); <br>props.put(<span class="hljs-string">&quot;acks&quot;</span>, <span class="hljs-string">&quot;1&quot;</span>); <br>props.put(<span class="hljs-string">&quot;retries&quot;</span>, <span class="hljs-number">10</span>); <br>props.put(<span class="hljs-string">&quot;retry.backoff.ms&quot;</span>, <span class="hljs-number">500</span>);<br><br><span class="hljs-type">KafkaProducer</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">String</span>&gt; producer = <span class="hljs-keyword">new</span> <span class="hljs-type">KafkaProducer</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">String</span>&gt;(props);<br></code></pre></td></tr></table></figure><h4 id="1-2-1-3-3、内存缓冲的大小"><a href="#1-2-1-3-3、内存缓冲的大小" class="headerlink" title="1.2.1.3. 3、内存缓冲的大小"></a>1.2.1.3. 3、内存缓冲的大小</h4><p>首先看看“<strong>buffer.memory</strong>”这个参数是什么意思？</p><p>Kafka 的客户端发送数据到服务器，一般都是要经过 <strong>缓冲</strong> 的，也就是说，<strong>通过 KafkaProducer 发送出去的消息都是先进入到客户端本地的内存缓冲里，然后把很多消息收集成一个一个的 Batch，再发送到 Broker 上去的</strong>。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/6.jpg"></p><p>所以这个“<strong>buffer.memory”的本质就是用来约束 KafkaProducer 能够使用的内存缓冲的大小的，他的默认值是 32MB</strong>。</p><p>你可以先想一下，如果这个内存缓冲设置的过小的话，可能会导致一个什么问题？</p><p>首先要明确一点，那就是在内存缓冲里大量的消息会缓冲在里面，形成一个一个的 Batch，每个 Batch 里包含多条消息。</p><p>然后 KafkaProducer 有一个 Sender 线程会把多个 Batch 打包成一个 Request 发送到 Kafka 服务器上去。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/7.jpg"></p><p>那么如果要是 <strong>内存设置的太小</strong>，可能 <strong>导致一个问题</strong>：消息快速的写入内存缓冲里面，但是 Sender 线程来不及把 Request 发送到 Kafka 服务器。</p><p>这样是不是会造成内存缓冲很快就被写满？一旦被写满，就会阻塞用户线程，不让继续往 Kafka 写消息了。</p><p>所以对于“buffer.memory”这个参数应该结合自己的实际情况来进行压测，你需要测算一下在生产环境，你的用户线程会以每秒多少消息的频率来写入内存缓冲。</p><p>比如说每秒 300 条消息，那么你就需要压测一下，假设内存缓冲就 32MB，每秒写 300 条消息到内存缓冲，是否会经常把内存缓冲写满？经过这样的压测，你可以调试出来一个合理的内存大小。</p><h4 id="1-2-1-4-4、多少数据打包为一个-Batch-合适？"><a href="#1-2-1-4-4、多少数据打包为一个-Batch-合适？" class="headerlink" title="1.2.1.4. 4、多少数据打包为一个 Batch 合适？"></a>1.2.1.4. 4、多少数据打包为一个 Batch 合适？</h4><p>接着你需要思考第二个问题，就是你的“<strong>batch.size</strong>”应该如何设置？<strong>这决定了你的每个 Batch 要存放多少数据就可以发送出去了</strong>。</p><p>比如说你要是给一个 Batch 设置成是 16KB 的大小，那么里面凑够 16KB 的数据就可以发送了。</p><p>这个 <strong>参数的默认值是 16KB</strong>，一般可以尝试把这个参数调节大一些，然后利用自己的生产环境发消息的负载来测试一下。</p><p>比如说发送消息的频率就是每秒 300 条，那么如果比如“batch.size”调节到了 32KB，或者 64KB，是否可以提升发送消息的整体吞吐量。</p><p>因为理论上来说，提升 batch 的大小，可以允许更多的数据缓冲在里面，那么一次 Request 发送出去的数据量就更多了，这样吞吐量可能会有所提升。</p><p>但是 <strong>不能无限的大</strong>，过于大了之后，要是数据老是缓冲在 Batch 里迟迟不发送出去，那么岂不是你发送消息的延迟就会很高，<strong>导致高延迟问题</strong>。</p><p>比如说，一条消息进入了 Batch，但是要等待 5 秒钟 Batch 才凑满了 64KB，才能发送出去。那这条消息的延迟就是 5 秒钟。</p><p>所以需要在这里按照生产环境的发消息的速率，调节不同的 Batch 大小自己测试一下最终出去的吞吐量以及消息的 延迟，设置一个最合理的参数。</p><h4 id="1-2-1-5-5、要是一个-Batch-迟迟无法凑满怎么办？"><a href="#1-2-1-5-5、要是一个-Batch-迟迟无法凑满怎么办？" class="headerlink" title="1.2.1.5. 5、要是一个 Batch 迟迟无法凑满怎么办？"></a>1.2.1.5. 5、要是一个 Batch 迟迟无法凑满怎么办？</h4><p>要是一个 Batch 迟迟无法凑满，此时就需要引入另外一个参数了，“<strong>linger.ms</strong>”</p><p><strong>含义是一个 Batch 被创建之后，最多过多久，不管这个 Batch 有没有写满，都必须发送出去了</strong>。</p><p>给大家举个例子，比如说 batch.size 是 16kb，但是现在某个低峰时间段，发送消息很慢。</p><p>这就导致可能 Batch 被创建之后，陆陆续续有消息进来，但是迟迟无法凑够 16KB，难道此时就一直等着吗？</p><p>当然不是，假设你现在设置“linger.ms”是 50ms，那么只要这个 Batch 从创建开始到现在已经过了 50ms 了，哪怕他还没满 16KB，也要发送他出去了。</p><p>所以“linger.ms”决定了你的消息一旦写入一个 Batch，最多等待这么多时间，他一定会跟着 Batch 一起发送出去。</p><p>避免一个 Batch 迟迟凑不满，导致消息一直积压在内存里发送不出去的情况。<strong>这是一个很关键的参数。</strong></p><p>这个参数一般要非常慎重的来设置，要配合 batch.size 一起来设置。</p><p>举个例子，首先假设你的 Batch 是 32KB，那么你得估算一下，正常情况下，一般多久会凑够一个 Batch，比如正常来说可能 20ms 就会凑够一个 Batch。</p><p>那么你的 linger.ms 就可以设置为 25ms，也就是说，正常来说，大部分的 Batch 在 20ms 内都会凑满，但是你的 linger.ms 可以保证，哪怕遇到低峰时期，20ms 凑不满一个 Batch，还是会在 25ms 之后强制 Batch 发送出去。</p><p>如果要是你把 linger.ms 设置的太小了，比如说默认就是 0ms，或者你设置个 5ms，那可能导致你的 Batch 虽然设置了 32KB，但是经常是还没凑够 32KB 的数据，5ms 之后就直接强制 Batch 发送出去，这样也不太好其实，会导致你的 Batch 形同虚设，一直凑不满数据。</p><h4 id="1-2-1-6-6、最大请求大小"><a href="#1-2-1-6-6、最大请求大小" class="headerlink" title="1.2.1.6. 6、最大请求大小"></a>1.2.1.6. 6、最大请求大小</h4><p><strong>“max.request.size”这个参数决定了每次发送给 Kafka 服务器请求的最大大小</strong>，同时也会限制你一条消息的最大大小也不能超过这个参数设置的值，这个其实可以根据你自己的消息的大小来灵活的调整。</p><p>给大家举个例子，你们公司发送的消息都是那种大的报文消息，每条消息都是很多的数据，一条消息可能都要 20KB。</p><p>此时你的 batch.size 是不是就需要调节大一些？比如设置个 512KB？然后你的 buffer.memory 是不是要给的大一些？比如设置个 128MB？</p><p>只有这样，才能让你在大消息的场景下，还能使用 Batch 打包多条消息的机制。但是此时“max.request.size”是不是也得同步增加？</p><p>因为可能你的一个请求是很大的，默认他是 1MB，你是不是可以适当调大一些，比如调节到 5MB？</p><h4 id="1-2-1-7-7、重试机制"><a href="#1-2-1-7-7、重试机制" class="headerlink" title="1.2.1.7. 7、重试机制"></a>1.2.1.7. 7、重试机制</h4><p><strong>“retries”和“retries.backoff.ms”决定了重试机制，也就是如果一个请求失败了可以重试几次，每次重试的间隔是多少毫秒</strong>。</p><p>这个大家适当设置几次重试的机会，给一定的重试间隔即可，比如给 100ms 的重试间隔。</p><h4 id="1-2-1-8-8、持久化机制"><a href="#1-2-1-8-8、持久化机制" class="headerlink" title="1.2.1.8. 8、持久化机制"></a>1.2.1.8. 8、持久化机制</h4><p>“acks”参数决定了发送出去的消息要采用什么样的持久化策略，这个涉及到了很多其他的概念，大家可以参考之前专门为“acks”写过的一篇文章。</p><p><strong>参考</strong>：<a href="https://mp.weixin.qq.com/s/YLrGg-jx5ddmHECmdccppw"></a></p><h2 id="1-3-三、消息中间件消费到的消息处理失败怎么办？"><a href="#1-3-三、消息中间件消费到的消息处理失败怎么办？" class="headerlink" title="1.3. 三、消息中间件消费到的消息处理失败怎么办？"></a>1.3. 三、消息中间件消费到的消息处理失败怎么办？</h2><p>消息中间件最核心的作用是：解耦、异步、削峰。</p><p>假如有如下的系统：</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/8.jpg"></p><p>生产中存在这种情况：如果独立仓库系统或者第三方物流系统故障了，导致仓储系统消费到一条订单消息之后，尝试进行发货失败，也就是对这条消费到的消息处理失败。这种情况，怎么处理？</p><h4 id="1-3-1-死信队列的使用：处理失败的消息"><a href="#1-3-1-死信队列的使用：处理失败的消息" class="headerlink" title="1.3.1. 死信队列的使用：处理失败的消息"></a>1.3.1. 死信队列的使用：处理失败的消息</h4><p>一般生产环境中，如果你有丰富的架构设计经验，都会在使用 MQ 的时候设计两个队列：一个是 <strong>核心业务队列</strong>，一个是 <strong>死信队列</strong>。</p><p>核心业务队列，就是比如上面专门用来让订单系统发送订单消息的，然后另外一个死信队列就是用来处理异常情况的。</p><p>面试被问到这个问题时，必须要结合你自己的业务实践经验来说。</p><p>比如说要是第三方物流系统故障了，此时无法请求，那么仓储系统每次消费到一条订单消息，尝试通知发货和配送，都会遇到对方的接口报错。</p><p>此时仓储系统就可以把这条消息拒绝访问，或者标志位处理失败！<strong>注意，这个步骤很重要。</strong></p><p>一旦标志这条消息处理失败了之后，MQ 就会把这条消息转入提前设置好的一个死信队列中。</p><p>然后你会看到的就是，在第三方物流系统故障期间，所有订单消息全部处理失败，全部会转入死信队列。</p><p>然后你的仓储系统得专门有一个后台线程，监控第三方物流系统是否正常，能否请求的，不停的监视。</p><p>一旦发现对方恢复正常，这个后台线程就从死信队列消费出来处理失败的订单，重新执行发货和配送的通知逻辑。</p><p><strong>死信队列的使用，其实就是 MQ 在生产实践中非常重要的一环，也就是架构设计必须要考虑的</strong>。</p><p>整个过程，如下图所示：</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/9.jpg"></p><h2 id="1-4-四、Kafka-选举"><a href="#1-4-四、Kafka-选举" class="headerlink" title="1.4. 四、Kafka 选举"></a>1.4. 四、Kafka 选举</h2><p>Kafka 中的选举大致可以分为三大类：</p><ul><li>控制器的选举</li><li>分区 leader 的选举</li><li>消费者相关的选举</li></ul><h4 id="1-4-1-1、控制器选举"><a href="#1-4-1-1、控制器选举" class="headerlink" title="1.4.1. 1、控制器选举"></a>1.4.1. 1、控制器选举</h4><p>在 Kafka 集群中会有一个或多个 broker，其中有一个 broker 会被选举为控制器（Kafka Controller），它负责管理整个集群中所有分区和副本的状态等工作。</p><p>比如 <strong>当某个分区的 leader 副本出现故障时，由控制器负责为该分区选举新的 leader 副本</strong>。再比如当检测到某个分区的 ISR 集合发生变化时，由控制器负责通知所有 broker 更新其元数据信息。</p><p>Kafka Controller 的选举是依赖 Zookeeper 来实现的，在 Kafka 集群中那个 broker 能够成功创建&#x2F;controller 这个临时（Ephemeral）节点他就可以成为 Kafka Controller。</p><p>这里需要说明一下的是 Kafka Controller 的实现还是相当复杂的，涉及到各个方面的内容，如果你掌握了 Kafka Controller，你就掌握了 Kafka 的“半壁江山”。</p><h4 id="1-4-2-2、分区-leader-的选举"><a href="#1-4-2-2、分区-leader-的选举" class="headerlink" title="1.4.2. 2、分区 leader 的选举"></a>1.4.2. 2、分区 leader 的选举</h4><p>分区 leader 副本的选举 <strong>由 Kafka Controller 负责具体实施</strong>。</p><p>当创建分区（创建主题或增加分区都有创建分区的动作）或分区上线（比如分区中原先的 leader 副本下线，此时分区需要选举一个新的 leader 上线来对外提供服务）的时候都需要执行 leader 的选举动作。</p><p>基本思路是按照 AR 集合中副本的顺序查找第一个存活的副本，并且这个副本在 ISR 集合中。</p><p>一个分区的 AR 集合在分配的时候就被指定，并且只要不发生重分配的情况，集合内部副本的顺序是保持不变的，而分区的 ISR 集合中副本的顺序可能会改变。</p><p>注意：这里是根据 AR 的顺序而不是 ISR 的顺序进行选举的。这个说起来比较抽象，有兴趣的读者可以手动关闭&#x2F;开启某个集群中的 broker 来观察一下具体的变化。</p><p>还有一些情况也会发生分区 leader 的选举，比如当分区进行重分配（reassign）的时候也需要执行 leader 的选举动作。</p><p>这个思路比较简单：从重分配的 AR 列表中找到第一个存活的副本，且这个副本在目前的 ISR 列表中。</p><p>再比如当发生优先副本（preferred replica partition leader election）的选举时，直接将优先副本设置为 leader 即可，AR 集合中的第一个副本即为优先副本。</p><p>还有一种情况就是当某节点被优雅地关闭（也就是执行 ControlledShutdown）时，位于这个节点上的 leader 副本都会下线，所以与此对应的分区需要执行 leader 的选举。</p><p>这里的具体思路为：从 AR 列表中找到第一个存活的副本，且这个副本在目前的 ISR 列表中，与此同时还要确保这个副本不处于正在被关闭的节点上。</p><h4 id="1-4-3-3、消费者相关的选择"><a href="#1-4-3-3、消费者相关的选择" class="headerlink" title="1.4.3. 3、消费者相关的选择"></a>1.4.3. 3、消费者相关的选择</h4><p>组协调器 GroupCoordinator 需要为消费组内的消费者选举出一个消费组的 leader，这个选举的算法也很简单，分两种情况分析。</p><ul><li><strong>如果消费组内还没有 leader，那么第一个加入消费组的消费者即为消费组的 leader</strong>。</li><li><strong>如果某一时刻 leader 消费者由于某些原因退出了消费组，那么会重新选举一个新的 leader，这个重新选举 leader 的过程又更“随意”了，相关代码如下</strong>：</li></ul><figure class="highlight scala"><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 scala"><span class="hljs-comment">//scala code.</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> members = <span class="hljs-keyword">new</span> mutable.<span class="hljs-type">HashMap</span>[<span class="hljs-type">String</span>, <span class="hljs-type">MemberMetadata</span>]<br><span class="hljs-keyword">var</span> leaderId = members.keys.head<br></code></pre></td></tr></table></figure><p>解释一下这 2 行代码：在 GroupCoordinator 中消费者的信息是以 HashMap 的形式存储的，其中 key 为消费者的 member_id，而 value 是消费者相关的元数据信息。</p><p>leaderId 表示 leader 消费者的 member_id，它的取值为 HashMap 中的第一个键值对的 key，这种选举的方式基本上和随机无异。</p><p>总体上来说，消费组的 leader 选举过程是很随意的。</p><p>到这里就结束了吗？还有分区分配策略的选举呢。</p><p>或许你对此有点陌生，但是用过 Kafka 的同学或许对 partition.assignment.strategy（取值为 RangeAssignor、RoundRobinAssignor、StickyAssignor 等）这个参数并不陌生。</p><p>每个消费者都可以设置自己的分区分配策略，对消费组而言需要从各个消费者呈报上来的各个分配策略中选举一个彼此都“信服”的策略来进行整体上的分区分配。</p><p>这个分区分配的选举并非由 leader 消费者决定，而是根据消费组内的各个消费者投票来决定的。</p><p><strong>参考</strong>：<a href="https://mp.weixin.qq.com/s/XvDpq1xxXPzRoRKMO-MxeQ"></a></p><h2 id="1-5-五、如何保证消息不被重复消费？（如何保证消息消费的幂等性）"><a href="#1-5-五、如何保证消息不被重复消费？（如何保证消息消费的幂等性）" class="headerlink" title="1.5. 五、如何保证消息不被重复消费？（如何保证消息消费的幂等性）"></a>1.5. 五、如何保证消息不被重复消费？（如何保证消息消费的幂等性）</h2><h3 id="1-5-1-面试官心理分析"><a href="#1-5-1-面试官心理分析" class="headerlink" title="1.5.1. 面试官心理分析"></a>1.5.1. 面试官心理分析</h3><p>其实这是很常见的一个问题，这俩问题基本可以连起来问。既然是消费消息，那肯定要考虑会不会重复消费？能不能避免重复消费？或者重复消费了也别造成系统异常可以吗？这个是 MQ 领域的基本问题，其实本质上还是问你 <strong>使用消息队列如何保证幂等性</strong>，这个是你架构里要考虑的一个问题。</p><h3 id="1-5-2-面试题剖析"><a href="#1-5-2-面试题剖析" class="headerlink" title="1.5.2. 面试题剖析"></a>1.5.2. 面试题剖析</h3><p>回答这个问题，首先大概说一说可能会有哪些重复消费的问题。</p><p>首先，比如 RabbitMQ、RocketMQ、Kafka，都有可能会出现消息重复消费的问题，挑 Kafka 来举个例子，说说怎么重复消费吧。</p><p>Kafka 实际上有个 offset 的概念，就是每个消息写进去，都有一个 offset，代表消息的序号，然后 consumer 消费了数据之后，<strong>每隔一段时间</strong>（<strong>定时定期</strong>），会把自己消费过的消息的 offset 提交一下，表示“我已经消费过了，下次我要是重启啥的，你就让我继续从上次消费到的 offset 来继续消费吧”。</p><p>但是，你有时候重启系统，看你怎么重启了，如果碰到点着急的，直接 kill 进程了，再重启。这会导致 consumer 有些消息处理了，但是 <strong>没来得及提交 offset，重启之后，少数消息会再次消费一次</strong>。</p><p>例如，数据 1&#x2F;2&#x2F;3 依次进入 kafka，kafka 会给这三条数据每条分配一个 offset，代表这条数据的序号，我们就假设分配的 offset 依次是 152&#x2F;153&#x2F;154。消费者从 kafka 去消费的时候，也是按照这个顺序去消费。假如当消费者消费了 <code>offset=153</code> 的这条数据，刚准备去提交 offset 到 zookeeper，此时消费者进程被重启了。那么此时消费过的数据 1&#x2F;2 的 offset 并没有提交，kafka 也就不知道你已经消费了 <code>offset=153</code> 这条数据。那么重启之后，消费者会找 kafka 说，嘿，哥儿们，你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功，那么数据 1&#x2F;2 会再次传过来，如果此时消费者没有去重的话，那么就会导致重复消费。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/10.png"></p><p><strong>如何保证消息队列消费的幂等性</strong>？</p><p>回答这个问题需要结合业务思考，有如下几个思路：</p><ul><li>比如数据要写库，先根据主键查一下，如果这数据都有了，就别插入了，update 一下。</li><li>比如是写 Redis，那没问题了，因为每次都是 set，天然幂等性。</li><li>比如不是上面两个场景，那做的稍微复杂一点，你需要让生产者发送每条数据的时候，里面 <strong>加一个全局唯一的 id</strong>，类似订单 id 之类的东西，然后你这里消费到了之后，先根据这个 id 去比如 Redis 里查一下，之前消费过吗？如果没有消费过，你就处理，然后这个 id 写 Redis。如果消费过了，就别处理，保证别重复处理相同的消息即可。</li><li>比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了，重复数据插入只会报错，不会导致数据库中出现脏数据。</li></ul><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/11.png"></p><h2 id="1-6-六、如何保证消息的可靠性传输？（如何处理消息丢失的问题？）"><a href="#1-6-六、如何保证消息的可靠性传输？（如何处理消息丢失的问题？）" class="headerlink" title="1.6. 六、如何保证消息的可靠性传输？（如何处理消息丢失的问题？）"></a>1.6. 六、如何保证消息的可靠性传输？（如何处理消息丢失的问题？）</h2><h2 id="1-7-面试官心理分析"><a href="#1-7-面试官心理分析" class="headerlink" title="1.7. 面试官心理分析"></a>1.7. 面试官心理分析</h2><p>这个是肯定的，用 MQ 有个基本原则，就是 <strong>数据不能多一条，也不能少一条</strong>，不能多，就是前面说的 [重复消费和幂等性问题。不能少，就是说这数据别搞丢了。那这个问题你必须得考虑一下。</p><p>如果说你这个是用 MQ 来传递非常核心的消息，比如说计费、扣费的一些消息，那必须确保这个 MQ 传递过程中 <strong>绝对不会把计费消息给弄丢</strong>。</p><h2 id="1-8-面试题剖析"><a href="#1-8-面试题剖析" class="headerlink" title="1.8. 面试题剖析"></a>1.8. 面试题剖析</h2><p>数据的丢失问题，可能出现在 <strong>生产者、MQ、消费者</strong> 中，从 Kafka 来分析一下。</p><h3 id="1-8-1-Kafka"><a href="#1-8-1-Kafka" class="headerlink" title="1.8.1. Kafka"></a>1.8.1. Kafka</h3><h3 id="1-8-2-1、消费者丢失数据"><a href="#1-8-2-1、消费者丢失数据" class="headerlink" title="1.8.2. 1、消费者丢失数据"></a>1.8.2. 1、消费者丢失数据</h3><p>唯一可能导致消费者弄丢数据的情况，是消费到了这个消息，然后消费者那边 <strong>自动提交了 offset</strong>，让 Kafka 以为你已经消费好了这个消息，但其实你才刚准备处理这个消息，你还没处理，你自己就挂了，此时这条消息就丢咯。</p><p>由于 Kafka 会自动提交 offset，那么只要 <strong>关闭自动提交</strong> offset，在处理完之后自己手动提交 offset，就可以保证数据不会丢。但是此时确实还是 <strong>可能会有重复消费</strong>，比如你刚处理完，还没提交 offset，结果自己挂了，此时肯定会重复消费一次，自己保证幂等性就好了。</p><p>生产环境碰到的一个问题是 Kafka 消费者消费到了数据之后是写到一个内存的 queue 里先缓冲一下，结果有的时候，你刚把消息写入内存 queue，然后消费者会自动提交 offset。然后此时我们重启了系统，就会导致内存 queue 里还没来得及处理的数据就丢失了。</p><h3 id="1-8-3-2、Kafka-弄丢数据"><a href="#1-8-3-2、Kafka-弄丢数据" class="headerlink" title="1.8.3. 2、Kafka 弄丢数据"></a>1.8.3. 2、Kafka 弄丢数据</h3><p>这块比较常见的一个场景，就是 Kafka 某个 broker 宕机，然后重新选举 partition 的 leader。如果此时其他的 follower 刚好还有些数据没有同步，结果此时 leader 挂了，然后选举某个 follower 成 leader 之后，不就少了一些数据？这就丢了一些数据啊。</p><p>所以此时一般是要求起码设置如下 4 个参数：</p><ul><li>给 topic 设置 <code>replication.factor</code> 参数：这个值必须大于 1，要求每个 partition 必须有至少 2 个副本。</li><li>在 Kafka 服务端设置 <code>min.insync.replicas</code> 参数：这个值必须大于 1，这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系，没掉队，这样才能确保 leader 挂了还有一个 follower 吧。</li><li>在 producer 端设置 <code>acks=all</code>：这个是要求每条数据，必须是 <strong>写入所有 replica 之后，才能认为是写成功了</strong>。</li><li>在 producer 端设置 <code>retries=MAX</code>（很大很大很大的一个值，无限次重试的意思）：这个是 <strong>要求一旦写入失败，就无限重试</strong>，卡在这里了。</li></ul><p>我们生产环境就是按照上述要求配置的，这样配置之后，至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障，进行 leader 切换时，数据不会丢失。</p><h3 id="1-8-4-3、生产者会不会弄丢数据？"><a href="#1-8-4-3、生产者会不会弄丢数据？" class="headerlink" title="1.8.4. 3、生产者会不会弄丢数据？"></a>1.8.4. 3、生产者会不会弄丢数据？</h3><p>如果按照上述的思路设置了 <code>acks=all</code>，一定不会丢，要求是，你的 leader 接收到消息，所有的 follower 都同步到了消息之后，才认为本次写成功了。如果没满足这个条件，生产者会自动不断的重试，重试无限次。</p><h2 id="1-9-七、如何保证消息的顺序性？"><a href="#1-9-七、如何保证消息的顺序性？" class="headerlink" title="1.9. 七、如何保证消息的顺序性？"></a>1.9. 七、如何保证消息的顺序性？</h2><p>Kafka：比如说我们建了一个 topic，有三个 partition。生产者在写的时候，其实可以指定一个 key，比如说我们指定了某个订单 id 作为 key，那么这个订单相关的数据，一定会被分发到同一个 partition 中去，而且这个 partition 中的数据一定是有顺序的。<br>消费者从 partition 中取出来数据的时候，也一定是有顺序的。到这里，顺序还是 ok 的，没有错乱。接着，我们在消费者里可能会搞 <strong>多个线程来并发处理消息</strong>。因为如果消费者是单线程消费处理，而处理比较耗时的话，比如处理一条消息耗时几十 ms，那么 1 秒钟只能处理几十条消息，这吞吐量太低了。而多个线程并发跑的话，顺序可能就乱掉了。</p><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/12.png"></p><h4 id="1-9-1-解决方案："><a href="#1-9-1-解决方案：" class="headerlink" title="1.9.1. 解决方案："></a>1.9.1. 解决方案：</h4><ul><li>一个 topic，一个 partition，一个 consumer，内部单线程消费，单线程吞吐量太低，一般不会用这个。</li><li>写 N 个 <strong>内存 queue</strong>，具有相同 key 的数据都到同一个内存 queue；然后对于 N 个线程，每个线程分别消费一个内存 queue 即可，这样就能保证顺序性。</li></ul><p><img src="https://github.com/XU-ZHOU/Java/blob/master/pictures/13.png"></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-5、负载均衡-Ribbon</title>
      <link href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-5%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1-Ribbon/"/>
      <url>/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-5%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1-Ribbon/</url>
      
        <content type="html"><![CDATA[<hr><p>我们添加了@LoadBalanced 注解，即可实现负载均衡功能，这是什么原理呢？</p><h1 id="1-负载均衡原理"><a href="#1-负载均衡原理" class="headerlink" title="1. 负载均衡原理"></a>1. 负载均衡原理</h1><p><span style="background-color:#ff00ff">SpringCloud 底层其实是利用了一个名为 Ribbon 的组件，来实现负载均衡功能的。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304224133.png" alt="image-20210713224517686"></p><p>那么我们发出的请求明明是 <a href="http://userservice/user/1">http://userservice/user/1</a>，怎么变成了 <a href="http://localhost:8081/">http://localhost:8081</a> 的呢？</p><h1 id="2-源码跟踪"><a href="#2-源码跟踪" class="headerlink" title="2. 源码跟踪"></a>2. 源码跟踪</h1><p>为什么我们只输入了 service 名称就可以访问了呢？之前还要获取 ip 和端口。</p><p>显然有人帮我们<span style="background-color:#ff00ff">根据 service 名称，获取到了服务实例的 ip 和端口</span>。它就是 <code>LoadBalancerInterceptor</code>，这个类会对 RestTemplate 的请求进行拦截，然后从 Eureka 中根据服务 id 获取服务列表，随后利用负载均衡算法得到真实的服务地址信息，替换服务 id。</p><p>我们进行源码跟踪：</p><h2 id="2-1-LoadBalancerIntercepor"><a href="#2-1-LoadBalancerIntercepor" class="headerlink" title="2.1. LoadBalancerIntercepor"></a>2.1. LoadBalancerIntercepor</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304231125.png" alt="image.png"></p><p>可以看到这里的 intercept 方法，拦截了用户的 HttpRequest 请求，然后做了几件事：</p><ul><li><code>request.getURI()</code>：<span style="background-color:#00ff00">获取请求 uri</span>，本例中就是 <a href="http://user-service/user/8">http://user-service/user/8</a></li><li><code>originalUri.getHost()</code>：<span style="background-color:#00ff00">获取 uri 路径的主机名</span>，其实就是服务 id，<code>user-service</code></li><li><code>this.loadBalancer.execute()</code>：处理服务 id，和用户请求。</li></ul><p>这里的 <code>this.loadBalancer</code> 是 <code>LoadBalancerClient</code> 类型，我们继续跟入。</p><h2 id="2-2-LoadBalancerClient"><a href="#2-2-LoadBalancerClient" class="headerlink" title="2.2. LoadBalancerClient"></a>2.2. LoadBalancerClient</h2><p>继续跟入 execute 方法：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304224009.png" alt="image.png"></p><p>代码是这样的：</p><ul><li>getLoadBalancer(serviceId)：<span style="background-color:#ff00ff">根据服务 id 获取 ILoadBalancer，而 ILoadBalancer 会拿着服务 id 去 eureka 中获取服务列表并保存起来</span>。</li><li>getServer(loadBalancer)：<span style="background-color:#ff00ff">利用内置的负载均衡算法，从服务列表中选择一个</span> 在本例中，可以看到获取了 8082 端口的服务</li></ul><p>放行后，再次访问并跟踪，发现获取的是 8081：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304232315.png" alt="image.png"></p><p>果然实现了负载均衡。</p><h2 id="2-3-负载均衡策略-IRule"><a href="#2-3-负载均衡策略-IRule" class="headerlink" title="2.3. 负载均衡策略 IRule"></a>2.3. 负载均衡策略 IRule</h2><p>在刚才的代码中，可以看到<span style="background-color:#ff00ff">获取服务使通过一个 <code>getServer</code> 方法来做负载均衡</span>:</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304224002.png" alt="image.png"></p><p>我们继续跟入：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304223958.png" alt="image.png"></p><p>继续跟踪源码 chooseServer 方法，发现这么一段代码：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304223953.png" alt="image.png"></p><p>我们看看这个 rule 是谁：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304223950.png" alt="image.png"></p><p>这里的 rule 默认值是一个 <code>RoundRobinRule</code>，看类的介绍：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304223945.png" alt="image.png"></p><p>这不就是轮询的意思嘛。<br>到这里，整个负载均衡的流程我们就清楚了。</p><h2 id="2-4-总结"><a href="#2-4-总结" class="headerlink" title="2.4. 总结"></a>2.4. 总结</h2><p><span style="display:none">%%<br>▶49.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230312-1455%%</span>❕ ^pxs57b</p><p>SpringCloudRibbon 的底层采用了一个<span style="background-color:#ff0000">拦截器</span>，<span style="background-color:#ff00ff">拦截了 RestTemplate 发出的请求，对地址做了修改</span>。用一幅图来总结一下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304224511.png" alt="image.png"></p><p>基本流程如下：</p><ol><li><code>LoadBalancerIntercepor</code> 拦截我们的 RestTemplate 请求 <a href="http://userservice/user/1">http://userservice/user/1</a></li><li><code>RibbonLoadBalancerClient</code> 会从请求 url 中获取服务名称，也就是 user-service</li><li><code>DynamicServerListLoadBalancer</code> 根据 user-service 到 eureka 拉取服务列表</li><li><code>Eureka</code> 返回列表，localhost:8081、localhost:8082</li><li><code>IRule</code> 利用内置负载均衡规则，从列表中选择一个，例如 localhost:8081</li><li><code>RibbonLoadBalancerClient</code> <span style="background-color:#ff00ff">修改请求地址</span>，用 localhost:8081 替代 userservice，得到 <a href="http://localhost:8081/user/1">http://localhost:8081/user/1</a>，发起真实请求</li></ol><h1 id="3-负载均衡策略"><a href="#3-负载均衡策略" class="headerlink" title="3. 负载均衡策略"></a>3. 负载均衡策略</h1><h2 id="3-1-负载均衡策略"><a href="#3-1-负载均衡策略" class="headerlink" title="3.1. 负载均衡策略"></a>3.1. 负载均衡策略</h2><p>负载均衡的规则都定义在 IRule 接口中，而 IRule 有很多不同的实现类：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304222952.png" alt="image.png"></p><p>不同规则的含义如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305071024.png" alt="image.png"></p><p>默认的实现就是 <span style="background-color:#ff00ff">ZoneAvoidanceRule</span>，是一种轮询方案</p><h2 id="3-2-详细介绍"><a href="#3-2-详细介绍" class="headerlink" title="3.2. 详细介绍"></a>3.2. 详细介绍</h2><p><strong>IRule</strong><br>这是所有负载均衡策略的父接口，里边的核心方法就是 choose 方法，用来选择一个服务实例。<br><strong>AbstractLoadBalancerRule</strong><br>AbstractLoadBalancerRule 是一个抽象类，里边主要定义了一个 ILoadBalancer，这里定义它的目的主要是辅助负责均衡策略选取合适的服务端实例。</p><h3 id="3-2-1-RandomRule"><a href="#3-2-1-RandomRule" class="headerlink" title="3.2.1. RandomRule"></a>3.2.1. RandomRule</h3><p>看名字就知道，这种负载均衡策略就是<span style="background-color:#ffff00">随机选择一个服务实例</span>，看源码我们知道，在 RandomRule 的无参构造方法中初始化了一个 Random 对象，然后在它重写的 choose 方法又调用了 choose(ILoadBalancer lb, Object key) 这个重载的 choose 方法，在这个重载的 choose 方法中，&#x3D;&#x3D;每次利用 random 对象生成一个不大于服务实例总数的随机数&#x3D;&#x3D;，并将该数作为下标所以获取一个服务实例。</p><h3 id="3-2-2-RoundRobinRule"><a href="#3-2-2-RoundRobinRule" class="headerlink" title="3.2.2. RoundRobinRule"></a>3.2.2. RoundRobinRule</h3><p>RoundRobinRule 这种负载均衡策略叫做<span style="background-color:#ffff00">线性轮询负载均衡策略</span>。这个类的 choose(ILoadBalancer lb, Object key) 函数整体逻辑是这样的：开启一个计数器 count，在 while 循环中遍历服务清单，获取清单之前先通过 incrementAndGetModulo 方法获取一个下标，这个下标是一个不断自增长的数先加 1 然后和服务清单总数取模之后获取到的（所以这个下标从来不会越界），拿着下标再去服务清单列表中取服务，每次循环计数器都会加 1，如果连续 10 次都没有取到服务，则会报一个警告 <code>No available alive servers after 10 tries from load balancer: XXXX</code></p><h3 id="3-2-3-RetryRule（在轮询的基础上进行重试）"><a href="#3-2-3-RetryRule（在轮询的基础上进行重试）" class="headerlink" title="3.2.3. RetryRule（在轮询的基础上进行重试）"></a>3.2.3. RetryRule（在轮询的基础上进行重试）</h3><p>看名字就知道这种负载均衡策略带有重试功能。首先 RetryRule 中又定义了一个 subRule，它的实现类是 RoundRobinRule，然后在 RetryRule 的 choose(ILoadBalancer lb, Object key) 方法中，每次还是采用 RoundRobinRule 中的 choose 规则来选择一个服务实例，如果选到的实例正常就返回，如果选择的服务实例为 null 或者已经失效，则<span style="background-color:#00ff00">在失效时间 deadline 之前不断的进行重试</span>（重试时获取服务的策略还是 RoundRobinRule 中定义的策略），如果超过了 deadline 还是没取到则会返回一个 null。</p><h3 id="3-2-4-WeightedResponseTimeRule"><a href="#3-2-4-WeightedResponseTimeRule" class="headerlink" title="3.2.4. WeightedResponseTimeRule"></a>3.2.4. WeightedResponseTimeRule</h3><p>（权重 —nacos 的 NacosRule ，Nacos 还扩展了一个自己的基于配置的权重扩展）<br>WeightedResponseTimeRule 是 RoundRobinRule 的一个子类，在 WeightedResponseTimeRule 中对 RoundRobinRule 的功能进行了扩展，<br>WeightedResponseTimeRule 中会根据每一个实例的运行情况来给计算出该实例的一个权重，然后在挑选实例的时候则根据权重进行挑选，这样能<br>够实现更优的实例调用。WeightedResponseTimeRule 中有一个名叫 DynamicServerWeightTask 的定时任务，默认情况下每隔 30 秒会计算一次<br>各个服务实例的权重，权重的计算规则也很简单，<span style="background-color:#00ff00">如果一个服务的平均响应时间越短则权重越大，那么该服务实例被选中执行任务的概率也就越大</span>。</p><h3 id="3-2-5-ClientConfigEnabledRoundRobinRule"><a href="#3-2-5-ClientConfigEnabledRoundRobinRule" class="headerlink" title="3.2.5. ClientConfigEnabledRoundRobinRule"></a>3.2.5. ClientConfigEnabledRoundRobinRule</h3><p>ClientConfigEnabledRoundRobinRule 选择策略的实现很简单，内部定义了 RoundRobinRule，choose 方法还是采用了 RoundRobinRule 的<br>choose 方法，所以它的选择策略<span style="background-color:#00ff00">和 RoundRobinRule 的选择策略一致</span>，不赘述。</p><h3 id="3-2-6-BestAvailableRule"><a href="#3-2-6-BestAvailableRule" class="headerlink" title="3.2.6. BestAvailableRule"></a>3.2.6. BestAvailableRule</h3><p>BestAvailableRule 继承自 ClientConfigEnabledRoundRobinRule，它在 ClientConfigEnabledRoundRobinRule 的基础上主要增加了根据<br>loadBalancerStats 中保存的服务实例的状态信息来<span style="background-color:#00ff00">过滤掉失效的服务实例的功能，然后顺便找出并发请求最小的服务实例来使用</span>。然而<br>loadBalancerStats 有可能为 null，如果 loadBalancerStats 为 null，则 BestAvailableRule 将采用它的父类即<br>ClientConfigEnabledRoundRobinRule 的服务选取策略（线性轮询）。</p><h3 id="3-2-7-ZoneAvoidanceRule"><a href="#3-2-7-ZoneAvoidanceRule" class="headerlink" title="3.2.7. ZoneAvoidanceRule"></a>3.2.7. ZoneAvoidanceRule</h3><p><span style="background-color:#ff00ff">默认规则，复合判断 server 所在区域的性能和 server 的可用性选择服务器</span><br>ZoneAvoidanceRule 是 PredicateBasedRule 的一个实现类，只不过这里多一个过滤条件，ZoneAvoidanceRule 中的过滤条件是以<br>ZoneAvoidancePredicate 为主过滤条件和以<br>AvailabilityPredicate 为次过滤条件组成的一个叫做 CompositePredicate 的组合过滤条件，过滤成功之后，继续采用线性轮询<br>(RoundRobinRule) 的方式从过滤结果中选择一个出来。<br>AvailabilityFilteringRule（先过滤掉故障实例，再选择并发较小的实例）<br> 过滤掉一直连接失败的被标记为 circuit tripped 的后端 Server，并过滤掉那些高并发的后端 Server 或者使用一个 AvailabilityPredicate 来<br>包含过滤 server 的逻辑，其实就是检查 status 里记录的各个 Server 的运行状态</p><h2 id="3-3-自定义负载均衡策略"><a href="#3-3-自定义负载均衡策略" class="headerlink" title="3.3. 自定义负载均衡策略"></a>3.3. 自定义负载均衡策略</h2><p>通过定义 IRule 实现可以修改负载均衡规则，有两种方式：</p><ol><li><p>代码方式：在 order-service 中的 OrderApplication 类中，定义一个新的 IRule：<br><span style="background-color:#ffff00">这是一种全局配置，不推荐</span></p><figure class="highlight plaintext"><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 plaintext">@Bean  <br>public IRule randomRule()&#123;  <br>    return new RandomRule();  <br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>配置文件方式：在 order-service 的 application.yml 文件中，添加新的配置也可以修改规则：<br><span style="background-color:#00ff00">按服务配置，更灵活</span></p><figure class="highlight plaintext"><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 plaintext">userservice: # 给某个微服务配置负载均衡规则，这里是userservice服务  <br>  ribbon:  <br>    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 <br></code></pre></td></tr></table></figure></li></ol><blockquote><p><strong>注意</strong>，<span style="background-color:#ff00ff">一般用默认的负载均衡规则，不做修改</span>。</p></blockquote><h1 id="4-饥饿加载"><a href="#4-饥饿加载" class="headerlink" title="4. 饥饿加载"></a>4. 饥饿加载</h1><p><span style="display:none">%%<br>▶48.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230312-1358%%</span>❕ ^x4qqqw</p><p>Ribbon <span style="background-color:#ff00ff">默认是采用懒加载</span>，即<span style="background-color:#ff00ff">第一次访问时才会去创建</span> <code>LoadBalanceClient</code>，请求时间会很长。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312135815.png" alt="image.png"></p><p><span style="background-color:#ff00ff">而饥饿加载则会在项目启动时创建，降低第一次访问的耗时</span>，通过下面配置开启饥饿加载：</p><figure class="highlight plaintext"><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 plaintext">ribbon:    <br>  eager-load:    <br>    enabled: true    <br>    clients: userservice<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312140030.png" alt="image.png"></p><h1 id="5-重试机制"><a href="#5-重试机制" class="headerlink" title="5. 重试机制"></a>5. 重试机制</h1><span style="display:none">- [ ] 🚩 - 重试机制 - 🏡 2023-03-12 18:41</span>#todo<h1 id="6-RestTemplate"><a href="#6-RestTemplate" class="headerlink" title="6. RestTemplate"></a>6. RestTemplate</h1><h2 id="6-1-是什么"><a href="#6-1-是什么" class="headerlink" title="6.1. 是什么"></a>6.1. 是什么</h2><p>RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端，RestTemplate 提供了多种便捷访问远程 Http 服务的方法, 能够大大提高客户端的编写效率。</p><p>调用 RestTemplate 的默认构造函数，RestTemplate 对象在底层通过使用 java.net 包下的实现创建 HTTP 请求，可以通过使用 ClientHttpRequestFactory 指定不同的 HTTP 请求方式。</p><p>ClientHttpRequestFactory 接口主要提供了两种实现方式</p><ol><li>一种是 SimpleClientHttpRequestFactory，使用 J2SE 提供的方式（既 java.net 包提供的方式）创建底层的 Http 请求连接。</li><li>一种方式是使用 HttpComponentsClientHttpRequestFactory 方式，底层使用 HttpClient 访问远程的 Http 服务，使用 HttpClient 可以配置连接池和证书等信息。</li></ol><p>RestTemplate 的核心之一 Http Client。</p><p>目前通过 RestTemplate 的源码可知，RestTemplate 可支持多种 Http Client 的 http 的访问，如下所示：</p><ul><li>基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory，默认。</li><li>基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory</li><li>基于 OkHttp3 的 OkHttpClientHttpRequestFactory。</li><li>基于 Netty4 的 Netty4ClientHttpRequestFactory。</li></ul><p>其中 HttpURLConnection 和 HttpClient 为原生的网络访问类，OkHttp3 采用了 OkHttp3 的框架，Netty4 采用了 Netty 框架。</p><h2 id="6-2-配置"><a href="#6-2-配置" class="headerlink" title="6.2. 配置"></a>6.2. 配置</h2><h3 id="6-2-1-RestTempate-的访问的超时设置"><a href="#6-2-1-RestTempate-的访问的超时设置" class="headerlink" title="6.2.1. RestTempate 的访问的超时设置"></a>6.2.1. RestTempate 的访问的超时设置</h3><p>例如，我用的是 Httpclient 的连接池，RestTemplate 的超时设置依赖 HttpClient 的内部的三个超时时间设置。</p><p>HttpClient 内部有三个超时时间设置：连接池获取可用连接超时，连接超时，读取数据超时：</p><p><strong>1.setConnectionRequestTimeout 从连接池中获取可用连接超时：设置从 connect Manager 获取 Connection 超时时间，单位毫秒。</strong></p><p>HttpClient 中的要用连接时尝试从连接池中获取，若是在等待了一定的时间后还没有获取到可用连接（比如连接池中没有空闲连接了）则会抛出获取连接超时异常。</p><p><strong>2.连接目标超时 connectionTimeout，单位毫秒。</strong></p><p>指的是连接目标 url 的连接超时时间，即客服端发送请求到与目标 url 建立起连接的最大时间。如果在该时间范围内还没有建立起连接，则就抛出 connectionTimeOut 异常。</p><p>如测试的时候，将 url 改为一个不存在的 url：“<a href="http://test.com”/">http://test.com”</a> ，超时时间 3000ms 过后，系统报出异常：   org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms</p><p><strong>3.等待响应超时（读取数据超时）socketTimeout ，单位毫秒。</strong></p><p>连接上一个 url 后，获取 response 的返回等待时间 ，即在与目标 url 建立连接后，等待放回 response 的最大时间，在规定时间内没有返回响应的话就抛出 SocketTimeout。</p><p>测试时，将 socketTimeout 设置很短，会报等待响应超时。</p><p>我遇到的问题，restTemplate 请求到一个高可用的服务时，返回的超时时间是设置值的 2 倍，是因为负载均衡器返回的重定向，导致 httpClient 底层认为没有超时，又请求一次，如果负载均衡器下有两个节点，就耗费 connectionTimeout 的双倍时间。</p><h2 id="6-3-连接池"><a href="#6-3-连接池" class="headerlink" title="6.3. 连接池"></a>6.3. 连接池</h2><p><a href="https://zhuanlan.zhihu.com/p/384627133">https://zhuanlan.zhihu.com/p/384627133</a></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-黑马程序员"><a href="#8-1-黑马程序员" class="headerlink" title="8.1. 黑马程序员"></a>8.1. 黑马程序员</h2><p>微服务开发框架 SpringCloud+RabbitMQ+Docker+Redis+ 搜索 + 分布式微服务全技术栈课程 ^4ps1v8</p><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=17&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=17&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><p>[[SpringCloud01]]</p><h2 id="8-2-其他"><a href="#8-2-其他" class="headerlink" title="8.2. 其他"></a>8.2. 其他</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210925135730907.png" alt="image-20210925135730907"></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册与发现-12、Zookeeper</title>
      <link href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-12%E3%80%81Zookeeper/"/>
      <url>/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-12%E3%80%81Zookeeper/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-节点类型"><a href="#1-节点类型" class="headerlink" title="1. 节点类型"></a>1. 节点类型</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327132528.png" alt="image.png"></p><h1 id="2-选举过程"><a href="#2-选举过程" class="headerlink" title="2. 选举过程"></a>2. 选举过程</h1><h2 id="2-1-概述"><a href="#2-1-概述" class="headerlink" title="2.1. 概述"></a>2.1. 概述</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327145406.png" alt="image.png"></p><h2 id="2-2-初始化选举"><a href="#2-2-初始化选举" class="headerlink" title="2.2. 初始化选举"></a>2.2. 初始化选举</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327131555.png" alt="image.png"></p><h2 id="2-3-崩溃恢复"><a href="#2-3-崩溃恢复" class="headerlink" title="2.3. 崩溃恢复"></a>2.3. 崩溃恢复</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327131737.png" alt="image.png"></p><h1 id="3-写数据流程"><a href="#3-写数据流程" class="headerlink" title="3. 写数据流程"></a>3. 写数据流程</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327133951.png" alt="image.png"></p><p>如果是请求的 leader，则最后是由 leader 通知 Client 数据写成功了。</p><h1 id="4-数据同步"><a href="#4-数据同步" class="headerlink" title="4. 数据同步"></a>4. 数据同步</h1><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002122124593.png" alt="image-20211002122124593"></p><h1 id="5-监听器原理"><a href="#5-监听器原理" class="headerlink" title="5. 监听器原理"></a>5. 监听器原理</h1><h2 id="5-1-Watch-机制"><a href="#5-1-Watch-机制" class="headerlink" title="5.1. Watch 机制"></a>5.1. Watch 机制</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327173040.png" alt="image.png"></p><h3 id="5-1-1-详细逻辑"><a href="#5-1-1-详细逻辑" class="headerlink" title="5.1.1. 详细逻辑"></a>5.1.1. 详细逻辑</h3><p>Zookeeper 是一个分布式协调组件，为分布式架构下的多个应用组件提供了顺序访问控制能力。它的数据存储采用了类似于文件系统的树形结构，以节点的方式来管理存储在 Zookeeper 上的数据。</p><p>Zookeeper 提供了一个 Watch 机制，可以让客户端感知到 Zookeeper Server 上存储的数据变化，这样一种机制可以让 Zookeeper 实现很多的场景，比如配置中心、注册中心等。</p><p>Watch 机制采用了 <span style="background-color:#ff00ff">Push 的方式</span>来实现，也就是说客户端和 Zookeeper Server 会建立一个长连接，一旦监听的指定节点发生了变化，就会通过这个长连接把变化的事件推送给客户端。<br>Watch 的具体流程分为几个部分：<br>首先，是客户端通过指定命令比如 exists、get，对特定路径增加 watch 然后服务端收到请求以后，<span style="background-color:#ff00ff">用 HashMap 保存这个客户端会话以及对应关注的节点路径</span>，同时客户端也会<span style="background-color:#ff00ff">使用 HashMap 存储指定节点和事件回调函数的对应关系</span>。<br>当服务端指定被 watch 的节点发生变化后，就会找到这个节点对应的会话，把变化的事件和节点信息发给这个客户端。 客户端收到请求以后，从 <code>ZkWatcherManager</code> 里面对应的回调方法进行调用， 完成事件变更的通知。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230601193046.png" alt="image.png"></p><h2 id="5-2-存在问题"><a href="#5-2-存在问题" class="headerlink" title="5.2. 存在问题"></a>5.2. 存在问题</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002131830455.png" alt="image-20211002131830455"></p><p><a href="https://www.bilibili.com/video/BV1t7411j7P7?p=4">https://www.bilibili.com/video/BV1t7411j7P7?p=4</a></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211017173229688.png" alt="image-20211017173229688"></p><h1 id="6-面试题"><a href="#6-面试题" class="headerlink" title="6. 面试题"></a>6. 面试题</h1><h2 id="6-1-生产集群安装多少台-ZK"><a href="#6-1-生产集群安装多少台-ZK" class="headerlink" title="6.1. 生产集群安装多少台 ZK"></a>6.1. 生产集群安装多少台 ZK</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327145656.png" alt="image.png"></p><h1 id="7-应用场景"><a href="#7-应用场景" class="headerlink" title="7. 应用场景"></a>7. 应用场景</h1><h2 id="7-1-集群管理-副本机制"><a href="#7-1-集群管理-副本机制" class="headerlink" title="7.1. 集群管理 - 副本机制"></a>7.1. 集群管理 - 副本机制</h2><p>在多个节点组成的集群中，为了保证集群的 HA 特性，每个节点都会冗余一份数据副本。这种情况下需要保证客户端访问集群中的任意一个节点都是最新的数据<br>Zookeeper 提供了 CP 的模型，来保证集群中的每个节点的数据一致性，当然 Zk 本身的集群并不是 CP 模型，而是顺序一致性模型，如果要保证 CP 特性，需要调用 <code>sync</code> 同步方法。</p><h2 id="7-2-master-选举"><a href="#7-2-master-选举" class="headerlink" title="7.2. master 选举"></a>7.2. master 选举</h2><p>在多个节点组成的集群中，为了降低集群数据同步的复杂度，一般会存在 Master 和 Slave 两种角色的节点，Master 负责事务和非事务请求处理，Slave 负责非事务请求处理。但是在分布式系统中如何确定某个节点是 Master 还是 Slave，也成了一个难度不小的挑战。基于这三类常见场景的需求，所以产生了 Zookeeper 这样一个中间件。它是一个分布式开源协调组件，简单来说，就是类似于一个裁判员的角色，专门负责协调和解决分布式系统中的各类问题。比如，针对上述描述的问题，Zookeeper 都可以解决。<br>Zookeeper 可以利用持久化节点来存储和管理其他集群节点的信息，从而进行 Master 选举机制。或者还可以利用集群中的有序节点特性，来实现 Master 选举。 <span style="background-color:#ff00ff">目前主流的 Kafka、Hbase、Hadoop 都是通过 Zookeeper 来实现集群节点的主从选举</span>。<br>总的来说，Zookeeper 就是经典的分布式数据一致性解决方案，致力于为分布式应用提供高性能、高可用，并且具有严格顺序访问控制能力的分布式协调服务。它底层通过基于 Paxos 算法演化而来的 ZAB 协议实现。</p><h2 id="7-3-监听服务器节点动态上下线案例⭐️🔴"><a href="#7-3-监听服务器节点动态上下线案例⭐️🔴" class="headerlink" title="7.3. 监听服务器节点动态上下线案例⭐️🔴"></a>7.3. 监听服务器节点动态上下线案例⭐️🔴</h2><p><span style="background-color:#ff00ff">先在集群上创建&#x2F;servers 永久节点</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327173040.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327143500.png" alt="image.png"></p><h2 id="7-4-分布式锁-临时顺序节点⭐️🔴"><a href="#7-4-分布式锁-临时顺序节点⭐️🔴" class="headerlink" title="7.4. 分布式锁 - 临时顺序节点⭐️🔴"></a>7.4. 分布式锁 - 临时顺序节点⭐️🔴</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230601-1007%%</span>❕ ^018oy5</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327144509.png" alt="image.png"></p><h1 id="8-相关算法"><a href="#8-相关算法" class="headerlink" title="8. 相关算法"></a>8. 相关算法</h1><p><a href="https://www.bilibili.com/video/BV1to4y1C7gw?p=31&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1to4y1C7gw?p=31&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="8-1-ZAB-协议"><a href="#8-1-ZAB-协议" class="headerlink" title="8.1. ZAB 协议"></a>8.1. ZAB 协议</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327170120.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327170926.png" alt="image.png"></p><h2 id="8-2-CAP-理论"><a href="#8-2-CAP-理论" class="headerlink" title="8.2. CAP 理论"></a>8.2. CAP 理论</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327170657.png" alt="image.png"></p><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><h2 id="9-1-尚硅谷大数据"><a href="#9-1-尚硅谷大数据" class="headerlink" title="9.1. 尚硅谷大数据"></a>9.1. 尚硅谷大数据</h2><h3 id="9-1-1-视频"><a href="#9-1-1-视频" class="headerlink" title="9.1.1. 视频"></a>9.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1to4y1C7gw?p=4&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1to4y1C7gw?p=4&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="9-1-2-资料"><a href="#9-1-2-资料" class="headerlink" title="9.1.2. 资料"></a>9.1.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">005</span>-分布式专题/012_Zookeeper的替身<br></code></pre></td></tr></table></figure><p><code>尚硅谷大数据技术之Zookeeper</code></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试专题-4、微服务</title>
      <link href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
      <url>/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-微服务架构的优缺点"><a href="#1-微服务架构的优缺点" class="headerlink" title="1. 微服务架构的优缺点"></a>1. 微服务架构的优缺点</h1><p>1. 演变而来（从单体应用演变过来）<br>2. 初期评估起手就上微服务</p><h2 id="1-1-面相服务-单一职责"><a href="#1-1-面相服务-单一职责" class="headerlink" title="1.1. 面相服务 单一职责"></a>1.1. 面相服务 单一职责</h2><p>避免业务重复开发</p><h2 id="1-2-分工协作"><a href="#1-2-分工协作" class="headerlink" title="1.2. 分工协作"></a>1.2. 分工协作</h2><p><strong>单体</strong>：影响开发效率,发布和迭代性差；项目启动慢，    每个人对整体的项目都要有所把握；  业务缩减后如果<br>语言不一致开发人员面临流失。<br><strong>拆分</strong>：提高开发效率和敏捷性；单个服务启动快， 专人处理专事专注自己的服务；  充分利用项目开发人员<br>（哪怕是不同的语言不同框架，不同存储技术，也可以）</p><h2 id="1-3-并发能力"><a href="#1-3-并发能力" class="headerlink" title="1.3. 并发能力"></a>1.3. 并发能力</h2><p><strong>单体</strong>：整体集群，易造成系统资源浪费；   之前下单功能要去集群无法准确评测最大并发量， 因为所有的<br>功能都在一起，无法准确预估扩容的服务器。<br><strong>拆分</strong>：服务集群，充分利用服务器资源；现在只需要针对下单服务进行压测就可以得到，下单功能具体<br>能承受的并发量最高水位，从而更准确的进行扩容。</p><h2 id="1-4-隔离能力"><a href="#1-4-隔离能力" class="headerlink" title="1.4. 隔离能力"></a>1.4. 隔离能力</h2><p>服务之间调用做好隔离、容错、降级，可以避免出现级联错误</p><h2 id="1-5-维护能力"><a href="#1-5-维护能力" class="headerlink" title="1.5. 维护能力"></a>1.5. 维护能力</h2><p><strong>单体</strong>：随着业务量增加，应用慢慢膨胀，后续可能会变得牵一发而动全身，难以维护。<br><strong>拆分</strong>：根据功能垂直拆分，责任更加分明，维护更加精准。<br>容错<br><strong>单体</strong>：单点故障，一个功能 OOM 导致整个应用都不可用<br><strong>拆分</strong>：弱依赖的服务出现故障，可以进行熔断（隔离） 依然不影响主业务正常使用</p><h2 id="1-6-扩展"><a href="#1-6-扩展" class="headerlink" title="1.6. 扩展"></a>1.6. 扩展</h2><p>单体：难以技术升级<br>拆分：新的服务采用任意新技术（技术多样性）</p><h2 id="1-7-缺点"><a href="#1-7-缺点" class="headerlink" title="1.7. 缺点"></a>1.7. 缺点</h2><h3 id="1-7-1-分布式"><a href="#1-7-1-分布式" class="headerlink" title="1.7.1. 分布式"></a>1.7.1. 分布式</h3><p>分布式系统较难编程，因为远程调用速度很慢，并且总是面临失败的风险。对于开发人员的技术要求更高</p><h3 id="1-7-2-最终一致性"><a href="#1-7-2-最终一致性" class="headerlink" title="1.7.2. 最终一致性"></a>1.7.2. 最终一致性</h3><p>对于分布式系统而言，保持强一致性非常困难，这意味着每个人都必须管理最终一致性。</p><h3 id="1-7-3-运维复杂性"><a href="#1-7-3-运维复杂性" class="headerlink" title="1.7.3. 运维复杂性"></a>1.7.3. 运维复杂性</h3><p>微服务必定带来开发、上线、运维的复杂度的提高，如果说单体应用复杂度为 10，实施了微服务后的复杂度将是 100，<br>配备了相应的工具和平台后，可以将复杂度降低到 50，但仍然比单体复杂的多。</p><h3 id="1-7-4-隐式接口"><a href="#1-7-4-隐式接口" class="headerlink" title="1.7.4. 隐式接口"></a>1.7.4. 隐式接口</h3><p>服务和服务之间通过接口来“联系”，当某一个服务更改接口格式时，可能涉及到此接口的所有服务都需要做调整。</p><h3 id="1-7-5-重复劳动"><a href="#1-7-5-重复劳动" class="headerlink" title="1.7.5. 重复劳动"></a>1.7.5. 重复劳动</h3><p>在很多服务中可能都会使用到同一个功能，而这一功能点没有足够大到提供一个服务的程度，这个时候可能不同的服务<br>团队都会单独开发这一功能，重复的业务逻辑，这违背了良好的软件工程中的很多原则。</p><h1 id="2-SOA、分布式、微服务之间有什么关系和区别"><a href="#2-SOA、分布式、微服务之间有什么关系和区别" class="headerlink" title="2. SOA、分布式、微服务之间有什么关系和区别"></a>2. SOA、分布式、微服务之间有什么关系和区别</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225160809.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225160151.png" alt="image.png"></p><ol><li>分布式架构是指将单体架构中的各个部分拆分，然后部署不同的机器或进程中去，SOA 和微服务基本上都是分布式架构的</li><li>SOA 是一种面向服务的架构，系统的所有服务都注册在总线上，当调用服务时，从总线上查找服务信息，然后调用</li><li>微服务是一种更彻底的面向服务的架构，将系统中各个功能个体抽成一个个小的应用程序，基本保持一个应用对应的一个服务的架构</li></ol><h1 id="3-微服务怎么拆分"><a href="#3-微服务怎么拆分" class="headerlink" title="3. 微服务怎么拆分"></a>3. 微服务怎么拆分</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225184236.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225184647.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225194822.png" alt="image.png"></p><p>1、高内聚低耦合，职责单一，服务粒度适中，服务不要太细（有的团队甚至一个接口一个服务，一个表一个服务）<br>2、以业务模型切入：比如产品，用户，订单为一个模型来切入）<br>3、演进式拆分：刚开始不要划分太细，可以随着迭代过程来逐步优化。  </p><p>微服务 1.0，仅使用注册发现，基于 SpringCloud 或者 Dubbo 进行开发，目前意图实施微服务的传统企业大部分处于这个阶段，或者正从单体应用，向这个阶段过渡，处于 0.5 的阶段；<br>微服务 2.0，使用了熔断，限流，降级等服务治理策略，并配备完整微服务工具和平台，目前大部分互联网企业处于这个阶段。传统企业中的领头羊，在做互联网转型的过程中，正在向这个阶段过渡，处于 1.5 的阶段；<br>微服务 3.0，Service Mesh 将服务治理作为通用组件，下沉到平台层实现，使得应用层仅仅关注业务逻辑，平台层可以根据业务监控自动调度和参数调整，实现 AIOps 和智能调度。目前一线互联网公司在进行这方面的尝试</p><h1 id="4-常用分布式组件及作用"><a href="#4-常用分布式组件及作用" class="headerlink" title="4. 常用分布式组件及作用"></a>4. 常用分布式组件及作用</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225170435.png" alt="image.png"></p><h1 id="5-分布式之日志监控方案"><a href="#5-分布式之日志监控方案" class="headerlink" title="5. 分布式之日志监控方案"></a>5. 分布式之日志监控方案</h1><h1 id="6-SpringCloud-常见组件"><a href="#6-SpringCloud-常见组件" class="headerlink" title="6. SpringCloud 常见组件"></a>6. SpringCloud 常见组件</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225224605.png" alt="image.png"></p><h2 id="6-1-技术对比"><a href="#6-1-技术对比" class="headerlink" title="6.1. 技术对比"></a>6.1. 技术对比</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312114119.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312114225.png" alt="image.png"></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-黑马程序员-SpringCloud"><a href="#8-1-黑马程序员-SpringCloud" class="headerlink" title="8.1. 黑马程序员 SpringCloud"></a>8.1. 黑马程序员 SpringCloud</h2><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=163&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=163&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><p>[[微服务常见面试题]]</p>]]></content>
      
      
      
    </entry>
    
    
    
    <entry>
      <title>面试专题-6、分布式组件</title>
      <link href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/"/>
      <url>/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-服务注册与发现"><a href="#1-服务注册与发现" class="headerlink" title="1. 服务注册与发现"></a>1. 服务注册与发现</h1><a href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-6%E3%80%81Nacos/" title="服务注册与发现-6、Nacos">服务注册与发现-6、Nacos</a><h1 id="2-网关"><a href="#2-网关" class="headerlink" title="2. 网关"></a>2. 网关</h1><a href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-8%E3%80%81%E7%BD%91%E5%85%B3-GateWay/" title="分布式专题-8、网关-GateWay">分布式专题-8、网关-GateWay</a><h1 id="3-服务熔断降级限流"><a href="#3-服务熔断降级限流" class="headerlink" title="3. 服务熔断降级限流"></a>3. 服务熔断降级限流</h1><h2 id="3-1-服务雪崩"><a href="#3-1-服务雪崩" class="headerlink" title="3.1. 服务雪崩"></a>3.1. 服务雪崩</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301155601.png" alt="image.png"></p><p><strong>服务雪崩</strong>： 因服务提供者的不可用导致服务调用者的不可用, 并将不可用逐渐放大的过程，就叫服务雪崩效应<br><strong>解决方式</strong>：<br><span style="background-color:#00ff00">通过熔断机制</span>，当一个服务挂了，被影响的服务能够及时熔断，使用 Fallback 数据保证流程在非关键服务不可用的情况下，仍然可以进行。<br><span style="background-color:#00ff00">通过线程池和消息队列机制实现异步化</span>，允许服务快速失败，当一个服务因为过慢而阻塞，被影响服务可以在超时后快速失败，不会影响整个调用链路。</p><h2 id="3-2-服务限流"><a href="#3-2-服务限流" class="headerlink" title="3.2. 服务限流"></a>3.2. 服务限流</h2><p>是指在高并发请求下，为了保护系统，可以对访问服务的请求进行数量上的限制，从而防止系统不被大量请求压垮，在秒杀中，限流是非常重要的。</p><h2 id="3-3-服务熔断"><a href="#3-3-服务熔断" class="headerlink" title="3.3. 服务熔断"></a>3.3. 服务熔断</h2><p>当服务 A 调用的某个服务 B 不可用时，上游服务 A 为了保证自己不受影响，及时切断与服务 B 的通讯。以防服务雪崩。防止服务雪崩一种措施。</p><h2 id="3-4-服务降级"><a href="#3-4-服务降级" class="headerlink" title="3.4. 服务降级"></a>3.4. 服务降级</h2><p>提前预想好另外一种兜底措施，可以进行后期补救。直到服务 B 恢复，再恢复和 B 服务的正常通讯。当被调用服务不可用时的一种兜底措施。</p><h3 id="3-4-1-哪些场景用到了限流、降级？怎么配的？"><a href="#3-4-1-哪些场景用到了限流、降级？怎么配的？" class="headerlink" title="3.4.1. 哪些场景用到了限流、降级？怎么配的？"></a>3.4.1. 哪些场景用到了限流、降级？怎么配的？</h3><h4 id="3-4-1-1-服务降级的预案"><a href="#3-4-1-1-服务降级的预案" class="headerlink" title="3.4.1.1. 服务降级的预案"></a>3.4.1.1. 服务降级的预案</h4><p>在进行降级之前要对系统进行梳理，提前将一些 不重要 或 不紧急 的服务（弱依赖）或任务进行服务的 延迟使用 或 暂停使用。 （积分）<br>看看哪些服务是必须誓死保护，哪些系统是能够丢卒保帅；具体可以参考日志级别设置预案：<br><strong>一般</strong>：有些服务偶尔因为网络抖动或者服务正在上线而超时，可以自动降级；<br><strong>警告</strong>：有些服务在一段时间内成功率有波动（如在 95~100% 之间），可以自动降级或人工降级，并发送告警；<br><strong>错误</strong>：可用率低于 90%，或者连接池被占用满了，或者访问量突然猛增到系统能承受的最大阀值，此时可以根据情况自动降级或者人工降级；<br><strong>严重错误</strong>：因为特殊原因数据错误了，此时需要紧急人工降级 </p><h4 id="3-4-1-2-QPS-并发配置"><a href="#3-4-1-2-QPS-并发配置" class="headerlink" title="3.4.1.2. QPS 并发配置"></a>3.4.1.2. QPS 并发配置</h4><ol><li>测试会提供准确的数据；</li><li>自己算： 二八法则<br>计算关系：<br>QPS &#x3D; 并发量 &#x2F; 平均响应时间<br>并发量 &#x3D; QPS * 平均响应时间<br>根据以上计算关系，我们来预估下单日访问量在 1000W 需要多大的 QPS 来支持：<br>通常情况下，80% 的访问量集中在 20% 的时间，算一下这 1000w pv 实际需要机器达到多少 qps 才能满足，<br>qps &#x3D; (1000w * 0.8) &#x2F; (24 * 3600 * 0.2)<br>qps &#x3D; 462.9</li><li>根据压力测试的反馈，单台机子的 QPS 是多少，利用以上结果就可以算出需要几台机器或大致推算出需不需要使用缓存配置<br>方案一： 使用集群服务器 不使用缓存服务器<br>方案二： 使用集群服务器 同时使用缓存服务器 (推荐)<br>例子：<br> 每秒可以处理的请求数 QPS（TPS）：每秒钟可以处理的请求或者事务的数量。<br>    并发数： 系统同一时候处理的请求数量（事务数）<br>    响应时间：  一般取平均响应时间<br>QPS（TPS）&#x3D; 并发数&#x2F;平均响应时间<br>并发数 &#x3D; QPS<em>平均响应时间<br>例子:<br> 一个典型的上班签到系统，早上 8 点上班。7 点半到 8 点这 30 分钟的时间里用户会登录签到系统进行签到。公司员工为 1000<br>人，平均每一个员上登录签到系统的时长为 5 分钟。能够用以下的方法计算。<br>（1）QPS &#x3D; 1000&#x2F;(30×60) 事务&#x2F;秒<br>（2）平均响应时间为 &#x3D; 5×60  秒<br>（3）并发数&#x3D; QPS</em> 平均响应时间 &#x3D; 1000&#x2F;(30×60) ×(5×60)&#x3D;166.7<br>再看如果老板要求实现多少并发数，在反推出机器需要多少 QPS，看是否集群配置。</li></ol><h2 id="3-5-Sentinal"><a href="#3-5-Sentinal" class="headerlink" title="3.5. Sentinal"></a>3.5. Sentinal</h2><a href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-10%E3%80%81%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7(%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4)-Sentinel/" title="服务治理-10、限流熔断降级(服务保护)-Sentinel">服务治理-10、限流熔断降级(服务保护)-Sentinel</a><h1 id="4-分布式事务"><a href="#4-分布式事务" class="headerlink" title="4. 分布式事务"></a>4. 分布式事务</h1><p>可以参考： <a href="/2022/12/29/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-2%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/" title="事务-2、分布式事务">事务-2、分布式事务</a> ^lnql61</p><h2 id="4-1-Seata-的实现原理-AT2PC-变种⭐️🔴"><a href="#4-1-Seata-的实现原理-AT2PC-变种⭐️🔴" class="headerlink" title="4.1. Seata 的实现原理 -AT2PC 变种⭐️🔴"></a>4.1. Seata 的实现原理 -AT2PC 变种⭐️🔴</h2><p><span style="display:none">%%<br>▶60.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1852%%</span>❕ ^a3vjds</p><p>分布式事务： <a href="https://www.jianshu.com/p/044e95223a17">https://www.jianshu.com/p/044e95223a17</a>  ⭐️🔴<br>在应用中 Seata 整体事务逻辑基于两阶段提交的模型，核心概念包含三个角色：<br>TC：事务协调者，即独立运行的 seata-server，用于接收事务注册，提交和回滚。<br>TM：事务发起者。用来告诉 TC 全局事务的开始，提交，回滚。<br>RM：事务资源，每一个 RM 都会作为一个分支事务注册在 TC。<br><strong>AT(Auto Transaction) 模式</strong></p><h2 id="4-2-执行流程"><a href="#4-2-执行流程" class="headerlink" title="4.2. 执行流程"></a>4.2. 执行流程</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230404-2144%%</span>❕ ^14aw6x</p><p>假设运行：update product set name &#x3D; ‘GTS’ where name &#x3D; ‘TXC’;    &#x2F;&#x2F; id&#x3D;1</p><h3 id="4-2-1-TM-开启全局事务"><a href="#4-2-1-TM-开启全局事务" class="headerlink" title="4.2.1. TM 开启全局事务"></a>4.2.1. TM 开启全局事务</h3><p> TM 向 TC 申请开启一个全局事务，全局事务创建并生成一个全局唯一的 XID。<br> <span style="background-color:#ff00ff">XID 在微服务调用链路的上下文中传播</span>。</p><h3 id="4-2-2-第一阶段-各-RM-执行并提交分支事务"><a href="#4-2-2-第一阶段-各-RM-执行并提交分支事务" class="headerlink" title="4.2.2. 第一阶段 - 各 RM 执行并提交分支事务"></a>4.2.2. 第一阶段 - 各 RM 执行并提交分支事务</h3><p>1. 解析 SQL：得到 SQL 的类型（UPDATE），表（product），条件（where name &#x3D; ‘TXC’）等相关的信息。<br>2. 查询前镜像：根据解析得到的条件信息，生成查询语句，定位数据。 <code>select * from product where name = &#39;TXC&#39; </code> 镜像前数据<br>3. 执行业务 SQL：更新这条记录的 name 为 ‘GTS’。<br>4. 查询后镜像：根据前镜像的结果，通过 主键 定位数据。<code>select * from produc where name = &#39;TXC&#39; </code> 镜像后数据<br>5. RM 在同一个本地事务中执行业务 SQL 和 UNDO_LOG 数据的插入<br>把<span style="background-color:#ff00ff">前后镜像数据以及业务 SQL 相关的信息</span>组成一条回滚日志记录，插入到 UNDO_LOG 表中。<br><span style="background-color:#ff0000">提交前</span>，<span style="background-color:#ff00ff">RM 向 TC 注册分支</span>：<span style="background-color:#ff0000">申请</span> product 表中，主键值等于 1 的记录的<span style="background-color:#ff0000">全局锁</span> <br>如果申请不到，则说明有其他事务也在对这条记录进行操作，因此它会在一段时间内重试，重试失败则回滚本地事务，并向 TC 汇报本地事务执行失败。等待全局锁的情况如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228173404.png" alt="image.png"></p><p>等不到全局锁回滚本地事务的情况，请看防止脏写的 3 种情况</p><p>6. <span style="background-color:#ff00ff">RM 本地事务提交：业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。释放本地锁，但此时全局锁并没有释放，全局锁的释放取决于二阶段是提交命令还是回滚命令。</span></p><h3 id="4-2-3-TM-发起全局决议"><a href="#4-2-3-TM-发起全局决议" class="headerlink" title="4.2.3. TM 发起全局决议"></a>4.2.3. TM 发起全局决议</h3><p>TM 将本地事务提交的结果上报给 TC。并向 TC 发起针对 XID 的全局提交或回滚决议。TC 根据所有的分支事务执行结果，向 RM 下发提交或回滚命令。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228135405.png" alt="image.png"></p><h3 id="4-2-4-第二阶段-TM-决议后通知-TC-发起全局提交"><a href="#4-2-4-第二阶段-TM-决议后通知-TC-发起全局提交" class="headerlink" title="4.2.4. 第二阶段 - TM 决议后通知 TC 发起全局提交"></a>4.2.4. 第二阶段 - TM 决议后通知 TC 发起全局提交</h3><p>TC 调度 XID 下管辖的全部分支事务完成提交请求</p><ol><li>RM 如果收到 TC 的提交命令，首先立即释放相关记录的全局锁 (其实锁是在 TC 端维护的)</li><li>然后把提交请求放入一个异步任务的队列中，马上返回提交成功的结果给 TC。异步队列中的提交请求真正执行时，<span style="background-color:#00ff00">只是删除相应 UNDO LOG 记录而已</span>。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228172840.png" alt="image.png"></p><h3 id="4-2-5-第二阶段-TM-决议后通知-TC-发起全局回滚"><a href="#4-2-5-第二阶段-TM-决议后通知-TC-发起全局回滚" class="headerlink" title="4.2.5. 第二阶段 - TM 决议后通知 TC 发起全局回滚"></a>4.2.5. 第二阶段 - TM 决议后通知 TC 发起全局回滚</h3><p>TC 调度 XID 下管辖的全部分支事务完成回滚请求</p><ol><li>所有 RM 开启一个本地事务</li><li>通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。</li><li>数据校验：拿 UNDO LOG 中的<span style="background-color:#ff00ff">后镜与当前数据进行比较</span>，如果有不同，说明数据被当前全局事务之外的动作做了修改（出现脏写），转人工处理（Seata 因为无法感知这个脏写如何发生，此时只能打印日志和触发异常通知，告知用户需要人工介入） 。人工没有脏写就简单了：根据 UNDO LOG 中的<span style="background-color:#ff00ff">前镜像和业务 SQL 的相关信息</span>生成并执行回滚的语句</li><li>提交本地事务。并把本地事务的执行结果（即分支事务回滚的结果）上报给 TC。</li><li><span style="background-color:#ff0000">最后释放相关记录的全局锁</span></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228173006.png" alt="image.png"></p><h2 id="4-3-GlobalTransactionScanner-原理"><a href="#4-3-GlobalTransactionScanner-原理" class="headerlink" title="4.3. @GlobalTransactionScanner 原理"></a>4.3. @GlobalTransactionScanner 原理</h2><p><a href="https://www.cnblogs.com/wxbty/p/10411190.html">https://www.cnblogs.com/wxbty/p/10411190.html</a></p><h2 id="4-4-Seata-的全局事务隔离级别⭐️🔴"><a href="#4-4-Seata-的全局事务隔离级别⭐️🔴" class="headerlink" title="4.4. Seata 的全局事务隔离级别⭐️🔴"></a>4.4. Seata 的全局事务隔离级别⭐️🔴</h2><p><span style="display:none">%%<br>▶59.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1850%%</span>❕ ^mm3ts9</p><p>脏读脏写的解决方案<br><a href="https://seata.io/zh-cn/blog/seata-at-lock.html">https://seata.io/zh-cn/blog/seata-at-lock.html</a></p><h3 id="4-4-1-如何防止脏写"><a href="#4-4-1-如何防止脏写" class="headerlink" title="4.4.1. 如何防止脏写"></a>4.4.1. 如何防止脏写</h3><p><span style="display:none">%%<br>▶8.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1001%%</span>❕ ^qngn57</p><p>先来看一下使用 Seata AT 模式是怎么产生脏写的：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305095414.png"></p><p><em>注：分支事务执行过程省略其它过程。</em></p><p>业务一开启全局事务，其中包含分支事务 A（修改 A）和分支事务 B（修改 B），业务二修改 A，其中业务一执行分支事务 A 先获取本地锁，业务二则等待业务一执行完分支事务 A 之后，获得本地锁修改 A 并入库，业务一在执行分支事务时发生异常了，由于分支事务 A 的数据被业务二修改，导致业务一的全局事务无法回滚。</p><p>如何防止脏写？</p><h4 id="4-4-1-1-GlobalTransactional"><a href="#4-4-1-1-GlobalTransactional" class="headerlink" title="4.4.1.1. @GlobalTransactional"></a>4.4.1.1. @GlobalTransactional</h4><p>1、业务二执行时加 <code>@GlobalTransactional</code> 注解：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305095359.png"></p><p><em>注：分支事务执行过程省略其它过程。</em></p><p>业务二在执行全局事务过程中，分支事务 A <span style="background-color:#ff00ff">提交前注册分支事务获取全局锁</span>时，发现业务业务一全局锁还没执行完，因此业务二提交不了，抛异常回滚，所以不会发生脏写。</p><h4 id="4-4-1-2-GlobalLock"><a href="#4-4-1-2-GlobalLock" class="headerlink" title="4.4.1.2. @GlobalLock"></a>4.4.1.2. @GlobalLock</h4><p>2、业务二执行时加 <code>@GlobalLock</code> 注解：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305095351.png"></p><p><em>注：分支事务执行过程省略其它过程。</em></p><p>与 <code>@GlobalTransactional</code> 注解效果类似，只不过不需要开启全局事务，只在本地事务提交前，检查全局锁是否存在。</p><h4 id="4-4-1-3-GlobalLock-select-for-update"><a href="#4-4-1-3-GlobalLock-select-for-update" class="headerlink" title="4.4.1.3. @GlobalLock + select for update"></a>4.4.1.3. @GlobalLock + select for update</h4><p>2、业务二执行时加 <code>@GlobalLock</code> 注解 + <code>select for update</code> 语句：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305095340.png"></p><p>如果加了 <code>select for update</code> 语句，则会在 update 前检查全局锁是否存在，只有当全局锁释放之后，业务二才能开始执行 updateA 操作。</p><p>加 select for update 的作用是可以重试。</p><h3 id="4-4-2-如何防止脏读"><a href="#4-4-2-如何防止脏读" class="headerlink" title="4.4.2. 如何防止脏读"></a>4.4.2. 如何防止脏读</h3><p>Seata AT 模式的脏读是指在全局事务未提交前，被其它业务读到已提交的分支事务的数据，根本原因是 <span style="background-color:#ff00ff">Seata 默认的全局事务是读未提交</span></p><p>那么怎么避免脏读现象呢？</p><p>业务二查询 A 时加 <code>@GlobalLock</code> 注解 + <code>select for update</code> 语句：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305095324.png"></p><p>加 <code>select for update</code> 语句会在执行 SQL 前检查全局锁是否存在，只有当全局锁完成之后，才能继续执行 SQL，这样就防止了脏读。</p><h3 id="4-4-3-select-for-update"><a href="#4-4-3-select-for-update" class="headerlink" title="4.4.3. select for update"></a>4.4.3. select for update</h3><p>Seata 由于一阶段 RM 自动提交本地事务的原因，默认隔离级别为 Read Uncommitted。如果希望隔离级别为 Read Committed，那么可以使用 <code>SELECT...FOR UPDATE</code> 语句。<span style="background-color:#00ff00">Seata 引擎重写了 <code>SELECT...FOR UPDATE</code> 语句执行逻辑</span>，<code>SELECT...FOR UPDATE</code> 语句的执行<span style="background-color:#ff0000">会先申请全局锁</span>，如果全局锁被其他事务持有，则释放本地锁（回滚 <code>SELECT...FOR UPDATE</code> 语句的本地执行）并重试。这个过程中，查询是被 block 住的，直到全局锁拿到，即读取的相关数据是已提交的才返回。<br><span style="background-color:#00ff00">除了能够检查是否有全局锁</span>，<span style="background-color:#ff00ff">加 select for update 还有个好处，就是可以重试。</span></p><p>出于总体性能上的考虑，Seata 目前的方案并没有对所有 SELECT 语句都进行代理，仅针对 FOR UPDATE 的 SELECT 语句。</p><h2 id="4-5-集中管理全局锁的考虑"><a href="#4-5-集中管理全局锁的考虑" class="headerlink" title="4.5. 集中管理全局锁的考虑"></a>4.5. 集中管理全局锁的考虑</h2><p>全局锁是由 TC 也就是 server 来集中维护，而不是在数据库维护的。这样做有两点好处：</p><ul><li>一方面：锁的释放非常快，尤其是在全局提交的情况下，收到全局提交的请求，锁马上就释放掉了，不需要与 RM 或数据库进行一轮交互。</li><li>另外一方面：因为锁不是数据库维护的，从数据库层面看，数据没有锁定。这也就是给极端情况下，业务 <strong>降级</strong> 提供了方便，事务协调器异常导致的一部分异常事务，不会 block 后面业务的继续进行。</li></ul><h1 id="5-负载均衡"><a href="#5-负载均衡" class="headerlink" title="5. 负载均衡"></a>5. 负载均衡</h1><h2 id="5-1-Ribbon-原理"><a href="#5-1-Ribbon-原理" class="headerlink" title="5.1. Ribbon 原理"></a>5.1. Ribbon 原理</h2><a href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-5%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1-Ribbon/" title="分布式专题-5、负载均衡-Ribbon">分布式专题-5、负载均衡-Ribbon</a><h1 id="6-远程服务调用"><a href="#6-远程服务调用" class="headerlink" title="6. 远程服务调用"></a>6. 远程服务调用</h1><a href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-7%E3%80%81%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8-Feign%E4%B8%8EOpenFeign/" title="分布式专题-7、远程调用-Feign与OpenFeign">分布式专题-7、远程调用-Feign与OpenFeign</a><h1 id="7-SpringCloudAlibaba"><a href="#7-SpringCloudAlibaba" class="headerlink" title="7. SpringCloudAlibaba"></a>7. SpringCloudAlibaba</h1><p>尚硅谷 2020-3.2 第二季最新课程 SpringCloud H 版 +SpringCloud Alibaba 构成</p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><h2 id="9-1-黑马程序员"><a href="#9-1-黑马程序员" class="headerlink" title="9.1. 黑马程序员"></a>9.1. 黑马程序员</h2><p><span style="display:none">%%<br>▶46.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️%%</span>❕ ^n93hnw</p><h3 id="9-1-1-视频"><a href="#9-1-1-视频" class="headerlink" title="9.1.1. 视频"></a>9.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=173&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=173&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="9-1-2-资料"><a href="#9-1-2-资料" class="headerlink" title="9.1.2. 资料"></a>9.1.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/Enterprise/0003-Architecture/005-分布式专题/1、微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程/<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/黑马资料-微服务架构的分布式事务控制解决方案<br></code></pre></td></tr></table></figure><h2 id="9-3-网络笔记"><a href="#9-3-网络笔记" class="headerlink" title="9.3. 网络笔记"></a>9.3. 网络笔记</h2><p>分布式事务： <a href="https://www.jianshu.com/p/044e95223a17">https://www.jianshu.com/p/044e95223a17</a>  ⭐️🔴</p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-13、Dubbo</title>
      <link href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-13%E3%80%81Dubbo/"/>
      <url>/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-13%E3%80%81Dubbo/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Dubbo"><a href="#1-Dubbo" class="headerlink" title="1. Dubbo"></a>1. Dubbo</h1><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p>Dubbo 是一款高性能、轻量级的开源 RPC 框架。由 10 层模式构成，整个分层依赖由上至下。通过这张图我们也可以将 Dubbo 理解为三层模式：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230520214554.png" alt="image.png"></p><p>第一层的 Business 业务逻辑层由我们自己来提供接口和实现还有一些配置信息。<br>第二层的 RPC 调用的核心层负责封装和实现整个 RPC 的调用过程、负载均衡、集群容错、代理等核心功能。<br>第三层的 Remoting 则是对网络传输协议和数据转换的封装。</p><h2 id="1-2-核心能力-特性"><a href="#1-2-核心能力-特性" class="headerlink" title="1.2. 核心能力 (特性)"></a>1.2. 核心能力 (特性)</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512110804.png" alt="image.png"></p><h2 id="1-3-架构"><a href="#1-3-架构" class="headerlink" title="1.3. 架构"></a>1.3. 架构</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512111824.png" alt="image.png"></p><h2 id="1-4-协议"><a href="#1-4-协议" class="headerlink" title="1.4. 协议"></a>1.4. 协议</h2><h3 id="1-4-1-Dubbo-协议"><a href="#1-4-1-Dubbo-协议" class="headerlink" title="1.4.1. Dubbo 协议"></a>1.4.1. Dubbo 协议</h3><p>Dubbo 缺省协议采用<span style="background-color:#ff00ff">单一长连接和 NIO 异步通讯</span>，适合于<span style="background-color:#ff00ff">小数据量大并发</span>的服务调用，<span style="background-color:#ff00ff">以及服务消费者机器数远大于服务提供者机器数</span>的情况。</p><p>反之，Dubbo 缺省协议<span style="background-color:#ff0000">不适合传送大数据量的服务，比如传文件，传视频等，除非请求量很低。</span></p><p>缺省协议，使用基于 netty <code>3.2.5.Final</code> 和 hessian2 <code>3.2.1-fixed-2(Alibaba embed version)</code> 的 tbremoting 交互。</p><ul><li>连接个数：单连接</li><li>连接方式：长连接</li><li>传输协议：TCP</li><li>传输方式：NIO 异步传输</li><li><span style="background-color:#ff00ff">序列化：Hessian 二进制序列化</span></li><li>适用范围：<span style="background-color:#ff00ff">传入传出参数数据包较小（建议小于 100K），消费者比提供者个数多，单一消费者无法压满提供者，尽量不要用 dubbo 协议传输大文件或超大字符串。</span></li><li>适用场景：常规远程服务方法调用</li></ul><h4 id="1-4-1-1-为什么不适合大文件"><a href="#1-4-1-1-为什么不适合大文件" class="headerlink" title="1.4.1.1. 为什么不适合大文件"></a>1.4.1.1. 为什么不适合大文件</h4><p><a href="https://blog.csdn.net/qq_43842093/article/details/126332133">https://blog.csdn.net/qq_43842093/article/details/126332133</a></p><h3 id="1-4-2-其他协议"><a href="#1-4-2-其他协议" class="headerlink" title="1.4.2. 其他协议"></a>1.4.2. 其他协议</h3><p>通信框架方面，Dubbo 默认采用了 Netty 作为通信框架。<br>通信协议方面，Dubbo 除了支持私有的 Dubbo 协议外，还支持 RMI 协议、Hession 协议、HTTP 协议、Thrift 协议等。<br>序列化格式方面，Dubbo 支持多种序列化格式，比如 Dubbo、Hession、JSON、 Kryo、FST 等。</p><h1 id="2-工作原理"><a href="#2-工作原理" class="headerlink" title="2. 工作原理"></a>2. 工作原理</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230530154755.png" alt="image.png"></p><ol><li>服务启动的时候，provider 和 consumer 根据配置信息，连接到注册中心 register，分别向注册中心注册和订阅服务</li><li>register 根据服务订阅关系，返回 provider 信息到 consumer，同时 consumer 会把 provider 信息缓存到本地。如果信息有变更，consumer 会收到来自 register 的推送</li><li>consumer 生成代理对象，同时根据负载均衡策略，选择一台 provider ，同时定时向 monitor 记录接口的调用次数和时间信息</li><li>consumer 拿到代理对象之后，通过代理对象发起接口调用</li><li>provider 收到请求后对数据进行反序列化，然后通过代理调用具体的接口实现</li></ol><h2 id="2-1-负载均衡⭐️🔴"><a href="#2-1-负载均衡⭐️🔴" class="headerlink" title="2.1. 负载均衡⭐️🔴"></a>2.1. 负载均衡⭐️🔴</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002222615305.png" alt="image-20211002222615305"></p><h2 id="2-2-超时与重试"><a href="#2-2-超时与重试" class="headerlink" title="2.2. 超时与重试"></a>2.2. 超时与重试</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327100224.png" alt="image.png"></p><p><span style="background-color:#ff00ff">默认使用 consumer 中的 timeout 为 1000ms</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327095831.png" alt="image.png"></p><p><span style="background-color:#ff00ff">该数字不包含刚开始的 1 次</span></p><h3 id="2-2-1-配置优先级"><a href="#2-2-1-配置优先级" class="headerlink" title="2.2.1. 配置优先级"></a>2.2.1. 配置优先级</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327100922.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327100959.png" alt="image.png"></p><h2 id="2-3-服务容错⭐️🔴"><a href="#2-3-服务容错⭐️🔴" class="headerlink" title="2.3. 服务容错⭐️🔴"></a>2.3. 服务容错⭐️🔴</h2><p><a href="https://www.bilibili.com/video/BV1t7411j7P7?p=15">https://www.bilibili.com/video/BV1t7411j7P7?p=15</a></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211018060627326.png" alt="image-20211018060627326"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002223619323.png" alt="image-20211002223619323"></p><h2 id="2-4-服务降级"><a href="#2-4-服务降级" class="headerlink" title="2.4. 服务降级"></a>2.4. 服务降级</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002223240884.png" alt="image-20211002223240884"></p><h2 id="2-5-延迟暴露"><a href="#2-5-延迟暴露" class="headerlink" title="2.5. 延迟暴露"></a>2.5. 延迟暴露</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211018060908252.png" alt="image-20211018060908252"></p><h2 id="2-6-线程模型"><a href="#2-6-线程模型" class="headerlink" title="2.6. 线程模型"></a>2.6. 线程模型</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211018061104632.png" alt="image-20211018061104632"></p><h2 id="3-注册中心"><a href="#3-注册中心" class="headerlink" title="3. 注册中心"></a>3. 注册中心</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214522.jpg" alt="image-20200422011315464"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214516.jpg" alt="image-20200422011522254"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214511.jpg" alt="image-20200422011945717"></p><h4 id="3-1-配置方式"><a href="#3-1-配置方式" class="headerlink" title="3.1. 配置方式"></a>3.1. 配置方式</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214446.jpg" alt="image-20200422084117654"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214424.jpg" alt="image-20200422084312824"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214417.jpg" alt="image-20200422084432841"></p><h1 id="5-源码分析"><a href="#5-源码分析" class="headerlink" title="5. 源码分析"></a>5. 源码分析</h1><h2 id="5-1-标签解析"><a href="#5-1-标签解析" class="headerlink" title="5.1. 标签解析"></a>5.1. 标签解析</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924073517589.png" alt="image-20210924073517589"></p><ol><li><p>启动容器的 main 方法执行时就会调用标签解析器<br> Spring 解析配置文件的总接口：<strong>BeanDefinitionParser</strong>，其中 Dubbo 的实现类为 <strong>DubboBeanDefinitionParser</strong>，其中的方法为 <strong>parse()</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327215252.jpg" alt="image-20200413112454357"></p></li><li><p>在 DubboBeanDefinitionParser 创建时，有一个 <code>DubboNameSpaceHandler</code> 类的 init() 方法，会给每一个标签注册一个解析器，解析器中就绑定了 class 类别<br>   <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327215246.jpg" alt="image-20200413114008915"></p></li><li><p>在 DubboDefinitionParser 的 parse() 中就能根据标签找到对应的 bean 来封装配置文件中的信息。值得注意的是 service 标签对应的叫 <code>ServiceBean</code>，reference 对应的叫 <code>ReferenceBean</code>，其他的都叫 xxxConfig，为什么与众不同，因为有超能力，分别用于暴露服务和引用服务。</p></li></ol><h2 id="5-2-ServiceBean-版"><a href="#5-2-ServiceBean-版" class="headerlink" title="5.2. ServiceBean 版"></a>5.2. ServiceBean 版</h2><p><a href="https://blog.51cto.com/u_15281317/2942413">https://blog.51cto.com/u_15281317/2942413</a></p><h2 id="5-3-Dubbo-启动原理-2-7-7-注解版"><a href="#5-3-Dubbo-启动原理-2-7-7-注解版" class="headerlink" title="5.3. Dubbo 启动原理 -2.7.7- 注解版"></a>5.3. Dubbo 启动原理 -2.7.7- 注解版</h2><p><a href="https://juejin.cn/post/7024797126336970766#heading-6">https://juejin.cn/post/7024797126336970766#heading-6</a><br><a href="https://blog.csdn.net/yang1060887552/article/details/122836658?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-5-122836658-blog-107958704.235%5Ev27%5Epc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-5-122836658-blog-107958704.235%5Ev27%5Epc_relevant_multi_platform_whitelistv3&utm_relevant_index=10">Dubbo源码阅读四：在Spring下DubboBootstrap的启动过程</a></p><p><a href="https://juejin.cn/post/6945839380799946766">https://juejin.cn/post/6945839380799946766</a></p><h3 id="5-3-1-流程图"><a href="#5-3-1-流程图" class="headerlink" title="5.3.1. 流程图"></a>5.3.1. 流程图</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330223915.png" alt="image.png"></p><h3 id="5-3-2-属性配置解析器-EnableDubboConfig"><a href="#5-3-2-属性配置解析器-EnableDubboConfig" class="headerlink" title="5.3.2. 属性配置解析器 -@EnableDubboConfig"></a>5.3.2. 属性配置解析器 -@EnableDubboConfig</h3><p>当你使用 <code>@EnableDubbo</code> 注解启动 Dubbo 的时候，会加载它的 <code>@EnableDubboConfig</code> 和 <code>@DubboComponentScan</code> 注解，分别用于处理 Dubbo 属性配置和解析 Dubbo 服务</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-meta">@Target(&#123;ElementType.TYPE&#125;)</span><br><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-meta">@Inherited</span><br><span class="hljs-meta">@Documented</span><br><span class="hljs-comment">// 处理配置文件中（例如yaml文件）的dubbo配置</span><br><span class="hljs-meta">@EnableDubboConfig</span><br><span class="hljs-comment">// 扫描并解析使用了dubbo注解的组件（例如@Service）</span><br><span class="hljs-meta">@DubboComponentScan</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> EnableDubbo &#123;<br>    ......<br>&#125;<br></code></pre></td></tr></table></figure><p><code>@EnableDubboConfig</code> 和 <code>@DubboComponentScan</code> 注解使用了 Spring 的 <code>@Import</code> 注解来加载具体的实现类。</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">@Import(DubboConfigConfigurationRegistrar.class)</span><br><span class="hljs-meta">@Import(DubboComponentScanRegistrar.class)</span><br></code></pre></td></tr></table></figure><h4 id="5-3-2-1-DubboConfigConfigurationRegistrar"><a href="#5-3-2-1-DubboConfigConfigurationRegistrar" class="headerlink" title="5.3.2.1. DubboConfigConfigurationRegistrar"></a>5.3.2.1. DubboConfigConfigurationRegistrar</h4><p><code>DubboConfigConfigurationRegistrar</code> 用于将不同的属性加载到不同的配置文件中<br><code>registerBeans</code> 方法最终通过 <code>ConfigurationBeanBindingsRegister</code> 将解析之后的配置类注册到 <code>BeanDefinitionMap</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401070059.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401070142.png" alt="image.png"><br>最终在 Spring 容器中他们会被初始化成若干对象，例如：<code>dubbo:registry</code> 会转换成 <code>org.apache.dubbo.config.RegistryConfig#0</code></p><h3 id="5-3-3-注解解析器-DubboComponentScan"><a href="#5-3-3-注解解析器-DubboComponentScan" class="headerlink" title="5.3.3. 注解解析器 -@DubboComponentScan"></a>5.3.3. 注解解析器 -@DubboComponentScan</h3><h4 id="5-3-3-1-DubboComponentScanRegistrar"><a href="#5-3-3-1-DubboComponentScanRegistrar" class="headerlink" title="5.3.3.1. DubboComponentScanRegistrar"></a>5.3.3.1. DubboComponentScanRegistrar</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230331083325.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230331083400.png" alt="image.png"></p><p><code>DubboComponentScanRegistrar</code> 主要是用来将 <code>ServiceAnnotationBeanPostProcessor</code> （2.7 以上为 <code>ServiceClassPostProcessor</code>）注册到 <code>BeanDefinitionMap</code> 中。<br>在 Spring 调用 BeanFactory 相关的后置处理器（<code>invokeBeanFactoryPostProcessors</code>）时，会使用 <code>ServiceAnnotationBeanPostProcessor</code> 将 <code>@DubboService</code> 相关注解注册到 <code>BeanDefinitionMap</code></p><p>  <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401070742.png" alt="image.png"><br><span style="background-color:#ff00ff">在 <code>ServiceClassPostProcessor</code> 中，它注册了一个 dubbo 监听器，用于监听 Spring 容器的刷新、关闭事件，同时也将 <code>@DubboService</code> 注解的类注册到了 <code>BeanDefinitionMap</code> 中</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401070910.png" alt="image.png"></p><h3 id="5-3-4-启动"><a href="#5-3-4-启动" class="headerlink" title="5.3.4. 启动"></a>5.3.4. 启动</h3><p><a href="https://juejin.cn/post/7024797126336970766#heading-7">https://juejin.cn/post/7024797126336970766#heading-7</a></p><h4 id="5-3-4-1-ServiceClassPostProcessor"><a href="#5-3-4-1-ServiceClassPostProcessor" class="headerlink" title="5.3.4.1. ServiceClassPostProcessor"></a>5.3.4.1. ServiceClassPostProcessor</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401071355.png" alt="image.png"></p><h5 id="5-3-4-1-1-DubboBootstrapApplicationListener"><a href="#5-3-4-1-1-DubboBootstrapApplicationListener" class="headerlink" title="5.3.4.1.1. DubboBootstrapApplicationListener"></a>5.3.4.1.1. DubboBootstrapApplicationListener</h5><p>伴随着 Spring 容器的启动，在 invokeBeanFactoryPostProcessors 阶段我们注册了 dubbo 相关的组件到 IOC，在 finishBeanFactoryInitialization(beanFactory) Dubbo 的组件被初始化、实例化，最后 Dubbo 通过监听 Spring 事件的方式完成启动器的调用、服务导出等操作<br><code>DubboBootstrap</code> 的启动是通过监听 Spring 事件实现的。Spring 会在容器 Refresh 的最后一步发送一个事件 <code>ContextRefreshedEvent</code>，表示容器刷新完毕。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401071549.png" alt="image.png"></p><p>对于 <code>ContextRefreshedEvent</code> 事件的监听，最终调用了 dubboBootstrap.start() 方法，在这个方法里，Dubbo 完成了对服务的导出（暴露），导出服务的过程中，<span style="background-color:#ff00ff">用到了 DUBBO SPI 机制</span></p><h5 id="5-3-4-1-2-ServiceConfig"><a href="#5-3-4-1-2-ServiceConfig" class="headerlink" title="5.3.4.1.2. ServiceConfig"></a>5.3.4.1.2. ServiceConfig</h5><h5 id="5-3-4-1-3-ServiceNameMappingListener"><a href="#5-3-4-1-3-ServiceNameMappingListener" class="headerlink" title="5.3.4.1.3. ServiceNameMappingListener"></a>5.3.4.1.3. ServiceNameMappingListener</h5><p>ServiceNameMapping 及其子类承接了对注册中心的调用，以 nacos 为例调用逻辑如下图<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401142610.png" alt="image.png"></p><h2 id="5-4-服务暴露与引用原理-2-7-7-注解版"><a href="#5-4-服务暴露与引用原理-2-7-7-注解版" class="headerlink" title="5.4. 服务暴露与引用原理 -2.7.7- 注解版"></a>5.4. 服务暴露与引用原理 -2.7.7- 注解版</h2><p><a href="https://juejin.cn/post/6874731589243240461">https://juejin.cn/post/6874731589243240461</a></p><h2 id="5-5-服务暴露原理-xml-版⭐️🔴"><a href="#5-5-服务暴露原理-xml-版⭐️🔴" class="headerlink" title="5.5. 服务暴露原理 -xml 版⭐️🔴"></a>5.5. 服务暴露原理 -xml 版⭐️🔴</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230328-0629%%</span>❕ ^gl53ku</p><h3 id="5-5-1-封装-serviceBean"><a href="#5-5-1-封装-serviceBean" class="headerlink" title="5.5.1. 封装 serviceBean"></a>5.5.1. 封装 serviceBean</h3><p>IOC 容器启动时，BeanDefinitionParser 的实现类 DubboBeanDefinitionParser，会解析 dubbo 的标签，而在解析 service 标签时，会将 service 的 Beandefinition 封装成 serviceBean</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327081610.jpg" alt="image-20200413162808979"></p><h3 id="5-5-2-实现了-【InitializingBean】"><a href="#5-5-2-实现了-【InitializingBean】" class="headerlink" title="5.5.2. 实现了 【InitializingBean】"></a>5.5.2. 实现了 【InitializingBean】</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924073843703.png" alt="image-20210924073843703"></p><h4 id="5-5-2-1-afterpropertiesSet-保存信息到-serviceBean"><a href="#5-5-2-1-afterpropertiesSet-保存信息到-serviceBean" class="headerlink" title="5.5.2.1. afterpropertiesSet- 保存信息到 serviceBean"></a>5.5.2.1. afterpropertiesSet- 保存信息到 serviceBean</h4><p>在组件实例化完成，属性设置完之后，调用 <code>afterpropertiesSet() </code> 方法，把配置中的 provider、application、module、registries、monitor、protocols 等的信息保存起来，保存到当前的 serviceBean 里面，最后组装成 URL</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924080634932.png" alt="image-20210924080634932"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924080658454.png" alt="image-20210924080658454"></p><h3 id="5-5-3-实现了【-ApplicationListener】"><a href="#5-5-3-实现了【-ApplicationListener】" class="headerlink" title="5.5.3. 实现了【 ApplicationListener】"></a>5.5.3. 实现了【 ApplicationListener】</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924074228912.png" alt="image-20210924074228912"></p><p><span style="background-color:#ff0000">📢 : 2.7 版本注解版变动挺大，没有实现 ApplicationListener</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330165227.png" alt="image.png"></p><h4 id="5-5-3-1-onApplicationEvent-加载注册中心的信息"><a href="#5-5-3-1-onApplicationEvent-加载注册中心的信息" class="headerlink" title="5.5.3.1. onApplicationEvent- 加载注册中心的信息"></a>5.5.3.1. onApplicationEvent- 加载注册中心的信息</h4><p>IOC 容器刷新完成，所有对象都创建完成之后，回调 onApplicationEvent()，执行 export、doExport、doExportUrls 等方法，其中会根据不同的协议暴露不同的服务</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924080346850.png" alt="image-20210924080346850"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924080923918.png" alt="image-20210924080923918"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924080952304.png" alt="image-20210924080952304"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924081059881.png" alt="image-20210924081059881"></p><h4 id="5-5-3-2-将目标服务实现类、接口、URL-等信息封装成-invoker-执行器"><a href="#5-5-3-2-将目标服务实现类、接口、URL-等信息封装成-invoker-执行器" class="headerlink" title="5.5.3.2. 将目标服务实现类、接口、URL 等信息封装成 invoker 执行器"></a>5.5.3.2. 将目标服务实现类、接口、URL 等信息封装成 invoker 执行器</h4><p><span style="background-color:#ff00ff">使用 proxyFactory 将目标服务实现类、接口、URL 等信息封装成 invoker 执行器</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924081252224.png" alt="image-20210924081252224"></p><h4 id="5-5-3-3-protocol-export-warpperInvoker-暴露-invoker"><a href="#5-5-3-3-protocol-export-warpperInvoker-暴露-invoker" class="headerlink" title="5.5.3.3. protocol.export(warpperInvoker)- 暴露 invoker"></a>5.5.3.3. protocol.export(warpperInvoker)- 暴露 invoker</h4><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924081305979.png" alt="image-20210924081305979"></p><p>基于 Java 的 SPI 机制，会得到 2 个 protocol</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924101256388.png" alt="image-20210924101256388"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924092702515.png" alt="image-20210924092702515"></p><p><a href="https://www.bilibili.com/video/BV1AP4y1Y7YX?p=28">https://www.bilibili.com/video/BV1AP4y1Y7YX?p=28</a></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002202328113.png" alt="image-20211002202328113"></p><h5 id="5-5-3-3-1-RegistryProtocol-export-暴露-Regisry"><a href="#5-5-3-3-1-RegistryProtocol-export-暴露-Regisry" class="headerlink" title="5.5.3.3.1. RegistryProtocol.export- 暴露 Regisry"></a>5.5.3.3.1. RegistryProtocol.export- 暴露 Regisry</h5><p>export 中有 2 个关键方法：</p><h6 id="5-5-3-3-1-1-doLocalExport"><a href="#5-5-3-3-1-1-doLocalExport" class="headerlink" title="5.5.3.3.1.1. doLocalExport"></a>5.5.3.3.1.1. doLocalExport</h6><p><span style="background-color:#ff00ff">invokeDelegete 暴露会进入 DubboProtocol.class 中的 export 方法</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002203437343.png" alt="image-20211002203437343"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924093034314.png" alt="image-20210924093034314"></p><p><span style="background-color:#ff00ff">invokeDelegete 暴露会进入 DubboProtocol.class 中的 export 方法</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002203919831.png" alt="image-20211002203919831"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002203950166.png" alt="image-20211002203950166"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002204038478.png" alt="image-20211002204038478"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924093208709.png" alt="image-20210924093208709"></p><p><span style="background-color:#ff00ff">接下面的 DubboProtocol.export</span></p><h6 id="5-5-3-3-1-2-DubboProtocol-export-暴露-Dubbo-启动-netty-服务器"><a href="#5-5-3-3-1-2-DubboProtocol-export-暴露-Dubbo-启动-netty-服务器" class="headerlink" title="5.5.3.3.1.2. DubboProtocol.export- 暴露 Dubbo- 启动 netty 服务器"></a>5.5.3.3.1.2. DubboProtocol.export- 暴露 Dubbo- 启动 netty 服务器</h6><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924093438794.png" alt="image-20210924093438794"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002204347042.png" alt="image-20211002204347042"></p><p><span style="background-color:#ff00ff">在 createServer 中启动 netty 服务器，监听 20882 端口</span></p><h4 id="5-5-3-4-ProviderConsumerRegTable-registerProvider-注册服务"><a href="#5-5-3-4-ProviderConsumerRegTable-registerProvider-注册服务" class="headerlink" title="5.5.3.4. ProviderConsumerRegTable.registerProvider- 注册服务"></a>5.5.3.4. ProviderConsumerRegTable.registerProvider- 注册服务</h4><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924093845033.png" alt="image-20210924093845033"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924081709274.png" alt="image-20210924081709274"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211002204723685.png" alt="image-20211002204723685"></p><h2 id="5-6-服务引用原理-xml-版⭐️🔴"><a href="#5-6-服务引用原理-xml-版⭐️🔴" class="headerlink" title="5.6. 服务引用原理 -xml 版⭐️🔴"></a>5.6. 服务引用原理 -xml 版⭐️🔴</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230328-0653%%</span>❕ ^3aliel</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924084531649.png" alt="image-20210924084531649"></p><h3 id="5-6-1-实现了-【FactoryBean】"><a href="#5-6-1-实现了-【FactoryBean】" class="headerlink" title="5.6.1. 实现了 【FactoryBean】"></a>5.6.1. 实现了 【FactoryBean】</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924100429599.png" alt="image-20210924100429599"></p><ol><li>解析 reference 注解时，发现引用 UserService，就需要在容器中找，因为 ReferenceBean 实现了 FactoryBean，所以调用 getObject 方法</li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924100827788.png" alt="image-20210924100827788"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924100802949.png" alt="image-20210924100802949"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003070252288.png" alt="image-20211003070252288"></p><ol start="2"><li>在 get() 方法中调用了创建代理的 <code>createProxy()</code> 方法，在这个方法里有用到了通过 SPI 方式加载的 Protocol 类<br>map 中存放的是 reference 标签中的属性，用来创建代理类，然后调用 refer，doRefer，subscribe 等关键方法</li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003070217503.png" alt="image-20211003070217503"></p><ol start="3"><li>与暴露方法同样的，引用方法也有 2 个实现，分别是 DubboProtocol 和 RegistryProtocol 中的 refer() 方法</li></ol><p><code>urls</code> 是注册中心的地址<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003070628762.png" alt="image-20211003070628762"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924101141683.png" alt="image-20210924101141683"></p><h3 id="5-6-2-RegistryProtocol-refer"><a href="#5-6-2-RegistryProtocol-refer" class="headerlink" title="5.6.2. RegistryProtocol.refer"></a>5.6.2. RegistryProtocol.refer</h3><ol start="4"><li>先调用 RegistryProtocol 中的 refer() 方法，在该方法里到注册中心订阅服务，获取服务信息。</li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924110432057.png" alt="image-20210924110432057"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924110754573.png" alt="image-20210924110754573"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924110832104.png" alt="image-20210924110832104"></p><p><span style="background-color:#ff00ff">subscribe 方法会调用 dubboProtocol.refer</span></p><h3 id="5-6-3-DubboProtocol-refer"><a href="#5-6-3-DubboProtocol-refer" class="headerlink" title="5.6.3. DubboProtocol.refer"></a>5.6.3. DubboProtocol.refer</h3><ol start="5"><li>在订阅服务的方法中调用了 DubboProtocol 的 refer() 方法，方法中 getClients() 方法中创建客户端。<br><span style="background-color:#ff00ff">getClients()</span> → <span style="background-color:#ff00ff">initClient()</span></li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924084254521.png" alt="image-20210924084254521"></p><p><span style="background-color:#ff00ff">Exchanges.connect(url,xxx)</span></p><ol start="6"><li><span style="background-color:#ff00ff">创建 netty 的客户端，然后将带有连接属性的 invoker 返回</span></li><li><span style="background-color:#ff00ff">最后，将 invoker 注册到 ProviderConsumerRegTable 中，注册地址为订阅地址</span></li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924111102833.png" alt="image-20210924111102833"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924111128709.png" alt="image-20210924111128709"></p><h2 id="5-7-服务调用原理-xml-版"><a href="#5-7-服务调用原理-xml-版" class="headerlink" title="5.7. 服务调用原理 -xml 版"></a>5.7. 服务调用原理 -xml 版</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924085205572.png" alt="image-20210924085205572"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924111722548.png" alt="image-20210924111722548"></p><h3 id="5-7-1-进入-InvokerInvocationHandler"><a href="#5-7-1-进入-InvokerInvocationHandler" class="headerlink" title="5.7.1. 进入 InvokerInvocationHandler"></a>5.7.1. 进入 InvokerInvocationHandler</h3><ol><li>代理对象层层封装了 Invoker 对象，里面是一些真正要执行的方法<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924111756013.png" alt="image-20210924111756013"></li></ol><h3 id="5-7-2-执行-new-RpcInvocation-方法"><a href="#5-7-2-执行-new-RpcInvocation-方法" class="headerlink" title="5.7.2. 执行 new RpcInvocation 方法"></a>5.7.2. 执行 new RpcInvocation 方法</h3><ol start="2"><li>可以在刚开始给代理对象加一些 filter，比如 cache、mock(比如服务降级) 功能</li><li>其中 InvokerInvocationHanlder 中的 invoke() 方法先到 MockClusterInvoker 类中，这个类可以封装多个 Invoker 对象，比如 failover、failback 功能的 Invoker<br>首先会进入 MockClusterInvoker 中，获取失败容错信息</li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003072302993.png" alt="image-20211003072302993"></p><h3 id="5-7-3-获取负载均衡机制"><a href="#5-7-3-获取负载均衡机制" class="headerlink" title="5.7.3. 获取负载均衡机制"></a>5.7.3. 获取负载均衡机制</h3><ol start="4"><li>多个 Invoker 可以经过通过 SPI 获取到的负载均衡机制进行负载均衡<br>在注册中心找想要执行的方法列表，如果是多个则获取负载均衡机制，然后执行 doInvoker</li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003071436483.png" alt="image-20211003071436483"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924111843322.png" alt="image-20210924111843322"></p><ol start="5"><li>最终底层真正执行方法的 Invoker 是 DubboInvoker，通过 client 调用底层 Netty 客户端进行交互<br>doInvoke 方法进入 FailoverClusterInvoker 中，随机选择一个 Invoker，然后执行 invoker.invoke</li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003072550600.png" alt="image-20211003072550600"></p><h3 id="5-7-4-经过多个-filter"><a href="#5-7-4-经过多个-filter" class="headerlink" title="5.7.4. 经过多个 filter"></a>5.7.4. 经过多个 filter</h3><p>invoker 是封装的 filter，会经过多个 filter</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003084214043.png" alt="image-20211003084214043"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003082843646.png" alt="image-20211003082843646"></p><h3 id="5-7-5-DubboInvoker-方法中的-doInvoke-方法"><a href="#5-7-5-DubboInvoker-方法中的-doInvoke-方法" class="headerlink" title="5.7.5. DubboInvoker 方法中的 doInvoke 方法"></a>5.7.5. DubboInvoker 方法中的 doInvoke 方法</h3><p>经过多个 filter 之后，是 DubboInvoker 方法中的 doInvoke 方法，真正执行功能的方法</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210924112129947.png" alt="image-20210924112129947"></p><h3 id="5-7-6-currentClient-request"><a href="#5-7-6-currentClient-request" class="headerlink" title="5.7.6. currentClient.request"></a>5.7.6. currentClient.request</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003084855353.png" alt="image-20211003084855353"></p><h1 id="6-分层设计"><a href="#6-分层设计" class="headerlink" title="6. 分层设计"></a>6. 分层设计</h1><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003102405068.png" alt="image-20211003102405068"><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211003102603060.png" alt="image-20211003102603060"></p><h1 id="7-协议"><a href="#7-协议" class="headerlink" title="7. 协议"></a>7. 协议</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214815.jpg" alt="image-20200422012747019"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214810.jpg" alt="image-20200422012807396"></p><h1 id="8-与-SpringBoot-整合"><a href="#8-与-SpringBoot-整合" class="headerlink" title="8. 与 SpringBoot 整合"></a>8. 与 SpringBoot 整合</h1><p>SpringBoot 与 dubbo 整合的三种方式：</p><p>1）、导入 dubbo-starter，在 application.properties 配置属性，使用@Service【暴露服务】使用@Reference【引用服务】</p><p>2）、保留 dubbo xml 配置文件; 导入 dubbo-starter，使用@ImportResource 导入 dubbo 的配置文件即可</p><p>3）、使用注解 API 的方式：将每一个组件手动创建到容器中,让 dubbo 来扫描其他的组件</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214642.jpg" alt="image-20200422000348930"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230327214636.jpg" alt="image-20200422000305379"></p><h1 id="9-Dubbo-SPI-与-JDK-SPI-区别"><a href="#9-Dubbo-SPI-与-JDK-SPI-区别" class="headerlink" title="9. Dubbo SPI 与 JDK SPI 区别"></a>9. Dubbo SPI 与 JDK SPI 区别</h1><p><a href="https://juejin.cn/post/6872138926216511501">https://juejin.cn/post/6872138926216511501</a></p><h1 id="10-面试题"><a href="#10-面试题" class="headerlink" title="10. 面试题"></a>10. 面试题</h1><h2 id="10-1-与-SpringCloud-的区别"><a href="#10-1-与-SpringCloud-的区别" class="headerlink" title="10.1. 与 SpringCloud 的区别"></a>10.1. 与 SpringCloud 的区别</h2><a href="/2023/05/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-16%E3%80%81SpringCloud/" title="分布式专题-16、SpringCloud">分布式专题-16、SpringCloud</a><h2 id="10-2-动态感知服务下线"><a href="#10-2-动态感知服务下线" class="headerlink" title="10.2. 动态感知服务下线"></a>10.2. 动态感知服务下线</h2><p><a href="https://www.bilibili.com/video/BV1Z44y1372E/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Z44y1372E/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="10-3-容错机制"><a href="#10-3-容错机制" class="headerlink" title="10.3. 容错机制"></a>10.3. 容错机制</h2><p><a href="https://www.bilibili.com/video/BV1hP4y1u77c/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1hP4y1u77c/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="10-4-对-Dubbo-的理解"><a href="#10-4-对-Dubbo-的理解" class="headerlink" title="10.4. 对 Dubbo 的理解"></a>10.4. 对 Dubbo 的理解</h2><p><a href="https://www.bilibili.com/video/BV1FU4y1K7C7/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1FU4y1K7C7/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="10-5-文字面试题"><a href="#10-5-文字面试题" class="headerlink" title="10.5. 文字面试题"></a>10.5. 文字面试题</h2><p>[[Dubbo面试题 47道.pdf]]</p><h1 id="11-参考与感谢"><a href="#11-参考与感谢" class="headerlink" title="11. 参考与感谢"></a>11. 参考与感谢</h1><h2 id="11-1-Dubbo-教程-雷丰阳-尚硅谷"><a href="#11-1-Dubbo-教程-雷丰阳-尚硅谷" class="headerlink" title="11.1. Dubbo 教程 _ 雷丰阳 _ 尚硅谷"></a>11.1. Dubbo 教程 _ 雷丰阳 _ 尚硅谷</h2><h3 id="11-1-1-视频"><a href="#11-1-1-视频" class="headerlink" title="11.1.1. 视频"></a>11.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1Gb411T7Ha?p=12">https://www.bilibili.com/video/BV1Gb411T7Ha?p=12</a></p><h3 id="11-1-2-资料"><a href="#11-1-2-资料" class="headerlink" title="11.1.2. 资料"></a>11.1.2. 资料</h3><p>注解版</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">/Volumes/Seagate Bas/<span class="hljs-number">001</span>-study/语言工具/Dubbo/课件、资料/课件<br></code></pre></td></tr></table></figure><h2 id="11-2-黑马程序员"><a href="#11-2-黑马程序员" class="headerlink" title="11.2. 黑马程序员"></a>11.2. 黑马程序员</h2><h3 id="11-2-1-视频"><a href="#11-2-1-视频" class="headerlink" title="11.2.1. 视频"></a>11.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1VE411q7dX?p=8&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1VE411q7dX?p=8&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="11-2-2-资料"><a href="#11-2-2-资料" class="headerlink" title="11.2.2. 资料"></a>11.2.2. 资料</h3><p>非注解版：Spring、SpringMVC 整合</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">/Users/Enterprise/<span class="hljs-number">0003</span>-Architecture/<span class="hljs-number">005</span>-分布式专题/<span class="hljs-number">2</span>、分布式开发框架Dubbo/资料--分布式开发框架Dubbo<br></code></pre></td></tr></table></figure><h2 id="11-3-微服务-dubbo-和-springcloud-如何抉择？"><a href="#11-3-微服务-dubbo-和-springcloud-如何抉择？" class="headerlink" title="11.3. 微服务 dubbo 和 springcloud 如何抉择？"></a>11.3. 微服务 dubbo 和 springcloud 如何抉择？</h2><p><a href="https://www.bilibili.com/video/BV1rJ411t7MF?p=5">https://www.bilibili.com/video/BV1rJ411t7MF?p=5</a></p><h2 id="11-4-其他"><a href="#11-4-其他" class="headerlink" title="11.4. 其他"></a>11.4. 其他</h2><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">013</span>-DemoCode/JavaYouth/docs/dubbo-sourcecode-v1<br></code></pre></td></tr></table></figure><h2 id="11-5-技术文章摘抄"><a href="#11-5-技术文章摘抄" class="headerlink" title="11.5. 技术文章摘抄"></a>11.5. 技术文章摘抄</h2><h3 id="11-5-1-视频"><a href="#11-5-1-视频" class="headerlink" title="11.5.1. 视频"></a>11.5.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1Bc411V7AR?p=19&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Bc411V7AR?p=19&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="11-5-2-文档"><a href="#11-5-2-文档" class="headerlink" title="11.5.2. 文档"></a>11.5.2. 文档</h3><p><a href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Dubbo%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB%E4%B8%8E%E5%AE%9E%E6%88%98-%E5%AE%8C/24%20%20%E4%BB%8E%20Protocol%20%E8%B5%B7%E6%89%8B%EF%BC%8C%E7%9C%8B%E6%9C%8D%E5%8A%A1%E6%9A%B4%E9%9C%B2%E5%92%8C%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8%E7%9A%84%E5%85%A8%E6%B5%81%E7%A8%8B%EF%BC%88%E4%B8%8A%EF%BC%89.md">看服务暴露和服务引用的全流程</a></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">/Volumes/Seagate Bas/<span class="hljs-number">001</span>-ArchitectureRoad/<span class="hljs-number">000</span>-极客时间/<span class="hljs-number">31</span>-从<span class="hljs-number">0</span>开始学微服务/<span class="hljs-number">03</span>-模块二 落地微服务 (<span class="hljs-number">14</span>讲)/<span class="hljs-number">14</span>丨开源RPC框架如何选型？.pdf<br></code></pre></td></tr></table></figure><h2 id="11-6-网络笔记"><a href="#11-6-网络笔记" class="headerlink" title="11.6. 网络笔记"></a>11.6. 网络笔记</h2><p>[[Dubbo基础及原理机制 - 胡小华 - 博客园]]</p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务治理-10、限流熔断降级(服务保护)-Sentinel</title>
      <link href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-10%E3%80%81%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7(%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4)-Sentinel/"/>
      <url>/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-10%E3%80%81%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7(%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4)-Sentinel/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-雪崩问题"><a href="#1-雪崩问题" class="headerlink" title="1. 雪崩问题"></a>1. 雪崩问题</h1><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p><span style="background-color:#ff00ff">微服务调用链路中的某个服务故障，引起整个链路中的所有微服务都不可用，这就是雪崩。</span></p><h2 id="1-2-产生原因"><a href="#1-2-产生原因" class="headerlink" title="1.2. 产生原因"></a>1.2. 产生原因</h2><p>微服务中，服务间调用关系错综复杂，一个微服务往往依赖于多个其它微服务。</p><p>如果服务提供者 I 发生了故障，当前的应用的部分业务因为依赖于服务 I，因此也会被阻塞。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315101121.png" alt="png"></p><p>但是，依赖服务 I 的业务请求被阻塞，用户不会得到响应，则 tomcat 的这个线程不会释放，于是越来越多的用户请求到来，越来越多的线程会阻塞：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315101235.png" alt="png"></p><p><span style="background-color:#ff00ff">服务器支持的线程和并发数有限</span>，请求一直阻塞，会导致服务器资源耗尽，从而导致所有其它服务都不可用，那么当前服务也就不可用了。</p><h2 id="1-3-雪崩问题解决方案"><a href="#1-3-雪崩问题解决方案" class="headerlink" title="1.3. 雪崩问题解决方案"></a>1.3. 雪崩问题解决方案</h2><p><span style="display:none">%%<br>▶11.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1023%%</span>❕ ^wihp2i</p><p>解决雪崩问题的常见方式有四种：<br><strong>超时处理</strong>：设定超时时间，请求超过一定时间没有响应就返回错误信息，不会无休止等待<br>**舱壁模式 (线程隔离)**：限定每个业务能使用的线程数，避免耗尽整个 tomcat 的资源，因此也叫线程隔离<br><strong>熔断降级</strong>：由断路器统计业务执行的异常比例，如果超出阈值则会熔断该业务，拦截访问该业务的一切请求<br><strong>流量控制</strong>：限制业务访问的 QPS，避免服务因流量的突增而故障</p><h3 id="1-3-1-超时处理"><a href="#1-3-1-超时处理" class="headerlink" title="1.3.1. 超时处理"></a>1.3.1. 超时处理</h3><p>设定超时时间，请求超过一定时间没有响应就返回错误信息，不会无休止等待<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315102401.png" alt="image-20210715172820438"></p><h3 id="1-3-2-舱壁模式-线程隔离"><a href="#1-3-2-舱壁模式-线程隔离" class="headerlink" title="1.3.2. 舱壁模式 (线程隔离)"></a>1.3.2. 舱壁模式 (线程隔离)</h3><p>限定每个业务能使用的线程数，避免耗尽整个 tomcat 的资源，因此<span style="background-color:#ff00ff">也叫线程隔离。</span> <span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1018%%</span>❕ ^pshp1j</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315101900.png" alt="image-20210715173215243"></p><h3 id="1-3-3-熔断降级"><a href="#1-3-3-熔断降级" class="headerlink" title="1.3.3. 熔断降级"></a>1.3.3. 熔断降级</h3><p>由断路器统计业务执行的异常比例，如果超出阈值则会熔断该业务，拦截访问该业务的一切请求<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315102516.png" alt="image.png"></p><h3 id="1-3-4-流量控制"><a href="#1-3-4-流量控制" class="headerlink" title="1.3.4. 流量控制"></a>1.3.4. 流量控制</h3><p>限制业务访问的 QPS，避免服务因流量的突增而故障<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315102655.png" alt="image.png"></p><h3 id="1-3-5-总结"><a href="#1-3-5-总结" class="headerlink" title="1.3.5. 总结"></a>1.3.5. 总结</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315103017.png" alt="image.png"></p><h1 id="2-微服务保护技术"><a href="#2-微服务保护技术" class="headerlink" title="2. 微服务保护技术"></a>2. 微服务保护技术</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301160639.png" alt="image ^sncpe2.png"></p><h1 id="3-限流"><a href="#3-限流" class="headerlink" title="3. 限流"></a>3. 限流</h1><h2 id="3-1-流控模式"><a href="#3-1-流控模式" class="headerlink" title="3.1. 流控模式"></a>3.1. 流控模式</h2><p>在添加限流规则时，点击高级选项，可以选择三种 <strong>流控模式</strong>：</p><ul><li>直接：统计当前资源的请求，触发阈值时对当前资源直接限流，也是<span style="background-color:#ff00ff">默认的模式</span></li><li>关联：统计与当前资源相关的另一个资源，触发阈值时，对当前资源限流</li><li>链路：统计从指定链路访问到本资源的请求，触发阈值时，对指定链路限流</li></ul><h3 id="3-1-1-关联"><a href="#3-1-1-关联" class="headerlink" title="3.1.1. 关联"></a>3.1.1. 关联</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313080407.png" alt="image-20210715201827886"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313081158.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313081332.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313081419.png" alt="image.png"></p><h3 id="3-1-2-链路"><a href="#3-1-2-链路" class="headerlink" title="3.1.2. 链路"></a>3.1.2. 链路</h3><p>对请求来源的限流控制</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313081532.png" alt="image.png"></p><p><span style="display:none">%%<br>▶11.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1428%%</span>❕ ^3uq11z</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313081639.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313083444.png" alt="image.png"></p><h2 id="3-2-流控效果"><a href="#3-2-流控效果" class="headerlink" title="3.2. 流控效果"></a>3.2. 流控效果</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313084046.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315082752.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313083851.png" alt="image.png"></p><h2 id="3-3-热点参数限流"><a href="#3-3-热点参数限流" class="headerlink" title="3.3. 热点参数限流"></a>3.3. 热点参数限流</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313084320.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313084454.png" alt="image.png"></p><p>需要使用注解 <code>@SentinelResource</code>，给热点参数限流方法再加一个资源 id 名称，比如下面的 “hot” <span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0749%%</span>❕ ^wj1oql</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313084531.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313084625.png" alt="image.png"></p><h2 id="3-4-流量整形"><a href="#3-4-流量整形" class="headerlink" title="3.4. 流量整形"></a>3.4. 流量整形</h2><p>排队等待的流控效果，可以起到流量整形的效果</p><h1 id="4-隔离和降级"><a href="#4-隔离和降级" class="headerlink" title="4. 隔离和降级"></a>4. 隔离和降级</h1><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=145&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=145&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="4-1-线程隔离-舱壁模式"><a href="#4-1-线程隔离-舱壁模式" class="headerlink" title="4.1. 线程隔离 (舱壁模式)"></a>4.1. 线程隔离 (舱壁模式)</h2><p><span style="background-color:#ff00ff">Sentinel 默认采用信号量隔离</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313092837.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313093103.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313093309.png" alt="image.png"></p><h2 id="4-2-熔断降级"><a href="#4-2-熔断降级" class="headerlink" title="4.2. 熔断降级"></a>4.2. 熔断降级</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313085436.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313093408.png" alt="image.png"></p><h3 id="4-2-1-断路器"><a href="#4-2-1-断路器" class="headerlink" title="4.2.1. 断路器"></a>4.2.1. 断路器</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313093528.png" alt="image.png"></p><h3 id="4-2-2-熔断策略"><a href="#4-2-2-熔断策略" class="headerlink" title="4.2.2. 熔断策略"></a>4.2.2. 熔断策略</h3><h4 id="4-2-2-1-慢调用"><a href="#4-2-2-1-慢调用" class="headerlink" title="4.2.2.1. 慢调用"></a>4.2.2.1. 慢调用</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313093708.png" alt="image.png"></p><h4 id="4-2-2-2-异常比例、异常数"><a href="#4-2-2-2-异常比例、异常数" class="headerlink" title="4.2.2.2. 异常比例、异常数"></a>4.2.2.2. 异常比例、异常数</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313093950.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313094217.png" alt="image.png"></p><h3 id="4-2-3-Feign-整合-Sentinel"><a href="#4-2-3-Feign-整合-Sentinel" class="headerlink" title="4.2.3. Feign 整合 Sentinel"></a>4.2.3. Feign 整合 Sentinel</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313085645.png" alt="image.png"></p><p><span style="background-color:#ff00ff">Feign 开启 Sentinel 功能后，Sentinel 就会把 Feign 的调用视作一种资源，从而能够进行熔断降级操作。</span> <span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0903%%</span>❕ ^gdjmu1</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315090437.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315090451.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313090008.png" alt="image.png"></p><h2 id="4-3-授权规则"><a href="#4-3-授权规则" class="headerlink" title="4.3. 授权规则"></a>4.3. 授权规则</h2><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=146&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=146&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="4-3-1-作用"><a href="#4-3-1-作用" class="headerlink" title="4.3.1. 作用"></a>4.3.1. 作用</h3><p><span style="background-color:#ff00ff">对请求者身份来源的校验，防止绕过网关直接调用微服务资源。只允许网关过来的请求</span> <span style="display:none">%%<br>▶14.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1131%%</span>❕ ^krg0d5</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315113258.png" alt="image.png"></p><p><span style="background-color:#ff00ff">流控应用后面输入框中调用方名称填写请求来源，比如我们在请求头中增加一个表示 gateway 来源的请求头，例子中我们直接简单的使用字符串 “gateway”</span></p><h3 id="4-3-2-使用方法"><a href="#4-3-2-使用方法" class="headerlink" title="4.3.2. 使用方法"></a>4.3.2. 使用方法</h3><h4 id="4-3-2-1-在-GateWay-中加请求头"><a href="#4-3-2-1-在-GateWay-中加请求头" class="headerlink" title="4.3.2.1. 在 GateWay 中加请求头"></a>4.3.2.1. 在 GateWay 中加请求头</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313094814.png" alt="image.png"><br><span style="background-color:#ff00ff">加上此配置之后，凡是从网关路由到微服务的请求中都带有名称为 origin，值为 “gateway” 的请求头</span>，而从浏览器中直接发送的请求 (企图绕过网关) 中没有约定的请求头，就会被拦截过滤掉。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322085839.png" alt="image.png"></p><h4 id="4-3-2-2-Sentinel-解析请求头"><a href="#4-3-2-2-Sentinel-解析请求头" class="headerlink" title="4.3.2.2. Sentinel 解析请求头"></a>4.3.2.2. Sentinel 解析请求头</h4><p>Sentinel 是通过 RequestOriginParser 这个接口的 parseOrigin 来获取请求的来源的。<br>所以，需要编写一个实现类，实现获取请求来源的接口 <code>RequestOriginParser</code> 的 <code>parseOrigin</code> 方法，让 Sentinel 获取到请求来源，从而让其能够做过滤判断。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313094836.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313095019.png" alt="image.png"></p><h4 id="4-3-2-3-优化"><a href="#4-3-2-3-优化" class="headerlink" title="4.3.2.3. 优化"></a>4.3.2.3. 优化</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313095354.png" alt="image.png"></p><p><span style="background-color:#00ff00">这一块可以使用 starter 引入的方式，就不需要在每个微服务中都编写同样的逻辑了</span><br><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0928%%</span>❕ ^ez1ivw</p><h3 id="4-3-3-自定义异常"><a href="#4-3-3-自定义异常" class="headerlink" title="4.3.3. 自定义异常"></a>4.3.3. 自定义异常</h3><h1 id="5-Sentinel-原理"><a href="#5-Sentinel-原理" class="headerlink" title="5. Sentinel 原理"></a>5. Sentinel 原理</h1><p><span style="display:none">%%<br>▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1119%%</span>❕ ^htxflw</p><p>[[Sentinel源码分析.md]]</p><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=176&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=176&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>Sentinel 实现限流、隔离、降级、熔断等功能，本质要做的就是两件事情：</p><ul><li><strong>统计数据</strong>：统计某个资源的访问数据（QPS、RT 等信息）</li><li><strong>规则判断</strong>：判断限流规则、隔离规则、降级规则、熔断规则是否满足</li></ul><p>这里的 <strong>资源</strong> 就是希望被 Sentinel 保护的业务，例如项目中定义的 controller 方法就是默认被 Sentinel 保护的资源、@SentinelResource 注解标注的自定义资源。</p><h2 id="5-1-ProcessorSlotChain"><a href="#5-1-ProcessorSlotChain" class="headerlink" title="5.1. ProcessorSlotChain"></a>5.1. ProcessorSlotChain</h2><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1136%%</span>❕ ^fazbix</p><p>实现上述功能的核心骨架是一个叫做 <code>ProcessorSlotChain</code> 的类。这个类基于<span style="background-color:#ff00ff">责任链模式</span>来设计，<span style="background-color:#ff00ff">将不同的功能（限流、降级、系统保护）封装为一个个的 Slot</span>，请求进入后逐个执行即可。<br><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230322-0713%%</span>❕ ^p5myd9</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313113025.png" alt="image-20210925092845529"></p><h3 id="5-1-1-责任链中的-Slot-分为两大类"><a href="#5-1-1-责任链中的-Slot-分为两大类" class="headerlink" title="5.1.1. 责任链中的 Slot 分为两大类"></a>5.1.1. 责任链中的 Slot 分为两大类</h3><h4 id="5-1-1-1-数据构建及统计部分（statistic）"><a href="#5-1-1-1-数据构建及统计部分（statistic）" class="headerlink" title="5.1.1.1. 数据构建及统计部分（statistic）"></a>5.1.1.1. 数据构建及统计部分（statistic）</h4><ol><li>NodeSelectorSlot：负责构建簇点链路中的节点（DefaultNode、EntranceNode，是一种特殊的 DefaultNode），将这些节点形成链路树</li><li>ClusterBuilderSlot：负责构建某个资源的 ClusterNode，ClusterNode 可以保存资源的运行信息（响应时间、QPS、block 数目、线程数、异常数等）以及来源信息（origin 名称）</li><li>StatisticSlot：负责统计实时调用数据，包括运行信息、来源信息等</li></ol><h4 id="5-1-1-2-规则判断部分（rule-checking）"><a href="#5-1-1-2-规则判断部分（rule-checking）" class="headerlink" title="5.1.1.2. 规则判断部分（rule checking）"></a>5.1.1.2. 规则判断部分（rule checking）</h4><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️⭐️】◼️⭐️-point-20230314-0747%%</span>❕ ^u9i9a5</p><ol><li>AuthoritySlot：负责授权规则（来源控制）</li><li>SystemSlot：负责系统保护规则</li><li>ParamFlowSlot：负责热点参数限流规则</li><li>FlowSlot：负责限流规则</li><li>DegradeSlot：负责降级规则</li></ol><h3 id="5-1-2-执行流程"><a href="#5-1-2-执行流程" class="headerlink" title="5.1.2. 执行流程"></a>5.1.2. 执行流程</h3><p><span style="display:none">%%<br>▶15.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1404%%</span>❕ ^8j0qwe</p><h4 id="5-1-2-1-自动装配-SentinelWebAutoConfiguration"><a href="#5-1-2-1-自动装配-SentinelWebAutoConfiguration" class="headerlink" title="5.1.2.1. 自动装配 -SentinelWebAutoConfiguration"></a>5.1.2.1. 自动装配 -SentinelWebAutoConfiguration</h4><p>其中的 spring.factories 声明需要就是自动装配的配置类，内容如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313131303.png" alt="image-20210925115740281"></p><p>我们先看 <code>SentinelWebAutoConfiguration</code> 这个类：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313132455.png" alt="image-20210925115824345"></p><h4 id="5-1-2-2-注册拦截器-SentinelWebInterceptor"><a href="#5-1-2-2-注册拦截器-SentinelWebInterceptor" class="headerlink" title="5.1.2.2. 注册拦截器 -SentinelWebInterceptor"></a>5.1.2.2. 注册拦截器 -SentinelWebInterceptor</h4><p>这个类实现了 WebMvcConfigurer，我们知道这个是 SpringMVC 自定义配置用到的类，可以配置 HandlerInterceptor：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313132625.png" alt="image-20210925115946064"></p><p>可以看到这里配置了一个 <code>SentinelWebInterceptor</code> 的拦截器。<br><code>SentinelWebInterceptor</code> 的声明如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313132704.png" alt="image-20210925120119030"></p><p>发现它继承了 <code>AbstractSentinelInterceptor</code> 这个类。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313132706.png" alt="image-20210925120221883"></p><p><code>HandlerInterceptor</code> 就是我们 SpringMVC 中的拦截器了。 <span style="background-color:#ff00ff">拦截器会拦截一切进入 controller 的方法，执行 <code>preHandle</code> 前置拦截方法</span>，而 Context 的初始化就是在这里完成的。</p><h4 id="5-1-2-3-创建-Context"><a href="#5-1-2-3-创建-Context" class="headerlink" title="5.1.2.3. 创建 Context"></a>5.1.2.3. 创建 Context</h4><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-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">preHandle</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span><br>    <span class="hljs-keyword">throws</span> Exception &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 获取资源名称，一般是controller方法的@RequestMapping路径，例如/order/&#123;orderId&#125;</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">resourceName</span> <span class="hljs-operator">=</span> getResourceName(request);<br>        <span class="hljs-keyword">if</span> (StringUtil.isEmpty(resourceName)) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        &#125;<br>        <span class="hljs-comment">// 从request中获取请求来源，将来做 授权规则 判断时会用</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">origin</span> <span class="hljs-operator">=</span> parseOrigin(request);<br>        <br>        <span class="hljs-comment">// 获取 contextName，默认是sentinel_spring_web_context</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">contextName</span> <span class="hljs-operator">=</span> getContextName(request);<br>        <span class="hljs-comment">// 创建 Context</span><br>        ContextUtil.enter(contextName, origin);<br>        <span class="hljs-comment">// 创建资源，名称就是当前请求的controller方法的映射路径</span><br>        <span class="hljs-type">Entry</span> <span class="hljs-variable">entry</span> <span class="hljs-operator">=</span> SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);<br>        request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    &#125; <span class="hljs-keyword">catch</span> (BlockException e) &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            handleBlockException(request, response, e);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            ContextUtil.exit();<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><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313143228.png" alt="image.png"></p><h5 id="5-1-2-3-1-Context-作用"><a href="#5-1-2-3-1-Context-作用" class="headerlink" title="5.1.2.3.1. Context 作用"></a>5.1.2.3.1. Context 作用</h5><p>我们发现簇点链路中除了 controller 方法、service 方法两个资源外，还多了一个默认的入口节点：<br><code>sentinel_spring_web_context</code>，是一个 EntranceNode 类型的节点<br>这个节点是在初始化 Context 的时候由 Sentinel 帮我们创建的。<br>那么，什么是 Context 呢？</p><ul><li>Context 代表调用链路上下文，贯穿一次调用链路中的所有资源（ <code>Entry</code>），基于 ThreadLocal。</li><li>Context 维持着入口节点（<code>entranceNode</code>）、本次调用链路的 curNode（当前资源节点）、调用来源（<code>origin</code>）等信息。</li><li><span style="background-color:#ff00ff">后续的 Slot 都可以通过 Context 拿到 DefaultNode 或者 ClusterNode，从而获取统计数据，完成规则判断</span></li><li>Context 初始化的过程中，会创建 EntranceNode，contextName 就是 EntranceNode 的名称</li></ul><h4 id="5-1-2-4-进入执行入口-SphU-entry"><a href="#5-1-2-4-进入执行入口-SphU-entry" class="headerlink" title="5.1.2.4. 进入执行入口 -SphU.entry"></a>5.1.2.4. 进入执行入口 -SphU.entry</h4><p>首先，回到一切的入口，<code>AbstractSentinelInterceptor</code> 类的 <code>preHandle</code> 方法：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313141107.png" alt="image-20210925142313050"></p><p>还有，<code>SentinelResourceAspect</code> 的环绕增强方法：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313141252.png" alt="image-20210925142438552"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313134102.png" alt="image.png"></p><h4 id="5-1-2-5-创建-ProcessorSlotChain"><a href="#5-1-2-5-创建-ProcessorSlotChain" class="headerlink" title="5.1.2.5. 创建 ProcessorSlotChain"></a>5.1.2.5. 创建 ProcessorSlotChain</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313134315.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313134928.png" alt="image.png"></p><h3 id="5-1-3-关键配置"><a href="#5-1-3-关键配置" class="headerlink" title="5.1.3. 关键配置"></a>5.1.3. 关键配置</h3><h4 id="5-1-3-1-统一入口"><a href="#5-1-3-1-统一入口" class="headerlink" title="5.1.3.1. 统一入口"></a>5.1.3.1. 统一入口</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313142530.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313142623.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313142640.png" alt="image.png"></p><p>默认为 true，统一入口为<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313142719.png" alt="image.png"></p><p><span style="background-color:#ff00ff">但如果做链路限流时，需要禁用统一入口，分成不同的链路，那么就需要设置这个参数为 false</span>。<a href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-10%E3%80%81%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7(%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4)-Sentinel/" title="服务治理-10、限流熔断降级(服务保护)-Sentinel">服务治理-10、限流熔断降级(服务保护)-Sentinel</a></p><h2 id="5-2-Node"><a href="#5-2-Node" class="headerlink" title="5.2. Node"></a>5.2. Node</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313130918.png" alt="image-20210925103029924"></p><p>所有的节点都可以记录对资源的访问统计数据，所以都是 StatisticNode 的子类。</p><p>按照作用分为两类 Node：</p><ul><li>DefaultNode：代表链路树中的每一个资源，一个资源出现在不同链路中时，会创建不同的 DefaultNode 节点。而树的入口节点叫 EntranceNode，是一种特殊的 DefaultNode</li><li>ClusterNode：代表资源，一个资源不管出现在多少链路中，只会有一个 ClusterNode。记录的是当前资源被访问的所有统计数据之和。</li></ul><p><span style="background-color:#ff00ff">DefaultNode 记录的是资源在当前链路中的访问数据，用来实现基于链路模式的限流规则。ClusterNode 记录的是资源在所有链路中的访问数据，实现默认模式、关联模式的限流规则。</span></p><p>例如：我们在一个 SpringMVC 项目中，有两个业务：</p><ul><li>业务 1：controller 中的资源 <code>/order/query</code> 访问了 service 中的资源 <code>/goods</code></li><li>业务 2：controller 中的资源 <code>/order/save</code> 访问了 service 中的资源 <code>/goods</code></li></ul><p>创建的链路图如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313131020.png" alt="image-20210925104726158"></p><h2 id="5-3-Entry"><a href="#5-3-Entry" class="headerlink" title="5.3. Entry"></a>5.3. Entry</h2><p>默认情况下，Sentinel <span style="background-color:#ff00ff">只会将 controller 中的方法作为被保护资源</span>，那么问题来了，我们该如何将自己的一段代码标记为一个 Sentinel 的资源呢？</p><h3 id="5-3-1-编码方式"><a href="#5-3-1-编码方式" class="headerlink" title="5.3.1. 编码方式"></a>5.3.1. 编码方式</h3><h3 id="5-3-2-基于注解标记资源"><a href="#5-3-2-基于注解标记资源" class="headerlink" title="5.3.2. 基于注解标记资源"></a>5.3.2. 基于注解标记资源</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313131242.png" alt="image-20210925141507603"></p><h4 id="5-3-2-1-实现原理-SentinelAutoConfiguration"><a href="#5-3-2-1-实现原理-SentinelAutoConfiguration" class="headerlink" title="5.3.2.1. 实现原理 -SentinelAutoConfiguration"></a>5.3.2.1. 实现原理 -SentinelAutoConfiguration</h4><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1321%%</span>❕ ^odpucd</p><p>来看下我们引入的 Sentinel 依赖包：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313131300.png" alt="image-20210925115601560"></p><p>其中的 spring.factories 声明需要就是自动装配的配置类，内容如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313131303.png" alt="image-20210925115740281"></p><p>我们来看下 <code>SentinelAutoConfiguration</code> 这个类：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313131400.png" alt="image-20210925141553785"></p><p>可以看到，在这里声明了一个 Bean，<code>SentinelResourceAspect</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></pre></td><td class="code"><pre><code class="hljs Java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Aspect for methods with &#123;<span class="hljs-doctag">@link</span> SentinelResource&#125; annotation.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> Eric Zhao</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@Aspect</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SentinelResourceAspect</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractSentinelAspectSupport</span> &#123;<br><span class="hljs-comment">// 切点是添加了 @SentinelResource注解的类</span><br>    <span class="hljs-meta">@Pointcut(&quot;@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)&quot;)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sentinelResourceAnnotationPointcut</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-comment">// 环绕增强</span><br>    <span class="hljs-meta">@Around(&quot;sentinelResourceAnnotationPointcut()&quot;)</span><br>    <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invokeResourceWithSentinel</span><span class="hljs-params">(ProceedingJoinPoint pjp)</span> <span class="hljs-keyword">throws</span> Throwable &#123;<br>        <span class="hljs-comment">// 获取受保护的方法</span><br>        <span class="hljs-type">Method</span> <span class="hljs-variable">originMethod</span> <span class="hljs-operator">=</span> resolveMethod(pjp);<br><span class="hljs-comment">// 获取 @SentinelResource注解</span><br>        <span class="hljs-type">SentinelResource</span> <span class="hljs-variable">annotation</span> <span class="hljs-operator">=</span> originMethod.getAnnotation(SentinelResource.class);<br>        <span class="hljs-keyword">if</span> (annotation == <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-comment">// Should not go through here.</span><br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalStateException</span>(<span class="hljs-string">&quot;Wrong state for SentinelResource annotation&quot;</span>);<br>        &#125;<br>        <span class="hljs-comment">// 获取注解上的资源名称</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">resourceName</span> <span class="hljs-operator">=</span> getResourceName(annotation.value(), originMethod);<br>        <span class="hljs-type">EntryType</span> <span class="hljs-variable">entryType</span> <span class="hljs-operator">=</span> annotation.entryType();<br>        <span class="hljs-type">int</span> <span class="hljs-variable">resourceType</span> <span class="hljs-operator">=</span> annotation.resourceType();<br>        <span class="hljs-type">Entry</span> <span class="hljs-variable">entry</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 创建资源 Entry</span><br>            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());<br>            <span class="hljs-comment">// 执行受保护的方法</span><br>            <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> pjp.proceed();<br>            <span class="hljs-keyword">return</span> result;<br>        &#125; <span class="hljs-keyword">catch</span> (BlockException ex) &#123;<br>            <span class="hljs-keyword">return</span> handleBlockException(pjp, annotation, ex);<br>        &#125; <span class="hljs-keyword">catch</span> (Throwable ex) &#123;<br>            Class&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Throwable</span>&gt;[] exceptionsToIgnore = annotation.exceptionsToIgnore();<br>            <span class="hljs-comment">// The ignore list will be checked first.</span><br>            <span class="hljs-keyword">if</span> (exceptionsToIgnore.length &gt; <span class="hljs-number">0</span> &amp;&amp; exceptionBelongsTo(ex, exceptionsToIgnore)) &#123;<br>                <span class="hljs-keyword">throw</span> ex;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) &#123;<br>                traceException(ex);<br>                <span class="hljs-keyword">return</span> handleFallback(pjp, annotation, ex);<br>            &#125;<br><br>            <span class="hljs-comment">// No fallback function can handle the exception, so throw it out.</span><br>            <span class="hljs-keyword">throw</span> ex;<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            <span class="hljs-keyword">if</span> (entry != <span class="hljs-literal">null</span>) &#123;<br>                entry.exit(<span class="hljs-number">1</span>, pjp.getArgs());<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>简单来说，@SentinelResource 注解就是一个标记，而 Sentinel 基于 AOP 思想，对被标记的方法做环绕增强，完成资源（<code>Entry</code>）的创建。</p><h1 id="6-面试题"><a href="#6-面试题" class="headerlink" title="6. 面试题"></a>6. 面试题</h1><h2 id="6-1-Sentinel-的线程隔离与-Hystix-的线程隔离有什么差别-⭐️🔴"><a href="#6-1-Sentinel-的线程隔离与-Hystix-的线程隔离有什么差别-⭐️🔴" class="headerlink" title="6.1. Sentinel 的线程隔离与 Hystix 的线程隔离有什么差别?⭐️🔴"></a>6.1. Sentinel 的线程隔离与 Hystix 的线程隔离有什么差别?⭐️🔴</h2><p>线程池隔离：在业务请求到达 Tomcat 后，会给每一个被隔离的业务创建各自独立的线程池<br>信号量隔离：基于计数的信号量隔离</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301171403.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301171512.png" alt="image.png"></p><p>Hystix 默认是基于线程池实现的线程隔离，每一个被隔离的业务都要创建一个独立的线程池，线程过多会带来额外的 CPU 开销，性能一般，但是隔离性更强。<br>Sentinel 是基于信号量（计数器）实现的线程隔离，不用创建线程池，性能较好，但是隔离性一般。</p><h2 id="6-2-Sentinel-的限流与-Gateway-的限流有什么差别？"><a href="#6-2-Sentinel-的限流与-Gateway-的限流有什么差别？" class="headerlink" title="6.2. Sentinel 的限流与 Gateway 的限流有什么差别？"></a>6.2. Sentinel 的限流与 Gateway 的限流有什么差别？</h2><p><span style="display:none">%%<br>▶15.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230308-1356%%</span>❕ ^n7rdsh</p><p>限流算法常见的有三种实现：滑动时间窗口、令牌桶算法、漏桶算法。</p><p>Gateway 则采用了基于 Redis 实现的令牌桶算法。 ^cinpaa</p><p>而 Sentinel 内部却比较复杂：</p><ul><li><strong>默认限流模式</strong> 是基于<span style="background-color:#00ff00">滑动时间窗口算法</span></li><li><strong>排队等待限流</strong> 模式则基于<span style="background-color:#00ff00">漏桶算法 (Leaky Bucket)</span></li><li><strong>热点参数限流模式</strong> 则是基于<span style="background-color:#00ff00">令牌桶算法 (Token Bucket)</span></li></ul><h3 id="6-2-1-固定窗口计数器算法"><a href="#6-2-1-固定窗口计数器算法" class="headerlink" title="6.2.1. 固定窗口计数器算法"></a>6.2.1. 固定窗口计数器算法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301172335.png" alt="image.png"><br>存在问题：前 1 秒的后半秒 3 个，后 1 秒的前半秒也有请求，就造成 1 秒内超过 3 个请求了，比如上图所示，前半秒 3 个后半秒 3 个达到了 6 个。</p><h3 id="6-2-2-滑动窗口计数器算法"><a href="#6-2-2-滑动窗口计数器算法" class="headerlink" title="6.2.2. 滑动窗口计数器算法"></a>6.2.2. 滑动窗口计数器算法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301173011.png" alt="image.png"></p><p>存在问题：仍然会存在超阈值 (比如 3 个) 的情况，比如下图 850ms 内来了 4 个请求<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301173133.png" alt="image.png"></p><h3 id="6-2-3-令牌桶算法"><a href="#6-2-3-令牌桶算法" class="headerlink" title="6.2.3. 令牌桶算法"></a>6.2.3. 令牌桶算法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301173342.png" alt="image.png"></p><h4 id="6-2-3-1-存在问题"><a href="#6-2-3-1-存在问题" class="headerlink" title="6.2.3.1. 存在问题"></a>6.2.3.1. 存在问题</h4><p>请求的时间分布不均匀时，可能出现短时间内达到阈值上限的情况。比如限流阈值为 5，设置令牌桶每秒生成 5 个令牌，前 1 秒没有任何请求过来，而第 2 秒一下子来了 10 个请求，那么第二秒总共生成的 10 个令牌就一下子被使用，而系统服务的上限是 5，那么就出现了过载现象。所以令牌桶的上限要设置为小于系统并发上线的数值，以防止出现上述情况。</p><h4 id="6-2-3-2-实现方式⭐️🔴"><a href="#6-2-3-2-实现方式⭐️🔴" class="headerlink" title="6.2.3.2. 实现方式⭐️🔴"></a>6.2.3.2. 实现方式⭐️🔴</h4><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1035%%</span>❕ ^06vdeq</p><p>实际实现并没有真正去生成什么令牌，而是<span style="background-color:#ff00ff">基于计数和记录上次请求的时间</span></p><ol><li>如果本次请求时间与上次请求时间差是 1 秒内的，如果此时桶里有令牌可用，那么令牌已使用计数加 1，并且消耗 1 个令牌。如果没有令牌了则拒绝请求</li><li>如果本次请求时间与上次请求时间差大于 1 秒，就比较已使用令牌数与时间差值可以生成的令牌数的大小，如果时间差值生成令牌数大于已使用令牌数，那么说明令牌还有富余，则接受请求，否则说明超出令牌生成能力范围，则拒绝请求。</li></ol><h3 id="6-2-4-漏桶算法"><a href="#6-2-4-漏桶算法" class="headerlink" title="6.2.4. 漏桶算法"></a>6.2.4. 漏桶算法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301174905.png" alt="image.png"></p><p>相比于令牌桶，漏桶的请求处理曲线非常平滑，也可以应对流量突发请求流量</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301175138.png" alt="image.png"></p><p><span style="background-color:#ff00ff">预期等待时长&#x3D;最近一次请求的预期等待时间 + 允许的间隔</span></p><h3 id="6-2-5-限流算法对比"><a href="#6-2-5-限流算法对比" class="headerlink" title="6.2.5. 限流算法对比"></a>6.2.5. 限流算法对比</h3><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1116%%</span>❕ ^6pp17h</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310104317.png" alt="image.png"></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-黑马程序员"><a href="#8-1-黑马程序员" class="headerlink" title="8.1. 黑马程序员"></a>8.1. 黑马程序员</h2><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=144&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=144&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程/day01-微服务保护/讲义/微服务保护.md<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot-1、基本原理</title>
      <link href="/2023/06/12/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/SpringBoot-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2023/06/12/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/SpringBoot-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-SpringBoot-的特性"><a href="#1-SpringBoot-的特性" class="headerlink" title="1. SpringBoot 的特性"></a>1. SpringBoot 的特性</h1><p><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=117&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=117&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="1-1-依赖管理"><a href="#1-1-依赖管理" class="headerlink" title="1.1. 依赖管理"></a>1.1. 依赖管理</h2><h3 id="1-1-1-父项目做依赖管理"><a href="#1-1-1-父项目做依赖管理" class="headerlink" title="1.1.1. 父项目做依赖管理"></a>1.1.1. 父项目做依赖管理</h3><p>   1、无需关注版本号，自动版本仲裁<br>   2、可以修改默认版本号</p><h3 id="1-1-2-starter-场景启动器"><a href="#1-1-2-starter-场景启动器" class="headerlink" title="1.1.2. starter 场景启动器"></a>1.1.2. starter 场景启动器</h3><p>  <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230226073735.png" alt="image.png"></p><p>1、见到很多 spring-boot-starter-xxx ： xxx 就某种场景<br>2、只要引入 starter，这个场景的所有常规需要的依赖我们都自动引入<br>3、SpringBoot 所有支持的场景<br><a href="https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter">https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter</a><br>4、见到的 xxx-spring-boot-starter： 第三方为我们提供的简化开发的场景启动器。<br>5、所有场景启动器最底层的依赖</p><h2 id="1-2-自动配置"><a href="#1-2-自动配置" class="headerlink" title="1.2. 自动配置"></a>1.2. 自动配置</h2><p>● 自动配好 Tomcat<br>          ○ 引入 Tomcat 依赖。<br>          ○ 配置 Tomcat<br>       ● 自动配好 SpringMVC<br>          ○ 引入 SpringMVC 全套组件<br>          ○ 自动配好 SpringMVC 常用组件（功能）<br>● 自动配好 Web 常见功能，如：字符编码问题<br>          ○ SpringBoot 帮我们配置好了所有 web 开发的常见场景<br>● 默认的包结构<br>          ○ 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来<br>          ○ 无需以前的包扫描配置<br>          ○ 想要改变扫描路径，@SpringBootApplication(scanBasePackages&#x3D;”com.atguigu”)<br>       ● 或者@ComponentScan 指定扫描路径<br>       ● 各种配置拥有默认值<br>          ○ 默认配置最终都是映射到某个类上，如：MultipartProperties<br>          ○ 配置文件的值最终会绑定每个类上，这个类会在容器中创建对象<br>       ● 按需加载所有自动配置项<br>          ○ 非常多的 starter<br>          ○ 引入了哪些场景这个场景的自动配置才会开启<br>          ○ SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面</p><h1 id="2-谈谈你对-SpringBoot-的理解-优点特性"><a href="#2-谈谈你对-SpringBoot-的理解-优点特性" class="headerlink" title="2. 谈谈你对 SpringBoot 的理解 (优点特性)"></a>2. 谈谈你对 SpringBoot 的理解 (优点特性)</h1><p>SpringBoot 的用来快速开发 Spring 应用的一个脚手架、其设计目的是用来简新 Spring 应用的初始搭建以及开发过程。</p><ol><li>SpringBoot 提供了很多内置的 Starter 结合自动配置，实现了对主流框架的<span style="background-color:#00ff00">无配置集成</span>、开箱即用；</li><li>SpringBoot 简化了开发，采用 JavaConfig 的方式可以使用<span style="background-color:#00ff00">零 xml</span>的方式进行开发；</li><li>SpringBoot<span style="background-color:#00ff00">内置 Web 容器</span>无需依赖外部 Web 服务器，省略了 Web.xml，直接运行 jar 文件就可以启动 web 应用；</li><li>SpringBoot 帮我管理了常用的第三方依赖的版本，<span style="background-color:#00ff00">减少出现版本冲突</span>的问题；</li><li>SpringBoot<span style="background-color:#00ff00">自带了监控功能</span>，可以监控应用程序的运行状况，或者内存、线程池、Http 请求统计等，同时还提供了优雅关闭应用程序等功能。</li></ol><h1 id="3-SpringBoot-自动配置原理-SpringBootApplication-⭐️🔴"><a href="#3-SpringBoot-自动配置原理-SpringBootApplication-⭐️🔴" class="headerlink" title="3. SpringBoot 自动配置原理 -@SpringBootApplication ⭐️🔴"></a>3. SpringBoot 自动配置原理 -@SpringBootApplication ⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230303-0844%%</span><br>^py2rrk<br><a href="https://www.yuque.com/atguigu/springboot/qb7hy2#UJZFM">https://www.yuque.com/atguigu/springboot/qb7hy2#UJZFM</a> ^c46210</p><h2 id="3-1-引导加载自动配置类"><a href="#3-1-引导加载自动配置类" class="headerlink" title="3.1. 引导加载自动配置类"></a>3.1. 引导加载自动配置类</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230326-2214%%</span>❕ ^dxovlh</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-meta">@SpringBootConfiguration</span><br><span class="hljs-meta">@EnableAutoConfiguration</span><br><span class="hljs-meta">@ComponentScan(excludeFilters = &#123; @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),</span><br><span class="hljs-meta">@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) &#125;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> SpringBootApplication&#123;&#125;   <br></code></pre></td></tr></table></figure><h3 id="3-1-1-SpringBootConfiguration"><a href="#3-1-1-SpringBootConfiguration" class="headerlink" title="3.1.1. @SpringBootConfiguration"></a>3.1.1. @SpringBootConfiguration</h3><p>@Configuration。代表当前是一个配置类</p><h3 id="3-1-2-ComponentScan"><a href="#3-1-2-ComponentScan" class="headerlink" title="3.1.2. @ComponentScan"></a>3.1.2. @ComponentScan</h3><p>指定扫描哪些，是一个 Spring 注解；<br>在 SpringBoot 中，在该注解属性中配置了排除规则：排除 2 种类型：<br>①继承了 TypeExcludeFilter 的类<br>②SpringBoot 的自动配置类</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">@ComponentScan(excludeFilters = &#123; @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),  </span><br><span class="hljs-meta">      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) &#125;)</span><br></code></pre></td></tr></table></figure><h3 id="3-1-3-EnableAutoConfiguration"><a href="#3-1-3-EnableAutoConfiguration" class="headerlink" title="3.1.3. @EnableAutoConfiguration"></a>3.1.3. @EnableAutoConfiguration</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222142714.png" alt="image.png"></p><h4 id="3-1-3-1-AutoConfigurationPackage"><a href="#3-1-3-1-AutoConfigurationPackage" class="headerlink" title="3.1.3.1. @AutoConfigurationPackage"></a>3.1.3.1. @AutoConfigurationPackage</h4><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230324-1703%%</span>❕ ^j6d450</p><p><code>@Import(AutoConfigurationPackages.Registrar.class)</code><br>自动配置包，指定了默认的包规则，将指定的<span style="background-color:#ff00ff">一个包下的所有组件</span>导入进来。</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-meta">@Import(AutoConfigurationPackages.Registrar.class)</span>  <br><span class="hljs-comment">//给容器中导入一个组件</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> AutoConfigurationPackage &#123;&#125;<br><span class="hljs-comment">//利用Registrar给容器中导入一系列组件</span><br><span class="hljs-comment">//将指定的一个包下的所有组件导入进来，MainApplication 所在包下。</span><br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230326072101.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222160725.png" alt="image.png"></p><h4 id="3-1-3-2-Import-AutoConfigurationImportSelector-class"><a href="#3-1-3-2-Import-AutoConfigurationImportSelector-class" class="headerlink" title="3.1.3.2. @Import(AutoConfigurationImportSelector.class)"></a>3.1.3.2. @Import(AutoConfigurationImportSelector.class)</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324173014.png" alt="image.png"></p><p>selectImports→getAutoConfigurationEntry 获取到所有需要导入到容器中的配置类</p><p><span style="background-color:#ff00ff">方法调用路径</span><br>    AutoConfigurationImportSelector. <code>getAutoConfigurationEntry</code> &#x3D;&#x3D;→&#x3D;&#x3D;  <span style="background-color:#ff00ff">SpringFactoriesLoader</span>. <code>loadSpringFactories</code> &#x3D;&#x3D;→&#x3D;&#x3D;<br>    classLoader.getResources(“<code>META-INF/spring.factories</code>“)</p><p><span style="background-color:#ff00ff">默认扫描当前系统里面所有 META-INF&#x2F;spring.factories 位置的文件</span> ❕<span style="display:none">%%<br>▶2.🏡⭐️◼️自定义 starter 就是依靠这种策略 ?🔜MSTM📝 可以被扫描到需要注入的组件◼️⭐️-point-20230226-0847%%</span></p><p>比如 <code>spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories</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></pre></td><td class="code"><pre><code class="hljs Java"><span class="hljs-number">1</span>、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件<br><span class="hljs-number">2</span>、调用List&lt;String&gt; configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类<br><span class="hljs-number">3</span>、利用工厂加载 Map&lt;String, List&lt;String&gt;&gt; <span class="hljs-title function_">loadSpringFactories</span><span class="hljs-params">(<span class="hljs-meta">@Nullable</span> ClassLoader classLoader)</span>；得到所有的组件<br><span class="hljs-number">4</span>、从META-INF/spring.factories位置来加载一个文件。<br>默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件<br>    spring-boot-autoconfigure-<span class="hljs-number">2.3</span><span class="hljs-number">.4</span>.RELEASE.jar包里面也有META-INF/spring.factories<br>    <br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222162310.png" alt="image.png"></p><h5 id="3-1-3-2-1-SPI-原理"><a href="#3-1-3-2-1-SPI-原理" class="headerlink" title="3.1.3.2.1. SPI 原理"></a>3.1.3.2.1. SPI 原理</h5><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-0804%%</span>❕ ^bypdob</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304124004.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304132455.png" alt="image.png"></p><h2 id="3-2-按需开启自动配置项"><a href="#3-2-按需开启自动配置项" class="headerlink" title="3.2. 按需开启自动配置项"></a>3.2. 按需开启自动配置项</h2><p>虽然我们 127 个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration，但按照条件装配规则（@Conditional），最终会按需配置。</p><h2 id="3-3-修改默认配置"><a href="#3-3-修改默认配置" class="headerlink" title="3.3. 修改默认配置"></a>3.3. 修改默认配置</h2><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-meta">@Bean</span><br><span class="hljs-meta">@ConditionalOnBean(MultipartResolver.class)</span>  <span class="hljs-comment">//容器中有这个类型组件</span><br><span class="hljs-meta">@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)</span> <span class="hljs-comment">//容器中没有这个名字 multipartResolver 的组件</span><br><span class="hljs-keyword">public</span> MultipartResolver <span class="hljs-title function_">multipartResolver</span><span class="hljs-params">(MultipartResolver resolver)</span> &#123;<br>            <span class="hljs-comment">//给@Bean标注的方法传入了对象参数，这个参数的值就会从容器中找。</span><br>            <span class="hljs-comment">//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范</span><br><span class="hljs-comment">// Detect if the user has created a MultipartResolver but named it incorrectly</span><br><span class="hljs-keyword">return</span> resolver;<br>&#125;<br>给容器中加入了文件上传解析器；<br><br></code></pre></td></tr></table></figure><p><span style="background-color:#00ff00">SpringBoot 默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先</span></p><h2 id="3-4-与-SpringMVC-配置的区别"><a href="#3-4-与-SpringMVC-配置的区别" class="headerlink" title="3.4. 与 SpringMVC 配置的区别"></a>3.4. 与 SpringMVC 配置的区别</h2><h3 id="3-4-1-关于-JSON-编码配置"><a href="#3-4-1-关于-JSON-编码配置" class="headerlink" title="3.4.1. 关于 JSON 编码配置"></a>3.4.1. 关于 JSON 编码配置</h3><p>Spring 整合 SpringMVC 时需要设置 JSON 格式<br>Springboot 不需要，因为在<artifactId>spring-boot-starter-web</artifactId>中<br>自动配置了 jacjson-databind，使用的是 <code>MappingJackson2HttpMessageConverter</code></p><h3 id="3-4-2-EnableAspectJAutoProxy⭐️🔴"><a href="#3-4-2-EnableAspectJAutoProxy⭐️🔴" class="headerlink" title="3.4.2. @EnableAspectJAutoProxy⭐️🔴"></a>3.4.2. @EnableAspectJAutoProxy⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302142824.png" alt="image.png"></p><p>在 Spring 中，如果不在配置类中添加@EnableAspectJAutoProxy，那么所有 <a href="https://so.csdn.net/so/search?q=%E5%88%87%E9%9D%A2&spm=1001.2101.3001.7020">切面</a> 注解是不生效的（springboot 因为有自动配置，所以不需要开发人员手工配置@EnableAspectJAutoProxy）</p><h2 id="3-5-自动配置总结"><a href="#3-5-自动配置总结" class="headerlink" title="3.5. 自动配置总结"></a>3.5. 自动配置总结</h2><ul><li>SpringBoot 先加载所有的自动配置类 xxxxxAutoConfiguration</li><li>每个自动配置类按照条件进行生效，默认都会绑定配置文件指定的值。xxxxProperties 里面拿。xxxProperties 和配置文件进行了绑定</li><li>生效的配置类就会给容器中装配很多组件</li><li>只要容器中有这些组件，相当于这些功能就有了</li><li>定制化配置<ul><li>用户直接自己@Bean 替换底层的组件</li><li>用户去看这个组件是获取的配置文件什么值就去修改。</li></ul></li></ul><p><strong>xxxxxAutoConfiguration —&gt; 组件 —&gt;</strong> <strong>xxxxProperties 里面拿值 —-&gt; application.properties</strong></p><h2 id="3-6-黑马补充"><a href="#3-6-黑马补充" class="headerlink" title="3.6. 黑马补充"></a>3.6. 黑马补充</h2><p><a href="https://www.bilibili.com/video/BV15b4y117RJ?p=172&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15b4y117RJ?p=172&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230325121857.png" alt="image.png"></p><h1 id="4-SpringBoot-的启动原理⭐️🔴"><a href="#4-SpringBoot-的启动原理⭐️🔴" class="headerlink" title="4. SpringBoot 的启动原理⭐️🔴"></a>4. SpringBoot 的启动原理⭐️🔴</h1><p><span style="display:none">%%<br>▶15.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1327%%</span>❕ ^ro0jas<br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230324-0703%%</span>❕ ^214fh8</p><p><strong>概述</strong></p><ol><li>运行 main 方法： 初始化 new SpringApplication  从 spring.factories  读取 ApplicationListener、ApplicationContextInitializer</li><li>运行 run 方法</li><li>读取环境变量配置信息</li><li>创建 springApplication 上下文： ServletWebServerApplicationContext</li><li>预初始化上下文 </li><li>调用 refresh 加载 ioc 容器 <br><code>invokeBeanFactoryPostProcessor </code>  <span style="background-color:#00ff00">解析@Import</span>:  加载所有的自动配置类<br><code>onRefresh</code>  <span style="background-color:#00ff00">创建 (内置)servlet 容器</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224181156.png" alt="image.png"></li></ol><p>在这个过程中 springboot 会调用很多监听器对外进行扩展<br><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=196&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=196&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="4-1-创建-SpringApplication"><a href="#4-1-创建-SpringApplication" class="headerlink" title="4.1. 创建 SpringApplication"></a>4.1. 创建 SpringApplication</h2><p>   <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224142822.png" alt="image.png"></p><ol><li>判定当前应用的类型。ClassUtils.isPresent → WebApplicationType.SERVLET</li><li>使用系统方法 <code>getSpringFactoriesInstances</code> 分别查找以下类型：<br>○ 查找 <font color=#ff0000>bootstrappers</font>：&#x3D;&#x3D;初始启动引导器&#x3D;&#x3D;<br>  去 <code>spring.factories</code> 文件中找 org.springframework.boot. <code>Bootstrapper</code><br>  放入 <code>List&lt;Bootstrapper&gt;</code><br>○ 查找 <font color=#ff0000>ApplicationContextInitializer</font>：&#x3D;&#x3D;初始化器&#x3D;&#x3D;<br>    去 spring.factories 找 ApplicationContextInitializer<br>    放入 <code>List&lt;ApplicationContextInitializer&lt;?&gt;&gt; initializers</code><br>○ 查找 <font color=#ff0000>ApplicationListener</font>  ：&#x3D;&#x3D;应用监听器&#x3D;&#x3D;<br>    去 spring.factories 找 ApplicationListener<br>    放入 <code>List&lt;ApplicationListener&lt;?&gt;&gt; listeners</code></li><li><span style="background-color:#ff00ff">保存当前启动类为配置类</span></li></ol><h2 id="4-2-运行-SpringApplication"><a href="#4-2-运行-SpringApplication" class="headerlink" title="4.2. 运行 SpringApplication"></a>4.2. 运行 SpringApplication</h2><h3 id="4-2-1-记录应用的启动时间-创建引导上下文-bootstrappers-intitialize"><a href="#4-2-1-记录应用的启动时间-创建引导上下文-bootstrappers-intitialize" class="headerlink" title="4.2.1. 记录应用的启动时间 - 创建引导上下文 -bootstrappers.intitialize"></a>4.2.1. 记录应用的启动时间 - 创建引导上下文 -bootstrappers.intitialize</h3><p>  ○ <strong>StopWatch</strong>，记录应用的启动时间<br>  ○ 创建引导上下文（Context 环境）createBootstrapContext()<br>    ■ <span style="background-color:#ff00ff">获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置</span><br>  ○ 让当前应用进入 headless 模式。java.awt.headless</p><h3 id="4-2-2-发布-ApplicationStartingEvent-事件"><a href="#4-2-2-发布-ApplicationStartingEvent-事件" class="headerlink" title="4.2.2. 发布 ApplicationStartingEvent 事件"></a>4.2.2. 发布 ApplicationStartingEvent 事件</h3><p>  ○ <span style="background-color:#ff00ff">获取所有 RunListener（运行监听器）</span><span style="background-color:#00ff00">【为了方便所有 Listener 进行事件感知】</span><br>    ■ getSpringFactoriesInstances 去 spring.factories 找 SpringApplicationRunListener.<br>  ○ <span style="background-color:#ff00ff">遍历 SpringApplicationRunListener 调用 starting 方法</span>；<br>    ■ 相当于通知所有感兴趣系统正在启动过程的人，项目正在 starting。</p><h3 id="4-2-3-读取环境配置信息发布-environmentPreparedEvent"><a href="#4-2-3-读取环境配置信息发布-environmentPreparedEvent" class="headerlink" title="4.2.3. 读取环境配置信息发布 environmentPreparedEvent"></a>4.2.3. 读取环境配置信息发布 environmentPreparedEvent</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224180423.png" alt="image.png"></p><p>  ○ <span style="background-color:#ff00ff">保存命令行参数</span>；ApplicationArguments<br>  ○ 准备环境 prepareEnvironment（）;<br>    ■ 返回或者创建基础环境信息对象。StandardServletEnvironment<br>    ■ 配置环境信息对象。<br><span style="background-color:#ff00ff"> ● 读取所有的配置源的配置属性值：<br><code>ConfigFileApplicationListener</code> 会监听 <code>onApplicationEnvironmentPreparedEvent</code> 事件来加载配置文件 <code>application.properties</code> 的环境变量</span><br>    ■ 绑定环境信息<br>    ■ 监听器调用 listener.environmentPrepared()；<span style="background-color:#ff00ff">通知所有的监听器当前环境准备完成</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227132255.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227124928.png" alt="image.png"></p><h3 id="4-2-4-根据项目类型创建-IOC-容器⭐️🔴"><a href="#4-2-4-根据项目类型创建-IOC-容器⭐️🔴" class="headerlink" title="4.2.4. 根据项目类型创建 IOC 容器⭐️🔴"></a>4.2.4. 根据项目类型创建 IOC 容器⭐️🔴</h3><p>  ○ <span style="background-color:#ff00ff">实例化 Spring 上下文（createApplicationContext（））</span><br>    ■ 根据项目类型（Servlet）创建相应的 WEB 容器<br>    ■ 当前会创建 <code>AnnotationConfigServletWebServerApplicationContext</code></p><h3 id="4-2-5-准备-IOC-容器基本信息-刷新前准备-contextPrepared-loaded"><a href="#4-2-5-准备-IOC-容器基本信息-刷新前准备-contextPrepared-loaded" class="headerlink" title="4.2.5. 准备 IOC 容器基本信息 (刷新前准备 -contextPrepared-loaded)"></a>4.2.5. 准备 IOC 容器基本信息 (刷新前准备 -contextPrepared-loaded)</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224180542.png" alt="image.png"></p><p>  ○ 准备 ApplicationContext IOC 容器的基本信息 prepareContext()<br>    ■ 保存环境信息<br>    ■ IOC 容器的后置处理流程。<br>    ■ 应用初始化器；applyInitializers；<br>      ● <span style="background-color:#ff00ff">遍历所有的 ApplicationContextInitializer 。调用 initialize</span>。来对 ioc 容器进行初始化扩展功能<br>      ● <span style="background-color:#ff00ff">遍历所有的 listener 调用 contextPrepared</span>。EventPublishRunListenr；通知所有的监听器 <code>contextPrepared</code><br>      <span style="background-color:#ffff00">● 将启动类作为配置类进行读取 –&gt;将配置注册为 BeanDefinition</span> <span style="background-color:#ff0000">未看到有这块代码</span><br>       ■ 所有的监听器调用 <code>contextLoaded</code>。<span style="background-color:#ff00ff">通知所有的监听器 contextLoaded</span>；</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227132333.png" alt="image.png"></p><h3 id="4-2-6-刷新-IOC-容器⭐️🔴"><a href="#4-2-6-刷新-IOC-容器⭐️🔴" class="headerlink" title="4.2.6. 刷新 IOC 容器⭐️🔴"></a>4.2.6. 刷新 IOC 容器⭐️🔴</h3><p>  ○ <span style="background-color:#ff00ff">刷新 IOC 容器。refreshContext</span><br>    ■ 创建容器中的所有组件（Spring 注解）<br>  ○ 容器刷新完成后工作 afterRefresh 无实现，可扩展</p><p><code>AnnotationConfigServletWebServerApplicationContext.refresh</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224180854.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224180945.png" alt="image.png"></p><h4 id="4-2-6-1-invokeBeanFactoryPostProcessors"><a href="#4-2-6-1-invokeBeanFactoryPostProcessors" class="headerlink" title="4.2.6.1. invokeBeanFactoryPostProcessors"></a>4.2.6.1. invokeBeanFactoryPostProcessors</h4><p><code>invokeBeanFactoryPostProcessor </code>  <span style="background-color:#00ff00">解析@Import</span>:  加载所有的自动配置类<br>❕<span style="display:none">%%<br>▶1.🏡⭐️◼️SpringBoot 启动时配置类解析的时机 ?🔜MSTM📝 跟 Spring 是相同的，都是在第 5 步 invokeBeanFactoryPostProcessors。而且在 onrefresh 方法中创建了 Servlet 容器◼️⭐️-point-20230224-1821%%</span></p><h4 id="4-2-6-2-onrefresh-创建子容器"><a href="#4-2-6-2-onrefresh-创建子容器" class="headerlink" title="4.2.6.2. onrefresh 创建子容器"></a>4.2.6.2. onrefresh 创建子容器</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224173708.png" alt="image.png"><br><code>onRefresh</code>  <span style="background-color:#00ff00">创建 (内置)servlet 容器</span></p><h3 id="4-2-7-记录启动结束时间并输出"><a href="#4-2-7-记录启动结束时间并输出" class="headerlink" title="4.2.7. 记录启动结束时间并输出"></a>4.2.7. 记录启动结束时间并输出</h3><h3 id="4-2-8-发布-ApplicationStartedEvent"><a href="#4-2-8-发布-ApplicationStartedEvent" class="headerlink" title="4.2.8. 发布 ApplicationStartedEvent"></a>4.2.8. 发布 ApplicationStartedEvent</h3><p>  ○ 所有监听器调用 listeners.started(context); <span style="background-color:#ff00ff">通知所有的监听器 started</span></p><h3 id="4-2-9-调用所有-runners"><a href="#4-2-9-调用所有-runners" class="headerlink" title="4.2.9. 调用所有 runners"></a>4.2.9. 调用所有 runners</h3><p>  ○ 调用所有 runners；callRunners()<br>    ■ 获取容器中的 ApplicationRunner<br>    ■ 获取容器中的 CommandLineRunner<br>    ■ 合并所有 runner 并且按照@Order 进行排序<br>    ■ 遍历所有的 runner。调用 run 方法<br>  ○ 如果以上有异常，<br>    ■ 调用 Listener 的 failed</p><h3 id="4-2-10-如果启动异常则发送-ApplicationFailedEvent"><a href="#4-2-10-如果启动异常则发送-ApplicationFailedEvent" class="headerlink" title="4.2.10. 如果启动异常则发送 ApplicationFailedEvent"></a>4.2.10. 如果启动异常则发送 ApplicationFailedEvent</h3><p>  ○ 调用所有监听器的 running 方法 listeners.running(context); <span style="background-color:#ff00ff">通知所有的监听器 running</span><br>  ○ running 如果有问题。继续通知 failed 。调用所有 Listener 的 failed；<span style="background-color:#ff00ff">通知所有的监听器 failed</span></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/584866-20201221110418589-124191923.jpg" alt="img"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/1032567-20160930171658625-1237959183.png" alt="img"></p><h1 id="5-嵌入式-Servlet-容器启动原理-V2"><a href="#5-嵌入式-Servlet-容器启动原理-V2" class="headerlink" title="5. 嵌入式 Servlet 容器启动原理 V2"></a>5. 嵌入式 Servlet 容器启动原理 V2</h1><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230324-0706%%</span>❕ ^kb445n</p><p>SpringBoot_V1:  <a href="https://www.bilibili.com/video/BV1mf4y1c7cV/?p=79&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1mf4y1c7cV/?p=79&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>SpringBoot_V2:  <a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=169&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=169&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222110234.png" alt="image.png"></p><h2 id="5-1-引入依赖触发自动配置"><a href="#5-1-引入依赖触发自动配置" class="headerlink" title="5.1. 引入依赖触发自动配置"></a>5.1. 引入依赖触发自动配置</h2><h3 id="5-1-1-容器工厂自动配置类⭐️🔴"><a href="#5-1-1-容器工厂自动配置类⭐️🔴" class="headerlink" title="5.1.1. 容器工厂自动配置类⭐️🔴"></a>5.1.1. 容器工厂自动配置类⭐️🔴</h3><p>当引入 <code>spring-boot-starter-web</code> 依赖时会在 SpringBoot 中添加并生效<br>servlet 容器工厂自动配置类： <code>ServletWebServerFactoryAutoConfiguration</code><br>(生效原理： <code>@ConditionalOnWebApplication(type = Type.SERVLET)</code>)<br>V1: EmbeddedServletContainerAutoConfiguration</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227100508.png" alt="image.png"></p><h3 id="5-1-2-Web-容器工厂-默认-Tomcat"><a href="#5-1-2-Web-容器工厂-默认-Tomcat" class="headerlink" title="5.1.2. Web 容器工厂 - 默认 Tomcat"></a>5.1.2. Web 容器工厂 - 默认 Tomcat</h3><p>该自动配置类通过@Import 导入了 (通过@ConditionalOnClass 判断决定使用哪一个) 一个 Web 容器工厂（默认 web-starter 会导入 tomcat 包)，即 <code>TomcatServletWebServerFactory</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230325201425.png" alt="image.png"></p><h3 id="5-1-3-Web-容器工厂定制化器的后置处理器"><a href="#5-1-3-Web-容器工厂定制化器的后置处理器" class="headerlink" title="5.1.3. Web 容器工厂定制化器的后置处理器"></a>5.1.3. Web 容器工厂定制化器的后置处理器</h3><h4 id="5-1-3-1-BeanPostProcessorsRegistrar"><a href="#5-1-3-1-BeanPostProcessorsRegistrar" class="headerlink" title="5.1.3.1. BeanPostProcessorsRegistrar"></a>5.1.3.1. BeanPostProcessorsRegistrar</h4><p>在 <code>ServletWebServerFactoryAutoConfiguration</code> 中还通过 <code>BeanPostProcessorsRegistrar</code> 引入了<span style="background-color:#ff00ff">容器工厂定制化器的后置处理器</span> <code>WebServerFactoryCustomizerBeanPostProcessor</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302090251.png" alt="image.png"></p><p><code>WebServerFactoryCustomizerBeanPostProcessor</code>，会获取所有的定制器，调用每个定制器的 customer 方法，<span style="background-color:#ff00ff">用来给 Servlet 容器进行赋值</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302090135.png" alt="image.png"></p><p>V1: <code>EmbeddedServletContainerCustomizerBeanPostProcessor</code></p><h4 id="5-1-3-2-WebServerFactoryCustomizer-容器工厂定制化器-⭐️🔴"><a href="#5-1-3-2-WebServerFactoryCustomizer-容器工厂定制化器-⭐️🔴" class="headerlink" title="5.1.3.2. WebServerFactoryCustomizer(容器工厂定制化器)⭐️🔴"></a>5.1.3.2. WebServerFactoryCustomizer(容器工厂定制化器)⭐️🔴</h4><p>我们在自定义容器配置时，也可以使用这个 <code>WebServerFactoryCustomizer</code><br>示例代码</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">import</span> org.springframework.boot.web.server.WebServerFactoryCustomizer;<br><span class="hljs-keyword">import</span> org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;<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_">CustomizationBean</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">WebServerFactoryCustomizer</span>&lt;ConfigurableServletWebServerFactory&gt; &#123;<br><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_">customize</span><span class="hljs-params">(ConfigurableServletWebServerFactory server)</span> &#123;<br>        server.setPort(<span class="hljs-number">9000</span>);<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="5-2-SpringBoot-应用执行-Run-方法"><a href="#5-2-SpringBoot-应用执行-Run-方法" class="headerlink" title="5.2. SpringBoot 应用执行 Run 方法"></a>5.2. SpringBoot 应用执行 Run 方法</h2><p><code>SpringApplication.run(Boot05WebAdminApplication.class, args);</code></p><h3 id="5-2-1-创建-IOC-容器"><a href="#5-2-1-创建-IOC-容器" class="headerlink" title="5.2.1. 创建 IOC 容器"></a>5.2.1. 创建 IOC 容器</h3><p>【创建 IOC 容器对象，并初始化容器，创建容器的每一个组件】；如果是 web 环境则创建 AnnotationConfig&#x3D;&#x3D;ServletWebServer&#x3D;&#x3D;ApplicationContext。如果不是 web 环境则创建 AnnotationConfigApplicationContext</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224184939.png" alt="image.png"></p><p>SpringBoot_V2:<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224190131.png" alt="image.png"></p><p>SpringBoot_V1:<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222190659.png" alt="image.png"></p><h3 id="5-2-2-调用父类-refresh"><a href="#5-2-2-调用父类-refresh" class="headerlink" title="5.2.2. 调用父类 refresh()"></a>5.2.2. 调用父类 refresh()</h3><p>刷新创建好的 IOC 容器<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222191503.png" alt="image.png"></p><h3 id="5-2-3-执行重写的-onRefresh-方法"><a href="#5-2-3-执行重写的-onRefresh-方法" class="headerlink" title="5.2.3. 执行重写的 onRefresh() 方法"></a>5.2.3. 执行重写的 onRefresh() 方法</h3><p>刷新 12 步中的第 9 步：web 的 IOC 容器 <code>ServletWebServerApplicationContext</code> <span style="background-color:#ff00ff">重写了 onRefresh 方法</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227090720.png" alt="image.png"><br>❕<span style="display:none">%%<br>▶1.🏡⭐️◼️新技能 get ?🔜MSTM📝 前面的图标都点点看看◼️⭐️-point-20230227-0907%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222191532.png" alt="image.png"></p><h3 id="5-2-4-从容器中获取-TomcatServletWebServerFactory"><a href="#5-2-4-从容器中获取-TomcatServletWebServerFactory" class="headerlink" title="5.2.4. 从容器中获取 TomcatServletWebServerFactory"></a>5.2.4. 从容器中获取 TomcatServletWebServerFactory</h3><p>V1：EmbeddedServletContainerFactory<br>因为前面有@Bean 注解注入，所以可以从 IOC 容器中获取嵌入式的 Servlet 容器工厂 TomcatServletWebServerFactory，然后就可以用来创建 Servlet 容器<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302090820.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302091509.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302091355.png" alt="image.png"></p><h3 id="5-2-5-后置处理器执行配置定制"><a href="#5-2-5-后置处理器执行配置定制" class="headerlink" title="5.2.5. 后置处理器执行配置定制"></a>5.2.5. 后置处理器执行配置定制</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302095054.png" alt="image.png"></p><p>当 TomcatServletWebServerFactory 创建 Servlet 容器对象时，后置处理器看这个对象，就来获取所有的定制器来定制 Servlet 容器的相关配置；</p><h3 id="5-2-6-创建-Servlet-容器并启动"><a href="#5-2-6-创建-Servlet-容器并启动" class="headerlink" title="5.2.6. 创建 Servlet 容器并启动"></a>5.2.6. 创建 Servlet 容器并启动</h3><p>通过 <code>getWebServer</code> 创建嵌入式的 Servlet 容器；<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302091847.png" alt="image.png"></p><h3 id="5-2-7-顺序总结"><a href="#5-2-7-顺序总结" class="headerlink" title="5.2.7. 顺序总结"></a>5.2.7. 顺序总结</h3><p>IOC 容器启动时创建 Servlet 容器，然后先启动嵌入式的 Servlet 容器，最后再将 IOC 容器中剩下的没有创建出的对象创建出来</p><h2 id="5-3-概述"><a href="#5-3-概述" class="headerlink" title="5.3. 概述"></a>5.3. 概述</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224190813.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224190858.png" alt="image.png"><br>❕<span style="display:none">%%<br>▶2.🏡⭐️◼️SpringBoot 中 SpringMVC 与 Spring 整合的 SpringMVC 区别 ?🔜MSTM📝 一般的 SpringMVC 是需要启动子容器的，而 SpringBoot 中只有 1 个容器，SpringMVC 是通过注入自动配置类生成的◼️⭐️-point-20230224-1911%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227093856.png" alt="image.png"></p><h1 id="6-SpringBoot-外置-Tomcat-启动原理⭐️🔴"><a href="#6-SpringBoot-外置-Tomcat-启动原理⭐️🔴" class="headerlink" title="6. SpringBoot 外置 Tomcat 启动原理⭐️🔴"></a>6. SpringBoot 外置 Tomcat 启动原理⭐️🔴</h1><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230324-0704%%</span>❕ ^er3z4j</p><p><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=51&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=51&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="6-1-操作步骤⭐️🔴"><a href="#6-1-操作步骤⭐️🔴" class="headerlink" title="6.1. 操作步骤⭐️🔴"></a>6.1. 操作步骤⭐️🔴</h2><p>1、必须创建一个 war 项目；<br>2、将嵌入式的 Tomcat 指定为 <code>provided</code></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></pre></td><td class="code"><pre><code class="hljs xml"><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-tomcat<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>provided<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></code></pre></td></tr></table></figure><p>3、必须编写一个 <code>SpringBootServletInitializer</code> 的子类，并重写 configure 方法里面的写法，遵照固定写法，将 SpringBoot 的启动类放入 <code>application.sources </code> 的方法参数中。 ❕<span style="display:none">%%<br>▶13.🏡⭐️◼️使用外置的 Tomcat 启动 SpringBoot 项目的方法 ?🔜MSTM📝 需要编写一个 SpringBootServletInitializer 的子类，并将 SpringBoot 启动类放入到重写的 Configurer 方法中 application.sources 的入参中。◼️⭐️-point-20230302-1656%%</span></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></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_">ExternalServletInitializer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">SpringBootServletInitializer</span> &#123;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> SpringApplicationBuilder <span class="hljs-title function_">configure</span><span class="hljs-params">(SpringApplicationBuilder application)</span> &#123;<br>        <span class="hljs-comment">//传入SpringBoot的主程序，</span><br>        <span class="hljs-keyword">return</span> application.sources(SpringBoot04WebJspApplication.class);<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>4、启动服务器就可以；</p><h2 id="6-2-底层原理⭐️🔴"><a href="#6-2-底层原理⭐️🔴" class="headerlink" title="6.2. 底层原理⭐️🔴"></a>6.2. 底层原理⭐️🔴</h2><p>利用了 servlet3.0 规范官方文档：  当 servlet 容器启动时候就会去所有 jar 包中的 <code>META-INF/services</code>  文件夹中找到 <code>javax.servlet.ServletContainerInitializer</code>，有的 jar 包中存在这个文件并在里面会绑定一个 <code>ServletContainerInitializer</code>.   当 servlet 容器启动时候就会去该文件中找到 ServletContainerInitializer 的实现类，从而 <code>创建它的实例并调用其 onstartUp 方法</code><br>❕<span style="display:none">%%<br>▶14.🏡⭐️◼️标准的 SPI 机制：Tomcat 启动后会遍历查看所有 jar 包中 META-INF&#x2F;services 目录下的 javax.servlet.ServletContainerInitializer 文件中配置的全限定名为 <code>org.springframework.web.SpringServletContainerInitializer</code>，new Instance 并放入感兴趣的类集合中，遍历调用他们的 onstartup 方法；伪 SPI 机制：SpringBoot 的自动配置类的加载方式，启动时查找 META-INF&#x2F;spring.factories 文件中 enableAutoConfiguration&#x3D;xxx,\，根据每个类上@ConditionalOn 等条件判断，将 xxx 加载并创建 Bean◼️⭐️-point-20230302-1827%%</span></p><h2 id="6-3-执行流程"><a href="#6-3-执行流程" class="headerlink" title="6.3. 执行流程"></a>6.3. 执行流程</h2><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1054%%</span>❕ ^7d9w7<br>1、启动 Tomcat 服务器<br>2、spring web 模块里有这个文件</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224210917.png" alt="image.png"></p><p>文件内容是： <code>org.springframework.web.SpringServletContainerInitializer</code><br>3、SpringServletContainerInitializer 将 handlerTypes 标注的所有类型的类传入到 onStartip 方法的 <code>Set&lt;Class&lt;?&gt;&gt;</code> 中，并为这些感兴趣类创建实例<br>4、每个创建好的 WebApplicationInitializer 调用自己的 onStratup<br>5、因为有下图所示的继承关系，我们编写的 <code>ExternalServletInitializer</code> 和 <code>SpringBootServletInitializer</code> 都会被创建实例，并执行各自的 <code>StartUp</code> 方法，然而 ExternalServletInitializer 没有该方法，所以会调用其父类 SpringBootServletInitializer 的 onstartUp 方法。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224204100.png" alt="image.png"></p><p>6、<code>SpringBootServletInitializer</code> 执行 onStartup 方法会执行 createRootApplicationContext，在调用其中的 <code>Configure</code> 时，由于其子类 <code>ExternalServletInitializer</code> 重写了这个方法，所以会调用子类重写的这个方法，那么就会将 SpringBoot 的主程序类传入进来。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224210646.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224204134.png" alt="image.png"></p><p>7、build SpringApplication 并启动；</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230325223837.png" alt="image.png"></p><h2 id="6-4-其他"><a href="#6-4-其他" class="headerlink" title="6.4. 其他"></a>6.4. 其他</h2><p><a href="https://www.bilibili.com/video/BV1gW411W76m?p=51&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1gW411W76m?p=51&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>嵌入式的 Servlet 容器：应用打成 jar 包<br>​优点：简单、便携<br>​缺点：默认不支持 JSP、优化定制比较复杂（使用定制器【ServerProperties、自定义定制器】，自己来编写嵌入式的容器工厂）</p><p>外置的 Servlet 容器：外面安装 Tomcat 是以 war 包的方式打包。<br><a href="https://www.bilibili.com/video/BV1gW411W76m?p=52&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1gW411W76m?p=52&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="7-SpringBoot-的核心注解"><a href="#7-SpringBoot-的核心注解" class="headerlink" title="7. SpringBoot 的核心注解"></a>7. SpringBoot 的核心注解</h1><p>1. @SpringBootApplication 注解：这个注解标识了一个 SpringBoot 工程，它实际上是另外三个注解的组合，这三个注解是：<br>        ① @SpringBootConfiguration ：这个注解实际就是一个@Configuration，表示启动类也是一个配置类<br>        ② @EnableAutoConfiguration：向 Spring 容器中导入了一个 Selector，用来加载 ClassPath 下 SpringFactories 中所定义的自动配置类，将这些自动加载为配置 Bean  <br>        ③ @ComponentScan，但这个注解是 Spring 提供的<br>2.  @Conditional 也很关键，如果没有它我们无法在自定义应用中进行定制开发<br>@ConditionalOnBean、 <br>@ConditionalOnClass、<br>@ConditionalOnExpression、<br>@ConditionalOnMissingBean 等。</p><h1 id="8-为什么-SpringBoot-的-jar-可以直接运行⭐️🔴"><a href="#8-为什么-SpringBoot-的-jar-可以直接运行⭐️🔴" class="headerlink" title="8. 为什么 SpringBoot 的 jar 可以直接运行⭐️🔴"></a>8. 为什么 SpringBoot 的 jar 可以直接运行⭐️🔴</h1><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【fatjar, spring-boot-loader.jar, MANIFEST-MF, mainclass, jarlauncher, startclass】◼️⭐️-point-20230303-1616%%</span> ^lf3ecs</p><h2 id="8-1-spring-boot-maven-plugin-打-Fat-jar"><a href="#8-1-spring-boot-maven-plugin-打-Fat-jar" class="headerlink" title="8.1. spring-boot-maven-plugin 打 Fat jar"></a>8.1. spring-boot-maven-plugin 打 Fat jar</h2><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-0847%%</span>❕ ^s0yjgz</p><ol><li><p>SpringBoot 提供了一个插件 <code>spring-boot-maven-plugin</code> 用于把程序打包成一个可执行的 jar 包。</p></li><li><p>SpringBoot 应用打包之后，生成一个 Fat jar(jar 包中包含 jar)，包含了<span style="background-color:#ff00ff">应用依赖的 jar 包和 <code>Spring Boot loader</code> 相关的类</span>。❕<span style="display:none">%%<br>▶10.🏡⭐️◼️与自定义 starter 里的类 spring-boot-starter 类对比记忆◼️⭐️-point-20230302-1516%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224133452.png" alt="image.png"></p></li></ol><p><span style="background-color:#ffff00">如果我们要看 spring-boot-loader 源码，那么就需要自己引入依赖</span><br>   <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224133402.png" alt="image.png"></p><h2 id="8-2-MANIFEST-MF-文件"><a href="#8-2-MANIFEST-MF-文件" class="headerlink" title="8.2. MANIFEST.MF 文件"></a>8.2. MANIFEST.MF 文件</h2><ol start="3"><li>java -jar 会去找 jar 中的 <code>MANIFEST.MF</code> 文件，在那里面找到真正的启动类（<code>Main-Class</code>），Fat jar 的启动 Main 函数是 <code>JarLauncher</code>，在 <code>spring-boot-loader.jar</code> 中</li></ol><p>[[JVM源码分析之JVM启动流程_猿灯塔_InfoQ写作社区#^qwkqfc]]</p><p> <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224123155.png" alt="image.png"></p><p>在 JarLauncher 类中的 main 方法中有 <code>launch</code> 方法，进入之后有 2 个非常重要的方法： <code>createClassLoader</code> 和 <code>launch</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224133833.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224133951.png" alt="image.png"></p><h2 id="8-3-LaunchedURLClassLoader"><a href="#8-3-LaunchedURLClassLoader" class="headerlink" title="8.3. LaunchedURLClassLoader"></a>8.3. LaunchedURLClassLoader</h2><ol start="4"><li><code>createClassLoader</code> 负责创建一 <code>LaunchedURLClassLoader</code> 来<span style="background-color:#ff00ff">加载 BOOT-INF\lib 下面的 jar</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224132056.png" alt="image.png"></li></ol><p> <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224123526.png" alt="image.png"></p><h2 id="8-4-launch"><a href="#8-4-launch" class="headerlink" title="8.4. launch"></a>8.4. launch</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224132303.png" alt="image.png"></p><p><span style="background-color:#ff00ff"><code>launch</code> 负责以一个新线程启动应用的启动类的 Main 函数（找到 manifest 中的 Start-Class）</span></p><h1 id="9-自定义-starter⭐️🔴"><a href="#9-自定义-starter⭐️🔴" class="headerlink" title="9. 自定义 starter⭐️🔴"></a>9. 自定义 starter⭐️🔴</h1><p><span style="display:none">%%<br>▶76.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-1507%%</span>📙❕ ^5tcs5g</p><p><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=70&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=70&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="9-1-使用场景⭐️🔴"><a href="#9-1-使用场景⭐️🔴" class="headerlink" title="9.1. 使用场景⭐️🔴"></a>9.1. 使用场景⭐️🔴</h2><p>在我们的日常开发工作中，经常会有一些独立于业务之外的配置模块，我们经常将其放到一个特定的包下，然后如果另一个工程需要复用这块功能的时候，只需要将其在 pom 中引用依赖即可，利用 SpringBoot 为我们完成自动装配即可。</p><p>常见的自定义 Starter 场景比如:</p><p>动态数据源<br>登录模块<br>基于 AOP 技术实现日志切面<br><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0932%%</span>❕ ^xbsang</p><p><strong>场景一：简化多服务公用框架集成</strong>- 封装 swagger 配置<br>众所周知，springboot 或者其他第三方所提供的 starter，都是<span style="background-color:#00ff00">做框架集成，通过简化配置，提高开发效率</span>，所以我们自定义 starter 的第一个应用场景也是基于这个思路。那我们日常开发工作中，有哪些框架是多个服务共用的，并且 springboot 或者其他第三方暂未提供，或者嫌弃第三方写的太烂，想自己重新实现的，都可以通过编写自定义 starter 来简化工作。我们公司采用微服务架构，每个服务都会使用 swagger 来生成在线接口文档。<span style="background-color:#ffff00">未封装 swagger-starter 之前，那么在每个服务里边，都需要增加 swagger 的配置类</span>。而封装 swagger-starter 之后，可以省去这一步的操作，还可以通过增加自定义配置来实现一些自定义的功能。比如我们公司安全部门要求生产环境不能对外开放 swagger 接口文档地址，那么我们就可以添加一个 enabled 的参数来代表 swagger 是否启用，默认启用，在生产环境的配置中将 enabled 设为 false 即可达到这个目的。类似的额外功能还有很多，比如增加请求头等等，其他的读者自行发掘。<br>上面提到的是业务无关性的 starter 应用场景，那么我们抛出一个问题，是否有业务相关且多个业务场景或者多个服务会使用的应用场景？根据这个问题的描述，我们至少可以列出以下几个业务相关场景。  <a href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-10%E3%80%81%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7(%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4)-Sentinel/" title="服务治理-10、限流熔断降级(服务保护)-Sentinel">服务治理-10、限流熔断降级(服务保护)-Sentinel</a><br><strong>场景二：服务间调用的鉴权。</strong><br>我们<span style="background-color:#ff00ff">公司内部服务之间互相调用需要进行鉴权</span>（还是安全部门的要求），由于服务间是通过 feign 来实现相互调用，所以无法通过网关来进行统一鉴权。实现方案是通过新增 feign 拦截器，&#x3D;&#x3D;在源头服务发起调用之前增加鉴权参数，请求到达目标服务后通过鉴权参数进行鉴权&#x3D;&#x3D;。这两步操作很明显是每个服务都需要的，那么这种情况下，我们就可以把这两步操作封装成 starter，达到简化开发的目的。同时，我们还可以通过增加配置，实现更细粒度的调用权限控制，比如订单服务只能调用库存服务的查询商品库存接口，而无法调用更新商品库存的接口。<br><strong>场景三：邮件，短信，验证码功能。</strong><br>  这些功能，在某些公司可能会放在 common 包里，但是这样其实会导致 common 包的臃肿，因为并不是所有服务都会使用到。有些公司（还是我们公司）可能对邮件服务器的访问有严格权限控制的，而且权限开通流程比较繁复的，那么会考虑做成服务，部署在已经具有访问权限的主机上，减去重复申请权限工作。如果除去这些限制，那么将这些功能封装成 starter 还是挺不错的，<span style="background-color:#ff00ff">可以避免 common 包的臃肿</span></p><h2 id="9-2-操作步骤"><a href="#9-2-操作步骤" class="headerlink" title="9.2. 操作步骤"></a>9.2. 操作步骤</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225092241.png" alt="image.png"></p><h3 id="9-2-1-确定场景需要使用依赖"><a href="#9-2-1-确定场景需要使用依赖" class="headerlink" title="9.2.1. 确定场景需要使用依赖"></a>9.2.1. 确定场景需要使用依赖</h3><h3 id="9-2-2-使用注解编写自动配置"><a href="#9-2-2-使用注解编写自动配置" class="headerlink" title="9.2.2. 使用注解编写自动配置"></a>9.2.2. 使用注解编写自动配置</h3><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-meta">@Configuration</span> <span class="hljs-comment">//指定这个类是一个配置类</span><br><span class="hljs-meta">@ConditionalOnXXX</span> <span class="hljs-comment">//在指定条件下成立的情况下自动配置类生效</span><br><span class="hljs-meta">@AutoConfigureAfter</span> <span class="hljs-comment">//指定自动配置类的顺序</span><br><span class="hljs-meta">@Bean</span> <span class="hljs-comment">//给容器中添加组件</span><br><br><span class="hljs-meta">@ConfigurationProperties</span> <span class="hljs-comment">//结合相关xxxProperties类来绑定相关的配置</span><br><span class="hljs-meta">@EnableConfigurationProperties</span> <span class="hljs-comment">//让xxxProperties生效加到容器中</span><br><br>自动配置类要能加载<br>将需要启动就加载的自动配置类，配置在META-INF/spring.factories<br># Auto Configure<br>org.springframework.boot.autoconfigure.EnableAutoConfiguration=\<br>org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\<br>org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225100054.png" alt="image.png"></p><h3 id="9-2-3-编写-2-个模块"><a href="#9-2-3-编写-2-个模块" class="headerlink" title="9.2.3. 编写 2 个模块"></a>9.2.3. 编写 2 个模块</h3><p><span style="display:none">%%<br>▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1118%%</span>❕ ^8m431i</p><ol><li><p>启动器空的 jar 只需要做依赖管理导入；比如 <code>xxx-spring-boot-starter</code>，使用方只需要引入这个启动器即可<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230326085427.png" alt="image.png"></p></li><li><p>专门写一个自动配置模块；比如 <code>xxx-spring-boot-starter-autoconfigurer</code>，所有的依赖项都放在这个模块的 <code>META-INF/spring.factories</code> 中，key 为 <code>org.springframework.boot.autoconfigure.EnableAutoConfiguration</code><br>  同时 pom 中需要添加基础依赖 <code>spring-boot-starter</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230326085456.png" alt="image.png"></p></li><li><p>启动器依赖自动配置，别人只需要引入 starter<br>xxx-spring-boot-starter<br>❕<span style="display:none">%%<br>▶1.🏡⭐️◼️如果自定义 starter 中用到了属性信息 ?🔜MSTM📝 可以编写一个属性类，使用注解@ConfigurationProperties，然后使用 setter 方法或者构造导入的方法关联到 Service 类上，在 Configurer 类里直接注入使用◼️⭐️-point-20230226-0812%%</span></p></li></ol><h2 id="9-3-与-common-包对比"><a href="#9-3-与-common-包对比" class="headerlink" title="9.3. 与 common 包对比"></a>9.3. 与 common 包对比</h2><ol><li>common 包不灵活，需要不需要的内容都在一个 common 包里，不需要的内容也随着 common 包引入到工程中</li><li>单个功能增加太多，common 包容易变的臃肿</li></ol><h2 id="9-4-示例代码"><a href="#9-4-示例代码" class="headerlink" title="9.4. 示例代码"></a>9.4. 示例代码</h2><p>[[SpringBoot-V1-入门简介#^u6byiq]]<br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;springboot2&#x2F;boot-09-customer-starter&#x2F;atguigu-hello-spring-boot-starter-autoconfigure&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;atguigu&#x2F;hello&#x2F;auto&#x2F;HelloServiceAutoConfiguration.java]]</p><p>[[SpringBoot-V1-入门简介#8 1 新建一个 starter]]</p><h1 id="10-配置读取原理和优先级规则⭐️🔴"><a href="#10-配置读取原理和优先级规则⭐️🔴" class="headerlink" title="10. 配置读取原理和优先级规则⭐️🔴"></a>10. 配置读取原理和优先级规则⭐️🔴</h1><p><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230303-1851%%</span> ^uxezha</p><h2 id="10-1-读取原理"><a href="#10-1-读取原理" class="headerlink" title="10.1. 读取原理"></a>10.1. 读取原理</h2><p>通过事件监听的方式读取的配置文件：<code>ConfigFileApplicationListener</code><br>优先级从高到低，高优先级的配置覆盖低优先级的配置，所有配置会形成互补配置。<br><code>ConfigFileApplicationListener</code> 会监听 <code>onApplicationEnvironmentPreparedEvent</code> 事件来加载配置文件 <code>application.properties</code> 的环境变量<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225112751.png" alt="image.png"></p><h2 id="10-2-优先级规则"><a href="#10-2-优先级规则" class="headerlink" title="10.2. 优先级规则"></a>10.2. 优先级规则</h2><h3 id="10-2-1-文件优先级"><a href="#10-2-1-文件优先级" class="headerlink" title="10.2.1. 文件优先级"></a>10.2.1. 文件优先级</h3><h4 id="10-2-1-1-外部配置源"><a href="#10-2-1-1-外部配置源" class="headerlink" title="10.2.1.1. 外部配置源"></a>10.2.1.1. 外部配置源</h4><p>常用：properties 文件、YAML 文件、环境变量、命令行参数；</p><h4 id="10-2-1-2-properties-优于-ymal"><a href="#10-2-1-2-properties-优于-ymal" class="headerlink" title="10.2.1.2. properties 优于 ymal"></a>10.2.1.2. properties 优于 ymal</h4><p>正常的情况是先加载 yml，接下来加载 properties 文件。如果相同的配置存在于两个文件中。最后会使用 properties 中的配置。最后读取的优先级最高。</p><p>两个配置文件中的端口号不一样会读取 properties 中的端口号</p><h3 id="10-2-2-查找覆盖优先级"><a href="#10-2-2-查找覆盖优先级" class="headerlink" title="10.2.2. 查找覆盖优先级"></a>10.2.2. 查找覆盖优先级</h3><p>配置文件查找位置：<span style="background-color:#ff00ff">查找覆盖顺序：优先级逐级变高</span></p><pre><code>        1. classpath 根路径        2. classpath 根路径下 config 目录        3. jar 包当前目录        4. jar 包当前目录的 config 目录        5. /config 子目录的直接子目录</code></pre><h3 id="10-2-3-加载生效优先级"><a href="#10-2-3-加载生效优先级" class="headerlink" title="10.2.3. 加载生效优先级"></a>10.2.3. 加载生效优先级</h3><p>配置文件加载顺序：<span style="background-color:#ff00ff">加载顺序：外部&gt;内部、加 profile&gt;不加 profile</span></p><pre><code>        1.  当前 jar 包内部的 application.properties 和 application.yml    　2.  当前 jar 包内部的 application-&#123;profile&#125;.properties 和 application-&#123;profile&#125;.yml    　3. 引用的外部 jar 包的 application.properties 和 application.yml    　4. 引用的外部 jar 包的 application-&#123;profile&#125;.properties 和 application-&#123;profile&#125;.yml</code></pre><h3 id="10-2-4-官方文档"><a href="#10-2-4-官方文档" class="headerlink" title="10.2.4. 官方文档"></a>10.2.4. 官方文档</h3><p><a href="https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config">https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225114206.png" alt="image.png"></p><p><span style="background-color:#ff00ff">指定环境优先，外部优先，后面的可以覆盖前面的同名配置项</span></p><h3 id="10-2-5-案例"><a href="#10-2-5-案例" class="headerlink" title="10.2.5. 案例"></a>10.2.5. 案例</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225112420.png" alt="image.png"></p><h1 id="11-SpringBoot-的默认日志实现框架及切换"><a href="#11-SpringBoot-的默认日志实现框架及切换" class="headerlink" title="11. SpringBoot 的默认日志实现框架及切换"></a>11. SpringBoot 的默认日志实现框架及切换</h1><p><a href="https://www.bilibili.com/video/BV1mf4y1c7cV?p=82&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1mf4y1c7cV?p=82&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=23&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=23&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230225115626.png" alt="image.png"></p><h2 id="11-1-SpringBoot-日志框架"><a href="#11-1-SpringBoot-日志框架" class="headerlink" title="11.1. SpringBoot 日志框架"></a>11.1. SpringBoot 日志框架</h2><p>1. SpringBoot 底层使用 slf4j+logback 的方式进行日志记录<br>            logback 桥接：logback-classic<br>2. SpringBoot 同时也把其他的日志都替换成了 slf4j；<br>            a. log4j2 适配： log4j-over-slf4j   (默认提供了适配器，如果切换 log4j2 只需要更换场景启动器即可)<br>            b. jul 适配：jul-to-slf4j <br>            c. jcl 适配：jcl-over-slf4j<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230326121315.png"></p><h2 id="11-2-将-logback-切换成-log4j2"><a href="#11-2-将-logback-切换成-log4j2" class="headerlink" title="11.2. 将 logback 切换成 log4j2"></a>11.2. 将 logback 切换成 log4j2</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230326130656.png" alt="image.png"></p><p>1. 将 logback 的场景启动器排除（slf4j 只能运行有 1 个桥接器）<br>2. 添加 log4j2 的场景启动器<br>3. 添加 log4j2 的配置文件</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></pre></td><td class="code"><pre><code class="hljs xml"><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">exclusions</span>&gt;</span><br>        <span class="hljs-comment">&lt;!-- 排除掉logback的依赖--&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">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-logging<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</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><br><span class="hljs-comment">&lt;!-- 添加log4j2依赖--&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-log4j2<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></code></pre></td></tr></table></figure><h2 id="11-3-将-logback-切换成-log4j"><a href="#11-3-将-logback-切换成-log4j" class="headerlink" title="11.3. 将 logback 切换成 log4j"></a>11.3. 将 logback 切换成 log4j</h2><p>1. 要将 logback 的桥接器排除</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></pre></td><td class="code"><pre><code class="hljs xml"><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">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>logback-classic<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>ch.qos.logback<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></code></pre></td></tr></table></figure><p>2. 添加 log4j 的桥接器</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></pre></td><td class="code"><pre><code class="hljs xml"><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.slf4j<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>slf4j‐log4j12<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></code></pre></td></tr></table></figure><p>3. 添加 log4j 的配置文件</p><h1 id="12-容器"><a href="#12-容器" class="headerlink" title="12. 容器"></a>12. 容器</h1><p> 在 Web 环境中是由 Spring 和 SpringMvc 两个容器组成的，在 SpringBoot 环境中只有一个容器 AnnotationConfigEmbeddedWebApplicationContext。也就是可以说是由 SpringBoot 容器管理的。</p><h2 id="12-1-配置嵌入式-Servlet-容器"><a href="#12-1-配置嵌入式-Servlet-容器" class="headerlink" title="12.1. 配置嵌入式 Servlet 容器"></a>12.1. 配置嵌入式 Servlet 容器</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222080746.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222080710.png" alt="image.png"></p><h2 id="12-2-嵌入式-Servlet-容器自动配置原理-v1⭐️🔴"><a href="#12-2-嵌入式-Servlet-容器自动配置原理-v1⭐️🔴" class="headerlink" title="12.2. 嵌入式 Servlet 容器自动配置原理 v1⭐️🔴"></a>12.2. 嵌入式 Servlet 容器自动配置原理 v1⭐️🔴</h2><p><span style="display:none">%%<br>▶8.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230302-1057%%</span><br><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=48&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=48&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230223220427.png" alt="image.png"> ^17d8bq</p><h3 id="12-2-1-引入依赖触发自动配置"><a href="#12-2-1-引入依赖触发自动配置" class="headerlink" title="12.2.1. 引入依赖触发自动配置"></a>12.2.1. 引入依赖触发自动配置</h3><p> 当加入 <code>Spring-boot-starter-web</code> 场景启动器依赖时，<code>@ConditionalOnWebApplication</code> 注解会触发 EmbeddedServletContainerAutoConfiguration 自动配置类开启嵌入式 Servlet 容器配置</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">@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)</span><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ConditionalOnWebApplication</span><br><span class="hljs-meta">@Import(BeanPostProcessorsRegistrar.class)</span><br><span class="hljs-comment">//给容器导入组件 后置处理器 在Bean初始化前后执行前置后置的逻辑 创建完对象还没属性赋值进行初始化工作</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">EmbeddedServletContainerAutoConfiguration</span> &#123;<br>    <span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ConditionalOnClass(&#123; Servlet.class, Tomcat.class &#125;)</span><span class="hljs-comment">//当前是否引入tomcat依赖</span><br>    <span class="hljs-comment">//判断当前容器没有用户自定义EmbeddedServletContainerFactory，就会创建默认的嵌入式容器</span><br><span class="hljs-meta">@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">EmbeddedTomcat</span> &#123;<br><br><span class="hljs-meta">@Bean</span><br><span class="hljs-keyword">public</span> TomcatEmbeddedServletContainerFactory <span class="hljs-title function_">tomcatEmbeddedServletContainerFactory</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_">TomcatEmbeddedServletContainerFactory</span>();<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="12-2-2-创建默认的嵌入式容器-EmbeddedServletContainerFactory"><a href="#12-2-2-创建默认的嵌入式容器-EmbeddedServletContainerFactory" class="headerlink" title="12.2.2. 创建默认的嵌入式容器 -EmbeddedServletContainerFactory"></a>12.2.2. 创建默认的嵌入式容器 -EmbeddedServletContainerFactory</h3><p>如果当前容器没有用户自定义 EmbeddedServletContainerFactory，就会创建一个默认的 TomcatEmbeddedServletContainerFactory，用来创建嵌入式容器，默认为 Tomcat，也支持其他嵌入式容器，3 种工厂分别可以创建 3 种 EmbeddedServletContainer，继承关系如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222085416.jpg" alt="24.EmdServletFactory"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222085423.jpg" alt="25.EmdServletContainer"></p><p>以 TomcatEmbeddedServletContainerFactory 为例，<span style="background-color:#00ff00">new 了一个 Tomcat 出来</span>并配置，然后传入 <code>getTomcatEmbeddedServletContainer</code> 方法，在该方法中会启动 Tomcat。SpringBoot2 中该方法名字为 getWebServer。</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-meta">@Override</span><br><span class="hljs-keyword">public</span> EmbeddedServletContainer <span class="hljs-title function_">getEmbeddedServletContainer</span><span class="hljs-params">(</span><br><span class="hljs-params">      ServletContextInitializer... initializers)</span> &#123;<br>   <span class="hljs-type">Tomcat</span> <span class="hljs-variable">tomcat</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Tomcat</span>();<br>    <span class="hljs-comment">//配置tomcat的基本环节</span><br>   <span class="hljs-type">File</span> <span class="hljs-variable">baseDir</span> <span class="hljs-operator">=</span> (<span class="hljs-built_in">this</span>.baseDirectory != <span class="hljs-literal">null</span> ? <span class="hljs-built_in">this</span>.baseDirectory<br>         : createTempDir(<span class="hljs-string">&quot;tomcat&quot;</span>));<br>   tomcat.setBaseDir(baseDir.getAbsolutePath());<br>   <span class="hljs-type">Connector</span> <span class="hljs-variable">connector</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Connector</span>(<span class="hljs-built_in">this</span>.protocol);<br>   tomcat.getService().addConnector(connector);<br>   customizeConnector(connector);<br>   tomcat.setConnector(connector);<br>   tomcat.getHost().setAutoDeploy(<span class="hljs-literal">false</span>);<br>   configureEngine(tomcat.getEngine());<br>   <span class="hljs-keyword">for</span> (Connector additionalConnector : <span class="hljs-built_in">this</span>.additionalTomcatConnectors) &#123;<br>      tomcat.getService().addConnector(additionalConnector);<br>   &#125;<br>   prepareContext(tomcat.getHost(), initializers);<br>    <span class="hljs-comment">//将配置好的tomcat传入进去；并且启动tomcat容器</span><br>   <span class="hljs-keyword">return</span> getTomcatEmbeddedServletContainer(tomcat);<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="12-2-3-嵌入式配置自动解析方式-EmbeddedServletContainerCustomizerBeanPostProcessor"><a href="#12-2-3-嵌入式配置自动解析方式-EmbeddedServletContainerCustomizerBeanPostProcessor" class="headerlink" title="12.2.3. 嵌入式配置自动解析方式 -EmbeddedServletContainerCustomizerBeanPostProcessor"></a>12.2.3. 嵌入式配置自动解析方式 -EmbeddedServletContainerCustomizerBeanPostProcessor</h3><p>有 2 种方式：ServerProperties、EmbeddedServletContainerCustomizer</p><p><strong>EmbeddedServletContainerCustomizer 自动应用 Servlet 容器配置原理</strong>：<br>是通过 <strong>EmbeddedServletContainerCustomizerBeanPostProcessor</strong> 来完成的</p><p>容器在配置类 <code>EmbeddedServletContainerAutoConfiguration</code> 中通过 <code>@Import(BeanPostProcessorsRegistrar.class)</code> 导入了 <strong>EmbeddedServletContainerCustomizerBeanPostProcessor</strong>，在 <code>postProcessBeforeInitialization</code> 方法中，判断如果当前初始化的是一个 ConfigurableEmbeddedServletContainer，就会获取所有的定制器，调用每个定制器的 customer 方法给 Servlet 容器进行赋值。</p><p><code>BeanPostProcessorsRegistrar.registerBeanDefinitions</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></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_">registerBeanDefinitions</span><span class="hljs-params">(AnnotationMetadata importingClassMetadata,</span><br><span class="hljs-params">      BeanDefinitionRegistry registry)</span> &#123;<br>   <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.beanFactory == <span class="hljs-literal">null</span>) &#123;<br>      <span class="hljs-keyword">return</span>;<br>   &#125;<br>   registerSyntheticBeanIfMissing(registry,<br>         <span class="hljs-string">&quot;embeddedServletContainerCustomizerBeanPostProcessor&quot;</span>,<br>         EmbeddedServletContainerCustomizerBeanPostProcessor.class);<br>   registerSyntheticBeanIfMissing(registry,<br>         <span class="hljs-string">&quot;errorPageRegistrarBeanPostProcessor&quot;</span>,<br>         ErrorPageRegistrarBeanPostProcessor.class);<br>&#125;<br></code></pre></td></tr></table></figure><p><code>EmbeddedServletContainerCustomizerBeanPostProcessor.postProcessBeforeInitialization</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> Object <span class="hljs-title function_">postProcessBeforeInitialization</span><span class="hljs-params">(Object bean, String beanName)</span><br>      <span class="hljs-keyword">throws</span> BeansException &#123;<br>    <span class="hljs-comment">//如果当前初始化的是一个ConfigurableEmbeddedServletContainer</span><br>   <span class="hljs-keyword">if</span> (bean <span class="hljs-keyword">instanceof</span> ConfigurableEmbeddedServletContainer) &#123;<br>      postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);<br>   &#125;<br>   <span class="hljs-keyword">return</span> bean;<br>&#125;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">postProcessBeforeInitialization</span><span class="hljs-params">(</span><br><span class="hljs-params">    ConfigurableEmbeddedServletContainer bean)</span> &#123;<br>    <span class="hljs-comment">//获取所有的定制器，调用每个定制器的customer方法给Servlet容器进行赋值</span><br>    <span class="hljs-keyword">for</span> (EmbeddedServletContainerCustomizer customizer : getCustomizers()) &#123;<br>        customizer.customize(bean);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">private</span> Collection&lt;EmbeddedServletContainerCustomizer&gt; <span class="hljs-title function_">getCustomizers</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.customizers == <span class="hljs-literal">null</span>) &#123;<br>        <span class="hljs-comment">// Look up does not include the parent context</span><br>        <span class="hljs-built_in">this</span>.customizers = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;EmbeddedServletContainerCustomizer&gt;(<br>            <span class="hljs-built_in">this</span>.beanFactory<br>            <span class="hljs-comment">//从容器中获取所有的这个类型的组件：EmbeddedServletContainerCustomizer</span><br>            <span class="hljs-comment">//定制Servlet,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件</span><br>            .getBeansOfType(EmbeddedServletContainerCustomizer.class,<br>                            <span class="hljs-literal">false</span>, <span class="hljs-literal">false</span>)<br>            .values());<br>        Collections.sort(<span class="hljs-built_in">this</span>.customizers, AnnotationAwareOrderComparator.INSTANCE);<br>        <span class="hljs-built_in">this</span>.customizers = Collections.unmodifiableList(<span class="hljs-built_in">this</span>.customizers);<br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.customizers;<br>&#125;<br></code></pre></td></tr></table></figure><p><span style="background-color:#00ff00">而 ServerProperties 也是 EmbeddedServletContainerCustomizer 实现类，所以也会被自动配置</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230222094902.png" alt="image.png"></p><h3 id="12-2-4-总结"><a href="#12-2-4-总结" class="headerlink" title="12.2.4. 总结"></a>12.2.4. 总结</h3><p>1）、SpringBoot 根据导入的依赖情况，给容器中添加响应的容器工厂比如 tomcat<br>对应的 <code>TomcatEmbeddedServletContainerFactory</code></p><p>2）、容器中某个组件要创建对象就要通过后置处理器；<br><code>EmbeddedServletContainerCustomizerBeanPostProcessor</code><br>只要是嵌入式的 Servlet 容器工厂，后置处理器就工作；</p><p>3）、后置处理器，从容器中获取的所有的 EmbeddedServletContainerCustomizer，调用定制器的定制方法</p><h2 id="12-3-定制化原理"><a href="#12-3-定制化原理" class="headerlink" title="12.3. 定制化原理"></a>12.3. 定制化原理</h2><ol><li>修改配置文件；</li><li>xxxxxCustomizer；</li></ol><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">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyWebServerFactoryCustomizer</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">WebServerFactoryCustomizer</span>&lt;ConfigurableServletWebServerFactory&gt; &#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_">customize</span><span class="hljs-params">(ConfigurableServletWebServerFactory server)</span> &#123;<br>        server.setPort(<span class="hljs-number">9000</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><ol start="3"><li>编写自定义的配置类 xxxConfiguration；+ @Bean 替换、增加容器中默认组件；视图解析器</li><li>Web 应用编写一个配置类实现 <code>WebMvcConfigurer</code> 即可定制化 web 功能；+ <code>@Bean</code> 给容器中再扩展一些组件</li></ol><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">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AdminWebConfig</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">WebMvcConfigurer</span><br></code></pre></td></tr></table></figure><ol start="5"><li>@EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管 SpringMVC，所有规则全部自己重新配置；实现定制和扩展功能<br>  ○ 原理<br>   ○ 1、WebMvcAutoConfiguration 默认的 SpringMVC 的自动配置功能类。静态资源、欢迎页…..<br>   ○ 2、一旦使用 @EnableWebMvc 会@Import(<code>DelegatingWebMvcConfiguration.class</code>)<br>   ○ 3、DelegatingWebMvcConfiguration 的作用，只保证 SpringMVC 最基本的使用<br> ■ 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效<br> ■ 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取<br> ■ public class DelegatingWebMvcConfiguration extends <code>WebMvcConfigurationSupport</code><br>   ○ 4、WebMvcAutoConfiguration 里面的配置要能生效必须  <span style="background-color:#ff00ff">@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)</span><br>   ○ 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。</li></ol><h1 id="13-Java-Config"><a href="#13-Java-Config" class="headerlink" title="13. Java Config"></a>13. Java Config</h1><h2 id="13-1-Configuration-proxyBeanMethods-x3D-false"><a href="#13-1-Configuration-proxyBeanMethods-x3D-false" class="headerlink" title="13.1. @Configuration(proxyBeanMethods &#x3D; false)"></a>13.1. @Configuration(proxyBeanMethods &#x3D; false)</h2><p>lite 模式下，直接返回新实例对象，不用生成代理。</p><p>Spring 5.2.0+ 的版本，建议你的配置类均采用 Lite 模式去做，即显示设置 <code>proxyBeanMethods = false</code>。Spring Boot 在 2.2.0 版本（依赖于 Spring 5.2.0）起就把它的所有的自动配置类的此属性改为了 false，即@Configuration(proxyBeanMethods &#x3D; false)，目的是为了提高 Spring 启动速度</p><p><a href="https://blog.csdn.net/yunxing323/article/details/108655250">https://blog.csdn.net/yunxing323/article/details/108655250</a></p><h2 id="13-2-Import"><a href="#13-2-Import" class="headerlink" title="13.2. @Import"></a>13.2. @Import</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210921204122547.png" alt="image-20210921204122547"></p><h1 id="14-Rest-原理"><a href="#14-Rest-原理" class="headerlink" title="14. Rest 原理"></a>14. Rest 原理</h1><h2 id="14-1-filter-wrapper"><a href="#14-1-filter-wrapper" class="headerlink" title="14.1. filter+wrapper"></a>14.1. filter+wrapper</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210921060612553.png" alt="image-20210921060612553"><br>Rest 原理（表单提交要使用 REST 的时候）<br>● 表单提交会带上 <code>_method=PUT</code><br>● 请求过来被 <code>HiddenHttpMethodFilter</code> 拦截<br>  ○ 请求是否正常，并且是 POST<br>    ■ 获取到 _method 的值。<br>    ■ 兼容以下请求；<code>PUT、DELETE、PATCH</code><br>    ■ 原生 request（post），包装模式 <code>requesWrapper</code> 重写了 getMethod 方法，返回的是传入的值。<br>    ■ 过滤器链放行的时候用 wrapper。以后的方法调用 getMethod 是调用 requesWrapper 的。</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210921060523227.png" alt="image-20210921060523227"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324190828.png" alt="image.png"></p><p>Rest 使用客户端工具，<br>● 如 PostMan 直接发送 Put、delete 等方式请求，无需 Filter，但要注意方式可用取值为 <code>GET、PUT、DELETE、PATCH</code>。</p><h2 id="14-2-自定义隐藏域名称"><a href="#14-2-自定义隐藏域名称" class="headerlink" title="14.2. 自定义隐藏域名称"></a>14.2. 自定义隐藏域名称</h2><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-comment">//自定义filter</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> HiddenHttpMethodFilter <span class="hljs-title function_">hiddenHttpMethodFilter</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-type">HiddenHttpMethodFilter</span> <span class="hljs-variable">methodFilter</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HiddenHttpMethodFilter</span>();<br>        methodFilter.setMethodParam(<span class="hljs-string">&quot;_m&quot;</span>);<br>        <span class="hljs-keyword">return</span> methodFilter;<br>    &#125;<br></code></pre></td></tr></table></figure><h1 id="15-请求映射原理⭐️🔴"><a href="#15-请求映射原理⭐️🔴" class="headerlink" title="15. 请求映射原理⭐️🔴"></a>15. 请求映射原理⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230410-0835%%</span>❕ ^ijjhr0</p><p><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=139&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=139&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>DispatchServlet</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210921061541902.png" alt="image-20210921061541902"></p><h2 id="15-1-请求路径"><a href="#15-1-请求路径" class="headerlink" title="15.1. 请求路径"></a>15.1. 请求路径</h2><p><code>HttpServlet.doGet</code> →  <code>FrameworkServlet.processRequest</code> → <code>FrameworkServlet.doService</code> → <code>DispatcherServlet.doService</code> → <code>DispatcherServlet.doDispatch</code> (每个请求都会调用这个方法)</p><h2 id="15-2-执行逻辑"><a href="#15-2-执行逻辑" class="headerlink" title="15.2. 执行逻辑"></a>15.2. 执行逻辑</h2><h3 id="15-2-1-handlerMappings"><a href="#15-2-1-handlerMappings" class="headerlink" title="15.2.1. handlerMappings"></a>15.2.1. handlerMappings</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211011085954656.png" alt="image-20211011085954656"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211011091433222.png" alt="image-20211011091433222"></p><p>● 请求进来，挨个尝试所有的 HandlerMapping 看是否有请求信息。<br>          ○ 如果有就找到这个请求对应的 handler<br>          ○ 如果没有就是下一个 HandlerMapping</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211011090131565.png" alt="image-20211011090131565"></p><h3 id="15-2-2-RequestMappingHeaderMapping"><a href="#15-2-2-RequestMappingHeaderMapping" class="headerlink" title="15.2.2. RequestMappingHeaderMapping"></a>15.2.2. RequestMappingHeaderMapping</h3><p>保存了所有@RequestMapping 和对应 handler 的所有映射关系</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211011090316527.png" alt="image-20211011090316527"></p><h3 id="15-2-3-获取-handler-逻辑"><a href="#15-2-3-获取-handler-逻辑" class="headerlink" title="15.2.3. 获取 handler 逻辑"></a>15.2.3. 获取 handler 逻辑</h3><h3 id="15-2-4-mappingRegistry"><a href="#15-2-4-mappingRegistry" class="headerlink" title="15.2.4. mappingRegistry"></a>15.2.4. mappingRegistry</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227103610.png" alt="image-20211011090316527"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211011090729432.png" alt="image-20211011090729432"></p><h1 id="16-其他"><a href="#16-其他" class="headerlink" title="16. 其他"></a>16. 其他</h1><h2 id="16-1-jar-包引入方式"><a href="#16-1-jar-包引入方式" class="headerlink" title="16.1. jar 包引入方式"></a>16.1. jar 包引入方式</h2><p><a href="https://cloud.tencent.com/developer/article/1500334">https://cloud.tencent.com/developer/article/1500334</a></p><h3 id="16-1-1-spring-boot-starter-parent"><a href="#16-1-1-spring-boot-starter-parent" class="headerlink" title="16.1.1. spring-boot-starter-parent"></a>16.1.1. spring-boot-starter-parent</h3><h3 id="16-1-2-spring-boot-dependencies"><a href="#16-1-2-spring-boot-dependencies" class="headerlink" title="16.1.2. spring-boot-dependencies"></a>16.1.2. spring-boot-dependencies</h3><p><a href="https://blog.csdn.net/haohao_g/article/details/99695535">https://blog.csdn.net/haohao_g/article/details/99695535</a></p><p><strong>使用场景</strong>：可能有自己的企业标准 parent</p><p>或者你可能只是比较喜欢明确声明所有的 Maven 配置</p><h2 id="16-2-多继承问题"><a href="#16-2-多继承问题" class="headerlink" title="16.2. 多继承问题"></a>16.2. 多继承问题</h2><p><a href="https://www.jianshu.com/p/7145f01ac3ad">https://www.jianshu.com/p/7145f01ac3ad</a></p><h2 id="16-3-dependencyManagement"><a href="#16-3-dependencyManagement" class="headerlink" title="16.3. dependencyManagement"></a>16.3. dependencyManagement</h2><p>dependencyManagement 节点的作用是统一 maven 引入依赖 JAR 包的版本号，可以看出 spring-boot-dependencies 最重要的一个作用就是对 springboot 可能用到的依赖 JAR 包做了版本号的控制管理</p><h1 id="17-与-Spring-的不同之处"><a href="#17-与-Spring-的不同之处" class="headerlink" title="17. 与 Spring 的不同之处"></a>17. 与 Spring 的不同之处</h1><h2 id="17-1-动态代理"><a href="#17-1-动态代理" class="headerlink" title="17.1. 动态代理"></a>17.1. 动态代理</h2><ol><li>Spring 是动态选择的，有接口就用 JDK 动态代理，否则使用 cglib 动态代理<br>可以通过 <code>exproxy-target-class</code><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/" title="1、Spring-基础">1、Spring-基础</a></li><li>SpringBoot 默认是 cglib 动态代理</li></ol><h2 id="17-2-SpringMVC-容器⭐️🔴"><a href="#17-2-SpringMVC-容器⭐️🔴" class="headerlink" title="17.2. SpringMVC 容器⭐️🔴"></a>17.2. SpringMVC 容器⭐️🔴</h2><ol><li>Spring 集成 SpringMVC 是通过 SPI 方式注册子容器来实现集成</li><li>SpringBoot 通过@Bean 注册了 DispatcherServlet，并没有像 Spring 集成 SpringMVC 中那样，通过 SPI 的方式创建子容器</li></ol><h1 id="18-参考与感谢"><a href="#18-参考与感谢" class="headerlink" title="18. 参考与感谢"></a>18. 参考与感谢</h1><h2 id="18-1-SpringBoot"><a href="#18-1-SpringBoot" class="headerlink" title="18.1. SpringBoot"></a>18.1. SpringBoot</h2><h3 id="18-1-1-视频"><a href="#18-1-1-视频" class="headerlink" title="18.1.1. 视频"></a>18.1.1. 视频</h3><p>01、P112–P198 为雷神 2021 版 springboot2 教程 &lt;– 建议直接看新版 02、2021 版直达链接: <a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=112&amp;spm_id_from=333.788.b_636f6d6d656e74.4">https://www.bilibili.com/video/BV1Et411Y7tQ?p=112&amp;spm_id_from=333.788.b_636f6d6d656e74.4</a><br>&#x2F;Volumes&#x2F;Seagate Bas&#x2F;001-ArchitectureRoad&#x2F;尚硅谷 Springboot 经典版（核心技术 and 整合篇）&#x2F;核心技术篇&#x2F;视频 3&#x2F;视频 3 ^yfxc8z</p><h3 id="18-1-2-代码"><a href="#18-1-2-代码" class="headerlink" title="18.1.2. 代码"></a>18.1.2. 代码</h3><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;SpringBoot</p><h3 id="18-1-3-笔记"><a href="#18-1-3-笔记" class="headerlink" title="18.1.3. 笔记"></a>18.1.3. 笔记</h3><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;002- 框架源码专题&#x2F;000-Spring&#x2F;SpringBoot<br>[[SpringBoot]]</p><h2 id="18-2-SpringBoot2"><a href="#18-2-SpringBoot2" class="headerlink" title="18.2. SpringBoot2"></a>18.2. SpringBoot2</h2><h3 id="18-2-1-视频"><a href="#18-2-1-视频" class="headerlink" title="18.2.1. 视频"></a>18.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV19K4y1L7MT?p=58&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV19K4y1L7MT?p=58&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="18-2-2-笔记"><a href="#18-2-2-笔记" class="headerlink" title="18.2.2. 笔记"></a>18.2.2. 笔记</h3><p>语雀： <a href="https://www.yuque.com/atguigu/springboot">https://www.yuque.com/atguigu/springboot</a></p><h3 id="18-2-3-代码"><a href="#18-2-3-代码" class="headerlink" title="18.2.3. 代码"></a>18.2.3. 代码</h3><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;springboot2</p><h2 id="18-3-汇总"><a href="#18-3-汇总" class="headerlink" title="18.3. 汇总"></a>18.3. 汇总</h2><blockquote><p>01、P112–P198 为雷神 2021 版 springboot2 教程 &lt;– 建议直接看新版 02、2021 版直达链接: <a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=112&amp;spm_id_from=333.788.b_636f6d6d656e74.4">https://www.bilibili.com/video/BV1Et411Y7tQ?p=112&amp;spm_id_from=333.788.b_636f6d6d656e74.4</a></p></blockquote><hr><p>2021 版配套笔记及源码：<br>          配套源码 - 雷神码云地址： <a href="https://gitee.com/leifengyang/springboot2">https://gitee.com/leifengyang/springboot2</a><br>      配套笔记 - 语雀地址： <a href="https://yuque.com/atguigu/springboot">https://yuque.com/atguigu/springboot</a></p><hr><p>旧版配套源码、文档等：<br>尚硅谷 springboot 核心篇 + 整合篇配套资料<br>链接: <a href="https://pan.baidu.com/s/1Yfv05ncJoP_gOHB6cm9jdg">https://pan.baidu.com/s/1Yfv05ncJoP_gOHB6cm9jdg</a> 提取码: 9h5i</p><h2 id="18-4-其他"><a href="#18-4-其他" class="headerlink" title="18.4. 其他"></a>18.4. 其他</h2><p><a href="https://www.bilibili.com/video/BV1zh411H79h?p=4">https://www.bilibili.com/video/BV1zh411H79h?p=4</a></p><h2 id="18-5-Spring"><a href="#18-5-Spring" class="headerlink" title="18.5. Spring"></a>18.5. Spring</h2><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/" title="1、Spring-基础">1、Spring-基础</a><h2 id="18-6-黑马⭐️🔴✅"><a href="#18-6-黑马⭐️🔴✅" class="headerlink" title="18.6. 黑马⭐️🔴✅"></a>18.6. 黑马⭐️🔴✅</h2><h3 id="18-6-1-视频"><a href="#18-6-1-视频" class="headerlink" title="18.6.1. 视频"></a>18.6.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV15b4y1a7yG?p=169&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15b4y1a7yG?p=169&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="18-6-2-资料"><a href="#18-6-2-资料" class="headerlink" title="18.6.2. 资料"></a>18.6.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">000</span>-Spring/SpringBoot/原理篇-资料<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>1、Spring-基础</title>
      <link href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/"/>
      <url>/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Spring-基本概念"><a href="#1-Spring-基本概念" class="headerlink" title="1. Spring 基本概念"></a>1. Spring 基本概念</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131132514.png" alt="image.png"></p><p><strong>POJO</strong>: 基于 POJO 的轻量级和最小侵入性编程；<br><strong>DI</strong>: 通过依赖注入和面向接口实现松耦合；<br><strong>AOP</strong>: 基于切面和惯例进行声明式编程；<br><strong>Template</strong>: 通过切面和模板减少样板式代码。</p><blockquote><p>比如我们要写 JDBC 这种牵扯到大量样板式的代码。我们其实只关注我们的 sql 语句（也就是它要实现什么功能），我们可不想看到他是如何连接的。等我们写好核心之后再去用切面进行连接，断开等。</p></blockquote><h2 id="1-1-POJO"><a href="#1-1-POJO" class="headerlink" title="1.1. POJO"></a>1.1. POJO</h2><a href="/2022/11/11/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-0%E3%80%81Java%E7%9B%B8%E5%85%B3%E5%90%8D%E8%AF%8D/" title="基本原理-0、Java相关名词">基本原理-0、Java相关名词</a><p>POJO 的全称是 Plain Old Java Object，简单又老的 Java 对象。这里的简单是相对来讲的。 EJB 2. x 的 Entity Beans 比较重量，需要实现 <code>javax.ejb</code> 的一些接口。而 POJO 就比较轻量，就是一个 Java 对象，不需要实现任何的接口。POJO 专指只有 private 属性以及 setter&#x2F;getter&#x2F;toString 的简单类，包括 DO&#x2F;DTO&#x2F;BO&#x2F;VO 等。</p><p>所以 POJO 本质上也是可以方便沟通的术语。</p><p>有了 POJO 这个名字，相比框架里面各种的对象概念，就容易理解多了，所以这个概念被很广地使用开来。可以用 POJO 来解释 JavaBean 这个惯例 (约定): <span style="background-color:#00ff00">JavaBean 就是可以序列化的 POJO，并且有无参构造器，可以使用 getter&#x2F;setter 来读写属性。</span>❕<span style="display:none">%%<br>0737-🏡⭐️◼️POJO 是一个约定，约定为只有 private 属性以及 getter、setter、toString 方法的简单的类，相对于 EJB 的 Entity Beans 是简单轻量的。也是领域模型中 DO,DTO,VO,BO 的统称。慢慢变成一种术语，可以用来解释 JavaBeans 这个约定，就是序列化且有无参构造的 POJO◼️⭐️-point-202302020737%%</span></p><p>Spring 的非侵入编程模型意味着使用 POJO 这种类在 Spring 应用和非 Spring 应用中都可以发挥同样的作用。</p><h2 id="1-2-约定-惯例"><a href="#1-2-约定-惯例" class="headerlink" title="1.2. 约定 (惯例)"></a>1.2. 约定 (惯例)</h2><p><span style="background-color:#ff00ff">约定优于配置</span><br>convention over configuration</p><h2 id="1-3-依赖注入和面向接口"><a href="#1-3-依赖注入和面向接口" class="headerlink" title="1.3. 依赖注入和面向接口"></a>1.3. 依赖注入和面向接口</h2><p>就是使用聚合 + 构造导入 (或者 setter 导入) 的方式扩展<br>设计模式中大量使用这种方式</p><h2 id="1-4-Spring-的核心"><a href="#1-4-Spring-的核心" class="headerlink" title="1.4. Spring 的核心"></a>1.4. Spring 的核心</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131135415.png" alt="image.png"><br>❕<span style="display:none">%%<br>0709-🏡⭐️◼️Spring 通过 IOC 实现依赖反转，由容器创建管理 Javabean 并维护 Javabean 之间清晰松散的耦合关系，解放程序员的精力放在业务逻辑编码上，同时通过 AOP 实现面向接口编程，可以无侵入的增强业务功能，进一步减少对业务代码的侵入性◼️⭐️-point-202302010709%%</span></p><h2 id="1-5-Spring-的优缺点"><a href="#1-5-Spring-的优缺点" class="headerlink" title="1.5. Spring 的优缺点"></a>1.5. Spring 的优缺点</h2><h3 id="1-5-1-优点"><a href="#1-5-1-优点" class="headerlink" title="1.5.1. 优点"></a>1.5.1. 优点</h3><ol><li><p><strong>方便解耦，简化开发</strong><br>Spring 就是一个大工厂，可以将所有对象的创建和依赖关系的维护，交给 Spring 管理。</p></li><li><p><strong>AOP 编程的支持</strong><br>Spring 提供面向切面编程，可以方便的实现对程序进行权限拦截、运行监控等功能。</p></li><li><p><strong>声明式事务的支持</strong><br>只需要通过配置就可以完成对事务的管理，而无需手动编程。</p></li><li><p><strong>方便程序的测试</strong><br>Spring 对 Junit4 支持，可以通过注解方便的测试 Spring 程序。</p></li><li><p><strong>方便集成各种优秀框架</strong><br>Spring 不排斥各种优秀的开源框架，其内部提供了对各种优秀框架的直接支持（如：Struts、Hibernate、MyBatis 等）。</p></li><li><p><strong>降低 JavaEE API 的使用难度</strong><br>Spring 对 JavaEE 开发中非常难用的一些 API（JDBC、JavaMail、远程调用等），都提供了封装，使这些 API 应用难度大大降低。</p></li><li><p><strong>Java 源码是经典学习范例</strong><br>Spring 的源码设计精妙、结构清晰、匠心独用，处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。Spring 框架源码无疑是 Java 技术的最佳实践范例。</p></li></ol><h3 id="1-5-2-缺点"><a href="#1-5-2-缺点" class="headerlink" title="1.5.2. 缺点"></a>1.5.2. 缺点</h3><ol><li>Spring 明明一个很轻量级的框架，却给人感觉大而全</li><li>Spring 依赖反射，反射影响性能</li><li>使用门槛升高，入门 Spring 需要较长时间</li></ol><h2 id="1-6-Spring-的核心模块"><a href="#1-6-Spring-的核心模块" class="headerlink" title="1.6. Spring 的核心模块"></a>1.6. Spring 的核心模块</h2><p>^ob40ki<br>^hasxle<br>大约 20 几个模块，总共 1300 多个文件，这些组件被分别整合在：核心容器、AOP、Aspect、Instrumentation、Messing、DataAccess、Web、Test 等几大模块中<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131144911.png" alt="image.png"></p><ol><li>spring core：提供了框架的基本组成部分，<span style="background-color:#00ff00">包括控制反转（Inversion of Control，IOC）和依赖注入（Dependency Injection，DI）功能</span>。</li><li>spring beans：提供了 BeanFactory，是<span style="background-color:#00ff00">工厂模式的一个经典实现</span>，Spring 将管理对象称为 Bean。</li><li>spring context：构建于 core 封装包基础上的 context 封装包，提供了一种框架式的对象访问方法。</li><li>spring jdbc：提供了一个 JDBC 的抽象层，消除了烦琐的 JDBC 编码和数据库厂商特有的错误代码解析， 用于简化 JDBC。</li><li>spring aop：提供了面向切面的编程实现，让你可以<span style="background-color:#00ff00">自定义拦截器、切点等</span>。</li><li>spring Web：提供了针对 Web 开发的集成特性，例如文件上传，利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。</li><li>spring test：主要为测试提供支持的，支持使用 JUnit 或 TestNG 对 Spring 组件进行单元测试和集成测试。</li></ol><h1 id="2-IOC-相关"><a href="#2-IOC-相关" class="headerlink" title="2. IOC 相关"></a>2. IOC 相关</h1><h2 id="2-1-什么是-IOC-容器"><a href="#2-1-什么是-IOC-容器" class="headerlink" title="2.1. 什么是 IOC 容器"></a>2.1. 什么是 IOC 容器</h2><p>控制反转即 IOC (Inversion of Control)，它把传统上<span style="background-color:#00ff00">由程序代码</span>直接操控的对象的调用权交给容器，<span style="background-color:#00ff00">通过容器来实现对象组件的装配和管理</span>。所谓的“控制反转”概念就是对组件对象控制权的转移，从程序代码本身转移到了外部容器。<br><span style="background-color:#00ff00">Spring IOC 负责创建对象，管理对象（通过依赖注入（DI）），装配对象，配置对象，并且管理这些对象的整个生命周期。</span></p><h2 id="2-2-IOC-的作用和意义"><a href="#2-2-IOC-的作用和意义" class="headerlink" title="2.2. IOC 的作用和意义"></a>2.2. IOC 的作用和意义</h2><p><strong>作用</strong></p><ol><li>管理对象的创建和依赖关系的维护</li><li>解耦，由容器去维护具体的对象</li><li>托管了类的生成过程，比如我们无需去关心类是如何完成代理的</li></ol><blockquote><p> 控制反转    控制了什么？<br>UserService service&#x3D;new UserService();   &#x2F;&#x2F; 耦合度太高 、维护不方便<br>引入 Ioc   就将创建对象的控制权交给 Spring 的 Ioc.   以前由程序员自己控制对象创建， 现在交给 Spring 的 Ioc 去创建， <br>如果要去使用对象需要通过 DI（依赖注入）@Autowired 自动注入 就可以使用对象 ;</p></blockquote><p><strong>意义</strong><br><span style="background-color:#00ff00">1. IOC 容器以最小的代价和最小的侵入性使松散耦合得以实现。</span><br><span style="background-color:#00ff00">2. IOC 容器支持加载服务时的饿汉式初始化和懒加载。</span><br>❕<span style="display:none">%%<br>▶10.🏡⭐️◼️IOC 容器的意义◼️⭐️-point-20230226-2231%%</span></p><h2 id="2-3-IOC-的实现机制"><a href="#2-3-IOC-的实现机制" class="headerlink" title="2.3. IOC 的实现机制"></a>2.3. IOC 的实现机制</h2><p>工厂模式 + 反射</p><h2 id="2-4-什么是-Spring-的依赖注入-DI-？IOC-和-DI-的区别是什么"><a href="#2-4-什么是-Spring-的依赖注入-DI-？IOC-和-DI-的区别是什么" class="headerlink" title="2.4. 什么是 Spring 的依赖注入 (DI)？IOC 和 DI 的区别是什么"></a>2.4. 什么是 Spring 的依赖注入 (DI)？IOC 和 DI 的区别是什么</h2><p>很多人把 IOC 和 DI 说成一个东西，笼统来说的话是没有问题的，但是本质上还是有所区别的，希望大家能够严谨一点，IOC 和 DI 是从不同的角度描述的同一件事，<span style="background-color:#00ff00">IOC 是从容器的角度描述，而 DI 是从应用程序的角度来描述，也可以这样说，IOC 是依赖倒置原则的设计思想，而 DI 是具体的实现方式</span></p><h2 id="2-5-BeanDefinition-的作用"><a href="#2-5-BeanDefinition-的作用" class="headerlink" title="2.5. BeanDefinition 的作用"></a>2.5. BeanDefinition 的作用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131200612.png" alt="image.png"></p><p>它主要负责存储 Bean 的定义信息，包括 beanClass、scope、lazyInit、dependsOn、autowireMode 等信息，决定 Bean 的生产方式。后续 BeanFactory 根据这些信息就行生产 Bean： 比如实例化 ，通过 class 进行反射进而得到实例对象 ， 比如 lazy  则不会在 ioc 加载时创建 Bean。</p><p>举例：由 BeanDefinition 中 Object 类型的 beanClass 强转为 String，然后通过反射得到 Class 对象<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131201517.png" alt="image.png"></p><h2 id="2-6-BeanFactory-和-ApplicationContext-有什么区别"><a href="#2-6-BeanFactory-和-ApplicationContext-有什么区别" class="headerlink" title="2.6. BeanFactory 和 ApplicationContext 有什么区别"></a>2.6. BeanFactory 和 ApplicationContext 有什么区别</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131120831.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131120859.png" alt="image.png"></p><h3 id="2-6-1-答案-2"><a href="#2-6-1-答案-2" class="headerlink" title="2.6.1. 答案 2"></a>2.6.1. 答案 2</h3><p>BeanFactory 和 ApplicationContext 是 Spring 的两大核心接口，都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。</p><h4 id="2-6-1-1-功能大小"><a href="#2-6-1-1-功能大小" class="headerlink" title="2.6.1.1. 功能大小"></a>2.6.1.1. 功能大小</h4><ol><li>BeanFactory：是 Spring 里面最底层的接口，包含了各种 Bean 的定义，读取 bean 配置文档，管理 bean 的加载、实例化，控制 bean 的生命周期，维护 bean 之间的依赖关系。</li><li>ApplicationContext 接口作为 BeanFactory 的派生，除了提供 BeanFactory 所具有的功能外，<span style="background-color:#00ff00">还提供了更完整的框架功能</span>：<blockquote><p>继承 MessageSource，因此支持国际化。<br>统一的资源文件访问方式。<br>提供在监听器中注册 bean 的事件。<br>同时加载多个配置文件。<br>载入多个（有继承关系）上下文 ，使得每一个上下文都专注于一个特定的层次，比如应用的 web 层。</p></blockquote></li></ol><p>资源文件访问方式： <a href="https://www.cnblogs.com/kongbubihai/p/15915434.html">https://www.cnblogs.com/kongbubihai/p/15915434.html</a></p><h4 id="2-6-1-2-加载方式"><a href="#2-6-1-2-加载方式" class="headerlink" title="2.6.1.2. 加载方式"></a>2.6.1.2. 加载方式</h4><ol><li>BeanFactroy 采用的是延迟加载形式来注入 Bean 的，<span style="background-color:#ff00ff">即只有在使用到某个 Bean 时 (调用 getBean())，才对该 Bean 进行加载实例化</span>。这样，我们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入，BeanFacotry 加载后，直至第一次使用调 getBean 方法才会抛出异常。</li><li>ApplicationContext，它是<span style="background-color:#ff00ff">在容器启动时，一次性创建了所有的 Bean</span>。这样，在容器启动时，我们就可以发现 Spring 中存在的配置错误，这样有利于检查所依赖属性是否注入。ApplicationContext 启动后预载入所有的单实例 Bean，通过预载入单实例 bean ，确保当你需要的时候，你就不用等待，因为它们已经创建好了。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131212230.png" alt="image.png"></p><p>下面的 3 个绿色的，都是功能扩展接口。</p><p>看下面的隶属 ApplicationContext 粉红色的 “高级容器”，依赖着 “低级容器”，这里说的是依赖，不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能：<span style="background-color:#00ff00">支持不同的信息源头，可以访问文件资源，支持应用事件（Observer 模式）</span>。</p><p>通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦！</p><p>左边灰色区域的是 “低级容器”， 只负载加载 Bean，获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置。生命周期事件回调等。</p><h2 id="2-7-BeanFactory-和-FactoryBean-有什么区别"><a href="#2-7-BeanFactory-和-FactoryBean-有什么区别" class="headerlink" title="2.7. BeanFactory 和 FactoryBean 有什么区别"></a>2.7. BeanFactory 和 FactoryBean 有什么区别</h2><ol><li>BeanFactory 是一个工厂，也就是一个容器，是来管理和生产 bean 的；</li><li>Spring 中有两种类型的 bean，一种是普通 bean，另一种是工厂 bean，即 FactoryBean。工厂 bean 跟普通 bean 不同，其返回的对象不是指定类的一个实例，其返回的是该工厂 bean 的 <code>getObject</code> 方法所返回的对象。工厂 bean 必须实现 <code>org.springframework.beans.factory.FactoryBean</code> 接口。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131210539.png" alt="image.png"></p><h2 id="2-8-有哪些不同类型的依赖注入实现方式"><a href="#2-8-有哪些不同类型的依赖注入实现方式" class="headerlink" title="2.8. 有哪些不同类型的依赖注入实现方式"></a>2.8. 有哪些不同类型的依赖注入实现方式</h2><p>依赖注入是时下最流行的 IOC 实现方式，依赖注入分为接口注入（Interface Injection），Setter 方法注入（Setter Injection）和构造器注入（Constructor Injection）三种方式。<span style="background-color:#ff0000">其中接口注入由于在灵活性和易用性比较差，现在从 Spring4 开始已被废弃。</span><br><strong>构造器依赖注入</strong>：构造器依赖注入通过容器触发一个类的构造器来实现的，该类有一系列参数，每个参数代表一个对其他类的依赖。<br><strong>Setter 方法注入</strong>：Setter 方法注入是容器通过调用无参构造器或无参 static 工厂 方法实例化 bean 之后，调用该 bean 的 setter 方法，即实现了基于 setter 的依赖注入。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131214444.png" alt="image.png"></p><p>最佳实践<br>两种依赖方式都可以使用，构造器注入和 Setter 方法注入。<span style="background-color:#ff00ff">最好的解决方案是用构造器参数实现强制依赖，即组合的方式；setter 方法实现可选依赖，即聚合的方式。</span></p><h2 id="2-9-Spring-中配置-注册-Bean-的方式⭐️🔴"><a href="#2-9-Spring-中配置-注册-Bean-的方式⭐️🔴" class="headerlink" title="2.9. Spring 中配置 (注册)Bean 的方式⭐️🔴"></a>2.9. Spring 中配置 (注册)Bean 的方式⭐️🔴</h2><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><h2 id="2-10-BeanDefinition-的加载过程"><a href="#2-10-BeanDefinition-的加载过程" class="headerlink" title="2.10. BeanDefinition 的加载过程"></a>2.10. BeanDefinition 的加载过程</h2><h1 id="3-生命周期相关"><a href="#3-生命周期相关" class="headerlink" title="3. 生命周期相关"></a>3. 生命周期相关</h1><h2 id="3-1-Spring-框架中-bean-的生命周期⭐️🔴⭐️🔴"><a href="#3-1-Spring-框架中-bean-的生命周期⭐️🔴⭐️🔴" class="headerlink" title="3.1. Spring 框架中 bean 的生命周期⭐️🔴⭐️🔴"></a>3.1. Spring 框架中 bean 的生命周期⭐️🔴⭐️🔴</h2><p>^fkjjz0</p><h3 id="3-1-1-BD-相关"><a href="#3-1-1-BD-相关" class="headerlink" title="3.1.1. BD 相关"></a>3.1.1. BD 相关</h3><a href="/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/" title="Spring-8、BeanDefinition">Spring-8、BeanDefinition</a><h3 id="3-1-2-整体流程"><a href="#3-1-2-整体流程" class="headerlink" title="3.1.2. 整体流程"></a>3.1.2. 整体流程</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131224135.png" alt="image.png"></p><ol><li>Spring 对 bean 进行实例化；</li><li>Spring 将值和 bean 的引用注入到 bean 对应的属性中；</li><li>如果 bean 实现了 BeanNameAware 接口，Spring 将 bean 的 ID 传递给 setBean-Name() 方法；</li><li>如果 bean 实现了 BeanFactoryAware 接口，Spring 将调用 setBeanFactory() 方法，BeanFactory 容器实例传入；</li><li>如果 bean 实现了 ApplicationContextAware 接口，Spring 将调用 setApplicationContext() 方法，将 bean 所在的应用上下文的引用传入进来；</li><li>如果 bean 实现了 BeanPostProcessor 接口，Spring 将调用它们的 postProcessBeforeInitialization() 方法；</li><li>如果 bean 实现了 InitializingBean 接口，Spring 将调用它们的 after-PropertiesSet() 方法。类似地，如果 bean 使用 initmethod 声明了初始化方法，该方法也会被调用；</li><li>如果 bean 实现了 BeanPostProcessor 接口，Spring 将调用它们的 post-ProcessAfterInitialization() 方法；</li></ol><p>此时，bean 已经准备就绪，可以被应用程序使用了，它们将一直驻留在应用上下文中，直到该应用上下文被销毁；<br>如果 bean 实现了 DisposableBean 接口，Spring 将调用它的 destroy() 接口方法。同样，如果 bean 使用 destroy-method 声明了销毁方法，该方法也会被调用。</p><h3 id="3-1-3-后置处理器相关⭐️🔴⭐️🔴"><a href="#3-1-3-后置处理器相关⭐️🔴⭐️🔴" class="headerlink" title="3.1.3. 后置处理器相关⭐️🔴⭐️🔴"></a>3.1.3. 后置处理器相关⭐️🔴⭐️🔴</h3><p>^n9drb6<br><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230321-2309%%</span>❕ ^jyw4v5</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230206174637.png" alt="image.png"><br>❕<span style="display:none">%%<br>1056-🏡⭐️◼️记忆方式 ?🔜MSTM📝 AutowiredAnnotationBPP&#x2F;CommonAnnotationBPP&#x3D;MI; AbstractAutoproxyCreator&#x3D;SB◼️⭐️-point-202302081056%%</span></p><ol><li><p>&#x3D;&#x3D;doCreateBean 之前&#x3D;&#x3D;，<code>InstantiationAwareBeanPostProcessor</code> 的 <code>postProcessBeforeInstantiation()</code><br> ①可以提前返回一个代理对象，而终止 bean 的创建给 BeanPostProcessors 一个机会来返回代理来替代真正的实例，应用实例化前的前置处理器，用户自定义动态代理的方式，针对于当前的被代理类需要经过标准的代理流程来创建对象。如果使用该扩展点，可以直接返回代理对象。<br> ② <a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a></p></li><li><p>&#x3D;&#x3D;在实例化之前&#x3D;&#x3D;，即 <code>createBeanInstance</code> 之前还可以利用 <code>SmartInstantiationAwareBeanPostProcessor</code> 的 <code>determineCandidateConstructors</code> 方法来指定构造函数</p><hr></li><li><p>①在<span style="background-color:#00ff00">实例化之后属性赋值之前</span>，在 <code>MergedBeanDefinitionPostProcessor</code> 的子实现接口 <code>AutowiredAnnotationBeanPostProcessor</code> 的 <code>postProcessMergedBeanDefinition</code> 方法中，解析@Autowired @Value 转换为 InjectionMetadata 并缓存在 <code>injectionMetadataCache</code> 中</p></li></ol> <a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-10%E3%80%81@Autowired/" title="Spring-10、@Autowired">Spring-10、@Autowired</a><p>  注意：<span style="background-color:#ff00ff">@Lazy 是 BD 属性，不是 Bean 属性，所以没有预解析的逻辑，只有 BeanDefinition 的解析逻辑，在第 5 大步中，不在 9 大后置处理器中。</span><br>  <a href="/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/" title="Spring-9、@Lazy">Spring-9、@Lazy</a></p><p>②在<span style="background-color:#00ff00">实例化之后属性赋值之前</span>，在 <code>MergedBeanDefinitionPostProcessor</code> 的子实现接口 <code>InitDestroyAnnotationBeanPostProcessor</code> 的子类 <code>CommonAnnotationBeanPostProcessor</code> 的 <code>postProcessMergedBeanDefinition</code> 方法中，处理@PostConstruct 和@PreDestroy 注解，调用父类 <code>InitDestroyAnnotationBeanPostProcessor</code> 的 <code>findLifecycleMetadata</code> 方法构建 lifecycleMetadata 并缓存在 <code>lifecycleMetadataCache</code> 中<br>   <a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-11%E3%80%81@PostConstruct/" title="Spring-11、@PostConstruct">Spring-11、@PostConstruct</a></p><ol start="4"><li><p>在<span style="background-color:#00ff00">实例化之后属性赋值之前</span>，将 <code>SmartInstantiationAwareBeanPostProcessor</code> 的子实现接口 <code>AbstractAutoProxyCreator</code> 的 <code>getEarlyBeanReference</code> 方法作为钩子函数放入三级缓存，待到依赖方真正赋值时，调用 <code>singletonFactory.getObject()</code> 就会返回被依赖方的动态代理对象，从而解决了 AOP 的循环依赖问题 ❕<span style="display:none">%%<br>1925-🏡⭐️◼️AbstractAutoProxyCreator 的作用原理 ?🔜MSTM📝 实例化之后属性赋值之前，将 AbstractAutoProxyCreator 的 getEarlyReferenceBean 方法作为钩子函数放入三级缓存，在依赖方属性填充时会获取到三级缓存并执行 singletonFactory. getObject，届时会调用 getEarlyReferenceBean，如果有 aop 的 BPP，就会返回一个动态代理对象◼️⭐️-point-202302051925%%</span></p><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-2%E3%80%81IOC/" title="Spring-2、IOC">Spring-2、IOC</a></li><li><p>在 <code>applyPropertyValues</code> <span style="background-color:#00ff00">真正赋值之前</span>，与 1 相同的，<code>InstantiationAwareBeanPostProcessor</code> 的 <code>postProcessAfterInstantiation</code>，如果实现改接口重写该方法，可以跳过属性赋值的步骤</p></li><li><p>在 <code>applyPropertyValues</code> <span style="background-color:#00ff00">真正赋值之前</span>，与 3-①相对应的，<code>InstantiationAwareBeanPostProcessor</code> 的子实现接口 <code>AutowiredAnnotationBeanPostProcessor</code> 的 <code>postProcessProperties</code> 方法中完成 bean 中@Autowired，@Inject，@Value 注解的解析并注入<br><a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-10%E3%80%81@Autowired/" title="Spring-10、@Autowired">Spring-10、@Autowired</a></p><a href="/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/" title="Spring-9、@Lazy">Spring-9、@Lazy</a>  @Lazy 的赋值是在这里</li></ol><hr><ol start="7"><li>在 <code>invokeInitMethods</code> <span style="background-color:#ff00ff">初始化之前</span>，与 3-②相对应的，BeanPostProcessor 的子类 <code>InitDestroyAnnotationBeanPostProcessor</code> 会调用 <code>postProcessBeforeInitialization</code> 方法，此时 <code>@PostConstruct</code> 注解的方法会被调用。确切的说应该是在 <code>afterPropertiesSet</code> 之前调用，这与 xml 方式配置的方式不同，后者是在 <code>afterPropertiesSet</code> 之后，执行 <code>invokeInitMethods</code> 之前调用<a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-11%E3%80%81@PostConstruct/" title="Spring-11、@PostConstruct">Spring-11、@PostConstruct</a></li></ol><hr><ol start="8"><li>在<span style="background-color:#ff0000">初始化完成之后</span>，如果有用到，则会调用 <code>BeanPostProcessor</code> 的子实现类 <code>AbstractAutoProxyCreator</code> 的 <code>postProcessAfterInitialization()</code> 方法来生成动态代理</li></ol><p>❕<span style="display:none">%%<br>0720-🏡⭐️◼️解析切面的地方 ?🔜MSTM📝 第一个 PP，InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation 中，解析切面并缓存所有通知到 advisorsCache 中◼️⭐️-point-202302120720%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230303112858.png"></p><h3 id="3-1-4-生命周期回调相关"><a href="#3-1-4-生命周期回调相关" class="headerlink" title="3.1.4. 生命周期回调相关"></a>3.1.4. 生命周期回调相关</h3><h4 id="3-1-4-1-初始化之前执行的生命周期的回调"><a href="#3-1-4-1-初始化之前执行的生命周期的回调" class="headerlink" title="3.1.4.1. 初始化之前执行的生命周期的回调"></a>3.1.4.1. 初始化之前执行的生命周期的回调</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205210842.png" alt="image.png"></p><p><span style="background-color:#ff00ff">真正初始化之前执行的生命周期回调</span>  ❕<span style="display:none">%%<br>2116-🏡⭐️◼️扩展点 7 中的 3 个都在哪里 ?🔜MSTM📝 ApplicationContextAwareBPP、ImportAwareBPP、InitDestoryAnnotationBPP 都是在真正初始方法执行之前执行◼️⭐️-point-202302052116%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201134805.png" alt="image.png"><br>❕<span style="display:none">%%<br>2237-🏡⭐️◼️invokeAwareMethods 方法有个重要点 ?🔜MSTM📝 就是对于实现 BeanFactoryAware 接口的 BPP，会设置 BeanFactory，在这个地方，AOP 设置了重要的属性 advisorRetrievalHelper、aspectJAdvisorsBuilder、aspectJAdvisorFactory，用于在后面实例化之前解析切面缓存通知◼️⭐️-point-20230216-2237%%</span></p><h4 id="3-1-4-2-初始化之后的生命周期的回调"><a href="#3-1-4-2-初始化之后的生命周期的回调" class="headerlink" title="3.1.4.2. 初始化之后的生命周期的回调"></a>3.1.4.2. 初始化之后的生命周期的回调</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201134931.png" alt="image.png"></p><h2 id="3-2-解释不同方式的自动装配，spring-自动装配-bean-有哪些方式？"><a href="#3-2-解释不同方式的自动装配，spring-自动装配-bean-有哪些方式？" class="headerlink" title="3.2. 解释不同方式的自动装配，spring 自动装配 bean 有哪些方式？"></a>3.2. 解释不同方式的自动装配，spring 自动装配 bean 有哪些方式？</h2><h3 id="3-2-1-XML"><a href="#3-2-1-XML" class="headerlink" title="3.2.1. XML"></a>3.2.1. XML</h3><p>在 spring 中，对象无需自己查找或创建与其关联的其他对象，由容器负责把需要相互协作的对象引用赋予各个对象，使用 autowire 来配置自动装载模式。<br>在 Spring 框架 xml 配置中共有 5 种自动装配：</p><ol><li>no：<span style="background-color:#ffff00">默认的方式是不进行自动装配的</span>，通过手工设置 ref 属性来进行装配 bean。</li><li>byName：通过 bean 的名称进行自动装配，如果一个 bean 的 property 与另一 bean 的 name 相同，就进行自动装配。</li><li>byType：通过参数的数据类型进行自动装配。</li><li>constructor：利用构造函数进行装配，并且构造函数的参数通过 byType 进行装配。</li></ol><h3 id="3-2-2-注解"><a href="#3-2-2-注解" class="headerlink" title="3.2.2. 注解"></a>3.2.2. 注解</h3><h4 id="3-2-2-1-Autowired-与-Qualifier"><a href="#3-2-2-1-Autowired-与-Qualifier" class="headerlink" title="3.2.2.1. @Autowired 与@Qualifier"></a>3.2.2.1. @Autowired 与@Qualifier</h4><ol><li>@Autowired 是<span style="background-color:#ff0000">根据类型自动装配</span>的，加上@Qualifier 则可以根据 byName 的方式自动装配。</li><li>@Qualifier 不能单独使用。</li></ol><h4 id="3-2-2-2-Autowired-与-Resource-异同"><a href="#3-2-2-2-Autowired-与-Resource-异同" class="headerlink" title="3.2.2.2. @Autowired 与@Resource 异同"></a>3.2.2.2. @Autowired 与@Resource 异同</h4><ol><li>@Autowired 与@Resource 都可以用来装配 bean。都可以写在字段上，或写在 setter 方法上。</li><li><span style="background-color:#ff00ff">@Autowired 默认按类型装配</span>（属于 Spring 规范），默认情况下必须要求依赖对象必须存在，如果要允许 null 值，可以设置它的 required 属性为 false，如：@Autowired(required&#x3D;false)。<span style="background-color:#ff00ff">如果我们想使用名称装配可以结合@Qualifier 注解进行使用</span></li><li>@Resource（属于 J2EE 复返），<span style="background-color:#ff00ff">默认按照名称进行装配</span>，名称可以通过 name 属性进行指定。如果没有指定 name 属性，当注解写在字段上时，默认取字段名进行按照名称查找，如果注解写在 setter 方法上默认取属性名进行装配。<span style="background-color:#00ff00">当找不到与名称匹配的 bean 时才按照类型进行装配</span>。但是需要注意的是，如果 name 属性一旦指定，就只会按照名称进行装配。</li><li>它们的作用相同都是用注解方式注入对象，但执行顺序不同。<span style="background-color:#ff00ff">@Autowired 先 byType，@Resource 先 byName。</span></li></ol><p>PS: Autowired 根据类型装配的原因：<br>!<a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-10%E3%80%81@Autowired/" title="Spring-10、@Autowired">Spring-10、@Autowired</a></p><p><a href="https://blog.csdn.net/King_weng/article/details/122057561">https://blog.csdn.net/King_weng/article/details/122057561</a></p><h2 id="3-3-哪些是重要的-bean-生命周期方法-能否重载"><a href="#3-3-哪些是重要的-bean-生命周期方法-能否重载" class="headerlink" title="3.3. 哪些是重要的 bean 生命周期方法 能否重载"></a>3.3. 哪些是重要的 bean 生命周期方法 能否重载</h2><p>有两个重要的 bean 生命周期方法，第一个是 setup ， 它是在容器加载 bean 的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。<br>bean 标签有两个重要的属性（init-method 和 destroy-method）。用它们你可以自己定制初始化和注销方法。它们也有相应的注解（@PostConstruct 和@PreDestroy）。</p><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><h2 id="3-4-使用-Autowired-注解自动装配的过程是怎样的⭐️🔴"><a href="#3-4-使用-Autowired-注解自动装配的过程是怎样的⭐️🔴" class="headerlink" title="3.4. 使用@Autowired 注解自动装配的过程是怎样的⭐️🔴"></a>3.4. 使用@Autowired 注解自动装配的过程是怎样的⭐️🔴</h2><p>^ov9bn5</p><a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-10%E3%80%81@Autowired/" title="Spring-10、@Autowired">Spring-10、@Autowired</a><h2 id="3-5-PostConstruct-原理⭐️🔴⭐️🔴"><a href="#3-5-PostConstruct-原理⭐️🔴⭐️🔴" class="headerlink" title="3.5. @PostConstruct 原理⭐️🔴⭐️🔴"></a>3.5. @PostConstruct 原理⭐️🔴⭐️🔴</h2><p>^ry6di8</p><a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-11%E3%80%81@PostConstruct/" title="Spring-11、@PostConstruct">Spring-11、@PostConstruct</a><h2 id="3-6-如何在-Spring-创建完所有-bean-之后做扩展"><a href="#3-6-如何在-Spring-创建完所有-bean-之后做扩展" class="headerlink" title="3.6. 如何在 Spring 创建完所有 bean 之后做扩展"></a>3.6. 如何在 Spring 创建完所有 bean 之后做扩展</h2><h3 id="3-6-1-基于-SmartInitializingSingleton-接口"><a href="#3-6-1-基于-SmartInitializingSingleton-接口" class="headerlink" title="3.6.1. 基于 SmartInitializingSingleton 接口"></a>3.6.1. 基于 SmartInitializingSingleton 接口</h3><p>在创建所有单例 Bean 的方法中： </p><p>1 finishBeanFactoryInitialization(beanFactory);<br>SmartInitializingSingleton 接口是在所有的 Bean 实例化完成以后，Spring 回调的方法, <br>所以这里也是一个扩展点，可以在单例 bean 全部完成实例化以后做处理。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202161432.png" alt="image.png"></p><h3 id="3-6-2-基于-Spring-事件监听"><a href="#3-6-2-基于-Spring-事件监听" class="headerlink" title="3.6.2. 基于 Spring 事件监听"></a>3.6.2. 基于 Spring 事件监听</h3><p>生命周期的最后一步是 finishRefresh();，这里面中有一个方法是 publishEvent<br>所以这里也可以进行扩展，监听 ContextRefreshedEvent 事件 。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202161549.png" alt="image.png"></p><h2 id="3-7-Spring-容器启动时，为什么先加载-BeanFactoryPostProcess"><a href="#3-7-Spring-容器启动时，为什么先加载-BeanFactoryPostProcess" class="headerlink" title="3.7. Spring 容器启动时，为什么先加载 BeanFactoryPostProcess"></a>3.7. Spring 容器启动时，为什么先加载 BeanFactoryPostProcess</h2><ol><li>因为 BeanDefinition 会在 ioc 容器加载的先注册， 而 BeanFactoryPostProcess 就是在所有的 BeanDefinition 注册完后做扩展的，所以要先加载 BeanFactoryPostProcess</li><li>解析配置类的组件  它就实现 BeanFactoryPostProcess， 所以要先去加载 BeanFactoryPostProcess</li></ol><h1 id="4-BeanDefinition-相关"><a href="#4-BeanDefinition-相关" class="headerlink" title="4. BeanDefinition 相关"></a>4. BeanDefinition 相关</h1><h2 id="4-1-BeanDefinition-的注册顺序"><a href="#4-1-BeanDefinition-的注册顺序" class="headerlink" title="4.1. BeanDefinition 的注册顺序"></a>4.1. BeanDefinition 的注册顺序</h2><p>1. @Configuration   <br>2. @Component <br>3. @Import—类<br>4. @Bean<br>5. @Import—ImportBeanDefinitionRegistrar</p><p>详细版<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202171228.png" alt="image.png"></p><h1 id="5-线程相关"><a href="#5-线程相关" class="headerlink" title="5. 线程相关"></a>5. 线程相关</h1><h2 id="5-1-Spring-如何处理线程并发问题⭐️🔴"><a href="#5-1-Spring-如何处理线程并发问题⭐️🔴" class="headerlink" title="5.1. Spring 如何处理线程并发问题⭐️🔴"></a>5.1. Spring 如何处理线程并发问题⭐️🔴</h2><p>^yqybkq<br>在一般情况下，只有无状态的 Bean 才可以在多线程环境下共享，在 Spring 中，绝大部分 Bean 都可以声明为 singleton 作用域，因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理，解决线程安全问题。</p><p>&#x3D;&#x3D;ThreadLocal&#x3D;&#x3D; 和 &#x3D;&#x3D;线程同步机制&#x3D;&#x3D;都是为了解决多线程中相同变量的访问冲突问题。<span style="background-color:#00ff00">同步机制采用了“时间换空间”的方式，仅提供一份变量，不同的线程在访问前需要获取锁，没获得锁的线程则需要排队</span>。<span style="background-color:#ff00ff">而 ThreadLocal 采用了“空间换时间”的方式</span>。</p><p>ThreadLocal 会为每一个线程提供一个<span style="background-color:#00ff00">独立的变量副本</span>，从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本，从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象，在编写多线程代码时，可以把不安全的变量封装进 ThreadLocal</p><h1 id="6-设计模式相关"><a href="#6-设计模式相关" class="headerlink" title="6. 设计模式相关"></a>6. 设计模式相关</h1><h2 id="6-1-Spring-中用到了哪些设计模式⭐️🔴⭐️🔴"><a href="#6-1-Spring-中用到了哪些设计模式⭐️🔴⭐️🔴" class="headerlink" title="6.1. Spring 中用到了哪些设计模式⭐️🔴⭐️🔴"></a>6.1. Spring 中用到了哪些设计模式⭐️🔴⭐️🔴</h2><ol><li>单例模式：Bean 默认为单例模式 [[内功心法专题-设计模式-3、单例模式]] ^4jhz9m</li><li>工厂模式：BeanFactory 就是简单工厂模式的体现，用来创建对象的实例 [[内功心法专题-设计模式-4、工厂模式]]</li><li>原型模式: [[内功心法专题-设计模式-5、原型模式]]</li><li>建造者模式: [[内功心法专题-设计模式-6、建造者模式]]</li><li>代理模式：Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术；<a href="/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7%E3%80%81%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/" title="设计模式-7、代理模式">设计模式-7、代理模式</a></li><li>适配器模式: <a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a></li><li>装饰者模式: [[内功心法专题-设计模式-9、装饰者模式]]</li><li>桥接模式: <a href="/2023/01/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-10%E3%80%81%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/" title="设计模式-10、桥接模式">设计模式-10、桥接模式</a></li><li>组合模式：[[内功心法专题-设计模式-12、组合模式]]</li><li>享元模式：[[内功心法专题-设计模式-13、享元模式]]</li><li>模板方法：用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate [[内功心法专题-设计模式-14、模板方法模式]]</li><li>策略模式：[[内功心法专题-设计模式-15、策略模式]]</li><li>命令模式：[[内功心法专题-设计模式-16、命令模式]]</li><li>责任链模式：[[内功心法专题-设计模式-17、责任链模式]]</li><li>观察者模式：定义对象键一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都会得到通知被制动更新，如 Spring 中 listener 的实现 ApplicationListener [[内功心法专题-设计模式-19、观察者模式]]</li><li>迭代器模式：[[内功心法专题-设计模式-21、迭代器模式]]</li><li>解释器模式：[[内功心法专题-设计模式-24、解释器模式]]<br>❕<span style="display:none">%%<br>2129-🏡⭐️◼️Spring 中用到了哪些设计模式 ?🔜MSTM📝 23 种设计模式中用到了 17 种，其中抽象工厂模式、外观模式、状态模式、中介者模式、访问者模式、备忘录模式 6 种没有使用到◼️⭐️-point-202302052129%%</span></li></ol><h2 id="6-2-单例-bean-的优势"><a href="#6-2-单例-bean-的优势" class="headerlink" title="6.2. 单例 bean 的优势"></a>6.2. 单例 bean 的优势</h2><ol><li>减少了新生成实例的消耗。新生成实例消耗包括两方面：第一，spring 会通过反射或者 cglib 来生成 bean 实例这都是耗性能的操作，其次给对象分配内存也会涉及复杂算法。 提供服务器内存的利用率 ，减少服务器内存消耗  </li><li>减少 jvm 垃圾回收由于不会给每个请求都新生成 bean 实例，所以自然回收的对象少了。</li><li>可以快速获取到 bean 因为单例的获取 bean 操作除了第一次生成之外其余的都是从缓存里获取的所以很快。❕<span style="display:none">%%<br>2135-🏡⭐️◼️单例 bean 有哪些好处 ?🔜MSTM📝 1. 减少了生成新实例的性能消耗，包括两部分：Spring 通过反射或者 cglib 方式生成对象的性能消耗、为对象分配内存的性能消耗。2. 减少 JVM 的 GC 和 STW。3. 可以快速获得 bean ◼️⭐️-point-202302052135%%</span></li></ol><h1 id="7-注解相关"><a href="#7-注解相关" class="headerlink" title="7. 注解相关"></a>7. 注解相关</h1><h2 id="7-1-Spring-有哪几种配置方式"><a href="#7-1-Spring-有哪几种配置方式" class="headerlink" title="7.1. Spring 有哪几种配置方式"></a>7.1. Spring 有哪几种配置方式</h2><p> 这里有三种重要的方法给 Spring 容器提供配置元数据。</p><p>1. XML 配置文件。  spring 诞生<br>spring.xml    <code> &lt;bean&gt;</code></p><p>2. 基于注解的配置。  Spring2.5+<br>spring.xml  + @Component  @Autowired</p><p>3. 基于 java 的配置。 JavaConfig  Spring3.0+<br>@Configuration   @Bean   ….</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230321224533.png" alt="image.png"></p><h2 id="7-2-JavaConfig-与-XML-方式-Spring-配置的不同⭐️🔴"><a href="#7-2-JavaConfig-与-XML-方式-Spring-配置的不同⭐️🔴" class="headerlink" title="7.2. JavaConfig 与 XML 方式 Spring 配置的不同⭐️🔴"></a>7.2. JavaConfig 与 XML 方式 Spring 配置的不同⭐️🔴</h2><p>^b5hhm9</p><a href="/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/" title="Spring-8、BeanDefinition">Spring-8、BeanDefinition</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203164128.png" alt="image.png"></p><h3 id="7-2-1-JavaConfig"><a href="#7-2-1-JavaConfig" class="headerlink" title="7.2.1. JavaConfig"></a>7.2.1. JavaConfig</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204122011.png" alt="image.png"></p><p><a href="https://www.processon.com/diagraming/63dd8eb4b59543238fa3a6a5">https://www.processon.com/diagraming/63dd8eb4b59543238fa3a6a5</a></p><h4 id="7-2-1-1-注册核心-BPP"><a href="#7-2-1-1-注册核心-BPP" class="headerlink" title="7.2.1.1. 注册核心 BPP"></a>7.2.1.1. 注册核心 BPP</h4><ol><li>在 <code>new AnnotationConfigApplicationContext(MainConfig.class)</code> 中的 <code>this</code> 方法中 <code>AnnotatedBeanDefinitionReader</code> 为工厂注册 6 大内置核心组件，其中 <code>ConfigurationClassPostProcessor</code> 是用来处理配置类的</li><li><code>刷新第5步：invokeBeanFactoryPostProcessors()</code><br><span style="background-color:#ff00ff">getBean</span> <code>ConfigurationClassPostProcessor</code></li></ol><h4 id="7-2-1-2-读取配置类-注册配置类"><a href="#7-2-1-2-读取配置类-注册配置类" class="headerlink" title="7.2.1.2. 读取配置类 (注册配置类)"></a>7.2.1.2. 读取配置类 (注册配置类)</h4><p>在 <code>new AnnotationConfigApplicationContext(MainConfig.class)</code> 中的 <code>register(componentClasses)</code> 方法中使用 <code>AnnotatedBeanDefinitionReader</code> 注册配置类</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203195618.png" alt="image.png"></p><h4 id="7-2-1-3-解析配置类-扫描注册引入类"><a href="#7-2-1-3-解析配置类-扫描注册引入类" class="headerlink" title="7.2.1.3. 解析配置类 (扫描注册引入类)"></a>7.2.1.3. 解析配置类 (扫描注册引入类)</h4><p>执行 <code>ConfigurationClassPostProcessor</code> 的 <code>postProcessor.postProcessBeanDefinitionRegistry()</code> 方法，把【配置类中的】所有 bean 的定义信息导入进来。由 ConfigurationClassParser 解析每一个配置类</p><p>核心方法，将完全填充好的 ConfigurationClass 实例转化为 BeanDefinition 注册入 IOC 容器 <code>this.reader.loadBeanDefinitions(configClasses)</code></p><h3 id="7-2-2-XML"><a href="#7-2-2-XML" class="headerlink" title="7.2.2. XML"></a>7.2.2. XML</h3><p><a href="https://www.processon.com/diagraming/63ddb70fc12afe0cadb59d2c">https://www.processon.com/diagraming/63ddb70fc12afe0cadb59d2c</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204121938.png" alt="image.png"></p><h4 id="7-2-2-1-读取配置文件"><a href="#7-2-2-1-读取配置文件" class="headerlink" title="7.2.2.1. 读取配置文件"></a>7.2.2.1. 读取配置文件</h4><h5 id="7-2-2-1-1-XmlBeanDefinitionReader"><a href="#7-2-2-1-1-XmlBeanDefinitionReader" class="headerlink" title="7.2.2.1.1. XmlBeanDefinitionReader"></a>7.2.2.1.1. XmlBeanDefinitionReader</h5><h5 id="7-2-2-1-2-DefaultDocumentLoader"><a href="#7-2-2-1-2-DefaultDocumentLoader" class="headerlink" title="7.2.2.1.2. DefaultDocumentLoader"></a>7.2.2.1.2. DefaultDocumentLoader</h5><p>此处获取 xml 文件的 document 对象，这个解析过程是由 documentLoader 完成的，最终开始将 resource 读取成一个 document 文档<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204095148.png" alt="image.png"></p><h4 id="7-2-2-2-解析配置类"><a href="#7-2-2-2-解析配置类" class="headerlink" title="7.2.2.2. 解析配置类"></a>7.2.2.2. 解析配置类</h4><h5 id="7-2-2-2-1-DefaultBeanDefinitionDocumentReader"><a href="#7-2-2-2-1-DefaultBeanDefinitionDocumentReader" class="headerlink" title="7.2.2.2.1. DefaultBeanDefinitionDocumentReader"></a>7.2.2.2.1. <strong>DefaultBeanDefinitionDocumentReader</strong></h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204095229.png" alt="image.png"></p><h2 id="7-3-Component-Controller-Repository-Service"><a href="#7-3-Component-Controller-Repository-Service" class="headerlink" title="7.3. Component Controller Repository Service"></a>7.3. Component Controller Repository Service</h2><ol><li>@Component：这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。</li><li>@Controller：这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。</li><li>@Service：此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component，因为它以更好的方式指定了意图。</li><li>@Repository：这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器，并使未经检查的异常有资格转换为 Spring DataAccessException。</li></ol><h2 id="7-4-Import-可以有几种用法-注册-BD-方法"><a href="#7-4-Import-可以有几种用法-注册-BD-方法" class="headerlink" title="7.4. @Import 可以有几种用法 (注册 BD 方法)"></a>7.4. @Import 可以有几种用法 (注册 BD 方法)</h2><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><p><strong>4 种</strong><br>1. 直接指定类 （如果配置类会按配置类正常解析、  如果是个普通类就会解析成 Bean)<br>2. 通过<span style="background-color:#00ff00">实现 ImportSelector 接口</span> 可以一次性注册多个，返回一个 string[]  每一个值就是类的完整类路径<br>a. 通过<span style="background-color:#00ff00">实现 DeferredImportSelector 接口</span>可以一次性注册多个，返回一个 string[]  每一个值就是类的完整类路径<br>i. 区别：DeferredImportSelector 顺序靠后<br>3. 通过<span style="background-color:#00ff00">实现 ImportBeanDefinitionRegistrar 接口</span>可以一次性注册多个，通过 BeanDefinitionRegistry 来动态注册 BeanDefinition</p><h2 id="7-5-Configuration-的作用解析原理"><a href="#7-5-Configuration-的作用解析原理" class="headerlink" title="7.5. @Configuration 的作用解析原理"></a>7.5. @Configuration 的作用解析原理</h2><p>@Configuration 用来代替 xml 配置方式 spring.xml 配置文件 bean 的吗？不全对<br>因为没有@Configuration 也是可以配置@Bean</p><h3 id="7-5-1-Configuration-加与不加有什么区别"><a href="#7-5-1-Configuration-加与不加有什么区别" class="headerlink" title="7.5.1. @Configuration 加与不加有什么区别"></a>7.5.1. @Configuration 加与不加有什么区别</h3><p><strong>作用：</strong><br>加了@Configuration 会为配置类创建 cglib 动态代理（保证配置类@Bean 方法调用 Bean 的单例），@Bean 方法的调用就会通过容器 getBean 进行获取<br><strong>原理：</strong></p><ol><li>创建 Spring 上下文的时候会在 this() 方法中注册一个解析配置的处理器 ConfigurationClassPostProcessor（它实现了 BeanFactoryPostProcessor 和<br>BeanDefinitionRegistryPostProcessor)</li><li>在 refresh () 方法第 5 步中，调用 <code>invokeBeanFactoryPostProcessor</code>，就会去调用<br><code>ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry</code> 进行解析配置（解析配置类就是去解析并注册各种注解的 BeanDefinition，比如@Bean @Configuration @Import @Component … 把这些注解的 BeanDefinition 解析并放入 BeanDefinitionMap 中</li><li>调用 invokeBeanFactoryPostProcessors，还会调用 <code>ConfigurationClassPostProcessor.postProcessBeanFactory</code>，在里面会调用 <code>enhanceConfigurationClasses(beanFactory)</code> 方法来创建 cglib 动态代理</li></ol><h2 id="7-6-Bean-之间的方法调用是怎么保证单例的"><a href="#7-6-Bean-之间的方法调用是怎么保证单例的" class="headerlink" title="7.6. @Bean 之间的方法调用是怎么保证单例的"></a>7.6. @Bean 之间的方法调用是怎么保证单例的</h2><p><a href="https://www.bilibili.com/video/BV1t44y1C73F?p=45&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1t44y1C73F?p=45&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>（ @Configuration 加与不加的区别是什么？）<br>1.如果希望@bean 的方法返回是对象是单例  需要在类上面加上@Configuration,<br>2.Spring 会在 invokeBeanFactoryPostProcessor  通过内置 BeanFactoryPostProcessor 中会 CGLib 生成动态代理代理<br>3.当@Bean 方法进行互调时， 则会通过 CGLIB 进行增强，通过调用的方法名作为 bean 的名称去 ioc 容器中获取，进而保证了@Bean 方法的单例</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202191415.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202191651.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204165509.png" alt="image.png"></p><h1 id="8-AOP-相关"><a href="#8-AOP-相关" class="headerlink" title="8. AOP 相关"></a>8. AOP 相关</h1><h2 id="8-1-是什么做什么"><a href="#8-1-是什么做什么" class="headerlink" title="8.1. 是什么做什么"></a>8.1. 是什么做什么</h2><p>AOP(Aspect-Oriented Programming)，一般称为面向切面编程，用于将那些与业务无关，但却对多个对象产生影响的公共行为和逻辑，抽取并封装为一个可重用的模块，这个模块被命名为“切面”(Aspect)，减少系统中的重复代码，降低了模块间的耦合度，同时提高了系统的可维护性。</p><p>可用于权限认证、日志、事务处理等。</p><p>AOP、OOP 在字面上虽然非常类似，但却是面向不同领域的两种设计思想。OOP(面向对象编程) 针对业务处理过程的实体及其属性和行为进行抽象封装，以获得更加清晰高效的逻辑单元划分。 而 AOP 作为面向对象的一种补充，则是针对业务处理过程中的切面进行提取， 已达到业务代码和公共行为代码之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。</p><h2 id="8-2-各种概念关系"><a href="#8-2-各种概念关系" class="headerlink" title="8.2. 各种概念关系"></a>8.2. 各种概念关系</h2><ol><li>AOP 实现的关键在于代理模式，AOP 代理主要分为静态代理和动态代理。静态代理的代表为 AspectJ；动态代理则以 Spring AOP 为代表。<a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a></li><li>Spring AOP 中的动态代理主要有两种方式，JDK 动态代理和 CGLIB 动态代理</li><li>Objenesis 是 CGLIB 的增强，不需要构造函数就可以代理</li></ol><blockquote><p>   <span style="background-color:#ff00ff">直接使用注入的代理类的属性是不行的</span><br>   当我们@Autowired 注入了代理类，如果想当然的直接使用被注入对象的属性，则一定会报空指针，而我们正常情况为什么没有发生问题呢，原因是我们一般都是调用该对象的方法，而不是直接使用其中的属性的，而调用其中的方法，则代理类处理完切面任务之后，会进入到真正的对象，而真正的对象里的各个属性都是有值的，不会发生空指针。</p></blockquote><p><a href="https://blog.csdn.net/qq_30095631/article/details/108086616">https://blog.csdn.net/qq_30095631/article/details/108086616</a></p><h2 id="8-3-解释一下-Spring-AOP-里面的几个名词"><a href="#8-3-解释一下-Spring-AOP-里面的几个名词" class="headerlink" title="8.3. 解释一下 Spring AOP 里面的几个名词"></a>8.3. 解释一下 Spring AOP 里面的几个名词</h2><p>（1）<strong>连接点（Join point</strong>）： 指定就是被增强的业务方法<br>（2）<strong>切面（Aspect）</strong>：  在 Spring Aop 指定就是“切面类” ，<span style="background-color:#00ff00">切面类管理着切点、通知</span>。<br>（3）<strong>切点（Pointcut）</strong>：  <span style="background-color:#00ff00">由他决定哪些方法需要增强、哪些不需要增强，  结合切点表达式进行实现</span><br>（4）<strong>通知（Advice）</strong>：     <span style="background-color:#00ff00">就是需要增加到业务方法中的公共代码，通知有很多种类型分别可以在需要增加的业务方法</span><br>不同位置进行执行（前置通知、后置通知、异常通知、返回通知、环绕通知）<br>（5）目标对象（Target Object）：  指定是增强的对象<br>（6）<strong>织入（Weaving）</strong> ：  spring aop 用的织入方式：动态代理。  就是为目标对象创建动态代理的过程就叫织入。</p><h2 id="8-4-Spring-通知有哪些类型？"><a href="#8-4-Spring-通知有哪些类型？" class="headerlink" title="8.4. Spring 通知有哪些类型？"></a>8.4. Spring 通知有哪些类型？</h2><p>在 AOP 术语中，在的某个特定的连接点上执行的动作<br>Spring 切面可以应用 5 种类型的通知：<br>1. 前置通知（Before）：在目标方法被调用之前调用通知功能；<br>2. 后置通知（After）：在目标方法完成之后调用通知，此时不会关心方法的输出是什么；<br>3. 返回通知（After-returning ）：在目标方法成功执行之后调用通知；<br>4. 异常通知（After-throwing）：在目标方法抛出异常后调用通知；<br>5. 环绕通知（Around）：通知包裹了被通知的方法，在被通知的方法调用之前和调用之后执行自定义的行为。</p><p>执行顺序：</p><p>1、正常执行：<br>@Around &gt; @Before ­­­&gt;方法 &gt; @After ­­­­&gt; @AfterReturning ­­­ &gt; @Around<br>2、异常执行：<br>@Around &gt; @Before­­­&gt;方法 &gt; @After­­­­ &gt; @AfterThrowing­­­</p><p>Spring 在<span style="background-color:#ff00ff">5.2.7 之后</span>就改变的 advice 的执行顺序</p><p>1、正常执行：<br>@Around &gt; @Before ­­­&gt;方法 ­­­­&gt; @AfterReturning ­­­&gt; @After &gt; @Around<br>2、异常执行：<br>@Around &gt; @Before­­­&gt;方法­­­­ &gt; @AfterThrowing­­­ &gt; @After</p><h2 id="8-5-Spring-AOP-与-AspectJ-AOP-有什么区别？"><a href="#8-5-Spring-AOP-与-AspectJ-AOP-有什么区别？" class="headerlink" title="8.5. Spring AOP 与 AspectJ AOP 有什么区别？"></a>8.5. Spring AOP 与 AspectJ AOP 有什么区别？</h2><h3 id="8-5-1-关系"><a href="#8-5-1-关系" class="headerlink" title="8.5.1. 关系"></a>8.5.1. 关系</h3><p>当在 Spring 中要使用@Aspect、@Before 等这些注解的时候， 就需要添加 AspectJ 相关依赖</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202211531.png" alt="image.png"></p><p>Spring Aop 提供了 AspectJ 的支持，但只用到的 AspectJ 的切点解析和匹配。 @Aspect、@Before 等这些注解都是由 AspectJ 发明的<br><span style="background-color:#ff00ff">AOP 实现的关键在于代理模式，AOP 代理主要分为静态代理和动态代理。静态代理的代表为 AspectJ；动态代理则以 Spring AOP 为代表。</span></p><h3 id="8-5-2-区别"><a href="#8-5-2-区别" class="headerlink" title="8.5.2. 区别"></a>8.5.2. 区别</h3><p>（1）AspectJ 是静态代理的增强，所谓静态代理，就是 AOP 框架会在编译阶段生成 AOP 代理类，因此也称为<span style="background-color:#ff00ff">编译时增强</span>，他会在编译阶段将 AspectJ(切面) 织入到 Java 字节码中，运行的时候就是增强之后的 AOP 对象。<br>（2）Spring AOP 使用的动态代理，它基于动态代理来实现。默认地，如果使用接口的，用 JDK 提供的动态代理实现，如果没有接口，使用 CGLIB 实现。</p><h2 id="8-6-JDK-动态代理和-CGLIB-动态代理的区别"><a href="#8-6-JDK-动态代理和-CGLIB-动态代理的区别" class="headerlink" title="8.6. JDK 动态代理和 CGLIB 动态代理的区别"></a>8.6. JDK 动态代理和 CGLIB 动态代理的区别</h2><p>Spring AOP 中的动态代理主要有两种方式，JDK 动态代理和 CGLIB 动态代理：</p><ol><li>JDK 动态代理<span style="background-color:#00ff00">只提供接口的代理，不支持类的代理</span>。<br>JDK 会在运行时为目标类生成一个动态代理类 $proxy*.class  . <br>  该代理类是实现了目标类接口， 并且代理类会<span style="background-color:#00ff00">实现接口所有的方法</span>增强代码。 <br> 调用时通过代理类先去调用处理类进行增强，再通过<span style="background-color:#ff00ff">反射</span>的方式进行调用目标方法。从而实现 AOP</li><li>如果代理类没有实现接口，那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。<br>CGLIB 的底层是<span style="background-color:#00ff00">通过 ASM 在运行时动态的生成目标类的一个子类</span>。（还有其他相关类，主要是为增强调用时效率）会生成多个 ，并且会重写父类所有的方法增强代码，调用时先通过代理类进行增强，再<span style="background-color:#ff00ff">直接调用父类对应的方法</span>进行调用目标方法。从而实现AOP。<br> <span style="background-color:#ffff00">CGLIB 是通过继承的方式做的动态代理，因此如果某个类被标记为 final</span>，那么它是无法使用 CGLIB 做动态代理的。CGLIB 除了生成目标子类代理类，<span style="background-color:#00ff00">还有一个 FastClass(路由类)，可以（但不是必须）让本类方法调用进行增强，而不会像 jdk 代理那样本类方法调用增强会失效</span>，但是为了 AOP 整体一致性，这个特性并未对外使用</li><li>在老版本 CGLIB 的速度是 JDK 速度的 10 倍左右, 但是实际上 JDK 的速度在版本升级的时候每次都提高很多性能,而 CGLIB 仍止步不前。在对 JDK 动态代理与 CGlib 动态代理的代码实验中看，1W 次执行下，JDK7 及 8 的动态代理性能比 CGlib 要好 20% 左右。</li></ol><h2 id="8-7-JavaConfig-方式如何启用-AOP-如何强制使用-cglib"><a href="#8-7-JavaConfig-方式如何启用-AOP-如何强制使用-cglib" class="headerlink" title="8.7. JavaConfig 方式如何启用 AOP? 如何强制使用 cglib"></a>8.7. JavaConfig 方式如何启用 AOP? 如何强制使用 cglib</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-0744%%</span>❕ ^sn8xbz</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-meta">@EnableAspectJAutoProxy</span><br>(proxyTargetClass = <span class="hljs-literal">true</span>) <span class="hljs-comment">//强制CGLIB</span><br>(exposeProxy = <span class="hljs-literal">true</span>) 在线程中暴露代理对象<span class="hljs-meta">@EnableAspectJAutoProxy</span><br></code></pre></td></tr></table></figure><p>^lcbyzk</p><blockquote><ol><li>Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。</li><li>SpringBoot 2.x 开始，为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。<span style="background-color:#ffff00">目标类没有实现接口或者接收代理类的类型不是共同父接口就会导致类型转换异常。</span></li><li>在 SpringBoot 2.x 中，如果需要默认使用 JDK 动态代理可以通过配置项 <code>spring.aop.proxy-target-class=false</code> 来进行修改，<code>proxyTargetClass</code> 配置已无效。<br>❕<span style="display:none">%%<br>1211-🏡⭐️◼️强制开启 CGLIB 代理的方法 ?🔜MSTM📝 增加配置参数 proxy-target-class&#x3D;”true”◼️⭐️-point-202302091211%%</span></li></ol></blockquote><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a><p>对比记忆：<a href="/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/" title="Spring-8、BeanDefinition">Spring-8、BeanDefinition</a><br>❕<span style="display:none">%%<br>0653-🏡⭐️◼️CGLIB 相关的 2 个配置 ?🔜MSTM📝 都是 proxy 开头，一个用于 AOP 中指定使用 cglib 作为动态代理的生成方式：proxytargetclass。一个用于方法 bean 是否需要 full 模式，即是否需要生成一个 cglib 动态代理：proxybeanmethod。◼️⭐️-point-202302100653%%</span></p><p>^1k8ap0</p><h2 id="8-8-AOP-失效-or-如何在同一个-service-中使用传播行为"><a href="#8-8-AOP-失效-or-如何在同一个-service-中使用传播行为" class="headerlink" title="8.8. AOP 失效 or 如何在同一个 service 中使用传播行为"></a>8.8. AOP 失效 or 如何在同一个 service 中使用传播行为</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230608-1708%%</span>❕ ^tzh2qj</p><p>解决方式：必须走代理， 重新拿到代理对象再次执行方法才能进行增强</p><p>1. 在本类中自动注入当前的 bean<br>2. 设置暴露当前代理对象到本地线程， 可以通过 AopContext.currentProxy() 拿到当前正在调用的动态代理对象</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">@EnableAspectJAutoProxy(exposeProxy=true)</span><br><span class="hljs-type">SpuInfoService</span> <span class="hljs-variable">proxy</span> <span class="hljs-operator">=</span> (SpuInfoService) AopContext.currentProxy();<br></code></pre></td></tr></table></figure><h2 id="8-9-介绍-AOP-有几种实现方式"><a href="#8-9-介绍-AOP-有几种实现方式" class="headerlink" title="8.9. 介绍 AOP 有几种实现方式"></a>8.9. 介绍 AOP 有几种实现方式</h2><ol><li>Spring 1.2 基于接口的配置：最早的 Spring AOP 是完全基于几个接口的，想看源码的同学可以从这里起步。</li><li>Spring 2.0 schema-based 配置：Spring 2.0 以后使用 XML 的方式来配置，使用命名空间 <aop ></aop></li><li>Spring 2.0 @AspectJ 配置：使用注解的方式来配置最方便的，还有，这里虽然叫做 @AspectJ，但是这个和 AspectJ 其实没啥关系。</li><li>AspectJ  方式，这种方式其实和 Spring 没有关系，采用 AspectJ 进行动态织入的方式实现 AOP，需要用 AspectJ 单独编译。</li></ol><h2 id="8-10-Spring-的-AOP-是在哪里创建的动态代理？"><a href="#8-10-Spring-的-AOP-是在哪里创建的动态代理？" class="headerlink" title="8.10. Spring 的 AOP 是在哪里创建的动态代理？"></a>8.10. Spring 的 AOP 是在哪里创建的动态代理？</h2><p>1. 正常的 Bean 会在 Bean 的生命周期的‘初始化’后， 通过 BeanPostProcessor.postProcessAfterInitialization 创建 aop 的动态代理<br>2. 还有一种特殊情况： 循环依赖的 Bean 会在 Bean 的生命周期‘属性注入’时存在的循环依赖的情况下， 也会为循环依赖的 Bean<br>通过 MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition 创建 aop</p><h2 id="8-11-Spring-的-Aop-的完整实现流程？"><a href="#8-11-Spring-的-Aop-的完整实现流程？" class="headerlink" title="8.11. Spring 的 Aop 的完整实现流程？"></a>8.11. Spring 的 Aop 的完整实现流程？</h2><p>^f9oy7j<br>Aop 的实现大致分为三大步：JavaConfig<br>当@EnableAspectJAutoProxy 会通过@Import 注册一个 BeanPostProcessor 处理 AOP</p><ol><li><p><strong>解析切面</strong>： 在 Bean 创建之前的第一个 Bean 后置处理器会去解析切面（解析切面中通知、切点，一个通知就会解析成一个 advisor(通知、切点)） <br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209132856.png" alt="image.png"></p></li><li><p><strong>创建动态代理</strong>：正常的 Bean 初始化后调用 BeanPostProcessor  拿到之前缓存的 advisor ，再通过 advisor 中 pointcut  判断当前 Bean 是否被切点表达式匹配，如果匹配，就会为 Bean 创建动态代理（创建方式：1.jdk 动态代理、 2.cglib)。</p></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209133211.png" alt="image.png"></p><ol start="3"><li><strong>调用</strong>：拿到动态代理对象，调用方法就会判断当前方法是否增强的方法，就会通过调用链的方式依次去执行通知<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209133351.png" alt="image.png"></li></ol><h1 id="9-事务相关"><a href="#9-事务相关" class="headerlink" title="9. 事务相关"></a>9. 事务相关</h1><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><h2 id="10-1-图灵徐庶"><a href="#10-1-图灵徐庶" class="headerlink" title="10.1. 图灵徐庶"></a>10.1. 图灵徐庶</h2><p><a href="https://www.bilibili.com/video/BV1mf4y1c7cV/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1mf4y1c7cV/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>[[Spring全家桶面试题—图灵徐庶.pdf]]<br><a href="https://www.processon.com/view/link/5f5075c763768959e2d109df#map">https://www.processon.com/view/link/5f5075c763768959e2d109df#map</a> ^b7goye</p><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>经验专题-序列化与反序列化-1、选型比较</title>
      <link href="/2023/04/26/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-1%E3%80%81%E9%80%89%E5%9E%8B%E6%AF%94%E8%BE%83/"/>
      <url>/2023/04/26/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-1%E3%80%81%E9%80%89%E5%9E%8B%E6%AF%94%E8%BE%83/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Java-序列化"><a href="#1-Java-序列化" class="headerlink" title="1. Java 序列化"></a>1. Java 序列化</h1><p><a href="https://cloud.tencent.com/developer/article/1752784">https://cloud.tencent.com/developer/article/1752784</a></p><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p>如今大部分的后端服务都是基于微服务架构实现的，服务按照业务划分被拆分，实现了服务的解耦，同时也带来了一些新的问题，比如不同业务之间的通信需要通过接口实现调用。<span style="background-color:#ff00ff">两个服务之间要共享一个数据对象，就需要从对象转换成二进制流，通过网络传输，传送到对方服务，再转换成对象，供服务方法调用。这个编码和解码的过程我们称之为序列化和反序列化</span>。</p><p>在高并发系统中，序列化的速度快慢，会影响请求的响应时间，序列化后的传输数据体积大，会导致网络吞吐量下降，所以，一个优秀的序列化框架可以提高系统的整体性能。</p><p>我们都知道 Java 提供了 RMI 框架可以实现服务与服务之间的接口暴露和调用，RMI 中对数据对象的序列化采用的是 Java 序列化。而目前主流的框架却很少使用到 Java 序列化，如 SpringCloud 使用的 Json 序列化，Dubbo 虽然兼容了 Java 序列化，但是默认还是使用的 Hessian 序列化。</p><p>Java 序列化</p><p>首先，来看看什么是 Java 序列化和实现原理。Java 提供了一种序列化机制，这种机制能<span style="background-color:#ff00ff">将一个对象序列化成二进制形式</span>，用于写入磁盘或输出到网络，同时将从网络或者磁盘中读取的字节数组，反序列化成对象，在程序中使用。</p><a href="/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-1%E3%80%81Serializable%E6%8E%A5%E5%8F%A3/" title="关键字和接口-1、Serializable接口">关键字和接口-1、Serializable接口</a><h2 id="1-2-特性"><a href="#1-2-特性" class="headerlink" title="1.2. 特性"></a>1.2. 特性</h2><ol><li>JDK 提供的两个输入、输出流对象 ObjectInputStream 和 ObjectOutputStream，它们<span style="background-color:#ff00ff">只能对实现了 Serializable 接口</span>的类的对象进行反序列化和序列化。</li><li>ObjectOutputStream 的默认序列化方式，仅对对象的非 transient 的实例变量进行序列化，<span style="background-color:#ff00ff">而不会序列化对象的 transient 的实例变量，也不会序列化静态变量</span>。</li><li>在实现了 Serializable 接口的类的对象中，会生成一个 serialVersionUID 的版本号，这个版本号有什么用呢？它会在反序列化过程中来验证序列化对象是否加载了反序列化的类，<span style="background-color:#ff00ff">如果是具有相同类名的不同版本号的类，在反序列化中是无法获取对象的</span>。</li><li>具体实现序列化的是 <span style="background-color:#ff00ff">writeObject 和 readObject</span>，通常这两个方法是默认的，我们也可以在实现 Serializable 接口的类中对其重写，定制属于自己的序列化和反序列化机制。</li><li>Java 序列化类中还定义了两个重写方法：writeReplace() 和 readResolve()，前者是用来在序列化之前替换序列化对象的，后者是用来在序列化之后对返回对象进行处理的。</li></ol><h2 id="1-3-缺陷"><a href="#1-3-缺陷" class="headerlink" title="1.3. 缺陷"></a>1.3. 缺陷</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230519-1946%%</span>❕ ^st48nr</p><h3 id="1-3-1-无法跨语言"><a href="#1-3-1-无法跨语言" class="headerlink" title="1.3.1. 无法跨语言"></a>1.3.1. 无法跨语言</h3><p>而 Java 序列化目前只支持 Java 语言实现的框架，其它语言大部分都没有使用 Java 的序列化框架，也没有实现 Java 序列化这套协议，因此，如果两个基于不同语言编写的应用程序之间通信，使用 Java 序列化，则无法实现两个应用服务之间传输对象的序列化和反序列化。</p><h3 id="1-3-2-容易被攻击"><a href="#1-3-2-容易被攻击" class="headerlink" title="1.3.2. 容易被攻击"></a>1.3.2. 容易被攻击</h3><p>我们知道对象是<span style="background-color:#ff00ff">通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的</span>，这个方法其实是一个神奇的构造器，<span style="background-color:#ffff00">它可以将类路径上几乎所有实现了 Serializable 接口的对象都实例化</span>。这也就意味着，在反序列化字节流的过程中，该方法可以执行任意类型的代码，这是非常危险的。</p><p>对于需要长时间进行反序列化的对象，不需要执行任何代码，也可以发起一次攻击。攻击者可以<span style="background-color:#ff00ff">创建循环对象链，然后将序列化后的对象传输到程序中反序列化，这种情况会导致 hashCode 方法被调用次数呈次方爆发式增长, 从而引发栈溢出异常。</span>例如下面这个案例就可以很好地说明。</p><p>之前 FoxGlove Security 安全团队的一篇论文中提到的：通过 Apache Commons Collections，Java 反序列化漏洞可以实现攻击，一度横扫了 WebLogic、WebSphere、JBoss、Jenkins、OpenNMS 的最新版，各大 Java Web Server 纷纷躺枪。</p><p>其实，Apache Commons Collections 就是一个第三方基础库，它扩展了 Java 标准库里的 Collection 结构，提供了很多强大的数据结构类型，并且实现了各种集合工具类。</p><p>实现攻击的原理：Apache Commons Collections 允许链式的任意的类函数反射调用，攻击者通过实现了 Java 序列化协议的端口，把攻击代码上传到服务器上，再由 Apache Commons Collections 里的 TransformedMap 来执行。</p><p><strong>如何解决这个漏洞？</strong></p><p>很多序列化协议都制定了一套数据结构来保存和获取对象。例如，JSON 序列化、ProtocolBuf 等，它们<span style="background-color:#ff00ff">只支持一些基本类型和数组数据类型，这样可以避免反序列化创建一些不确定的实例</span>。虽然它们的设计简单，但足以满足当前大部分系统的数据传输需求。我们也可以<span style="background-color:#ff00ff">通过反序列化对象白名单</span>来控制反序列化对象，可以重写 resolveClass 方法，并在该方法中校验对象名字。代码如下所示：</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-meta">@Override</span><br><span class="hljs-keyword">protected</span> Class <span class="hljs-title function_">resolveClass</span><span class="hljs-params">(ObjectStreamClass desc)</span> <span class="hljs-keyword">throws</span> IOException,ClassNotFoundException &#123;<br>  <span class="hljs-keyword">if</span> (!desc.getName().equals(Bicycle.class.getName())) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InvalidClassException</span>(<br>    <span class="hljs-string">&quot;Unauthorized deserialization attempt&quot;</span>, desc.getName());<br>  &#125;<br>  <span class="hljs-keyword">return</span> <span class="hljs-built_in">super</span>.resolveClass(desc);<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="1-3-3-序列化流大"><a href="#1-3-3-序列化流大" class="headerlink" title="1.3.3. 序列化流大"></a>1.3.3. 序列化流大</h3><p>序列化后的二进制流大小能体现序列化的性能。序列化后的二进制数组越大，占用的存储空间就越多，存储硬件的成本就越高。如果我们是进行网络传输，则占用的带宽就更多，这时就会影响到系统的吞吐量。</p><h3 id="1-3-4-性能太差"><a href="#1-3-4-性能太差" class="headerlink" title="1.3.4. 性能太差"></a>1.3.4. 性能太差</h3><p>上边说了 4 个 Java 序列化的缺点，其实业界有很多可以代替 Java 序列化的序列化框架，大部分都避免了 Java 默认序列化的一些缺陷，例如比较流行的 FastJson、Kryo、Protobuf、Hessian 等，这里就来简单的介绍一下 Protobuf 序列化框架。</p><h1 id="2-Protobuf"><a href="#2-Protobuf" class="headerlink" title="2. Protobuf"></a>2. Protobuf</h1><p><span style="background-color:#ff00ff">Protobuf 是由 Google 推出且支持多语言的序列化框架，目前在主流网站上的序列化框架性能对比测试报告中，Protobuf 无论是编解码耗时，还是二进制流压缩大小，都名列前茅。</span></p><p>Protobuf 以一个 .proto 后缀的文件为基础，这个文件描述了字段以及字段类型，通过工具可以生成不同语言的数据结构文件。在序列化该数据对象的时候，Protobuf 通过.proto 文件描述来生成 Protocol Buffers 格式的编码。</p><h1 id="3-选型"><a href="#3-选型" class="headerlink" title="3. 选型"></a>3. 选型</h1><p>Java 默认的序列化是通过 Serializable 接口实现的，只要类实现了该接口，同时生成一个默认的版本号，我们无需手动设置，该类就会自动实现序列化与反序列化。<br>Java 默认的序列化虽然实现方便，但却存在<span style="background-color:#ff0000">安全漏洞、不跨语言以及性能差等缺陷</span>，所以我强烈建议你避免使用 Java 序列化。<br>纵观主流序列化框架，FastJson、Protobuf、Kryo 是比较有特点的，而且性能以及安全方面都得到了业界的认可，我们可以结合自身业务来选择一种适合的序列化框架，来优化系统的序列化性能</p><h2 id="3-1-SpringBoot-集成-ES"><a href="#3-1-SpringBoot-集成-ES" class="headerlink" title="3.1. SpringBoot 集成 ES"></a>3.1. SpringBoot 集成 ES</h2><p>在集成 SpringBoot 与 ElasticSearch 时，关于 LocalDateTime 类型的序列化与反序列化报错，SpringBoot 默认的 Jackson 用起来不是很顺手（需要实例化 ObjectMapper），便计划使用 FastJson，然而，直接引入 FastJson 后，会与默认的 Jackson 发生冲突。。</p><p>原文链接： <a href="https://blog.csdn.net/u013810234/article/details/106975976">https://blog.csdn.net/u013810234/article/details/106975976</a></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><p><a href="https://cloud.tencent.com/developer/article/1752784">https://cloud.tencent.com/developer/article/1752784</a></p><p><a href="https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html">https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html</a><br><a href="https://www.cnblogs.com/mic112/p/15559723.html">https://www.cnblogs.com/mic112/p/15559723.html</a><br><a href="https://developer.afengblog.com/rear-end/java/798.html">https://developer.afengblog.com/rear-end/java/798.html</a><br><a href="https://juejin.cn/post/6974565210161954829">https://juejin.cn/post/6974565210161954829</a><br><a href="https://developer.aliyun.com/article/1136866">https://developer.aliyun.com/article/1136866</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-进阶-1、调优汇总</title>
      <link href="/2023/04/25/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-3%E3%80%81%E6%9E%B6%E6%9E%84%E8%B0%83%E4%BC%98%E6%B1%87%E6%80%BB/"/>
      <url>/2023/04/25/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-3%E3%80%81%E6%9E%B6%E6%9E%84%E8%B0%83%E4%BC%98%E6%B1%87%E6%80%BB/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-性能评价指标"><a href="#1-性能评价指标" class="headerlink" title="1. 性能评价指标"></a>1. 性能评价指标</h1><h2 id="1-1-响应时间"><a href="#1-1-响应时间" class="headerlink" title="1.1. 响应时间"></a>1.1. 响应时间</h2><p>提交请求和返回该请求的响应之间使用的时间，一般比较关注平均响应时间。<br>常用操作的响应时间列表:<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230426083110.png" alt="image.png"></p><h2 id="1-2-并发数"><a href="#1-2-并发数" class="headerlink" title="1.2. 并发数"></a>1.2. 并发数</h2><p>同一时刻，对服务器有实际交互的请求数。<br>和网站在线用户数的关联:1000 个同时在线用户数，可以估计并发数在 5% 到 15% 之间， 也就是同时并发数在 50~150 之间。</p><h2 id="1-3-吞吐量"><a href="#1-3-吞吐量" class="headerlink" title="1.3. 吞吐量"></a>1.3. 吞吐量</h2><h1 id="2-优化策略"><a href="#2-优化策略" class="headerlink" title="2. 优化策略"></a>2. 优化策略</h1><h2 id="2-1-避免过早优化"><a href="#2-1-避免过早优化" class="headerlink" title="2.1. 避免过早优化"></a>2.1. 避免过早优化</h2><p>不应该把大量的时间耗费在小的性能改进上，过早考虑优化是所有噩梦的根源。</p><p>所以，我们应该编写清晰，直接，易读和易理解的代码，真正的优化应该留到以后，等到 性能分析表明优化措施有巨大的收益时再进行。 但是过早优化，不表示我们应该编写已经知道的对性能不好的的代码结构。</p><h2 id="2-2-进行系统性能测试"><a href="#2-2-进行系统性能测试" class="headerlink" title="2.2. 进行系统性能测试"></a>2.2. 进行系统性能测试</h2><p>所有的性能调优，都有应该建立在性能测试的基础上，直觉很重要，但是要用数据说话，<br>可以推测，但是要通过测试求证</p><h2 id="2-3-寻找系统瓶颈，分而治之，逐步优化"><a href="#2-3-寻找系统瓶颈，分而治之，逐步优化" class="headerlink" title="2.3. 寻找系统瓶颈，分而治之，逐步优化"></a>2.3. 寻找系统瓶颈，分而治之，逐步优化</h2><p>性能测试后，对整个请求经历的各个环节进行分析，排查出现性能瓶颈的地方，定位问题，分析影响性能的的主要因素是什么?内存、磁盘 IO、网络、CPU，还是代码问题?架构设计不足?或者确实是系统资源不足?</p><h1 id="3-优化方案"><a href="#3-优化方案" class="headerlink" title="3. 优化方案"></a>3. 优化方案</h1><h2 id="3-1-前端优化"><a href="#3-1-前端优化" class="headerlink" title="3.1. 前端优化"></a>3.1. 前端优化</h2><h3 id="3-1-1-减少请求数"><a href="#3-1-1-减少请求数" class="headerlink" title="3.1.1. 减少请求数"></a>3.1.1. 减少请求数</h3><p>合并 CSS，Js，图片</p><h3 id="3-1-2-使用客户端缓冲"><a href="#3-1-2-使用客户端缓冲" class="headerlink" title="3.1.2. 使用客户端缓冲"></a>3.1.2. 使用客户端缓冲</h3><p>静态资源文件缓存在浏览器中，有关的属性 Cache-Control 和 Expires 如果文件发生了变化，需要更新，则通过改变文件名来解决。</p><h3 id="3-1-3-启用压缩"><a href="#3-1-3-启用压缩" class="headerlink" title="3.1.3. 启用压缩"></a>3.1.3. 启用压缩</h3><p>减少网络传输量，但会给浏览器和服务器带来性能的压力，需要权衡使用</p><h3 id="3-1-4-资源文件加载顺序"><a href="#3-1-4-资源文件加载顺序" class="headerlink" title="3.1.4. 资源文件加载顺序"></a>3.1.4. 资源文件加载顺序</h3><p>css 放在页面最上面，js 放在最下面</p><h3 id="3-1-5-减少-Cookie-传输"><a href="#3-1-5-减少-Cookie-传输" class="headerlink" title="3.1.5. 减少 Cookie 传输"></a>3.1.5. 减少 <strong>Cookie</strong> 传输</h3><p>cookie 包含在每次的请求和响应中，因此哪些数据写入 cookie 需要慎重考虑</p><h3 id="3-1-6-先给用户一个提示"><a href="#3-1-6-先给用户一个提示" class="headerlink" title="3.1.6. 先给用户一个提示"></a>3.1.6. 先给用户一个提示</h3><p>有时候在前端给用户一个提示，就能收到良好的效果。毕竟用户需要的是不要不理他。</p><h2 id="3-2-CDN-加速"><a href="#3-2-CDN-加速" class="headerlink" title="3.2. CDN 加速"></a>3.2. <strong>CDN</strong> 加速</h2><p>CDN，又称内容分发网络，本质仍然是一个缓存，而且是将数据缓存在用户最近的地方。</p><p>无法自行实现 CDN 的时候，可以考虑商用 CDN 服务。</p><h2 id="3-3-反向代理缓存"><a href="#3-3-反向代理缓存" class="headerlink" title="3.3. 反向代理缓存"></a>3.3. 反向代理缓存</h2><p>将静态资源文件缓存在反向代理服务器上，一般是 Nginx</p><h2 id="3-4-应用服务性能优化"><a href="#3-4-应用服务性能优化" class="headerlink" title="3.4. 应用服务性能优化"></a>3.4. 应用服务性能优化</h2><h3 id="3-4-1-缓存"><a href="#3-4-1-缓存" class="headerlink" title="3.4.1. 缓存"></a>3.4.1. 缓存</h3><h3 id="3-4-2-异步"><a href="#3-4-2-异步" class="headerlink" title="3.4.2. 异步"></a>3.4.2. 异步</h3><h3 id="3-4-3-集群"><a href="#3-4-3-集群" class="headerlink" title="3.4.3. 集群"></a>3.4.3. 集群</h3><h3 id="3-4-4-JVM-调优"><a href="#3-4-4-JVM-调优" class="headerlink" title="3.4.4. JVM 调优"></a>3.4.4. JVM 调优</h3><p>对 JVM 性能影响最大的是编译器。选择编译器是运行 java 程序首先要做的选择之一 热点编译的概念 对于程序来说，通常只有一部分代码被经常执行，这些关键代码被称为应用的热点，执行 的越多就认为是越热。将这些代码编译为本地机器特定的二进制码，可以有效提高应用性 能。</p><h4 id="3-4-4-1-选择编译器类型"><a href="#3-4-4-1-选择编译器类型" class="headerlink" title="3.4.4.1. 选择编译器类型"></a>3.4.4.1. 选择编译器类型</h4><p>-server，更晚编译，但是编译后的优化更多，性能更高</p><p>-client，很早就开始编译</p><p>-XX:+TieredCompilation，开启分层编译，可以让 jvm 在启动时启用 client 编译，随着代码 变热后再转为 server 编译。</p><p>缺省编译器取决于机器位数、操作系统和 CPU 数目。32 位的机器上，一般默认都是 client 编译，64 位机器上一般都是 server 编译，多核机器一般是 server 编译。</p><p>中的 mix mode 一般指编译时机:</p><p>-Xint 表示禁用 JIT，所有字节码都被解释执行，这个模式的速度最慢的。</p><p>-Xcomp 表示所有字节码都首先被编译成本地代码，然后再执行。 -Xmixed，默认模式，让 JIT 根据程序运行的情况，有选择地将某些代码编译成本地代 码。</p><p>-Xcomp 和 -Xmixed 到底谁的速度快，针对不同的程序可能有不同的结果，基本还是推荐用默认模式。</p><h4 id="3-4-4-2-代码缓存相关"><a href="#3-4-4-2-代码缓存相关" class="headerlink" title="3.4.4.2. 代码缓存相关"></a>3.4.4.2. 代码缓存相关</h4><p>在编译后，会有一个代码缓存保存编译后的代码，一旦这个缓存满了，jvm 将无法继续编译代码。</p><p>当 jvm 提示: CodeCache is full，就表示需要增加代码缓存大小。 –XX:ReservedCodeCacheSize&#x3D;N 可以用来调整这个大小。</p><p>方法内联</p><p>逃逸分析</p><h4 id="3-4-4-3-GC-调优"><a href="#3-4-4-3-GC-调优" class="headerlink" title="3.4.4.3. GC 调优"></a>3.4.4.3. GC 调优</h4><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-1%E3%80%81JVM-GC%E8%B0%83%E4%BC%98/" title="性能调优-进阶-1、JVM-GC调优">性能调优-进阶-1、JVM-GC调优</a><h3 id="3-4-5-代码"><a href="#3-4-5-代码" class="headerlink" title="3.4.5. 代码"></a>3.4.5. 代码</h3><h4 id="3-4-5-1-选择合适的数据结构"><a href="#3-4-5-1-选择合适的数据结构" class="headerlink" title="3.4.5.1. 选择合适的数据结构"></a>3.4.5.1. 选择合适的数据结构</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230426102618.png" alt="image.png"></p><h4 id="3-4-5-2-选择更优的算法"><a href="#3-4-5-2-选择更优的算法" class="headerlink" title="3.4.5.2. 选择更优的算法"></a>3.4.5.2. 选择更优的算法</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230426102637.png" alt="image.png"></p><h3 id="3-4-6-并发编程"><a href="#3-4-6-并发编程" class="headerlink" title="3.4.6. 并发编程"></a>3.4.6. 并发编程</h3><p>充分利用 <strong>CPU</strong> 多核， 实现线程安全的类，避免线程安全问题 同步下减少锁的竞争</p><h3 id="3-4-7-资源的复用"><a href="#3-4-7-资源的复用" class="headerlink" title="3.4.7. 资源的复用"></a>3.4.7. 资源的复用</h3><p>目的是减少开销很大的系统资源的创建和销毁，比如数据库连接，网络通信连接，线程资 源等等。</p><p>单例模式</p><p>池化技术</p><h3 id="3-4-8-反射优化"><a href="#3-4-8-反射优化" class="headerlink" title="3.4.8. 反射优化"></a>3.4.8. 反射优化</h3><a href="/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-1%E3%80%81%E5%8F%8D%E5%B0%84/" title="基本原理-1、反射">基本原理-1、反射</a><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-1%E3%80%81JVM-GC%E8%B0%83%E4%BC%98/" title="性能调优-进阶-1、JVM-GC调优">性能调优-进阶-1、JVM-GC调优</a><h3 id="3-4-9-序列化反序列化"><a href="#3-4-9-序列化反序列化" class="headerlink" title="3.4.9. 序列化反序列化"></a>3.4.9. 序列化反序列化</h3><a href="/2023/04/26/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-1%E3%80%81%E9%80%89%E5%9E%8B%E6%AF%94%E8%BE%83/" title="经验专题-序列化与反序列化-1、选型比较">经验专题-序列化与反序列化-1、选型比较</a><h2 id="3-5-数据库优化"><a href="#3-5-数据库优化" class="headerlink" title="3.5. 数据库优化"></a>3.5. 数据库优化</h2><a href="/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-8%E3%80%81SQL%E4%BC%98%E5%8C%96/" title="MySQL-8、SQL优化">MySQL-8、SQL优化</a><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>前端框架</title>
      <link href="/2023/04/24/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/"/>
      <url>/2023/04/24/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="瘦富客户端"><a href="#瘦富客户端" class="headerlink" title="瘦富客户端"></a>瘦富客户端</h1><p><a href="https://blog.csdn.net/ArvinSnow/article/details/87254362">https://blog.csdn.net/ArvinSnow/article/details/87254362</a><br><a href="https://blog.51cto.com/evan2008/107090">https://blog.51cto.com/evan2008/107090</a></p><h2 id="富客户端"><a href="#富客户端" class="headerlink" title="富客户端"></a>富客户端</h2><p>端技术充分利用本地机器的处理能力来处理数据，而不需要把某些数据发送到服务器处理，充分利用了本地机器的资源。</p><p>DWZ<br>FLEX</p><h2 id="瘦客户端"><a href="#瘦客户端" class="headerlink" title="瘦客户端"></a>瘦客户端</h2><p>然而随着应用向互联网上迁移，客户端数量剧增，维护和升级成为一件极其困难的事情。这个时候，人们想到了要给客户端“瘦身”，就是把业务层逻辑交给服务器端来完成，客户端仅仅完成人机交互界面和用于呈现运算结果。于是 B&#x2F;S 模式诞生了，客户端被浏览器所代替。</p><h1 id="Flex"><a href="#Flex" class="headerlink" title="Flex"></a>Flex</h1><p><a href="https://blog.csdn.net/ArvinSnow/article/details/87254362">https://blog.csdn.net/ArvinSnow/article/details/87254362</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230425083830.png" alt="image.png"></p><h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1><p><a href="https://www.slideshare.net/yiditushe/flex3">https://www.slideshare.net/yiditushe/flex3</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>APM</title>
      <link href="/2023/04/23/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/APM/"/>
      <url>/2023/04/23/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/APM/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="业务埋点"><a href="#业务埋点" class="headerlink" title="业务埋点"></a>业务埋点</h1><p>模仿业界的神策等，自研探针组件</p><h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1><p>[[APM 数据采集的两种方式深入对比——探针埋点VS互联数据_应用]]</p><p><a href="https://www.woshipm.com/data-analysis/4377422.html">https://www.woshipm.com/data-analysis/4377422.html</a></p><a href="/2022/12/17/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-3%E3%80%81%E5%AD%97%E8%8A%82%E7%A0%81%E5%A2%9E%E5%BC%BA/" title="基本原理-3、字节码增强">基本原理-3、字节码增强</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>NodeJs</title>
      <link href="/2023/04/22/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/NodeJs/"/>
      <url>/2023/04/22/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/NodeJs/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-背景知识"><a href="#1-背景知识" class="headerlink" title="1. 背景知识"></a>1. 背景知识</h1><h2 id="1-1-JavaScript-解析引擎"><a href="#1-1-JavaScript-解析引擎" class="headerlink" title="1.1. JavaScript 解析引擎"></a>1.1. JavaScript 解析引擎</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230423215257.png" alt="image.png"></p><h2 id="1-2-后端开发方式"><a href="#1-2-后端开发方式" class="headerlink" title="1.2. 后端开发方式"></a>1.2. 后端开发方式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424072532.png" alt="image.png"></p><h1 id="2-什么是-nodejs"><a href="#2-什么是-nodejs" class="headerlink" title="2. 什么是 nodejs"></a>2. 什么是 nodejs</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424072743.png" alt="image.png"></p><h1 id="3-模块化"><a href="#3-模块化" class="headerlink" title="3. 模块化"></a>3. 模块化</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424081213.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424081309.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424082015.png" alt="image.png"></p><h1 id="4-包管理工具"><a href="#4-包管理工具" class="headerlink" title="4. 包管理工具"></a>4. 包管理工具</h1><h2 id="4-1-多人协作"><a href="#4-1-多人协作" class="headerlink" title="4.1. 多人协作"></a>4.1. 多人协作</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424083131.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424083215.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424083234.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424083301.png" alt="image.png"></p><h1 id="5-Express"><a href="#5-Express" class="headerlink" title="5. Express"></a>5. Express</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424084916.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424084953.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424085044.png" alt="image.png"></p><h1 id="6-跨域问题"><a href="#6-跨域问题" class="headerlink" title="6. 跨域问题"></a>6. 跨域问题</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424092538.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424092614.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424092833.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424092856.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424093041.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424093134.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424093225.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424093457.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424101638.png" alt="image.png"></p><h1 id="7-Web-开发模式"><a href="#7-Web-开发模式" class="headerlink" title="7. Web 开发模式"></a>7. Web 开发模式</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424102427.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424102453.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424102537.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424102701.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424102837.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424102933.png" alt="image.png"></p><h1 id="8-身份认证"><a href="#8-身份认证" class="headerlink" title="8. 身份认证"></a>8. 身份认证</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424103151.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424103650.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424103915.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424104152.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230424104313.png" alt="image.png"></p><h1 id="9-实战经验"><a href="#9-实战经验" class="headerlink" title="9. 实战经验"></a>9. 实战经验</h1><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><h2 id="10-1-黑马"><a href="#10-1-黑马" class="headerlink" title="10.1. 黑马"></a>10.1. 黑马</h2><h3 id="10-1-1-视频"><a href="#10-1-1-视频" class="headerlink" title="10.1.1. 视频"></a>10.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1DS4y1Y7L4/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1DS4y1Y7L4/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="10-1-2-资料"><a href="#10-1-2-资料" class="headerlink" title="10.1.2. 资料"></a>10.1.2. 资料</h3><p>网络笔记<br><a href="https://blog.csdn.net/m0_52316372/article/details/124759435?spm=1001.2014.3001.5502">https://blog.csdn.net/m0_52316372/article/details/124759435?spm=1001.2014.3001.5502</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>拓展技术专题-云原生-1、云平台</title>
      <link href="/2023/04/17/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98-%E4%BA%91%E5%8E%9F%E7%94%9F-1%E3%80%81Kubernetes/"/>
      <url>/2023/04/17/008-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98/%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E4%B8%93%E9%A2%98-%E4%BA%91%E5%8E%9F%E7%94%9F-1%E3%80%81Kubernetes/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-特性"><a href="#1-特性" class="headerlink" title="1. 特性"></a>1. 特性</h1><p>大规模容器编排系统 kubernetes 具有以下特性：</p><ul><li><strong>服务发现和负载均衡</strong><br>Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器，如果进入容器的流量很大， Kubernetes 可以负载均衡并分配网络流量，从而使部署稳定。</li><li><strong>存储编排</strong><br>Kubernetes 允许你自动挂载你选择的存储系统，例如本地存储、公共云提供商等。</li><li><strong>自动部署和回滚</strong><br>你可以使用 Kubernetes 描述已部署容器的所需状态，它可以以受控的速率将实际状态 更改为期望状态。例如，你可以自动化 Kubernetes 来为你的部署创建新容器， 删除现有容器并将它们的所有资源用于新容器。</li><li><strong>自动完成装箱计算</strong><br>Kubernetes 允许你指定每个容器所需 CPU 和内存（RAM）。 当容器指定了资源请求时，Kubernetes 可以做出更好的决策来管理容器的资源。</li><li><strong>自我修复</strong><br>Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的 运行状况检查的容器，并且在准备好服务之前不将其通告给客户端。</li><li><strong>密钥与配置管理</strong><br>Kubernetes 允许你存储和管理敏感信息，例如密码、OAuth 令牌和 ssh 密钥。 你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置，也无需在堆栈配置中暴露密钥。</li></ul><h1 id="架构原理"><a href="#架构原理" class="headerlink" title="架构原理"></a>架构原理</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230514212227.png" alt="image.png"></p><h2 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h2><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><h2 id="3-1-尚硅谷"><a href="#3-1-尚硅谷" class="headerlink" title="3.1. 尚硅谷"></a>3.1. 尚硅谷</h2><h3 id="3-1-1-视频"><a href="#3-1-1-视频" class="headerlink" title="3.1.1. 视频"></a>3.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV13Q4y1C7hS?p=1&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13Q4y1C7hS?p=1&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="3-1-2-资料"><a href="#3-1-2-资料" class="headerlink" title="3.1.2. 资料"></a>3.1.2. 资料</h3><p><a href="https://www.yuque.com/leifengyang/oncloud/vfvmcd">https://www.yuque.com/leifengyang/oncloud/vfvmcd</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>经验专题-多租户-1、系统设计</title>
      <link href="/2023/04/17/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%A4%9A%E7%A7%9F%E6%88%B7-1%E3%80%81%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/"/>
      <url>/2023/04/17/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%A4%9A%E7%A7%9F%E6%88%B7-1%E3%80%81%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/</url>
      
        <content type="html"><![CDATA[<hr><p>1.web部分修改：</p><p>      a.在用户登录时，在线程变量（ThreadLocal）中记录租户的id</p><p>      b.修改 jdbc的实现 ：在提交sql时，从ThreadLocal中获取租户id,  添加sql 注释，把租户的schema 放到 注释中。例如：&#x2F;*!mycat :  schema &#x3D; test_01 *&#x2F; sql ;</p><p>  2.在db前面建立proxy层，代理所有web过来的数据库请求。proxy层是用mycat实现的，web提交的sql过来时在注释中指定schema, proxy层根据指定的schema 转发sql请求。</p><h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>经验专题-超时时间-1、超时问题汇总</title>
      <link href="/2023/04/15/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4-1%E3%80%81%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/"/>
      <url>/2023/04/15/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4-1%E3%80%81%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-RestTemplate"><a href="#1-RestTemplate" class="headerlink" title="1. RestTemplate"></a>1. RestTemplate</h1><p>HttpClient 内部有三个超时时间设置：连接池获取可用连接超时，连接超时，读取数据超时：</p><h2 id="1-1-ConnectionRequestTimeout"><a href="#1-1-ConnectionRequestTimeout" class="headerlink" title="1.1. ConnectionRequestTimeout"></a>1.1. ConnectionRequestTimeout</h2><p>从连接池中获取可用连接超时时间，设置从 connect Manager 获取 Connection 超时时间，单位毫秒</p><p>HttpClient 中的要用连接时尝试从连接池中获取，若是在等待了一定的时间后还没有获取到可用连接（比如连接池中没有空闲连接了）则会抛出获取连接超时异常。</p><h2 id="1-2-connectionTimeout"><a href="#1-2-connectionTimeout" class="headerlink" title="1.2. connectionTimeout"></a>1.2. connectionTimeout</h2><p><strong>连接目标超时 connectionTimeout，单位毫秒。</strong></p><p>指的是连接目标 url 的连接超时时间，即客服端发送请求到与目标 url 建立起连接的最大时间。如果在该时间范围内还没有建立起连接，则就抛出 connectionTimeOut 异常。</p><p>如测试的时候，将 url 改为一个不存在的 url：“<a href="http://test.com”/">http://test.com”</a> ，超时时间 3000ms 过后，系统报出异常：   org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms</p><h3 id="1-2-1-默认值-1："><a href="#1-2-1-默认值-1：" class="headerlink" title="1.2.1. 默认值 -1："></a>1.2.1. 默认值 -1：</h3><p>RestTemplate 默认是使用 SimpleClientHttpRequestFactory，内部是调用 jdk 的 HttpConnection，默认超时为 -1</p><h3 id="1-2-2-生产设置"><a href="#1-2-2-生产设置" class="headerlink" title="1.2.2. 生产设置"></a>1.2.2. 生产设置</h3><p>建议：<br>ConnectTimeout 大小视网络环境<br>ReadTimeout 大小视应用程序操作，适当大一点。<br>ConnectionRequestTimeout 不宜过长。</p><h2 id="1-3-socketTimeout"><a href="#1-3-socketTimeout" class="headerlink" title="1.3. socketTimeout"></a>1.3. socketTimeout</h2><p><strong>等待响应超时（读取数据超时）socketTimeout ，单位毫秒。</strong></p><p>连接上一个 url 后，获取 response 的返回等待时间 ，即在与目标 url 建立连接后，等待放回 response 的最大时间，在规定时间内没有返回响应的话就抛出 SocketTimeout。</p><p>测试时，将 socketTimeout 设置很短，会报等待响应超时。</p><h2 id="1-4-超时积压案例"><a href="#1-4-超时积压案例" class="headerlink" title="1.4. 超时积压案例"></a>1.4. 超时积压案例</h2><p><a href="https://www.jb51.net/article/235219.htm">https://www.jb51.net/article/235219.htm</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416113606.png" alt="image.png"></p><p><a href="https://leokongwq.github.io/2018/11/21/springboot-resttempate-timout.html">https://leokongwq.github.io/2018/11/21/springboot-resttempate-timout.html</a></p><h1 id="2-OpenFeign"><a href="#2-OpenFeign" class="headerlink" title="2. OpenFeign"></a>2. OpenFeign</h1><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230416-2153%%</span>❕ ^ptr2sl</p><h2 id="2-1-配置全局"><a href="#2-1-配置全局" class="headerlink" title="2.1. 配置全局"></a>2.1. 配置全局</h2><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></pre></td><td class="code"><pre><code class="hljs yaml"><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-comment">#建立连接所用的时间，适用于网络状况正常的情况下，两端连接所需要的时间,</span><br>        <span class="hljs-attr">ConnectTimeOut:</span> <span class="hljs-number">5000</span><br>        <span class="hljs-comment">#指建立连接后从服务端读取到可用资源所用的时间,默认为1s</span><br>        <span class="hljs-attr">ReadTimeOut:</span> <span class="hljs-number">5000</span><br></code></pre></td></tr></table></figure><h2 id="2-2-配置指定服务"><a href="#2-2-配置指定服务" class="headerlink" title="2.2. 配置指定服务"></a>2.2. 配置指定服务</h2><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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">feign:</span><br>  <span class="hljs-attr">client:</span><br>    <span class="hljs-attr">config:</span><br>      <span class="hljs-string">服务id:</span><br>        <span class="hljs-attr">connectTimeout:</span> <span class="hljs-number">1000</span><br>        <span class="hljs-attr">readTimeout:</span> <span class="hljs-number">2000</span><br>        <span class="hljs-attr">retryer:</span> <span class="hljs-string">feign.Retryer.Default</span> <span class="hljs-comment"># 重试</span><br></code></pre></td></tr></table></figure><h2 id="2-3-配置指定-contextId"><a href="#2-3-配置指定-contextId" class="headerlink" title="2.3. 配置指定 contextId"></a>2.3. 配置指定 contextId</h2><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></pre></td><td class="code"><pre><code class="hljs yaml"><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-comment"># 日志级别</span><br>        <span class="hljs-attr">loggerLevel:</span> <span class="hljs-string">full</span><br>        <span class="hljs-comment"># 超时设置</span><br>        <span class="hljs-attr">connectTimeout:</span> <span class="hljs-number">1500</span><br>        <span class="hljs-attr">readTimeout:</span> <span class="hljs-number">1500</span><br>      <span class="hljs-attr">payment-core:</span><br>        <span class="hljs-attr">connectTimeout:</span> <span class="hljs-number">5000</span><br>        <span class="hljs-attr">readTimeout:</span> <span class="hljs-number">5000</span><br>  <span class="hljs-comment"># 断路器</span><br>  <span class="hljs-attr">circuitbreaker:</span><br>    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><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></pre></td><td class="code"><pre><code class="hljs Java"><span class="hljs-meta">@FeignClient(name = &quot;payment-service&quot;, contextId = &quot;payment-core&quot;,  path = &quot;/payment&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">PaymentFeign</span> &#123;<br>    <span class="hljs-meta">@PostMapping(&quot;/create&quot;)</span><br>    PaymentVo <span class="hljs-title function_">create</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> <span class="hljs-meta">@Validated</span> PaymentDto paymentDto)</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="2-4-同时配置-Ribbon-和-OpenFeign-的超时时间"><a href="#2-4-同时配置-Ribbon-和-OpenFeign-的超时时间" class="headerlink" title="2.4. 同时配置 Ribbon 和 OpenFeign 的超时时间"></a>2.4. 同时配置 Ribbon 和 OpenFeign 的超时时间</h2><p>时间小的配置生效</p><h2 id="2-5-使用避坑"><a href="#2-5-使用避坑" class="headerlink" title="2.5. 使用避坑"></a>2.5. 使用避坑</h2><p><a href="https://www.modb.pro/db/103672">https://www.modb.pro/db/103672</a></p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-缓存-Redis-7、缓存一致性</title>
      <link href="/2023/04/14/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-3%E3%80%81%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7/"/>
      <url>/2023/04/14/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-3%E3%80%81%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-缓存更新策略"><a href="#1-缓存更新策略" class="headerlink" title="1. 缓存更新策略"></a>1. 缓存更新策略</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230502124144.png" alt="image.png"></p><p>低一致性需求，使用内存淘汰机制： <a href="/2023/03/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-2%E3%80%81%E8%BF%87%E6%9C%9F%E5%88%A0%E9%99%A4%E4%B8%8E%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5/" title="缓存-Redis-2、过期删除与淘汰策略">缓存-Redis-2、过期删除与淘汰策略</a></p><h1 id="2-缓存一致性方案"><a href="#2-缓存一致性方案" class="headerlink" title="2. 缓存一致性方案"></a>2. 缓存一致性方案</h1><p><span style="display:none">%%<br>▶18.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-2202%%</span>❕ ^m4wce9</p><h2 id="2-1-删除缓存还是更新缓存"><a href="#2-1-删除缓存还是更新缓存" class="headerlink" title="2.1. 删除缓存还是更新缓存"></a>2.1. 删除缓存还是更新缓存</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223151614.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223161240.png"></p><h2 id="2-2-先更新-DB-还是先删缓存"><a href="#2-2-先更新-DB-还是先删缓存" class="headerlink" title="2.2. 先更新 DB 还是先删缓存"></a>2.2. 先更新 DB 还是先删缓存</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223160925.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223162005.png"></p><h2 id="2-3-如何保证操作-DB-和删除缓存的原子性"><a href="#2-3-如何保证操作-DB-和删除缓存的原子性" class="headerlink" title="2.3. 如何保证操作 DB 和删除缓存的原子性"></a>2.3. 如何保证操作 DB 和删除缓存的原子性</h2><h2 id="2-4-总结-先更新数据库再删除缓存⭐️🔴"><a href="#2-4-总结-先更新数据库再删除缓存⭐️🔴" class="headerlink" title="2.4. 总结 - 先更新数据库再删除缓存⭐️🔴"></a>2.4. 总结 - 先更新数据库再删除缓存⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223160138.png"></p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315221917.png" alt="image.png"></p><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><h2 id="4-1-黑马"><a href="#4-1-黑马" class="headerlink" title="4.1. 黑马"></a>4.1. 黑马</h2><h3 id="4-1-1-视频"><a href="#4-1-1-视频" class="headerlink" title="4.1.1. 视频"></a>4.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?t=138.2&amp;p=38">https://www.bilibili.com/video/BV1cr4y1671t?t=138.2&amp;p=38</a></p><a href="/2022/12/21/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="缓存-Redis-1、基本原理">缓存-Redis-1、基本原理</a><h2 id="尚学堂"><a href="#尚学堂" class="headerlink" title="尚学堂"></a>尚学堂</h2><p><a href="https://www.bilibili.com/video/BV1JP411m7W2?t=1126.3&amp;p=84">https://www.bilibili.com/video/BV1JP411m7W2?t=1126.3&amp;p=84</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-0、JVM架构</title>
      <link href="/2023/04/08/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-0%E3%80%81JVM-%E6%80%BB%E4%BD%93%E4%BB%8B%E7%BB%8D/"/>
      <url>/2023/04/08/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-0%E3%80%81JVM-%E6%80%BB%E4%BD%93%E4%BB%8B%E7%BB%8D/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><h2 id="1-1-虚拟机"><a href="#1-1-虚拟机" class="headerlink" title="1.1. 虚拟机"></a>1.1. <strong>虚拟机</strong></h2><p>所谓虚拟机（Virtual Machine），就是一台虚拟的计算机。它是一款软件，用来执行一系列虚拟计算机指令。大体上，虚拟机可以分为系统虚拟机和程序虚拟机。</p><ul><li>大名鼎鼎的 Visual Box，Mware 就属于系统虚拟机，它们完全是对物理计算机的仿真，提供了一个可运行完整操作系统的软件平台。</li><li><span style="background-color:#ff00ff"> 程序虚拟机的典型代表就是 Java 虚拟机</span>，它专门为执行单个计算机程序而设计，在 Java 虚拟机中执行的指令我们称为 Java 字节码指令。</li></ul><h2 id="1-2-Java-虚拟机"><a href="#1-2-Java-虚拟机" class="headerlink" title="1.2. Java 虚拟机"></a>1.2. <strong>Java 虚拟机</strong></h2><p>Java 虚拟机是一台<span style="background-color:#ff00ff">执行 Java 字节码的虚拟计算机</span>，它拥有独立的运行机制，其运行的 Java 字节码也未必由 Java 语言编译而成。</p><h3 id="1-2-1-作用"><a href="#1-2-1-作用" class="headerlink" title="1.2.1. 作用"></a>1.2.1. 作用</h3><p>Java 虚拟机就是二进制字节码的运行环境，负责装载字节码到其内部，解释&#x2F;编译为对应平台上的机器指令执行。每一条 Java 指令，Java 虚拟机规范中都有详细定义，如怎么取操作数，怎么处理操作数，处理结果放在哪里。</p><h3 id="1-2-2-特点"><a href="#1-2-2-特点" class="headerlink" title="1.2.2. 特点"></a>1.2.2. 特点</h3><ul><li>一次编译，到处运行</li><li>自动内存管理</li><li>自动垃圾回收功能</li></ul><h3 id="1-2-3-位置"><a href="#1-2-3-位置" class="headerlink" title="1.2.3. 位置"></a>1.2.3. <strong>位置</strong></h3><p>  JVM 是运行在操作系统之上的，它与硬件没有直接的交互</p><h3 id="1-2-4-分类"><a href="#1-2-4-分类" class="headerlink" title="1.2.4. 分类"></a>1.2.4. 分类</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230510175342.png" alt="image.png"></p><h1 id="2-JVM-架构"><a href="#2-JVM-架构" class="headerlink" title="2. JVM 架构"></a>2. JVM 架构</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230409111757.jpg" alt="第02章_JVM架构-中.jpg"></p><h1 id="3-运行流程"><a href="#3-运行流程" class="headerlink" title="3. 运行流程"></a>3. 运行流程</h1><h2 id="3-1-类加载子系统"><a href="#3-1-类加载子系统" class="headerlink" title="3.1. 类加载子系统"></a>3.1. 类加载子系统</h2><p>类加载子系统通过 <strong>启动类加载器</strong>、<strong>扩展类加载器</strong>、<strong>应用类加载器</strong> 通过双亲委派机制将各自负责的路径下的编译好的.class 文件，经过加载、链接、初始化 3 步，装载到运行时数据区。它的主要功能是查找并验证类文件、完成相关内存空间的分配和属性赋值。分配空间的话，比如加载的类信息存放于一块称为方法区的内存空间。除了类的信息外，方法区中还会存放运行时常量池信息，可能还包括字符串字面量和数字常量。属性赋值：静态变量只是在准备阶段赋初始值，在初始化阶段赋定义值；而常量有编译期优化，赋初始值在编译期就已完成，赋定义值在准备阶段完成。</p><h2 id="3-2-运行时数据区"><a href="#3-2-运行时数据区" class="headerlink" title="3.2. 运行时数据区"></a>3.2. 运行时数据区</h2><p>类文件加载到内存之后由运行时数据区来完成数据存储和数据交换。运行时数据区又分为线程共享内存区和线程隔离内存区。线程共享内存区包括方法区和堆区，它们是程序员能够通过编写代码直接操作的内存区，而线程隔离内存区包括栈区、程序计数器和本地方法栈，它们是完全由 JVM 来调度的内存区域。</p><h3 id="3-2-1-方法区"><a href="#3-2-1-方法区" class="headerlink" title="3.2.1. 方法区"></a>3.2.1. 方法区</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230510191812.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230510191834.png" alt="image.png"></p><h3 id="3-2-2-堆"><a href="#3-2-2-堆" class="headerlink" title="3.2.2. 堆"></a>3.2.2. 堆</h3><p>而堆区呢，主要是用来存储 Java 对象的实例，也就是我们 new 的类都存在堆区。</p><h3 id="3-2-3-栈"><a href="#3-2-3-栈" class="headerlink" title="3.2.3. 栈"></a>3.2.3. 栈</h3><p>栈区是通过线程的方式运行来加载各种方法。</p><h3 id="3-2-4-程序计数器"><a href="#3-2-4-程序计数器" class="headerlink" title="3.2.4. 程序计数器"></a>3.2.4. 程序计数器</h3><h3 id="3-2-5-本地方法栈"><a href="#3-2-5-本地方法栈" class="headerlink" title="3.2.5. 本地方法栈"></a>3.2.5. 本地方法栈</h3><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试专题-7、八股文</title>
      <link href="/2023/03/31/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-7%E3%80%81%E5%85%AB%E8%82%A1%E6%96%87/"/>
      <url>/2023/03/31/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-7%E3%80%81%E5%85%AB%E8%82%A1%E6%96%87/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-死锁"><a href="#1-死锁" class="headerlink" title="1. 死锁"></a>1. 死锁</h1><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230411-2139%%</span>❕ ^mpr2re</p><p>死锁，简单来说就是两个或者两个以上的线程在执行的过程中，<span style="background-color:#ff00ff">争夺同一个共享资源造成的相互等待</span>的现象。 如果没有外部干预，线程会一直阻塞无法往下执行，这些一直处于相互等待资源的线程就称为死锁线程。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230411214209.png" alt="image.png"></p><p><strong>导致死锁的条件有四个，这四个条件同时满足就会产生死锁：</strong></p><ol><li>互斥条件，共享资源 X 和 Y 只能被一个线程占用；</li><li>请求和保持条件，线程 T1 已经取得共享资源 X，在等待共享资源 Y 的时候，不释放共享资源 X；</li><li>不可抢占条件，其他线程不能强行抢占线程 T1 占有的资源；</li><li>循环等待条件，线程 T1 等待线程 T2 占有的资源，线程 T2 等待线程 T1 占有的资源，就是循环等待。</li></ol><p>导致死锁之后，只能通过人工干预来解决，比如重启服务，或者杀掉某个线程。所以，只能在写代码的时候，去规避可能出现的死锁问题。<br>按照死锁发生的四个条件，只需要破坏其中的任何一个，就可以解决，但是，互斥条件是没办法破坏的，因为这是互斥锁的基本约束，其他三方条件都有办法来破坏：</p><ol><li><p>对于“请求和保持”这个条件，我们可以<span style="background-color:#ff00ff">一次性申请所有的资源</span>，这样就不存在等待了。</p></li><li><p>对于“不可抢占”这个条件，<span style="background-color:#ff00ff">加入超时机制</span>，占用部分资源的线程进一步申请其他资源时，如果申请不到，可以<span style="background-color:#ff00ff">主动释放它占有的资源</span>，这样不可抢占这个条件就破坏掉了。</p><p>并发 Lock 实现，如 ReentrantLock 还支持非阻塞式的获取锁操作 tryLock()，这是一个插队行为（barging），并不在乎等待的公平性，如果执行时对象恰好没有被独占，则直接获取锁。有时，我们希望条件允许就尝试插队，不然就按照现有公平性规则等待，一般采用下面的方法：</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">if (lock.tryLock() || lock.tryLock(timeout, unit)) &#123;  // ...   &#125; <br></code></pre></td></tr></table></figure><ol start="3"><li>对于“循环等待”这个条件，可以靠<span style="background-color:#ff00ff">按序申请资源来预防</span>。所谓按序申请，是指资源是有线性顺序的，申请的时候可以先申请资源序号小的，再申请资源序号大的，这样线性化后自然就不存在循环了。</li><li>尽量避免使用多个锁</li></ol><h2 id="1-1-资料"><a href="#1-1-资料" class="headerlink" title="1.1. 资料"></a>1.1. 资料</h2><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">003</span>-并发编程专题/<span class="hljs-number">62</span>-Java并发编程实战/<span class="hljs-number">03</span>-第一部分：并发理论基础 (<span class="hljs-number">13</span>讲)<br></code></pre></td></tr></table></figure><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">001</span>-基础知识专题/<span class="hljs-number">02</span>-Java核心技术<span class="hljs-number">36</span>讲/<span class="hljs-number">03</span>-模块二 Java进阶 (<span class="hljs-number">16</span>讲)<br></code></pre></td></tr></table></figure><h1 id="2-RabbitMQ-如何实现高可用"><a href="#2-RabbitMQ-如何实现高可用" class="headerlink" title="2. RabbitMQ 如何实现高可用"></a>2. RabbitMQ 如何实现高可用</h1><h2 id="2-1-普通集群"><a href="#2-1-普通集群" class="headerlink" title="2.1. 普通集群"></a>2.1. 普通集群</h2><p>这种集群模式下，各个节点只同步元数据，不同步队列中的消息。 其中元数据包含队列的名称、交换机名称及属性、交换机与队列的绑定关系等。 当我们发送消息和消费消息的时候，不管请求发送到 RabbitMQ 集群的哪个节点。 最终都会通过元数据定位到队列所在的节点去存储以及拉取数据。 很显然，这种集群方式并不能保证 Queue 的高可用，因为一旦 Queue 所在的节点挂了，那么这个 Queue 的消息就没办法访问了。 它的好处是通过多个节点分担了流量的压力，提升了消息的吞吐能力。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230411225054.png" alt="image.png"></p><h2 id="2-2-镜像集群"><a href="#2-2-镜像集群" class="headerlink" title="2.2. 镜像集群"></a>2.2. 镜像集群</h2><p>它和普通集群的区别在于，镜像集群中 Queue 的数据会在 RabbitMQ 集群的每 个节点存储一份。一旦任意一个节点发生故障，其他节点仍然可以继续提供服务。 所以这种集群模式实现了真正意义上的高可用。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230411225245.png" alt="image.png"><br>最后，在镜像集群的模式下，我们可以通过 Keepalived+HAProxy 来实现 RabbitMQ 集群的负载均衡。 其中： HAProxy 是一个能支持四层和七层的负载均衡器，可以实现对 RabbitMQ 集群 的负载均衡同时为了避免 HAProxy 的单点故障，可以再增加 Keepalived 实现 HAProxy 的主备，如果 HAProxy 主节点出现故障那么备份节点就会接管主节点 提供服务。 Keepalived 提供了一个虚拟 IP，业务只需要连接到虚拟 IP 即可。</p><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230411-2007%%</span>❕ ^9xqirb</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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">011</span>-面试专题/20w字面试文档<br></code></pre></td></tr></table></figure><h2 id="3-1-跟着-MIC-学架构"><a href="#3-1-跟着-MIC-学架构" class="headerlink" title="3.1. 跟着 MIC 学架构"></a>3.1. 跟着 MIC 学架构</h2><h3 id="3-1-1-视频"><a href="#3-1-1-视频" class="headerlink" title="3.1.1. 视频"></a>3.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1nG411b7Uk/?spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1nG411b7Uk/?spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="3-1-2-资料"><a href="#3-1-2-资料" class="headerlink" title="3.1.2. 资料"></a>3.1.2. 资料</h3><p>[[Java程序员求职突击手册.doc]]</p><p>[[MIC老师最新面试文档.pdf]]</p><h2 id="3-2-Tom-谈架构"><a href="#3-2-Tom-谈架构" class="headerlink" title="3.2. Tom 谈架构"></a>3.2. Tom 谈架构</h2><h3 id="3-2-1-视频"><a href="#3-2-1-视频" class="headerlink" title="3.2.1. 视频"></a>3.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1me4y1h7sD/?spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1me4y1h7sD/?spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="3-2-2-资料"><a href="#3-2-2-资料" class="headerlink" title="3.2.2. 资料"></a>3.2.2. 资料</h3><p>[[Tom老师面试资料文档.pdf]]</p><h2 id="3-3-其他文档资料"><a href="#3-3-其他文档资料" class="headerlink" title="3.3. 其他文档资料"></a>3.3. 其他文档资料</h2><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">011</span>-面试专题/部分Java面试突击资料/JAVA面试核心知识点整理(<span class="hljs-number">283</span>页).pdf<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-JDK-1、SPI</title>
      <link href="/2023/03/27/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/JDK-1%E3%80%81SPI/"/>
      <url>/2023/03/27/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/JDK-1%E3%80%81SPI/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-使用方法"><a href="#1-使用方法" class="headerlink" title="1. 使用方法"></a>1. 使用方法</h1><h2 id="1-1-约定"><a href="#1-1-约定" class="headerlink" title="1.1. 约定"></a>1.1. 约定</h2><p>Java SPI 就是这样做的，约定在 Classpath 下的 META-INF&#x2F;services&#x2F; 目录里创建一个 <strong>以<span style="background-color:#ff00ff">服务接口</span>命名的文件</strong>，然后 **文件里面记录的是此 jar 包提供的<span style="background-color:#ff00ff">具体实现类的全限定名</span>**。</p><p>这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包的 META-INF&#x2F;services&#x2F; 目录，再根据接口名找到文件，然后读取文件里面的内容去进行实现类的加载与实例化。</p><h2 id="1-2-示例"><a href="#1-2-示例" class="headerlink" title="1.2. 示例"></a>1.2. 示例</h2><h3 id="1-2-1-DriverManager-MySQL"><a href="#1-2-1-DriverManager-MySQL" class="headerlink" title="1.2.1. DriverManager-MySQL"></a>1.2.1. DriverManager-MySQL</h3><p>  比如我们看下 MySQL 是怎么做的。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330134524.png" alt="image.png"></p><p>再来看一下文件里面的内容。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330134540.png" alt="image.png"></p><h3 id="1-2-2-SpringMVC"><a href="#1-2-2-SpringMVC" class="headerlink" title="1.2.2. SpringMVC"></a>1.2.2. SpringMVC</h3><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/" title="Spring-4、SpringMVC">Spring-4、SpringMVC</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221225700.png" alt="image.png"></p><h1 id="2-源码原理"><a href="#2-源码原理" class="headerlink" title="2. 源码原理"></a>2. 源码原理</h1><p><span style="background-color:#ffff00">以下源码分析基于 JDK8</span></p><h2 id="2-1-Java-SPI-入口-ServiceLoader"><a href="#2-1-Java-SPI-入口-ServiceLoader" class="headerlink" title="2.1. Java SPI 入口-ServiceLoader"></a>2.1. Java SPI 入口-ServiceLoader</h2><p><code>ServiceLoader.load()</code></p><h2 id="2-2-运行逻辑"><a href="#2-2-运行逻辑" class="headerlink" title="2.2. 运行逻辑"></a>2.2. 运行逻辑</h2><p>在 ServiceLoader.load() 方法中，首先会尝试获取当前使用的 ClassLoader（获取当前线程绑定的 ClassLoader，查找失败后使用 SystemClassLoader），然后调用 reload() 方法，调用关系如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230328121539.png" alt="image.png"></p><p>在 reload() 方法中，首先会清理 providers 缓存（LinkedHashMap 类型的集合），&#x3D;&#x3D;该缓存用来记录 ServiceLoader 创建的实现对象&#x3D;&#x3D;，<span style="background-color:#ff00ff">其中 Key 为实现类的完整类名，Value 为实现类的对象</span>。之后创建 <code>LazyIterator</code> 迭代器，用于读取 SPI 配置文件并实例化实现类对象。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401092412.png" alt="image.png"></p><p>在前面的示例中，main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法：hasNext() 方法和 next() 方法。这里的 LazyIterator 中的 next() 方法最终调用的是其 nextService() 方法，hasNext() 方法最终调用的是 hasNextService() 方法，调用关系如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401095304.png" alt="image.png"></p><h3 id="2-2-1-hasNextService-查找"><a href="#2-2-1-hasNextService-查找" class="headerlink" title="2.2.1. hasNextService- 查找"></a>2.2.1. hasNextService- 查找</h3><p>首先来看 LazyIterator.hasNextService() 方法，该方法主要 <strong>负责查找 META-INF&#x2F;services 目录下的 SPI 配置文件</strong>，并进行遍历。</p><h3 id="2-2-2-nextService-实例化"><a href="#2-2-2-nextService-实例化" class="headerlink" title="2.2.2. nextService- 实例化"></a>2.2.2. nextService- 实例化</h3><p>在 hasNextService() 方法中完成 SPI 配置文件的解析之后，再来看 LazyIterator.nextService() 方法，该方法 <strong>负责实例化 hasNextService() 方法读取到的实现类</strong>，其中会将实例化的对象放到 providers 集合中缓存起来</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230328123939.png" alt="image.png"></p><h2 id="2-3-触发逻辑"><a href="#2-3-触发逻辑" class="headerlink" title="2.3. 触发逻辑"></a>2.3. 触发逻辑</h2><h3 id="2-3-1-DriverManager"><a href="#2-3-1-DriverManager" class="headerlink" title="2.3.1. DriverManager"></a>2.3.1. DriverManager</h3><p>静态代码块中的 <code>loadInitialDrivers</code> 方法中或者新版本的 <code>ensureDriversInitialized</code> 方法中，调用 <code>ServiceLoader.load(Driver.class)</code></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/" title="性能调优-基础-6、JVM-类装载子系统">性能调优-基础-6、JVM-类装载子系统</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401081243.png" alt="image.png"></p><h1 id="3-存在问题"><a href="#3-存在问题" class="headerlink" title="3. 存在问题"></a>3. 存在问题</h1><p>相信大家一眼就能看出来，Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且 <strong>将实现类全部实例化</strong>，假设一个实现类初始化过程比较消耗资源且耗时，但是你的代码里面又用不上它，这就产生了资源的浪费。</p><p>所以说 Java SPI 无法按需加载实现类。</p><h1 id="4-Java-中的-SPI"><a href="#4-Java-中的-SPI" class="headerlink" title="4. Java 中的 SPI"></a>4. Java 中的 SPI</h1><h2 id="4-1-SpringMVC"><a href="#4-1-SpringMVC" class="headerlink" title="4.1. SpringMVC"></a>4.1. SpringMVC</h2><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/" title="Spring-4、SpringMVC">Spring-4、SpringMVC</a><h2 id="4-2-DubboSPI"><a href="#4-2-DubboSPI" class="headerlink" title="4.2. DubboSPI"></a>4.2. DubboSPI</h2><a href="/2023/03/27/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-14%E3%80%81Dubbo-SPI/" title="分布式专题-14、Dubbo-SPI">分布式专题-14、Dubbo-SPI</a><h2 id="4-3-SpringBoot"><a href="#4-3-SpringBoot" class="headerlink" title="4.3. SpringBoot"></a>4.3. SpringBoot</h2><a href="/2023/06/12/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/SpringBoot-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="SpringBoot-1、基本原理">SpringBoot-1、基本原理</a><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-源码原理"><a href="#6-1-源码原理" class="headerlink" title="6.1. 源码原理"></a>6.1. 源码原理</h2><p>[[03 Dubbo SPI 精析，接口实现两极反转（上）.md]]<br><a href="https://juejin.cn/post/6872138926216511501#heading-2">https://juejin.cn/post/6872138926216511501#heading-2</a></p><h2 id="6-2-使用方法示例代码"><a href="#6-2-使用方法示例代码" class="headerlink" title="6.2. 使用方法示例代码"></a>6.2. 使用方法示例代码</h2><a href="/2023/03/27/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-14%E3%80%81Dubbo-SPI/" title="分布式专题-14、Dubbo-SPI">分布式专题-14、Dubbo-SPI</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题 -14、Dubbo-SPI</title>
      <link href="/2023/03/27/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-14%E3%80%81Dubbo-SPI/"/>
      <url>/2023/03/27/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-14%E3%80%81Dubbo-SPI/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-JDK-SPI"><a href="#1-JDK-SPI" class="headerlink" title="1. JDK-SPI"></a>1. JDK-SPI</h1><a href="/2023/03/27/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/JDK-1%E3%80%81SPI/" title="JDK-1、SPI">JDK-1、SPI</a><h2 id="1-1-优点"><a href="#1-1-优点" class="headerlink" title="1.1. 优点"></a>1.1. 优点</h2><p>使用 Java SPI 机制的优势是实现解耦，使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离，而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。</p><h2 id="1-2-缺点"><a href="#1-2-缺点" class="headerlink" title="1.2. 缺点"></a>1.2. 缺点</h2><ol><li>JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现，也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类，它也被加载并实例化了，这就造成了浪费;</li><li>获取某个实现类的方式不够灵活，只能通过 Iterator 形式获取，不能根据某个参数来获取对应的实现类；</li></ol><h1 id="2-DubboSPI"><a href="#2-DubboSPI" class="headerlink" title="2. DubboSPI"></a>2. DubboSPI</h1><h2 id="2-1-改进之处"><a href="#2-1-改进之处" class="headerlink" title="2.1. 改进之处"></a>2.1. 改进之处</h2><p>　SPI(Service Provider Interface) 是服务发现机制，Dubbo 没有使用 jdk SPI 而对其增强和扩展：</p><ol><li>按需加载，Dubbo SPI 配置文件采用 KV 格式存储，key 被称为扩展名，当我们在为一个接口查找具体实现类时，可以指定扩展名来选择相应的扩展实现，只实例化这一个扩展实现即可，无须实例化 SPI 配置文件中的其他扩展实现类，避免资源浪费，此外通过 KV 格式的 SPI 配置文件，当我们使用的一个扩展实现类所在的 jar 包没有引入到项目中时，Dubbo SPI 在抛出异常的时候，会携带该扩展名信息，而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度，提高了排查问题的效率。；</li><li>增加扩展类的 IOC 能力，Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类，而是在此基础上更进一步，如果该扩展类的属性依赖其他对象，则 Dubbo 会自动的完成该依赖对象的注入功能；</li><li>增加扩展类的 AOP 能力，Dubbo 扩展能力会自动的发现扩展类的包装类，完成包装类的构造，增强扩展类的功能；</li></ol><h2 id="2-2-DubboSPI-规范"><a href="#2-2-DubboSPI-规范" class="headerlink" title="2.2. DubboSPI 规范"></a>2.2. DubboSPI 规范</h2><ul><li>编写接口，接口必须加@SPI 注解，代表它是一个可扩展的接口。</li><li>编写实现类。</li><li>在 ClassPath 下的 <code>META-INF/dubbo</code> 目录创建以接口全限定名命名的文件，文件内容为 Key&#x3D;Value 格式，Key 是扩展点的名称，Value 是扩展点实现类的全限定名。</li><li>通过 ExtensionLoader 类获取扩展点实现。</li></ul><p>Dubbo 默认会扫描 <code>META-INF/services</code>、<code>META-INF/dubbo</code>、<code>META-INF/dubbo/internal</code> 三个目录下的配置，第一个是为了兼容 Java SPI，第三个是 Dubbo 内部使用的扩展点。 ​</p><p>Dubbo SPI 支持四种特性：自动包装、自动注入、自适应、自动激活。</p><h2 id="2-3-重要组件"><a href="#2-3-重要组件" class="headerlink" title="2.3. 重要组件"></a>2.3. 重要组件</h2><h3 id="2-3-1-ExtensionLoader"><a href="#2-3-1-ExtensionLoader" class="headerlink" title="2.3.1. ExtensionLoader"></a>2.3.1. ExtensionLoader</h3><p>ExtensionLoader 位于 dubbo-common 模块中的 extension 包中，<span style="background-color:#ff00ff">功能类似于 JDK SPI 中的 java.util.ServiceLoader</span>。Dubbo SPI 的核心逻辑几乎都封装在 ExtensionLoader 之中（&#x3D;&#x3D;其中就包括 @SPI 注解的处理逻辑&#x3D;&#x3D;），其使用方式如下所示：</p><figure class="highlight cpp"><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 cpp">Protocol protocol = ExtensionLoader <br>.<span class="hljs-built_in">getExtensionLoader</span>(Protocol.<span class="hljs-keyword">class</span>).<span class="hljs-built_in">getExtension</span>(<span class="hljs-string">&quot;dubbo&quot;</span>);<br></code></pre></td></tr></table></figure><p><strong>ExtensionLoader 的实例字段</strong></p><p><code>type（Class&lt;?&gt;类型）</code>：当前 ExtensionLoader 实例负责加载扩展接口。<br><code>cachedDefaultName（String类型）</code>：记录了 type 这个扩展接口上 @SPI 注解的 value 值，也就是默认扩展名。<br><code>cachedNames（ConcurrentMap&lt;Class&lt;?&gt;, String&gt;类型）</code>：缓存了该 ExtensionLoader 加载的扩展实现类与扩展名之间的映射关系。<br><code>cachedClasses（Holder&lt;Map&lt;String, Class&lt;?&gt;&gt;&gt;类型）</code>：缓存了该 ExtensionLoader 加载的<span style="background-color:#ff00ff">扩展名与扩展实现类之间的映射关系</span>。cachedNames 集合的反向关系缓存。<br><code>cachedInstances（ConcurrentMap&lt;String, Holder&gt;类型）</code>：<strong>缓存了该 ExtensionLoader 加载的扩展名与扩展实现对象之间的映射关系</strong>。</p><h3 id="2-3-2-strategies"><a href="#2-3-2-strategies" class="headerlink" title="2.3.2. strategies"></a>2.3.2. strategies</h3><p>LoadingStrategy 接口有三个实现（通过 JDK SPI 方式加载的），如下图所示，分别对应前面介绍的三个 Dubbo SPI 配置文件所在的目录，且都继承了 Prioritized 这个优先级接口，默认优先级是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs undefined">DubboInternalLoadingStrategy &gt; DubboLoadingStrategy &gt; ServicesLoadingStrateg<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401112802.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401112343.png" alt="image.png"></p><h3 id="2-3-3-EXTENSION-LOADERS"><a href="#2-3-3-EXTENSION-LOADERS" class="headerlink" title="2.3.3. EXTENSION_LOADERS"></a>2.3.3. EXTENSION_LOADERS</h3><p><code>ConcurrentMap&lt;Class, ExtensionLoader&gt;</code> 类型：Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例，该集合缓存了全部 ExtensionLoader 实例，其中的 <span style="background-color:#ff00ff">Key 为扩展接口，Value 为加载其扩展实现的 ExtensionLoader 实例</span>。</p><h3 id="2-3-4-EXTENSION-INSTANCES"><a href="#2-3-4-EXTENSION-INSTANCES" class="headerlink" title="2.3.4. EXTENSION_INSTANCES"></a>2.3.4. EXTENSION_INSTANCES</h3><p><code>ConcurrentMap&lt;Class&lt;?&gt;, Object&gt;</code> 类型：该集合缓存了<span style="background-color:#ff00ff">扩展实现类与其实例对象的映射关系</span>。比如在前文示例中，Key 为 Class，Value 为 DubboProtocol 对象。</p><h2 id="2-4-运行逻辑"><a href="#2-4-运行逻辑" class="headerlink" title="2.4. 运行逻辑"></a>2.4. 运行逻辑</h2><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230407-1737%%</span>❕ ^z97rrg</p><h3 id="2-4-1-getExtensionLoader"><a href="#2-4-1-getExtensionLoader" class="headerlink" title="2.4.1. getExtensionLoader"></a>2.4.1. getExtensionLoader</h3><p><code>ExtensionLoader.getExtensionLoader</code> 的方法，完成 ExtensionLoader 创建，该方法内部完成一些关于 type 的校验，然后根据 <code>EXTENSION_LOADERS</code> 缓存获取 ExtensionLoader 的实例，<code>EXTENSION_LOADERS</code> 集合缓存了<span style="background-color:#ff00ff">全部 ExtensionLoader 实例</span>，其中的 Key 为扩展接口，Value 为加载其扩展实现的 ExtensionLoader 实例，如果不存在缓存的时候，则主动创建一个并放入 <code>EXTENSION_LOADERS</code>。</p><h3 id="2-4-2-getExtension"><a href="#2-4-2-getExtension" class="headerlink" title="2.4.2. getExtension"></a>2.4.2. getExtension</h3><p>得到接口对应的 ExtensionLoader 对象之后会调用其 getExtension() 方法，根据传入的扩展名称从 cachedInstances 缓存中查找扩展实现的实例，最终将其实例化后返回<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401122532.png" alt="image.png"></p><h3 id="2-4-3-createExtension"><a href="#2-4-3-createExtension" class="headerlink" title="2.4.3. createExtension"></a>2.4.3. createExtension</h3><p>在 createExtension() 方法中完成了 SPI 配置文件的查找以及相应扩展实现类的实例化，同时还实现了自动装配以及自动 Wrapper 包装等功能。其核心流程是这样的：</p><ol><li>获取 cachedClasses 缓存，根据扩展名从 <code>cachedClasses</code> 缓存中<span style="background-color:#ff00ff">获取扩展实现类</span>。如果 cachedClasses 未初始化，则会扫描前面介绍的三个 SPI 目录获取查找相应的 SPI 配置文件，然后加载其中的扩展实现类，最后将扩展名和扩展实现类的映射关系记录到 cachedClasses 缓存中。这部分逻辑在 loadExtensionClasses() 和 loadDirectory() 方法中。</li><li><span style="background-color:#ff00ff">根据扩展实现类</span>从 <code>EXTENSION_INSTANCES</code> 缓存中查找<span style="background-color:#ff00ff">相应的实例</span>。如果查找失败，会通过反射创建扩展实现对象。</li><li><strong>自动装配</strong> 扩展实现对象中的属性（即调用其 setter）。这里涉及 ExtensionFactory 以及自动装配的相关内容，本课时后面会进行详细介绍。</li><li><strong>自动包装</strong> 扩展实现对象。这里涉及 Wrapper 类以及自动包装特性的相关内容，本课时后面会进行详细介绍。</li><li>如果扩展实现类实现了 Lifecycle 接口，在 initExtension() 方法中会调用 initialize() 方法进行初始化。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401125101.png" alt="image.png"></p><h2 id="2-5-Adaptive"><a href="#2-5-Adaptive" class="headerlink" title="2.5. @Adaptive"></a>2.5. @Adaptive</h2><p><a href="https://blog.51cto.com/u_15288542/3030280">https://blog.51cto.com/u_15288542/3030280</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401125946.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230401125732.png" alt="image.png"><br>AdaptiveExtensionFactory 不实现任何具体的功能，而是用来适配 ExtensionFactory 的 SpiExtensionFactory 和 SpringExtensionFactory 这两种实现。AdaptiveExtensionFactory 会根据运行时的一些状态来选择具体调用 ExtensionFactory 的哪个实现。</p><p>@Adaptive 注解还可以加到接口方法之上，Dubbo 会动态生成适配器类。例如，Transporter 接口有两个被 @Adaptive 注解修饰的方法：</p><span style="display:none">- [ ] 🚩 - 待研究：https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Dubbo%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB%E4%B8%8E%E5%AE%9E%E6%88%98-%E5%AE%8C/04%20%20Dubbo%20SPI%20%E7%B2%BE%E6%9E%90%EF%BC%8C%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E4%B8%A4%E6%9E%81%E5%8F%8D%E8%BD%AC%EF%BC%88%E4%B8%8B%EF%BC%89.md - 🏡 2023-04-01 13:04</span>#todo<h3 id="2-5-1-标注类-将该类作为最佳适配类"><a href="#2-5-1-标注类-将该类作为最佳适配类" class="headerlink" title="2.5.1. 标注类 - 将该类作为最佳适配类"></a>2.5.1. 标注类 - 将该类作为最佳适配类</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230331185306.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230331185236.png" alt="image.png"></p><h3 id="2-5-2-标注方法-静态代理该方法"><a href="#2-5-2-标注方法-静态代理该方法" class="headerlink" title="2.5.2. 标注方法 - 静态代理该方法"></a>2.5.2. 标注方法 - 静态代理该方法</h3><p>@Adapter 标签标注在方法上可以对该方法进行静态代理<br>如果接口的某个实现类上标注了@Adapter 注解，将直接调用该实现类，而不会进行静态代理</p><h2 id="2-6-自动装配"><a href="#2-6-自动装配" class="headerlink" title="2.6. 自动装配"></a>2.6. 自动装配</h2><p>在 createExtension() 方法中我们看到，Dubbo SPI 在拿到扩展实现类的对象（以及 Wrapper 类的对象）之后，还会调用 injectExtension() 方法扫描其全部 setter 方法，并根据 setter 方法的名称以及参数的类型，加载相应的扩展实现，然后调用相应的 setter 方法填充属性，这就实现了 Dubbo SPI 的自动装配特性。简单来说，自动装配属性就是在加载一个扩展点的时候，将其依赖的扩展点一并加载，并进行装配。</p><h2 id="2-7-自动包装"><a href="#2-7-自动包装" class="headerlink" title="2.7. 自动包装"></a>2.7. 自动包装</h2><p>Dubbo 中的一个扩展接口可能有多个扩展实现类，这些扩展实现类可能会包含一些相同的逻辑，如果在每个实现类中都写一遍，那么这些重复代码就会变得很难维护。Dubbo 提供的自动包装特性，就可以解决这个问题。 Dubbo 将多个扩展实现类的公共逻辑，抽象到 Wrapper 类中，Wrapper 类与普通的扩展实现类一样，也实现了扩展接口，在获取真正的扩展实现对象时，在其外面包装一层 Wrapper 对象，你可以理解成一层装饰器。</p><h1 id="3-IOC"><a href="#3-IOC" class="headerlink" title="3. IOC"></a>3. IOC</h1><p>IOC：对应 injectExtension 方法 查找 set 方法，根据参数找到依赖对象则注入。</p><h1 id="4-AOP"><a href="#4-AOP" class="headerlink" title="4. AOP"></a>4. AOP</h1><p>AOP：对应 WrapperClass，包装类是因为一个扩展接口可能有多个扩展实现类，而这些扩展实现类会有一个相同的或者公共的逻辑，如果每个实现类都写一遍代码就重复了，并且比较不好维护。因此就搞了个包装类，Dubbo 里帮你自动包装，只需要某个扩展类的构造函数只有一个参数，并且是扩展接口类型，就会被判定为包装类，然后记录到配置文件，用来包装别的实现类。</p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-源码原理"><a href="#6-1-源码原理" class="headerlink" title="6.1. 源码原理"></a>6.1. 源码原理</h2><p><a href="https://blog.51cto.com/u_15288542/3030280">https://blog.51cto.com/u_15288542/3030280</a></p><p><a href="https://www.cnblogs.com/grimmjx/p/10970643.html">https://www.cnblogs.com/grimmjx/p/10970643.html</a></p><p><a href="https://www.cnblogs.com/wtzbk/p/16618487.html">https://www.cnblogs.com/wtzbk/p/16618487.html</a></p><p><a href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Dubbo%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB%E4%B8%8E%E5%AE%9E%E6%88%98-%E5%AE%8C/04%20%20Dubbo%20SPI%20%E7%B2%BE%E6%9E%90%EF%BC%8C%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E4%B8%A4%E6%9E%81%E5%8F%8D%E8%BD%AC%EF%BC%88%E4%B8%8B%EF%BC%89.md">04 Dubbo SPI 精析，接口实现两极反转（下）</a></p><p><a href="https://juejin.cn/post/7040443722101850148#heading-3">https://juejin.cn/post/7040443722101850148#heading-3</a></p><h2 id="6-2-使用方法"><a href="#6-2-使用方法" class="headerlink" title="6.2. 使用方法"></a>6.2. 使用方法</h2><h3 id="6-2-1-Dubbo-SPI-使用姿势"><a href="#6-2-1-Dubbo-SPI-使用姿势" class="headerlink" title="6.2.1. Dubbo SPI 使用姿势"></a>6.2.1. Dubbo SPI 使用姿势</h3><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230328-1213%%</span>❕ ^u1bn4l</p><p><a href="https://www.jianshu.com/p/de465d70f63f">https://www.jianshu.com/p/de465d70f63f</a><br>示例代码：</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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">013</span>-DemoCode/dubbo-demo<br></code></pre></td></tr></table></figure><p><a href="https://segmentfault.com/a/1190000040208433">https://segmentfault.com/a/1190000040208433</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Netty</title>
      <link href="/2023/03/27/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6-20%E3%80%81Netty/"/>
      <url>/2023/03/27/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6-20%E3%80%81Netty/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Netty-介绍"><a href="#1-Netty-介绍" class="headerlink" title="1. Netty 介绍"></a>1. Netty 介绍</h1><p>Netty 是一个<span style="background-color:#ff00ff">异步的、基于事件驱动的</span>网络应用框架，用于快速开发可维护、高性能的网络服务器和客户端。Netty 在 Java 网络应用框架中的地位就好比：Spring 框架在 JavaEE 开发中的地位。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230329105012.png" alt="image.png"></p><p>RocketMQ、ES、Dubbo、ZK</p><ol><li><span style="background-color:#ff00ff">Kafka 自己实现了网络模型做 RPC。底层基于 Java NIO，采用和 Netty 一样的 Reactor 线程模型。</span></li><li>Redis 也是自己的 IO 多路复用和事件分发器，原理类似</li></ol><a href="/2023/05/13/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/MQ-Kafka/" title="MQ-Kafka">MQ-Kafka</a><h2 id="1-1-IO"><a href="#1-1-IO" class="headerlink" title="1.1. IO"></a>1.1. IO</h2><p>[[IO多路复用]]</p><h2 id="1-2-原生-NIO-存在的问题"><a href="#1-2-原生-NIO-存在的问题" class="headerlink" title="1.2. 原生 NIO 存在的问题"></a>1.2. 原生 NIO 存在的问题</h2><ol><li>NIO 的类库和 API 繁杂，使用麻烦：需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。</li><li>需要具备其他的额外技能：要熟悉 Java 多线程编程，因为 NIO 编程涉及到 Reactor 模式，你必须对多线程和网络编程非常熟悉，才能编写出高质量的 NIO 程序。</li><li>开发工作量和难度都非常大：例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。</li><li>JDK NIO 的 Bug：例如臭名昭著的 Epoll Bug，它会导致 Selector 空轮询，最终导致 CPU100%。直到 JDK1.7 版本该问题仍旧存在，没有被根本解决。</li></ol><h2 id="1-3-Netty-的优势"><a href="#1-3-Netty-的优势" class="headerlink" title="1.3. Netty 的优势"></a>1.3. Netty 的优势</h2><ul><li>Netty vs NIO，工作量大，bug 多<ul><li>需要自己构建协议</li><li>解决 TCP 传输问题，如粘包、半包</li><li>epoll 空轮询导致 CPU 100%</li><li>对 API 进行增强，使之更易用，如 FastThreadLocal &#x3D;&gt; ThreadLocal，ByteBuf &#x3D;&gt; ByteBuffer</li></ul></li><li>Netty vs 其它网络应用框架<ul><li>Mina 由 apache 维护，将来 3.x 版本可能会有较大重构，破坏 API 向下兼容性，Netty 的开发迭代更迅速，API 更简洁、文档更优秀</li><li>久经考验，16 年，Netty 版本</li></ul></li></ul><h1 id="2-Epoll"><a href="#2-Epoll" class="headerlink" title="2. Epoll"></a>2. Epoll</h1><p>[[框架源码专题-Redis-4、原理进阶-数据类型-单线程多路复用#^tmrkhl]]</p><h1 id="3-三种-Reactor-网络模型⭐️🔴"><a href="#3-三种-Reactor-网络模型⭐️🔴" class="headerlink" title="3. 三种 Reactor 网络模型⭐️🔴"></a>3. 三种 Reactor 网络模型⭐️🔴</h1><p><a href="https://juejin.cn/post/6892687008552976398#heading-20">https://juejin.cn/post/6892687008552976398#heading-20</a><br><a href="https://www.cnblogs.com/alimayun/p/12509771.html">https://www.cnblogs.com/alimayun/p/12509771.html</a></p><h2 id="3-1-传统阻塞-I-x2F-O-服务模型-gt-每客户端-1-线程"><a href="#3-1-传统阻塞-I-x2F-O-服务模型-gt-每客户端-1-线程" class="headerlink" title="3.1. 传统阻塞 I&#x2F;O 服务模型-&gt;每客户端 1 线程"></a>3.1. 传统阻塞 I&#x2F;O 服务模型-&gt;每客户端 1 线程</h2><h3 id="3-1-1-工作原理图"><a href="#3-1-1-工作原理图" class="headerlink" title="3.1.1. 工作原理图"></a>3.1.1. 工作原理图</h3><p>黄色的框表示对象，蓝色的框表示线程<br>白色的框表示方法（API）</p><h3 id="3-1-2-模型特点"><a href="#3-1-2-模型特点" class="headerlink" title="3.1.2. 模型特点"></a>3.1.2. 模型特点</h3><p>采用阻塞 IO 模式获取输入的数据<br>每个连接都需要独立的线程完成数据的输入，业务处理，数据返回</p><h3 id="3-1-3-问题分析"><a href="#3-1-3-问题分析" class="headerlink" title="3.1.3. 问题分析"></a>3.1.3. 问题分析</h3><ol><li>当并发数很大，就会创建大量的线程，占用很大系统资源</li><li>连接创建后，如果当前线程暂时没有数据可读，该线程会阻塞在 Handler 对象中的 read 操作，导致上面的处理线程资源浪费</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230531081128.png" alt="image.png"></p><h2 id="3-2-单-Reactor-单线程-gt-连接应答和业务处理使用同一个线程"><a href="#3-2-单-Reactor-单线程-gt-连接应答和业务处理使用同一个线程" class="headerlink" title="3.2. 单 Reactor 单线程-&gt;连接应答和业务处理使用同一个线程"></a>3.2. 单 Reactor 单线程-&gt;连接应答和业务处理使用同一个线程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230328221134.png" alt="image.png"></p><h3 id="3-2-1-方案说明"><a href="#3-2-1-方案说明" class="headerlink" title="3.2.1. 方案说明"></a>3.2.1. 方案说明</h3><ol><li>Select 是前面 I&#x2F;O 复用模型介绍的标准网络编程 API，可以实现应用程序通过一个阻塞对象监听多路连接请求</li><li>Reactor 对象通过 Select 监控客户端请求事件，收到事件后通过 Dispatch 进行分发</li><li>如果是建立连接请求事件，则由 Acceptor 通过 Accept 处理连接请求，然后创建一个 Handler 对象处理连接完成后的后续业务处理</li><li>如果不是建立连接事件，则 Reactor 会分发调用连接对应的 Handler 来响应</li><li>Handler 会完成 Read → 业务处理 → Send 的完整业务流程</li></ol><p>结合实例：服务器端<span style="background-color:#ff00ff">用一个线程</span>通过多路复用搞定所有的 IO 操作（包括连接，读、写等），编码简单，清晰明了，但是如果客户端连接数量较多，将无法支撑，前面的 NIO 案例就属于这种模型。</p><h3 id="3-2-2-优缺点分析"><a href="#3-2-2-优缺点分析" class="headerlink" title="3.2.2. 优缺点分析"></a>3.2.2. 优缺点分析</h3><ol><li>优点：模型简单，没有多线程、进程通信、竞争的问题，全部都在一个线程中完成</li><li>缺点：性能问题，只有一个线程，无法完全发挥多核 CPU 的性能。Handler 在处理某个连接上的业务时，整个进程无法处理其他连接事件，很容易导致性能瓶颈</li><li>缺点：可靠性问题，线程意外终止，或者进入死循环，会导致整个系统通信模块不可用，不能接收和处理外部消息，造成节点故障</li><li>使用场景：客户端的数量有限，业务处理非常快速，<span style="background-color:#ff00ff">比如 Redis 6 之前，所有任务都是单线程完成</span>，在业务处理的时间复杂度 O(1) 的情况。但 Redis 中具体的实现逻辑不一样，Redis 分为 3 个处理器：连接应答处理器、命令回复处理器、命令请求处理器。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230318073757.png" alt="image.png"></p><h2 id="3-3-单-Reactor-多线程"><a href="#3-3-单-Reactor-多线程" class="headerlink" title="3.3. 单 Reactor 多线程"></a>3.3. 单 Reactor 多线程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230328221243.png" alt="image.png"></p><h3 id="3-3-1-方案说明"><a href="#3-3-1-方案说明" class="headerlink" title="3.3.1. 方案说明"></a>3.3.1. 方案说明</h3><ol><li>Reactor 对象通过 Select 监控客户端请求事件，收到事件后，通过 Dispatch 进行分发</li><li>如果是建立连接请求，则由 Acceptor 通过 accept 处理连接请求，然后创建一个 Handler 对象处理完成连接后的各种事件</li><li>如果不是连接请求，则由 Reactor 分发调用连接对应的 handler 来处理（也就是说连接已经建立，后续客户端再来请求，那基本就是数据请求了，直接调用之前为这个连接创建好的 handler 来处理）</li><li><span style="background-color:#ff00ff">handler 只负责响应事件，不做具体的业务处理</span>（这样不会使 handler 阻塞太久），通过 read 读取数据后，会分发给后面的 worker 线程池的某个线程处理业务。【业务处理是最费时的，所以将业务处理交给线程池去执行】</li><li>worker 线程池会分配独立线程完成真正的业务，并将结果返回给 handler</li><li>handler 收到响应后，通过 send 将结果返回给 client</li></ol><h3 id="3-3-2-优缺点分析"><a href="#3-3-2-优缺点分析" class="headerlink" title="3.3.2. 优缺点分析"></a>3.3.2. 优缺点分析</h3><p>优点：可以充分利用多核 CPU 的处理能力。<br>缺点：多线程数据共享和访问比较复杂；Reactor 承担所有事件的监听和响应，在单线程中运行，高并发场景下容易成为性能瓶颈。</p><h2 id="3-4-主从-Reactor-多线程⭐️🔴"><a href="#3-4-主从-Reactor-多线程⭐️🔴" class="headerlink" title="3.4. 主从 Reactor 多线程⭐️🔴"></a>3.4. 主从 Reactor 多线程⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230328221403.png" alt="image.png"></p><p>针对单 Reactor 多线程模型中，Reactor 在单线程中运行，高并发场景下容易成为性能瓶颈，可以让 Reactor 在多线程中运行。</p><h3 id="3-4-1-方案说明"><a href="#3-4-1-方案说明" class="headerlink" title="3.4.1. 方案说明"></a>3.4.1. 方案说明</h3><p>SubReactor 是可以有多个的，如果只有一个 SubReactor 的话那和单 Reactor 多线程就没什么区别了。</p><ol><li>Reactor 主线程 MainReactor 对象通过 select 监听连接事件，收到事件后，通过 Acceptor 处理连接事件</li><li>当 Acceptor 处理连接事件后，MainReactor 将连接分配给 SubReactor</li><li>subreactor 将连接<span style="background-color:#ff00ff">加入到连接队列</span>进行监听，并创建 handler 进行各种事件处理</li><li>当有新事件发生时，subreactor 就会调用对应的 handler 处理</li><li>handler 通过 read 读取数据，分发给后面的 worker 线程处理</li><li>worker 线程池分配独立的 worker 线程进行业务处理，并返回结果</li><li>handler 收到响应的结果后，再通过 send 将结果返回给 client</li><li>Reactor 主线程可以对应多个 Reactor 子线程，即 MainRecator 可以关联多个 SubReactor</li></ol><h3 id="3-4-2-优缺点分析"><a href="#3-4-2-优缺点分析" class="headerlink" title="3.4.2. 优缺点分析"></a>3.4.2. 优缺点分析</h3><p>优点：父线程与子线程的数据交互简单职责明确，父线程只需要接收新连接，子线程完成后续的业务处理。</p><p>父线程与子线程的数据交互简单，Reactor 主线程只需要把新连接传给子线程，子线程无需返回数据。</p><p>这种模型在许多项目中广泛使用，包括 Nginx 主从 Reactor 多进程模型，Memcached 主从多线程，Netty 主从多线程模型的支持。</p><h2 id="3-5-Reactor-模式小结"><a href="#3-5-Reactor-模式小结" class="headerlink" title="3.5. Reactor 模式小结"></a>3.5. Reactor 模式小结</h2><h3 id="3-5-1-3-种模式用生活案例来理解"><a href="#3-5-1-3-种模式用生活案例来理解" class="headerlink" title="3.5.1. 3 种模式用生活案例来理解"></a>3.5.1. 3 种模式用生活案例来理解</h3><p>单 Reactor 单线程，前台接待员和服务员是同一个人，全程为顾客服务<br>单 Reactor 多线程，1 个前台接待员，多个服务员，接待员只负责接待<br>主从 Reactor 多线程，多个前台接待员，多个服务生</p><h3 id="3-5-2-Reactor-模式具有如下的优点"><a href="#3-5-2-Reactor-模式具有如下的优点" class="headerlink" title="3.5.2. Reactor 模式具有如下的优点"></a>3.5.2. Reactor 模式具有如下的优点</h3><p>响应快，不必为单个同步时间所阻塞，虽然 Reactor 本身依然是同步的（比如你第一个 SubReactor 阻塞了，我可以调下一个 SubReactor 为客户端服务）<br>可以最大程度的避免复杂的多线程及同步问题，并且避免了多线程&#x2F;进程的切换开销<br>扩展性好，可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源<br>复用性好，Reactor 模型本身与具体事件处理逻辑无关，具有很高的复用性</p><h3 id="3-5-3-注意点"><a href="#3-5-3-注意点" class="headerlink" title="3.5.3. 注意点"></a>3.5.3. 注意点</h3><p>注意：前面介绍的四种 reactor 模式在具体实现时遵循的原则是：每个文件描述符只由一个线程操作。这样可以轻轻松松解决消息收发的顺序性问题，也避免了关闭文件描述符的各种 race condition。一个线程可以操作多个文件描述符，但是一个线程不能操作别的线程拥有的文件描述符。这一点不难做到。epoll 也遵循了相同的原则。Linux 文档中并没有说明，当一个线程证阻塞在 epoll_wait 时，另一个线程往 epoll fd 添加一个新的监控 fd 会发生什么。新 fd 上的事件会不会在此次 epoll_wait 调用中返回？为了稳妥起见，我们应该吧对同一个 epoll fd 的操作 (添加、删除、修改等等) 都放到同一个线程中执行。</p><h3 id="3-5-4-与-Epoll-关系"><a href="#3-5-4-与-Epoll-关系" class="headerlink" title="3.5.4. 与 Epoll 关系"></a>3.5.4. 与 Epoll 关系</h3><p>reactor 就是对 epoll 进行封装，进行网络 IO 与业务的解耦，将 epoll 管理 IO 变成管理事件，整个程序由事件进行驱动执行。就像下图一样，有就绪事件返回，reactor：由事件驱动执行对应的回调函数；epoll：需要自己判断。</p><h1 id="4-Netty-的网络模型⭐️🔴"><a href="#4-Netty-的网络模型⭐️🔴" class="headerlink" title="4. Netty 的网络模型⭐️🔴"></a>4. Netty 的网络模型⭐️🔴</h1><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230402-1010%%</span>❕ ^9ze94j</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330085952.png" alt="image.png"></p><ol><li>Netty 抽象出两组线程池，<span style="background-color:#ff00ff">BossGroup 专门负责接收客户端的连接，WorkerGroup 专门负责网络的读写</span></li><li>BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup</li><li>NioEventLoopGroup 相当于一个事件循环组，这个组中含有多个事件循环，每一个事件循环是 NioEventLoop</li><li>NioEventLoop 表示一个不断循环的执行处理任务的线程，<span style="background-color:#ff00ff">每个 NioEventLoop 都有一个 Selector，用于监听绑定在它上面的 socket 的网络通讯</span></li><li>NioEventLoopGroup 可以有多个线程，即可以含有多个 NioEventLoop</li><li>每个 BossGroup 下面的 NioEventLoop 循环执行的步骤有 3 步<ol><li>轮询 accept 事件</li><li>处理 accept 事件，与 client 建立连接，生成 NioScocketChannel，并将其注册到 workerGroup 的某个 NioEventLoop 上的 Selector 上面</li><li>继续处理任务队列的任务，即 runAllTasks</li></ol></li><li>每个 WorkerGroup NioEventLoop 循环执行的步骤<ol><li>轮询 read，write 事件</li><li>处理 I&#x2F;O 事件，即 read，write 事件，在对应 NioScocketChannel 处理</li><li>处理任务队列的任务，即 runAllTasks</li></ol></li><li>每个 Worker NioEventLoop 处理业务时，会使用 pipeline（管道），pipeline 中包含了 channel（通道），即通过 pipeline 可以获取到对应通道，管道中维护了很多的处理器。</li></ol><p> <a href="https://blog.csdn.net/Youth_lql/article/details/115734142">https://blog.csdn.net/Youth_lql/article/details/115734142</a></p><h1 id="5-Netty-的逻辑架构⭐️🔴"><a href="#5-Netty-的逻辑架构⭐️🔴" class="headerlink" title="5. Netty 的逻辑架构⭐️🔴"></a>5. Netty 的逻辑架构⭐️🔴</h1><p>Netty 的逻辑处理架构是典型的网络分层架构设计的，分别为网络通信层、事件调度层和服务编排层。每一层各司其职，如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230329091100.png" alt="image.png"></p><h2 id="5-1-网络通信层"><a href="#5-1-网络通信层" class="headerlink" title="5.1. 网络通信层"></a>5.1. 网络通信层</h2><p>网络通信层的职责就是执行网络 I&#x2F;O 的操作。它支持多种网络协议和 I&#x2F;O 模型的连接操作。当网络数据读取到内核缓冲区后，会触发各种网络事件，这些网络事件会分发给事件调度层来处理。网络通信层有三个核心组件：<span style="background-color:#ff00ff">Bootstrap、ServerBootstrap 和 Channel</span>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230329222107.png" alt="image.png"></p><h3 id="5-1-1-Bootstrap"><a href="#5-1-1-Bootstrap" class="headerlink" title="5.1.1. Bootstrap"></a>5.1.1. Bootstrap</h3><p>意思是引导，一个 Netty 应用通常由一个 Bootstrap 开始，主要作用是配置整个 Netty 程序，串联各个组件，Netty 中 <span style="background-color:#ff00ff">Bootstrap 类是客户端程序的启动引导类，用于连接服务端的，一般用于 Client 的开发上</span></p><h3 id="5-1-2-ServerBootstrap"><a href="#5-1-2-ServerBootstrap" class="headerlink" title="5.1.2. ServerBootstrap"></a>5.1.2. ServerBootstrap</h3><p><span style="background-color:#ff00ff">是服务端启动引导类，用于服务端启动绑定本地端口</span>，一般绑定两个： EventLoopGroup(主从多线程 Reactor)，一个称为 boss，另外一个成为 worker。</p><p>Bootstrap&#x2F;ServerBootstrap 组件更加方便我们配置和启动 Netty 程序，它是整个 Netty 程序的入口，串联了 Netty 所有核心组件的初始化工作。</p><h3 id="5-1-3-channel"><a href="#5-1-3-channel" class="headerlink" title="5.1.3. channel"></a>5.1.3. channel</h3><p>Netty 网络通信的组件，能够用于执行网络 I&#x2F;O 操作。它是 Netty 网络通信的载体。Channel 提供了基本的 API 用于网络 I&#x2F;O 操作，例如 register 注册事件、bind 绑定事件、connect 连接事件、read 读事件、write 写事件、flush 数据冲刷等。</p><p>Netty 中的 Channel 是<span style="background-color:#ff00ff">基于 JDK NIO 的 Channel 上做了更高层次的抽象，同时屏蔽了底层 Socket 的复杂性，增强了 Channel 的功能，在使用 Netty 时不需要再直接跟 JDK Socket 打交道。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330074140.png" alt="image.png"></p><h2 id="5-2-事件调度层"><a href="#5-2-事件调度层" class="headerlink" title="5.2. 事件调度层"></a>5.2. 事件调度层</h2><p><span style="background-color:#ff00ff">事件调度层通过 Reactor 线程模型对各类事件进行聚合处理</span>，通过 Selector 主循环线程集成多种事件 (I&#x2F;O 事件、信号事件、定时事件等)，实际的业务处理逻辑交给服务编排层的 ChannelHandler 来处理。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330101838.png" alt="image.png"></p><p>事件调度层<span style="background-color:#ff00ff">最核心的组件就是 EventLoop 和 EventLoopGroup：EventLoopGroup 实质上是 Netty 基于 JDK 线程池的抽象，本质就是线程池，主要负责接收 I&#x2F;O 请求，并分配线程执行处理。而 EventLoop 可以理解成一个线程，EventLoop 创建出来之后会跟一个线程绑定并且处理 Channel 中的事件</span>。如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230329091313.png" alt="image.png"></p><h2 id="5-3-服务编排层"><a href="#5-3-服务编排层" class="headerlink" title="5.3. 服务编排层"></a>5.3. 服务编排层</h2><p>服务编排的职责是组装各类服务，它是 Netty 的核心处理链，用以实现网络事件的动态编排和有序传播。</p><p>服务编排的核心组件有：ChannelPipeline、ChannelHandler、ChannelHandlerContext。</p><h3 id="5-3-1-ChannelPipeline"><a href="#5-3-1-ChannelPipeline" class="headerlink" title="5.3.1. ChannelPipeline"></a>5.3.1. ChannelPipeline</h3><p>ChannelPipeline 是 Netty 的核心编排组件，负责组装各类的 ChannelHandler<br>ChannelPipeline 本质是一个双向链表，通过责任链模式将不同的 ChannelHandler 链接在一起。<br>每个 Channel 有且仅有一个 ChannelPipeline 与之对应。<br>当 I&#x2F;O 读写事件触发时，ChannelPipeline 会一次调用 ChannelHandler 列表对 Channel 的数据进行拦截和处理。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330095605.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230330100214.png" alt="image.png"></p><h3 id="5-3-2-ChannelHandler"><a href="#5-3-2-ChannelHandler" class="headerlink" title="5.3.2. ChannelHandler"></a>5.3.2. ChannelHandler</h3><p>实际数据的编解码以及加工处理的操作是由 ChannelHandler 来完成的</p><h3 id="5-3-3-ChannelHandlerContext"><a href="#5-3-3-ChannelHandlerContext" class="headerlink" title="5.3.3. ChannelHandlerContext"></a>5.3.3. ChannelHandlerContext</h3><p>每创建一个 Channel 都会绑定一个新的 ChannelPipeline，ChannelPipeline 中每加入一个 ChannelHandler 都会绑定一个 ChannelHandlerContext。由此可见，ChannelPipeline、ChannelHandlerContext、ChannelHandler 三个组件的关系是密切相关的。</p><p>ChannelHandlerContext 用于保存 ChannelHandler 上下文，通过 ChannelHandlerContext 我们可以知道 ChannelPipeline 和 ChannelHandler 的关联关系。</p><h1 id="6-Netty-特性"><a href="#6-Netty-特性" class="headerlink" title="6. Netty 特性"></a>6. Netty 特性</h1><h2 id="6-1-零拷贝"><a href="#6-1-零拷贝" class="headerlink" title="6.1. 零拷贝"></a>6.1. 零拷贝</h2><a href="/2023/05/27/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6-1%E3%80%81%E9%9B%B6%E6%8B%B7%E8%B4%9D/" title="并发进阶-1、零拷贝">并发进阶-1、零拷贝</a><h2 id="6-2-bytebuf"><a href="#6-2-bytebuf" class="headerlink" title="6.2. bytebuf"></a>6.2. bytebuf</h2><h3 id="6-2-1-ByteBuf-优势"><a href="#6-2-1-ByteBuf-优势" class="headerlink" title="6.2.1. ByteBuf 优势"></a>6.2.1. ByteBuf 优势</h3><ul><li>池化 - 可以重用池中 ByteBuf 实例，更节约内存，减少内存溢出的可能</li><li>读写指针分离，不需要像 ByteBuffer 一样切换读写模式</li><li>可以自动扩容</li><li>支持链式调用，使用更流畅</li><li>很多地方体现零拷贝，例如 slice、duplicate、CompositeByteBuf</li></ul><h2 id="6-3-高性能无锁队列-Mpsc"><a href="#6-3-高性能无锁队列-Mpsc" class="headerlink" title="6.3. 高性能无锁队列 (Mpsc)"></a>6.3. 高性能无锁队列 (Mpsc)</h2><p><a href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Netty%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%20RPC%20%E5%AE%9E%E8%B7%B5-%E5%AE%8C/22%20%20%E6%8A%80%E5%B7%A7%E7%AF%87%EF%BC%9A%E9%AB%98%E6%80%A7%E8%83%BD%E6%97%A0%E9%94%81%E9%98%9F%E5%88%97%20Mpsc%20Queue.md">22 技巧篇：高性能无锁队列 Mpsc Queue</a></p><p>队列相关：<a href="/2023/01/22/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-19%E3%80%81Queue/" title="并发编程专题-基础-19、Queue">并发编程专题-基础-19、Queue</a></p><p>Mpsc 的全称是 Multi Producer Single Consumer，多生产者单消费者。Mpsc Queue 可以保证多个生产者同时访问队列是线程安全的，而且同一时刻只允许一个消费者从队列中读取数据。Netty Reactor 线程中任务队列 taskQueue 必须满足多个生产者可以同时提交任务，所以 JCTools 提供的 Mpsc Queue 非常适合 Netty Reactor 线程模型。<br>MpscArrayQueue 还只是 Jctools 中的冰山一角，其中蕴藏着丰富的技术细节，我们对 MpscArrayQueue 的知识点做一个简单的总结。</p><ul><li>通过大量填充 long 类型变量解决伪共享问题。</li><li>环形数组的容量设置为 2 的次幂，可以通过位运算快速定位到数组对应下标。</li><li>入队 offer() 操作中 producerLimit 的巧妙设计，大幅度减少了主动获取消费者索引 consumerIndex 的次数，性能提升显著。</li><li>入队和出队操作中都大量使用了 UNSAFE 系列方法，针对生产者和消费者的场景不同，使用的 UNSAFE 方法也是不一样的。Jctools 在底层操作的运用上也是有的放矢，把性能发挥到极致。</li></ul><h2 id="6-4-内存池"><a href="#6-4-内存池" class="headerlink" title="6.4. 内存池"></a>6.4. 内存池</h2><p><a href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Netty%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%20RPC%20%E5%AE%9E%E8%B7%B5-%E5%AE%8C/13%20%20%E4%B8%BE%E4%B8%80%E5%8F%8D%E4%B8%89%EF%BC%9ANetty%20%E9%AB%98%E6%80%A7%E8%83%BD%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E8%AE%BE%E8%AE%A1%EF%BC%88%E4%B8%8A%EF%BC%89.md">13 举一反三：Netty 高性能内存管理设计（上）</a><br><a href="https://miaowenting.site/2020/02/09/Netty%E5%86%85%E5%AD%98%E6%B1%A0%E5%8C%96%E7%AE%A1%E7%90%86/">https://miaowenting.site/2020/02/09/Netty%E5%86%85%E5%AD%98%E6%B1%A0%E5%8C%96%E7%AE%A1%E7%90%86/</a></p><h2 id="6-5-解决黏包-粘包-半包"><a href="#6-5-解决黏包-粘包-半包" class="headerlink" title="6.5. 解决黏包 (粘包) 半包"></a>6.5. 解决黏包 (粘包) 半包</h2><h3 id="6-5-1-黏包-粘包"><a href="#6-5-1-黏包-粘包" class="headerlink" title="6.5.1. 黏包 (粘包)"></a>6.5.1. 黏包 (粘包)</h3><p>现象，发送 abc def，接收 abcdef<br>原因<br>      1. 应用层：接收方 ByteBuf 设置太大（Netty 默认 1024）<br>      2. 滑动窗口：假设发送方 256 bytes 表示一个完整报文，但由于接收方处理不及时且窗口大小足够大，这 256 bytes 字节就会缓冲在接收方的滑动窗口中，当滑动窗口中缓冲了多个报文就会粘包<br>      3. Nagle 算法：会造成粘包</p><h3 id="6-5-2-半包"><a href="#6-5-2-半包" class="headerlink" title="6.5.2. 半包"></a>6.5.2. 半包</h3><ul><li>现象，发送 abcdef，接收 abc def</li><li>原因<ul><li>应用层：接收方 ByteBuf 小于实际发送数据量</li><li>滑动窗口：假设接收方的窗口只剩了 128 bytes，发送方的报文大小是 256 bytes，这时放不下了，只能先发送前 128 bytes，等待 ack 后才能发送剩余部分，这就造成了半包</li><li>MSS 限制：当发送的数据超过 MSS 限制后，会将数据切分发送，就会造成半包</li></ul></li></ul><p><span style="background-color:#ff00ff">本质是因为 TCP 是流式协议，消息无边界</span></p><h1 id="7-面试题"><a href="#7-面试题" class="headerlink" title="7. 面试题"></a>7. 面试题</h1><h2 id="请说一下Netty中Reactor模式的理解"><a href="#请说一下Netty中Reactor模式的理解" class="headerlink" title="请说一下Netty中Reactor模式的理解"></a>请说一下Netty中Reactor模式的理解</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230613141532.png" alt="Xnip2023-06-13_14-15-17.png"></p><p>[[BIO,NIO,AIO,Netty面试题 35道.pdf]]</p><p>[[3.358道.pdf]]</p><p><a href="https://www.bilibili.com/video/BV1hG411G7MN/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1hG411G7MN/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.bilibili.com/video/BV1sN411F7C8/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1sN411F7C8/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230528-0902%%</span>❕ ^hefe8a</p><h2 id="9-1-黑马"><a href="#9-1-黑马" class="headerlink" title="9.1. 黑马"></a>9.1. 黑马</h2><h3 id="9-1-1-视频"><a href="#9-1-1-视频" class="headerlink" title="9.1.1. 视频"></a>9.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1py4y1E7oA/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1py4y1E7oA/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="9-1-2-资料"><a href="#9-1-2-资料" class="headerlink" title="9.1.2. 资料"></a>9.1.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">003</span>-并发编程专题/<span class="hljs-number">000</span>-Netty/黑马-Netty教程源码资料<br></code></pre></td></tr></table></figure><h2 id="9-2-尚硅谷-Netty-视频教程（B-站超火，好评如潮）"><a href="#9-2-尚硅谷-Netty-视频教程（B-站超火，好评如潮）" class="headerlink" title="9.2. 尚硅谷 Netty 视频教程（B 站超火，好评如潮）"></a>9.2. 尚硅谷 Netty 视频教程（B 站超火，好评如潮）</h2><p><a href="https://www.bilibili.com/video/BV1DJ411m7NR?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1DJ411m7NR?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="9-3-网络笔记"><a href="#9-3-网络笔记" class="headerlink" title="9.3. 网络笔记"></a>9.3. 网络笔记</h2><p>Netty 架构： <a href="https://www.maishuren.top/archives/netty-zheng-ti-jia-gou-jie-xi">https://www.maishuren.top/archives/netty-zheng-ti-jia-gou-jie-xi</a></p><p>尚硅谷学习笔记： <a href="https://blog.csdn.net/Youth_lql/article/details/115734142">https://blog.csdn.net/Youth_lql/article/details/115734142</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-7、索引原理</title>
      <link href="/2023/03/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-7%E3%80%81%E7%B4%A2%E5%BC%95%E5%8E%9F%E7%90%86/"/>
      <url>/2023/03/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-7%E3%80%81%E7%B4%A2%E5%BC%95%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-什么是索引"><a href="#1-什么是索引" class="headerlink" title="1. 什么是索引"></a>1. 什么是索引</h1><p>索引（index）是帮助 MySQL 高效获取数据的数据结构（有序）。在数据之外，数据库系统还维护者满足特定查找算法的数据结构，这些数据结构以某种方式引用（指向）数据，这样就可以在这些数据结构上实现高级查找算法，这种数据结构就是索引。</p><h2 id="1-1-索引优势劣势"><a href="#1-1-索引优势劣势" class="headerlink" title="1.1. 索引优势劣势"></a>1.1. 索引优势劣势</h2><p>优势<br>1） 类似于书籍的目录索引，提高数据检索的效率，降低数据库的 IO 成本。<br>2） 通过索引列对数据进行排序，降低数据排序的成本，降低 CPU 的消耗。</p><p>劣势<br>1） 实际上索引也是一张表，该表中保存了主键与索引字段，并指向实体类的记录，所以索引列也是要占用空间的。<br>2） 虽然索引大大提高了查询效率，同时却也降低更新表的速度，如对表进行 INSERT、UPDATE、DELETE。因为更新表时，MySQL 不仅要保存数据，还要保存一下索引文件每次更新添加了索引列的字段，都会调整因为更新所带来的键值变化后的索引信息。</p><h2 id="1-2-索引结构"><a href="#1-2-索引结构" class="headerlink" title="1.2. 索引结构"></a>1.2. 索引结构</h2><p>索引是在 MySQL 的存储引擎层中实现的，而不是在服务器层实现的。所以每种存储引擎的索引都不一定完全相同，也不是所有的存储引擎都支持所有的索引类型的。MySQL 目前提供了以下 4 种索引：</p><ul><li>BTREE 索引 ： 最常见的索引类型，大部分索引都支持 B 树索引。</li><li>HASH 索引：只有 Memory 引擎支持 ， 使用场景简单 。</li><li>R-tree 索引（空间索引）：空间索引是 MyISAM 引擎的一个特殊索引类型，主要用于地理空间数据类型，通常使用较少，不做特别介绍。</li><li>Full-text （全文索引） ：全文索引也是 MyISAM 的一个特殊索引类型，主要用于全文索引，InnoDB 从 Mysql5.6 版本开始支持全文索引。</li></ul><h3 id="1-2-1-MyIsAm"><a href="#1-2-1-MyIsAm" class="headerlink" title="1.2.1. MyIsAm"></a>1.2.1. MyIsAm</h3><p><strong>叶子节点存储真实数据的磁盘地址</strong><br>索引和数据分开存放<br>.myd 即 my data，表数据文件<br>.myi 即 my index，索引文件</p><h3 id="1-2-2-InnoDB"><a href="#1-2-2-InnoDB" class="headerlink" title="1.2.2. InnoDB"></a>1.2.2. InnoDB</h3><p>.ibd</p><h3 id="1-2-3-B-Tree-原理"><a href="#1-2-3-B-Tree-原理" class="headerlink" title="1.2.3. B+Tree 原理"></a>1.2.3. B+Tree 原理</h3><p>[[数据结构-0、数据结构与算法-汇总#^uxdwyi]]</p><h3 id="1-2-4-查询流程"><a href="#1-2-4-查询流程" class="headerlink" title="1.2.4. 查询流程"></a>1.2.4. 查询流程</h3><p><a href="https://xiaolincoding.com/mysql/index/page.html#b-%E6%A0%91%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E6%9F%A5%E8%AF%A2%E7%9A%84">https://xiaolincoding.com/mysql/index/page.html#b-%E6%A0%91%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E6%9F%A5%E8%AF%A2%E7%9A%84</a></p><p>InnoDB 里的 B+ 树中的 <span style="background-color:#ff0000">每个节点都是一个数据页</span>，结构示意图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230506113833.png" alt="image.png"></p><p>通过上图，我们看出 B+ 树的特点：</p><ul><li>只有叶子节点（最底层的节点）才存放了数据，非叶子节点（其他上层节）仅用来存放目录项作为索引。</li><li>非叶子节点分为不同层次，通过分层来降低每一层的搜索量；</li><li>所有节点按照索引键大小排序，构成一个双向链表，便于范围查询；</li></ul><p>我们再看看 B+ 树如何实现快速查找主键为 6 的记录，以上图为例子：</p><ul><li>从根节点开始，通过二分法快速定位到符合页内范围包含查询值的页，因为查询的主键值为 6，在 <code>[1, 7)</code> 范围之间，所以到页 30 中查找更详细的目录项；</li><li>在非叶子节点（页 30）中，继续通过二分法快速定位到符合页内范围包含查询值的页，主键值大于 5，所以就到叶子节点（页 16）查找记录；</li><li>接着，在叶子节点（页 16）中，通过槽查找记录时，使用二分法快速定位要查询的记录在哪个槽（哪个记录分组），定位到槽后，再遍历槽内的所有记录，找到主键为 6 的记录。</li></ul><p>可以看到，在定位记录所在哪一个页时，也是通过二分法快速定位到包含该记录的页。定位到该页后，又会在该页内进行二分法快速定位记录所在的分组（槽号），最后在分组内进行遍历查找。</p><h3 id="1-2-5-总结"><a href="#1-2-5-总结" class="headerlink" title="1.2.5. 总结"></a>1.2.5. 总结</h3><p>InnoDB 的数据是按「数据页」为单位来读写的，默认数据页大小为 16 KB。每个数据页之间通过双向链表的形式组织起来，物理上不连续，但是逻辑上连续。</p><p>数据页内包含用户记录，每个记录之间用单向链表的方式组织起来，为了加快在数据页内高效查询记录，设计了一个页目录，页目录存储各个槽（分组），且主键值是有序的，于是可以通过二分查找法的方式进行检索从而提高效率。</p><p>为了高效查询记录所在的数据页，InnoDB 采用 b+ 树作为索引，每个节点都是一个数据页。</p><p>如果叶子节点存储的是实际数据的就是聚簇索引，一个表只能有一个聚簇索引；如果叶子节点存储的不是实际数据，而是主键值则就是二级索引，一个表中可以有多个二级索引。</p><p>在使用二级索引进行查找数据时，如果查询的数据能在二级索引找到，那么就是「索引覆盖」操作，如果查询的数据不在二级索引里，就需要先在二级索引找到主键值，需要去聚簇索引中获得数据行，这个过程就叫作「回表」。</p><h1 id="2-索引为什么这么快⭐️🔴"><a href="#2-索引为什么这么快⭐️🔴" class="headerlink" title="2. 索引为什么这么快⭐️🔴"></a>2. 索引为什么这么快⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230602-1357%%</span>❕ ^gkr68m</p><h2 id="2-1-局部性原理和-IO"><a href="#2-1-局部性原理和-IO" class="headerlink" title="2.1. 局部性原理和 IO"></a>2.1. 局部性原理和 IO</h2><p>考虑到磁盘 IO 是非常高昂的操作，计算机操作系统做了一些优化，当一次 IO 时，不光把当前磁盘地址的数据，而是把相邻的数据也都读取到内存缓冲区内，因为局部预读性原理告诉我们，当计算机访问一个地址的数据的时候，与其相邻的数据也会很快被访问到。<br>每一次 IO 读取的数据我们称之为一页 (page)。具体一页有多大数据跟操作系统有关，一般为 16k，<span style="background-color:#ff00ff">也就是我们读取一页内的数据时候，实际上才发生了一次 IO</span>，这个理论对于索引的数据结构设计非常有帮助。<br>数据库的 I&#x2F;O 操作的最小单位是页，<strong>InnoDB 数据页的默认大小是 16KB</strong>，意味着数据库每次读写都是以 16KB 为单位的，一次最少从磁盘中读取 16K 的内容到内存中，一次最少把内存中的 16K 内容刷新到磁盘中。</p><p>准确来说，只有命中了索引列的查询，才能提升效率。并且，即便是命中了索引，查询效率也不一定高，比如在性别字段上加索引。因为数据的散列度不高，导致可能会遍历整颗 B+ 树。<br>InnoDB 采用了 B+ 树这种多路平衡查找树来存储索引，使得在千万级数量的情况下，树的高度可以控制在 3 层以内。而层高代表磁盘 IO 的次数，因此基于索引查询减少了磁盘 IO 次数。</p><h2 id="2-2-Page-页"><a href="#2-2-Page-页" class="headerlink" title="2.2. Page(页)"></a>2.2. Page(页)</h2><ul><li><strong>页是 InnoDB 磁盘管理的最小单位</strong>。</li><li>在 InnoDB 存储引擎中，默认每个页的大小为 <strong>16KB</strong>。</li><li>从 InnoDB1.2.x 版本开始，可以通过参数 <strong>innodb_page_size</strong> 将页的大小设置为 4K，8K，16K。</li><li>InnoDB 存储引擎中，常见的页类型有：<strong>数据页，undo 页，系统页，事务数据页，插入缓冲位图页，插入缓冲空闲列表页</strong> 等。</li></ul><p>数据页包括七个部分，结构如下图</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230516161445.png" alt="image.png"></p><p>在 File Header 中有两个指针，分别指向上一个数据页和下一个数据页，连接起来的页相当于一个双向的链表，如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230516161515.png" alt="image.png"></p><h3 id="2-2-1-User-Records-用户记录"><a href="#2-2-1-User-Records-用户记录" class="headerlink" title="2.2.1. User Records(用户记录)"></a>2.2.1. User Records(用户记录)</h3><p><strong>数据页中的记录按照「主键」顺序组成单向链表</strong>，单向链表的特点就是插入、删除非常方便，但是检索效率不高，最差的情况下需要遍历链表上的所有节点才能完成检索。</p><h3 id="2-2-2-Page-Directory-页目录"><a href="#2-2-2-Page-Directory-页目录" class="headerlink" title="2.2.2. Page Directory(页目录)"></a>2.2.2. Page Directory(页目录)</h3><p>因此，数据页中有一个 <strong>页目录</strong>，起到记录的索引作用，一个数据页中的记录检索，因为一个数据页中的记录是有限的，且主键值是有序的，所以通过对所有记录进行分组，然后将组号（槽号）存储到页目录，使其起到索引作用，通过二分查找的方法快速检索到记录在哪个分组，来降低检索的时间复杂度。</p><p>那 InnoDB 是如何给记录创建页目录的呢？页目录与记录的关系如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230516162449.png" alt="image.png"></p><h2 id="2-3-总结"><a href="#2-3-总结" class="headerlink" title="2.3. 总结"></a>2.3. 总结</h2><p><span style="background-color:#00ff00">InnoDB 的数据是按「数据页」为单位来读写的，默认数据页大小为 16 KB。每个数据页之间通过双向链表的形式组织起来，物理上不连续，但是逻辑上连续。</span></p><p><span style="background-color:#00ff00">数据页内包含用户记录，每个记录之间用单向链表的方式组织起来，为了加快在数据页内高效查询记录，设计了一个页目录，页目录存储各个槽（分组），且主键值是有序的，于是可以通过二分查找法的方式进行检索从而提高效率。</span></p><p><span style="background-color:#00ff00">为了高效查询记录所在的数据页，InnoDB 采用 b+ 树作为索引，每个节点都是一个数据页。</span></p><p><span style="background-color:#00ff00">如果叶子节点存储的是实际数据的就是聚簇索引，一个表只能有一个聚簇索引；如果叶子节点存储的不是实际数据，而是主键值则就是二级索引，一个表中可以有多个二级索引。</span></p><p><span style="background-color:#00ff00">在使用二级索引进行查找数据时，如果查询的数据能在二级索引找到，那么就是「索引覆盖」操作，如果查询的数据不在二级索引里，就需要先在二级索引找到主键值，需要去聚簇索引中获得数据行，这个过程就叫作「回表」。</span></p><p><a href="https://tech.meituan.com/2014/06/30/mysql-index.html">https://tech.meituan.com/2014/06/30/mysql-index.html</a><br><a href="https://juejin.im/post/5d42f48cf265da03ab422e08">https://juejin.im/post/5d42f48cf265da03ab422e08</a><br><a href="https://www.bilibili.com/video/BV1p4411D7bV?p=4">https://www.bilibili.com/video/BV1p4411D7bV?p=4</a></p><h1 id="3-设计原则"><a href="#3-设计原则" class="headerlink" title="3. 设计原则"></a>3. 设计原则</h1><h2 id="3-1-索引字段"><a href="#3-1-索引字段" class="headerlink" title="3.1. 索引字段"></a>3.1. 索引字段</h2><p><strong>1. 较频繁的作为查询条件的字段应该创建索引</strong></p><p>提高数据查询检索的效率最有效的办法就是减少须要访问的数据量，从上面索引的益处中我们知道，索引正是减少通过索引键字段作为查询条件的 Query 的 IO 量之最有效手段。所以一般来说应该为较为频繁的查询条件字段创建索引。</p><p><strong>2. 唯一性太差的字段不适合单独创建索引，即使频繁作为查询条件</strong></p><p>唯一性太差的字段主要是指哪些呢？如状态字段、类型字段等这些字段中存放的数据可能总共就是那么几个或几十个值重复使用，每个值都会存在于成千上万 或更多的记录中。对于这类字段，完全没有必要创建单独的索引。因为即使创建了索引，MySQL Query Optimizer 大多数时候也不会去选择使用，如果什么时候 MySQL Query Optimizer 选择了这种索引，那么非常遗憾地告诉你，这可能会带来极大的性能问题。由于索引字段中每个值都含有大量的记录，那么存储引擎在根据索引 访问数据的时候会带来大量的随机 IO，甚至有些时候还会出现大量的重复 IO。</p><p><strong>3. 更新非常频繁的字段不适合创建索引</strong></p><p><strong>4. 不会出现在 WHERE 子句中的字段不该创建索引</strong></p><p><strong>5. 对于单键索引，尽量选择过滤性更好的索引（例如：手机号，邮件，身份证）</strong></p><p><strong>6. 组合索引，过滤性最好的字段在索引字段顺序中，位置越靠前越好</strong></p><p><strong>7. 选择组合索引时，尽量包含 where 中更多字段的索引</strong></p><h2 id="3-2-单键索引还是组合索引"><a href="#3-2-单键索引还是组合索引" class="headerlink" title="3.2. 单键索引还是组合索引"></a>3.2. <strong>单键索引还是组合索引</strong></h2><p>组合索引弊端：组合索引中因为有多个字段存在，理论上被更新的可能性肯定比单键索引要大很多，这样带来的附加成本也就比单键索引要高</p><p>在大概了解了 MySQL 各种类型的索引，以及索引本身的利弊与判断一个字段是否须要创建索引之后，就要着手创建索引来优化 Query 了。在很多时候，WHERE 子句中的过滤条件并不只是针对于单一的某个字段，经常会有多个字段一起作为查询过滤条件存在于 WHERE 子句中。在这种时候，就必须要判断是该仅仅为过滤性最好的字段建立索引，还是该在所有字段（过滤条件中的）上建立一个组合索引。</p><p>在一般的应用场景中，只要不是其中某个过滤字段在大多数场景下能过滤 90% 以上的数据，而其他的过滤字段会频繁的更新，一般更倾向于创建组合索引， 尤其是在并发量较高的场景下。因为当并发量较高的时候，即使只为每个 Query 节省了很少的 IO 消耗，但因为执行量非常大，所节省的资源总量仍然是非常可观的。</p><p><a href="https://www.jb51.net/article/50703.htm">https://www.jb51.net/article/50703.htm</a></p><h2 id="3-3-创建索引的一些规则"><a href="#3-3-创建索引的一些规则" class="headerlink" title="3.3. 创建索引的一些规则"></a>3.3. <strong>创建索引的一些规则</strong></h2><p>1、权衡索引个数与 DML 之间关系，DML 也就是插入、删除数据操作。这里需要权衡一个问题，建立索引的目的是为了提高查询效率的，但建立的索引过多，会影响插入、删除数据的速度，因为我们修改的表数据，索引也要跟着修改。这里需要权衡我们的操作是查询多还是修改多。<br>2、把索引与对应的表放在不同的表空间。当读取一个表时表与索引是同时进行的。如果表与索引和在一个表空间里就会产生资源竞争，放在两个表这空就可并行执行。<br>3、最好使用一样大小是块。 Oracle 默认五块，读一次 I&#x2F;O，如果你定义 6 个块或 10 个块都需要读取两次 I&#x2F;O。最好是 5 的整数倍更能提高效率。<br>4、如果一个表很大，建立索引的时间很长，因为建立索引也会产生大量的 redo 信息，所以在创建索引时可以设置不产生或少产生 redo 信息。只要表数据存在，索引失败了大不了再建，所以可以不需要产生 redo 信息。<br>5、建索引的时候应该根据具体的业务 SQL 来创建，特别是 where 条件，还有 where 条件的顺序，尽量将过滤大范围的放在后面，因为 SQL 执行是从后往前的。</p><p><a href="https://blog.csdn.net/zhongguoren666/article/details/6752153">https://blog.csdn.net/zhongguoren666/article/details/6752153</a></p><p>1、表的主键、外键必须有索引；<br>2、数据量超过 300 的表应该有索引；<br>3、经常与其他表进行连接的表，在连接字段上应该建立索引；<br>4、经常出现在 Where 子句中的字段，特别是大表的字段，应该建立索引；<br>5、索引应该建在选择性高的字段上；<br>6、索引应该建在小字段上，对于大的文本字段甚至超长字段，不要建索引；</p><h1 id="4-Oracle-索引优化"><a href="#4-Oracle-索引优化" class="headerlink" title="4. Oracle 索引优化"></a>4. Oracle 索引优化</h1><p><a href="https://blog.csdn.net/runrabbit/article/details/52151990">https://blog.csdn.net/runrabbit/article/details/52151990</a></p><p>Sql 优化:</p><p>当 Oracle 数据库拿到 SQL 语句时，其会根据查询优化器分析该语句，并根据分析结果生成查询执行计划。 也就是说，数据库是执行的查询计划，而不是 Sql 语句。 查询优化器有 rule-based-optimizer(基于规则的查询优化器) 和 Cost-Based-optimizer(基于成本的查询优化器)。 其中基于规则的查询优化器在 10g 版本中消失。 对于规则查询，其最后查询的是全表扫描。而 CBO 则会根据统计信息进行最后的选择。</p><p>1、先执行 From -&gt;Where -&gt;Group By-&gt;Order By<br>2、执行 From 字句是从右往左进行执行。因此必须选择记录条数最少的表放在右边。<br>3、对于 Where 字句其执行顺序是从后向前执行、因此可以过滤最大数量记录的条件必须写在 Where 子句的末尾，而对于多表之间的连接，则写在之前。 因为这样进行连接时，可以去掉大多不重复的项。　　<br>\4. SELECT 子句中避免使用 (_)ORACLE 在解析的过程中, 会将’_’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间<br>6、用 UNION 替换 OR(适用于索引列) union: 是将两个查询的结果集进行追加在一起，它不会引起列的变化。 由于是追加操作，需要两个结果集的列数应该是相关的， 并且相应列的数据类型也应该相当的。union 返回两个结果集，同时将两个结果集重复的项进行消除。 如果不进行消除，用 UNOIN ALL.</p><p>通常情况下, 用 UNION 替换 WHERE 子句中的 OR 将会起到较好的效果. 对索引列使用 OR 将造成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有 column 没有被索引, 查询效率可能会因为你没有选择 OR 而降低. 在下面的例子中, LOC_ID 和 REGION 上都建有索引.</p><p>　　高效: 　SELECT LOC_ID , LOC_DESC , REGION 　FROM LOCATION 　WHERE LOC_ID &#x3D; 10 　UNION 　SELECT LOC_ID , LOC_DESC , REGION 　FROM LOCATION 　WHERE REGION &#x3D; “MELBOURNE”</p><p>　　低效: 　SELECT LOC_ID , LOC_DESC , REGION 　FROM LOCATION 　WHERE LOC_ID &#x3D; 10 OR REGION &#x3D; “MELBOURNE” 　如果你坚持要用 OR, 那就需要返回记录最少的索引列写在最前面.</p><p>\7. 用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN 在许多基于基础表的查询中, 为了满足一个条件, 往往需要对另一个表进行联接. 在这种情况下, 使用 EXISTS(或 NOT EXISTS) 通常将提高查询的效率. 在子查询中, NOT IN 子句<span style="background-color:#ff00ff">将执行一个内部的排序和合并</span>. 无论在哪种情况下, NOT IN 都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用 NOT IN, 我们可以把它改写成外连接 (Outer Joins) 或 NOT EXISTS.</p><p>例子：</p><p>高效: SELECT * FROM EMP (基础表) WHERE EMPNO &gt; 0 AND EXISTS (SELECT ‘X’ FROM DEPT WHERE DEPT.DEPTNO &#x3D; EMP.DEPTNO AND LOC &#x3D; ‘MELB’)</p><p>低效: SELECT * FROM EMP (基础表) WHERE EMPNO &gt; 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC &#x3D; ‘MELB’)</p><h1 id="5-索引失效"><a href="#5-索引失效" class="headerlink" title="5. 索引失效"></a>5. 索引失效</h1><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230407-1009%%</span>❕ ^fey0iq</p><p>函数 - 计算 - 自动类型转换、orderby 没条件</p><h2 id="5-1-使用函数或计算"><a href="#5-1-使用函数或计算" class="headerlink" title="5.1. 使用函数或计算"></a>5.1. 使用函数或计算</h2><p>SELECT Col FROM tbl WHERE substr(name ,1 ,3 ) &#x3D; ‘ABC’ 或者 SELECT Col FROM tbl WHERE name LIKE ‘%ABC%’ 而 SELECT Col FROM tbl WHERE name LIKE ‘ABC%’ 会使用索引。</p><p>　 SELECT Col FROM tbl WHERE col &#x2F; 10 &gt; 10 则会使索引失效，应该改成 SELECT Col FROM tbl WHERE col &gt; 10 * 10</p><h2 id="5-2-字符串不加单引号-存在隐式类型转换"><a href="#5-2-字符串不加单引号-存在隐式类型转换" class="headerlink" title="5.2. 字符串不加单引号 - 存在隐式类型转换"></a>5.2. 字符串不加单引号 - 存在隐式类型转换</h2><p>在查询时，没有对字符串加单引号，MySQL 的查询优化器，会自动的进行类型转换，造成索引失效。</p><h2 id="5-3-用-or-连接没有索引的列-都失效"><a href="#5-3-用-or-连接没有索引的列-都失效" class="headerlink" title="5.3. 用 or 连接没有索引的列 - 都失效"></a>5.3. 用 or 连接没有索引的列 - 都失效</h2><p><span style="background-color:#ff00ff">如果 or 前的条件中的列有索引，而后面的列中没有索引，那么涉及的索引都不会被用到</span>。<br>示例，name 字段是索引列，而 createtime 不是索引列，中间是 or 进行连接是不走索引的 ：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319113950.png" alt="1556174aaaa994440"></p><p><span style="background-color:#ff00ff">用 and 却可以</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319114454.png" alt="image.png"></p><h2 id="5-4-order-by-没条件"><a href="#5-4-order-by-没条件" class="headerlink" title="5.4. order by 没条件"></a>5.4. order by 没条件</h2><p>没有过滤条件不走索引。<span style="background-color:#ff00ff">group by 不加过滤条件还是可以使用索引的。</span></p><h2 id="5-5-in-走索引，-not-in-索引失效"><a href="#5-5-in-走索引，-not-in-索引失效" class="headerlink" title="5.5. in 走索引， not in 索引失效"></a>5.5. in 走索引， not in 索引失效</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319122355.png" alt="155624aaa9602732"></p><h2 id="5-6-以-开头的-Like-模糊查询"><a href="#5-6-以-开头的-Like-模糊查询" class="headerlink" title="5.6. 以% 开头的 Like 模糊查询"></a>5.6. 以% 开头的 Like 模糊查询</h2><p>如果仅仅是尾部模糊匹配，索引不会失效。如果是头部模糊匹配，索引失效。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319114530.png" alt="155617aaa5114369"></p><p>解决方案 ：<br><span style="background-color:#ff00ff">通过覆盖索引来解决</span> <span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230319-1145%%</span>❕ ^zhh9w3</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319114606.png" alt="155aaa6247686483"></p><h2 id="5-7-如果-MySQL-评估使用索引比全表更慢"><a href="#5-7-如果-MySQL-评估使用索引比全表更慢" class="headerlink" title="5.7. 如果 MySQL 评估使用索引比全表更慢"></a>5.7. 如果 MySQL 评估使用索引比全表更慢</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319114645.png" alt="1556aaa175445210"></p><p>总共 13 条，北京市的有 10 条，接近全表扫描，优化器认为走索引还不如全表扫描，所以就不会走索引。而西安市就 1 条，走索引更快。</p><h2 id="5-8-复合索引"><a href="#5-8-复合索引" class="headerlink" title="5.8. 复合索引"></a>5.8. 复合索引</h2><h3 id="5-8-1-未全值匹配"><a href="#5-8-1-未全值匹配" class="headerlink" title="5.8.1. 未全值匹配"></a>5.8.1. 未全值匹配</h3><p>对索引中所有列都指定具体值，会使用索引，且跟字段的顺序无关<br>如果不是全值匹配，则需要遵守最左前缀法则。如果满足最左前缀，字段的顺序也可以改变。<span style="background-color:#ff00ff">最左的意思是，复合索引中最左边的列必须存在，与位置无关。</span></p><h3 id="5-8-2-违反最左前缀法则-缺失最左列"><a href="#5-8-2-违反最左前缀法则-缺失最左列" class="headerlink" title="5.8.2. 违反最左前缀法则 - 缺失最左列"></a>5.8.2. 违反最左前缀法则 - 缺失最左列</h3><p>如果索引了多列，要遵守最左前缀法则。指的是查询从索引的最左前列开始，并且不跳过索引中的列。</p><h3 id="5-8-3-出现跳跃某一列-只有最左列索引生效"><a href="#5-8-3-出现跳跃某一列-只有最左列索引生效" class="headerlink" title="5.8.3. 出现跳跃某一列 - 只有最左列索引生效"></a>5.8.3. 出现跳跃某一列 - 只有最左列索引生效</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319113327.png" alt="155dasd662203"></p><h3 id="5-8-4-范围查询的右边-列失效"><a href="#5-8-4-范围查询的右边-列失效" class="headerlink" title="5.8.4. 范围查询的右边 - 列失效"></a>5.8.4. 范围查询的右边 - 列失效</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319113435.png" alt="155617sss2256791"></p><p>根据前面的两个字段 name ， status 查询是走索引的，但是最后一个条件 address ，在范围条件的后面，所以没有用到索引。</p><h2 id="5-9-尽量使用覆盖索引-避免-select"><a href="#5-9-尽量使用覆盖索引-避免-select" class="headerlink" title="5.9. 尽量使用覆盖索引 - 避免 select *"></a>5.9. 尽量使用覆盖索引 - 避免 select <code>*</code></h2><p>尽量使用覆盖索引（只访问索引的查询（索引列完全包含查询列）），减少 select <code>*</code> 。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319113654.png" alt="1556173zzz928299"><br>如果查询列，超出索引列，也会降低性能。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319113725.png" alt="1556173zzz986068"></p><blockquote><p>TIP :</p></blockquote><pre><code>using index ：使用覆盖索引的时候就会出现using where：在查找使用索引的情况下，需要回表去查询所需的数据using index condition：查找使用了索引下推，但仍需要少量回表查询数据using index ; using where：查找使用了索引，但是需要的数据都在索引列中能找到，所以不需要回表查询数据 </code></pre><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230319-1210%%</span>❕ ^xkhbri</p><h2 id="5-10-is-Null-x2F-is-Not-Null-以成本估算为准"><a href="#5-10-is-Null-x2F-is-Not-Null-以成本估算为准" class="headerlink" title="5.10. is Null&#x2F;is Not Null- 以成本估算为准"></a>5.10. is Null&#x2F;is Not Null- 以成本估算为准</h2><p>如果某列建立索引，当进行 Select * from emp where depto is not null&#x2F;is null。则可能会使索引失效。是因为 MySQL 进行了成本计算，如果 null 值太多，那么 is null 成本太高，还不如全表扫描，所以不会走索引，而 is not null 就会走索引。反之亦然。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319122010.png" alt="1556180aa634889"></p><h2 id="5-11-尽量使用复合索引，而少使用单列索引"><a href="#5-11-尽量使用复合索引，而少使用单列索引" class="headerlink" title="5.11. 尽量使用复合索引，而少使用单列索引"></a>5.11. 尽量使用复合索引，而少使用单列索引</h2><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230319-1225%%</span>❕ ^9p2gib</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319122532.png" alt="image.png"></p><h1 id="6-索引使用"><a href="#6-索引使用" class="headerlink" title="6. 索引使用"></a>6. 索引使用</h1><h2 id="6-1-回表"><a href="#6-1-回表" class="headerlink" title="6.1. 回表"></a>6.1. 回表</h2><p>通过辅助索引获取到主键索引值，然后再到主键索引查找数据的过程。<br>如果通过辅助索引查找的主键值超过主键索引的 80%，则 CBO 可能优化为全表扫描</p><h2 id="6-2-覆盖索引"><a href="#6-2-覆盖索引" class="headerlink" title="6.2. 覆盖索引"></a>6.2. 覆盖索引</h2><p>尽量使用覆盖索引<span style="background-color:#ff00ff">（只访问索引的查询（索引列完全包含查询列））</span>，减少 select <code>*</code> 。<br>extra 为 <code>using index</code> ：使用覆盖索引的时候就会出现</p><h2 id="6-3-索引下推"><a href="#6-3-索引下推" class="headerlink" title="6.3. 索引下推"></a>6.3. 索引下推</h2><p>联合索引前面字段使用了范围查询 (比如大于小于或者 like)，导致后面索引列无法使用时。利用复合索引和索引下推可以有效减少回表操作。</p><p><a href="https://xiaolincoding.com/mysql/base/how_select.html#%E6%89%A7%E8%A1%8C%E5%99%A8">https://xiaolincoding.com/mysql/base/how_select.html#%E6%89%A7%E8%A1%8C%E5%99%A8</a><br>简称 ICP，Index Condition Pushdown(ICP) 是 MySQL5.6 中新特性，是⼀种在存储引擎层使⽤索引过滤数据的⼀种优化⽅式，ICP 可以减少存储引擎访问基表的次数以及 MySQL 服务器访问存储引擎的次数。举个例⼦来说⼀下：我们需要查询 name 以 javacode35 开头的，性别为 1 的记录数，sql 如下：<br>select count(id) from test1 a where name like ‘javacode35%’ and sex &#x3D; 1;</p><h3 id="6-3-1-一般过程"><a href="#6-3-1-一般过程" class="headerlink" title="6.3.1. 一般过程"></a>6.3.1. 一般过程</h3><ol><li>⾛ name 索引检索出以 javacode35 的第⼀条记录，得到记录的 id</li><li>利⽤ id 去主键索引中查询出这条记录 R1</li><li>判断 R1 中的 sex 是否为 1，然后重复上⾯的操作，直到找到所有记录为⽌。<br>上⾯的过程中需要⾛ name 索引以及需要回表操作。</li></ol><h3 id="6-3-2-索引下推"><a href="#6-3-2-索引下推" class="headerlink" title="6.3.2. 索引下推"></a>6.3.2. 索引下推</h3><p>所谓索引下推是指<span style="background-color:#ff0000">如果存在联合索引</span>，使用了前面二级索引，查询出记录值之后，即使后面的索引字段无法使用，通过将<span style="background-color:#ff00ff">判断工作从执行器下放到存储引擎</span>进行判断，<strong>而无需再回表，或者减少回表的次数</strong>。<br>如果采⽤ ICP 的⽅式，我们可以这么做，创建⼀个 (name,sex) 的组合索引，查询过程如下：</p><ol><li>⾛ (name,sex) 索引检索出以 javacode35 的第⼀条记录，可以得到 (name,sex,id)，记做 R1</li><li>判断 R1.sex 是否为 1，然后重复上⾯的操作，直到找到所有记录为⽌<br>这个过程中不需要回表操作了，通过索引的数据就可以完成整个条件的过滤，速<br>度⽐上⾯的更快⼀些</li></ol><p>当你的查询语句的执行计划里，出现了 Extra 为 <code>Using index condition</code>，那么说明使用了索引下推的优化。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406131437.png" alt="image.png"></p><h3 id="6-3-3-其他补充"><a href="#6-3-3-其他补充" class="headerlink" title="6.3.3. 其他补充"></a>6.3.3. 其他补充</h3><p>现在有下面这条查询语句：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">select * from t_user  where age &gt; 20 and reward = 100000;<br></code></pre></td></tr></table></figure><p>联合索引当遇到范围查询 (&gt;、&lt;) 就会停止匹配，也就是 <strong>age 字段能用到联合索引，但是 reward 字段则无法利用到索引</strong>。具体原因这里可以看这篇：<a href="https://xiaolincoding.com/mysql/index/index_interview.html#%E6%8C%89%E5%AD%97%E6%AE%B5%E4%B8%AA%E6%95%B0%E5%88%86%E7%B1%BB">索引常见面试题(opens new window)</a></p><p>那么，不使用索引下推（MySQL 5.6 之前的版本）时，执行器与存储引擎的执行流程是这样的：</p><ul><li>Server 层首先调用存储引擎的接口定位到满足查询条件的第一条二级索引记录，也就是定位到 age &gt; 20 的第一条记录；</li><li>存储引擎根据二级索引的 B+ 树快速定位到这条记录后，获取主键值，然后 <strong>进行回表操作</strong>，将完整的记录返回给 Server 层；</li><li>Server 层在判断该记录的 reward 是否等于 100000，如果成立则将其发送给客户端；否则跳过该记录；</li><li>接着，继续向存储引擎索要下一条记录，存储引擎在二级索引定位到记录后，获取主键值，然后回表操作，将完整的记录返回给 Server 层；</li><li>如此往复，直到存储引擎把表中的所有记录读完。</li></ul><p>可以看到，没有索引下推的时候，每查询到一条二级索引记录，都要进行回表操作，然后将记录返回给 Server，接着 Server 再判断该记录的 reward 是否等于 100000。</p><p>而使用索引下推后，判断记录的 reward 是否等于 100000 的工作交给了存储引擎层，过程如下 ：</p><ul><li>Server 层首先调用存储引擎的接口定位到满足查询条件的第一条二级索引记录，也就是定位到 age &gt; 20 的第一条记录；</li><li>存储引擎定位到二级索引后，<strong>先不执行回表</strong> 操作，而是先判断一下该索引中包含的列（reward 列）的条件（reward 是否等于 100000）是否成立。如果 <strong>条件不成立</strong>，则直接 <strong>跳过该二级索引</strong>。如果 <strong>成立</strong>，则 <strong>执行回表</strong> 操作，将完成记录返回给 Server 层。</li><li>Server 层在判断其他的查询条件（本次查询没有其他条件）是否成立，如果成立则将其发送给客户端；否则跳过该记录，然后向存储引擎索要下一条记录。</li><li>如此往复，直到存储引擎把表中的所有记录读完。</li></ul><p>可以看到，使用了索引下推后，虽然 reward 列无法使用到联合索引，但是因为它包含在联合索引（age，reward）里，所以直接在存储引擎过滤出满足 reward &#x3D; 100000 的记录后，才去执行回表操作获取整个记录。相比于没有使用索引下推，节省了很多回表操作。</p><p>当你发现执行计划里的 Extr 部分显示了 “Using index condition”，说明使用了索引下推。</p><h1 id="7-索引分析"><a href="#7-索引分析" class="headerlink" title="7. 索引分析"></a>7. 索引分析</h1><h2 id="7-1-查看索引"><a href="#7-1-查看索引" class="headerlink" title="7.1. 查看索引"></a>7.1. 查看索引</h2><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">show</span> status <span class="hljs-keyword">like</span> <span class="hljs-string">&#x27;Handler_read%&#x27;</span>;<br><br><span class="hljs-keyword">show</span> <span class="hljs-keyword">global</span> status <span class="hljs-keyword">like</span> <span class="hljs-string">&#x27;Handler_read%&#x27;</span>;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319124339.png" alt="1552885sss364563"></p><figure class="highlight plaintext"><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 plaintext">Handler_read_first：索引中第一条被读的次数。如果较高，表示服务器正执行大量全索引扫描（这个值越低越好）。<br><br>Handler_read_key：如果索引正在工作，这个值代表一个行被索引值读的次数，如果值越低，表示索引得到的性能改善不高，因为索引不经常使用（这个值越高越好）。<br><br>Handler_read_next ：按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列，该值增加。<br><br>Handler_read_prev：按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY ... DESC。<br><br>Handler_read_rnd ：根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高，意味着运行效率低，应该建立索引来补救。<br><br>Handler_read_rnd_next：在数据文件中读下一行的请求数。如果你正进行大量的表扫描，该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。<br></code></pre></td></tr></table></figure><h1 id="8-索引优先级"><a href="#8-索引优先级" class="headerlink" title="8. 索引优先级"></a>8. 索引优先级</h1><h2 id="8-1-多个索引优先级是如何匹配的"><a href="#8-1-多个索引优先级是如何匹配的" class="headerlink" title="8.1. 多个索引优先级是如何匹配的"></a>8.1. 多个索引优先级是如何匹配的</h2><ol><li>主键（唯一索引）匹配</li><li>全值匹配（单值匹配）</li><li>最左前缀匹配</li><li>范围匹配</li><li>索引扫描</li><li>全表扫描</li></ol><h1 id="9-实战经验"><a href="#9-实战经验" class="headerlink" title="9. 实战经验"></a>9. 实战经验</h1><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><h2 id="10-1-为什么大家说-mysql-数据库单表最大两千万？依据是啥？"><a href="#10-1-为什么大家说-mysql-数据库单表最大两千万？依据是啥？" class="headerlink" title="10.1. 为什么大家说 mysql 数据库单表最大两千万？依据是啥？"></a>10.1. 为什么大家说 mysql 数据库单表最大两千万？依据是啥？</h2><p><a href="https://ost.51cto.com/posts/11397">https://ost.51cto.com/posts/11397</a></p><h2 id="10-2-黑马"><a href="#10-2-黑马" class="headerlink" title="10.2. 黑马"></a>10.2. 黑马</h2><h3 id="10-2-1-视频"><a href="#10-2-1-视频" class="headerlink" title="10.2.1. 视频"></a>10.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1zJ411M7TB?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zJ411M7TB?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="10-2-2-资料"><a href="#10-2-2-资料" class="headerlink" title="10.2.2. 资料"></a>10.2.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/002-框架源码专题/002-DB/002-MySQL/黑马<br></code></pre></td></tr></table></figure><p><a href="https://www.cnblogs.com/aspwebchh/p/6652855.html">https://www.cnblogs.com/aspwebchh/p/6652855.html</a></p><p><a href="https://blog.csdn.net/linminqin/article/details/44342205">https://blog.csdn.net/linminqin/article/details/44342205</a></p><p><a href="https://www.cnblogs.com/aspnethot/articles/1504082.html">https://www.cnblogs.com/aspnethot/articles/1504082.html</a></p><h2 id="10-3-小林-coding"><a href="#10-3-小林-coding" class="headerlink" title="10.3. 小林 coding"></a>10.3. 小林 coding</h2><p><a href="https://xiaolincoding.com/mysql/lock/how_to_lock.html#gap-lock">https://xiaolincoding.com/mysql/lock/how_to_lock.html#gap-lock</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-9、SQL应用优化</title>
      <link href="/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-9%E3%80%81SQL%E5%BA%94%E7%94%A8%E4%BC%98%E5%8C%96/"/>
      <url>/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-9%E3%80%81SQL%E5%BA%94%E7%94%A8%E4%BC%98%E5%8C%96/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-常用-SQL-技巧"><a href="#1-常用-SQL-技巧" class="headerlink" title="1. 常用 SQL 技巧"></a>1. 常用 SQL 技巧</h1><p>MySQL 的性能优化我认为可以分为 4 大部分 l 硬件和操作系统层面的优化 l 架构设计层面的优化 l MySQL 程序配置优化 l SQL 优化硬件及操作系统层面优化从硬件层面来说，影响 Mysql 性能的因素有，CPU、可用内存大小、磁盘读写速度、网络带宽从操作系层面来说，应用文件句柄数、操作系统网络的配置都会影响到 Mysql 性能。这部分的优化一般由 DBA 或者运维工程师去完成。在硬件基础资源的优化中，我们重点应该关注服务本身承载的体量，然后提出合理的指标要求，避免出现资源浪费！ 架构设计层面的优化 MySQL 是一个磁盘 IO 访问量非常频繁的关系型数据库<br>在高并发和高性能的场景中.MySQL 数据库必然会承受巨大的并发压力，而此时，我们的优化方式可以分为几个部分。 1. 搭建 Mysql 主从集群，单个 Mysql 服务容易单点故障，一旦服务器宕机，将会导致依赖 Mysql 数据库的应用全部无法响应。主从集群或者主主集群可以保证服务的高可用性。 2. 读写分离设计，在读多写少的场景中，通过读写分离的方案，可以避免读写冲突导致的性能影响 3. 引入分库分表机制，通过分库可以降低单个服务器节点的 IO 压力，通过分表的方式可以降低单表数据量，从而提升 sql 查询的效率。 4. 针对热点数据，可以引入更为高效的分布式数据库，比如 Redis、MongoDB 等，他们可以很好的缓解 Mysql 的访问压力，同时还能提升数据检索性能。 MySQL 程序配置优化 MySQL 是一个经过互联网大厂验证过的生产级别的成熟数据库，对于 Mysql 数据库本身的优化，一般是通过 Mysql 中的配置文件 my.cnf 来完成的，比如。 Mysql5.7 版本默认的最大连接数是 151 个，这个值可以在 my.cnf 中修改。 binlog 日志，默认是不开启缓存池 bufferpoll 的默认大小配置等。由于这些配置一般都和用户安装的硬件环境以及使用场景有关系，因此这些配置官方只会提供一个默认值，具体情况还得由使用者来修改。关于配置项的修改，需要关注两个方面<br>l 配置的作用域，分为会话级别和全局 l 是否支持热加载因此，针对这两个点，我们需要注意的是： l 全局参数的设定对于已经存在的会话无法生效 l 会话参数的设定随着会话的销毁而失效 l 全局类的统一配置建议配置在默认配置文件中，否则重启服务会导致配置失效 SQL 优化又能分为三步曲 l 第一、慢 SQL 的定位和排查我们可以通过慢查询日志和慢查询日志分析工具得到有问题的 SQL 列表。 l 第二、执行计划分析<br>针对慢 SQL，我们可以使用关键字 explain 来查看当前 sql 的执行计划.可以重点 关注 type key rows filterd 等字段 ，从而定位该 SQL 执行慢的根本原因。再有 的放矢的进行优化 l 第三、使用 show profile 工具 Show Profile 是 MySQL 提供的可以用来分析当前会话中，SQL 语句资源消耗情 况的工具，可用于 SQL 调优的测量。在当前会话中.默认情况下处于 show profile 是关闭状态，打开之后保存最近 15 次的运行结果 针对运行慢的 SQL，通过 profile 工具进行详细分析.可以得到 SQL 执行过程中 所有的资源开销情况. 如 IO 开销,CPU 开销,内存开销等. 以上就是我对 MySQL 性能优化的理解。 好的，看完高手的回答后，相信各位对 MySQL 性能优化有了一定的理解了，最 后我在给各位总结一下常见的 SQL 优化规则： l SQL 的查询一定要基于索引来进行数据扫描 l 避免索引列上使用函数或者运算,这样会导致索引失效 l where 字句中 like %号,尽量放置在右边 l 使用索引扫描,联合索引中的列从左往右,命中越多越好. l 尽可能使用 SQL 语句用到的索引完成排序,避免使用文件排序的方式 l 查询有效的列信息即可.少用 * 代替列信息 l 永远用小结果集驱动大结果集</p><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><h2 id="3-1-黑马"><a href="#3-1-黑马" class="headerlink" title="3.1. 黑马"></a>3.1. 黑马</h2><h3 id="3-1-1-视频"><a href="#3-1-1-视频" class="headerlink" title="3.1.1. 视频"></a>3.1.1. 视频</h3><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230319-1358%%</span>❕ ^c4nii3<br><a href="https://www.bilibili.com/video/BV1zJ411M7TB?p=70&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zJ411M7TB?p=70&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="3-1-2-资料"><a href="#3-1-2-资料" class="headerlink" title="3.1.2. 资料"></a>3.1.2. 资料</h3><p>[[Mysql高级-day03]]</p><h4 id="3-1-2-1-MySQL-操作日志-AOP-案例"><a href="#3-1-2-1-MySQL-操作日志-AOP-案例" class="headerlink" title="3.1.2.1. MySQL 操作日志 AOP 案例"></a>3.1.2.1. MySQL 操作日志 AOP 案例</h4><p><a href="https://www.bilibili.com/video/BV1zJ411M7TB?p=109&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zJ411M7TB?p=109&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-8、SQL优化</title>
      <link href="/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-8%E3%80%81SQL%E4%BC%98%E5%8C%96/"/>
      <url>/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-8%E3%80%81SQL%E4%BC%98%E5%8C%96/</url>
      
        <content type="html"><![CDATA[<hr><p><a href="https://segmentfault.com/a/1190000040598165">https://segmentfault.com/a/1190000040598165</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319083952.png" alt="image.png"></p><h1 id="1-优化步骤概览"><a href="#1-优化步骤概览" class="headerlink" title="1. 优化步骤概览"></a>1. 优化步骤概览</h1><h2 id="1-1-查看-SQL-执行频率"><a href="#1-1-查看-SQL-执行频率" class="headerlink" title="1.1. 查看 SQL 执行频率"></a>1.1. 查看 SQL 执行频率</h2><h2 id="1-2-定位低效率执行-SQL"><a href="#1-2-定位低效率执行-SQL" class="headerlink" title="1.2. 定位低效率执行 SQL"></a>1.2. 定位低效率执行 SQL</h2><h2 id="1-3-explain-分析执行计划"><a href="#1-3-explain-分析执行计划" class="headerlink" title="1.3. explain 分析执行计划"></a>1.3. explain 分析执行计划</h2><h2 id="1-4-show-profile-查看-SQL-耗时"><a href="#1-4-show-profile-查看-SQL-耗时" class="headerlink" title="1.4. show profile 查看 SQL 耗时"></a>1.4. show profile 查看 SQL 耗时</h2><h2 id="1-5-trace-分析优化器执行计划"><a href="#1-5-trace-分析优化器执行计划" class="headerlink" title="1.5. trace 分析优化器执行计划"></a>1.5. trace 分析优化器执行计划</h2><h1 id="2-执行频率"><a href="#2-执行频率" class="headerlink" title="2. 执行频率"></a>2. 执行频率</h1><h2 id="2-1-show-status-like-‘Com-’"><a href="#2-1-show-status-like-‘Com-’" class="headerlink" title="2.1. show status like ‘Com_______’"></a>2.1. show status like ‘Com_______’</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">show</span> status <span class="hljs-keyword">like</span> <span class="hljs-string">&#x27;Com_______&#x27;</span>;<br></code></pre></td></tr></table></figure><p>当前连接<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319101417.png" alt="image.png"><br>全局<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319101431.png" alt="image.png"></p><h1 id="3-定位低效-SQL"><a href="#3-定位低效-SQL" class="headerlink" title="3. 定位低效 SQL"></a>3. 定位低效 SQL</h1><h2 id="3-1-show-processlist"><a href="#3-1-show-processlist" class="headerlink" title="3.1. show processlist"></a>3.1. show processlist</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319092312.png" alt="image.png"></p><p><span style="background-color:#ff00ff">慢查询日志在查询结束以后才记录，所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题</span>，可以使用 show processlist 命令查看当前 MySQL 在进行的线程，包括线程的状态、是否锁表等，可以实时地查看 SQL 的执行情况，同时对一些锁表操作进行优化。</p><h2 id="3-2-慢查询日志"><a href="#3-2-慢查询日志" class="headerlink" title="3.2. 慢查询日志"></a>3.2. 慢查询日志</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319092404.png" alt="image.png"></p><h1 id="4-explain-查看执行计划"><a href="#4-explain-查看执行计划" class="headerlink" title="4. explain- 查看执行计划"></a>4. explain- 查看执行计划</h1><h2 id="4-1-explain"><a href="#4-1-explain" class="headerlink" title="4.1. explain"></a>4.1. explain</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210923051250605.png" alt="image-20210923051250605"></p><h2 id="4-2-type"><a href="#4-2-type" class="headerlink" title="4.2. type"></a>4.2. type</h2><p>查询执行的类型，描述了查询是如何执行的。所有值的顺序从最优到最差排序为：</p><p><span style="background-color:#ff00ff">system &gt; const &gt; eq_ref &gt; ref &gt; fulltext &gt; ref_or_null &gt; index_merge &gt; unique_subquery &gt; index_subquery &gt; range &gt; index &gt; ALL</span></p><p>常见的几种类型具体含义如下：</p><ol><li>system：如果表使用的引擎对于表行数统计是精确的 (如：MyISAM)，且表中只有一行记录的情况下，访问方法是 system ，是 const 的一种特例。</li><li>const：表中最多只有一行匹配的记录，一次查询就可以找到，常用于<span style="background-color:#ff00ff">使用主键或唯一索引</span>的所有字段作为查询条件。</li><li>eq_ref：当连表查询时，前一张表的行在当前这张表中只有一行与之对应。是除了 system 与 const 之外最好的 join 方式，常用于<span style="background-color:#ff00ff">使用主键或唯一索引的所有字段作为连表条件</span>。</li><li>ref：<span style="background-color:#ff00ff">使用普通索引作为查询条件</span>，查询结果可能找到多个符合条件的行。</li><li>index_merge：<span style="background-color:#ff00ff">当查询条件使用了多个索引</span>时，表示开启了 Index Merge 优化，此时执行计划中的 key 列列出了使用到的索引。</li><li>range：<span style="background-color:#ff00ff">对索引列进行范围查询</span>，执行计划中的 key 列表示哪个索引被使用了。</li><li>index：查询遍历了整棵索引树，与 ALL 类似，只不过扫描的是索引，而索引一般在内存中，速度更快。</li><li>ALL：全表扫描。</li></ol><h2 id="4-3-extra"><a href="#4-3-extra" class="headerlink" title="4.3. extra"></a>4.3. extra</h2><p>这列包含了 MySQL 解析查询的额外信息，通过这些信息，可以更准确的理解 MySQL 到底是如何执行查询的。常见的值如下：</p><ol><li>Using FileSort：通过对返回数据进行排序，也就是通常说的 filesort 排序，<span style="background-color:#ff00ff">所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序</span>。</li><li>Using temporary：MySQL 需要创建临时表来存储查询的结果，常见于 ORDER BY 和 GROUP BY。</li><li>Using index：表明查询使用了<span style="background-color:#ff00ff">覆盖索引</span>，不用回表，查询效率非常高。</li><li>Using index condition：表示查询优化器选择使用了<span style="background-color:#ff00ff">索引条件下推</span>这个特性。</li><li>Using where：表明查询使用了 WHERE 子句进行条件过滤。<span style="background-color:#ff00ff">一般在没有使用到索引的时候会出现</span>。</li><li>Using join buffer (Block Nested Loop)：连表查询的方式，表示当被驱动表的没有使用索引的时候，MySQL 会先将驱动表读出来放到 join buffer 中，再遍历被驱动表与驱动表进行查询。</li></ol><p>这里提醒下，<span style="background-color:#ff00ff">当 Extra 列包含 Using filesort 或 Using temporary 时</span>，MySQL 的性能可能会存在问题，需要尽可能避免。</p><h1 id="5-profiles-查看耗时"><a href="#5-profiles-查看耗时" class="headerlink" title="5. profiles- 查看耗时"></a>5. profiles- 查看耗时</h1><p><a href="https://segmentfault.com/a/1190000023470437">https://segmentfault.com/a/1190000023470437</a></p><p>Mysql 从 5.0.37 版本开始增加了对 show profiles 和 show profile 语句的支持。show profiles 能够在做 SQL 优化时帮助我们<span style="background-color:#ff00ff">了解时间都耗费到哪里去了</span>。</p><p>通过 have_profiling 参数，能够看到当前 MySQL 是否支持 profile：</p><figure class="highlight plaintext"><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 plaintext">select version();  -- 先查看mysql版本是否高于5.0.37<br>show variables like &#x27;have_profiling&#x27;; -- 首先查看是否支持该功能<br>set profiling=on; -- 默认是关闭的，开启该功能<br>show variables like &#x27;profiling%&#x27;; -- 查看开启状态。15表示历史缓存sql的个数<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319171755.png" alt="15524884zzz01999"></p><p>执行完上述命令之后，再执行 show profiles 指令，来查看 SQL 语句执行的耗时：</p><h2 id="5-1-show-profiles"><a href="#5-1-show-profiles" class="headerlink" title="5.1. show profiles"></a>5.1. show profiles</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319101929.png" alt="15png9017940"></p><h2 id="5-2-show-profile-for-query-x"><a href="#5-2-show-profile-for-query-x" class="headerlink" title="5.2. show profile for query x"></a>5.2. show profile for query x</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319171926.png" alt="1552489aaa053763"></p><p>TIP ：<br>  Sending data 状态表示 MySQL 线程开始访问数据行并把结果返回给客户端，而不仅仅是返回个客户端。由于在 Sending data 状态下，MySQL 线程往往需要做大量的磁盘读取操作，所以经常是整个查询中耗时最长的状态。</p><p>在获取到最消耗时间的线程状态后，MySQL 支持进一步选择 all、cpu、block io 、context switch、page faults 等明细类型类查看 MySQL 在使用什么资源上耗费了过高的时间。例如，选择查看 CPU 的耗费时间 ：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319172016.png" alt="1552489aaa671119"></p><h1 id="6-trace-分析优化器执行计划"><a href="#6-trace-分析优化器执行计划" class="headerlink" title="6. trace- 分析优化器执行计划"></a>6. trace- 分析优化器执行计划</h1><p>MySQL5.6 提供了对 SQL 的跟踪 trace, 通过 trace 文件能够进一步了解为什么优化器选择 A 计划, 而不是选择 B 计划。</p><p>打开 trace ，设置格式为 JSON，并设置 trace 最大能够使用的内存大小，避免解析过程中因为默认内存过小而不能够完整展示。</p><figure class="highlight plaintext"><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 plaintext">SET optimizer_trace=&quot;enabled=on&quot;,end_markers_in_json=on;<br>set optimizer_trace_max_mem_size=1000000;<br></code></pre></td></tr></table></figure><p>执行 SQL 语句 ：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">select * from tb_item where id &lt; 4;<br></code></pre></td></tr></table></figure><p>最后，检查 information_schema.optimizer_trace 就可以知道 MySQL 是如何执行 SQL 的 ：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">select * from information_schema.optimizer_trace\G;<br></code></pre></td></tr></table></figure><h1 id="7-优化场景-优化内容-⭐️🔴"><a href="#7-优化场景-优化内容-⭐️🔴" class="headerlink" title="7. 优化场景 (优化内容)⭐️🔴"></a>7. 优化场景 (优化内容)⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230415-1317%%</span>❕ ^62llgx</p><h2 id="7-1-概览"><a href="#7-1-概览" class="headerlink" title="7.1. 概览"></a>7.1. 概览</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319183850.png" alt="image.png"></p><h2 id="7-2-索引使用"><a href="#7-2-索引使用" class="headerlink" title="7.2. 索引使用"></a>7.2. 索引使用</h2><a href="/2023/03/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-7%E3%80%81%E7%B4%A2%E5%BC%95%E5%8E%9F%E7%90%86/" title="MySQL-7、索引原理">MySQL-7、索引原理</a><h3 id="7-2-1-查看使用情况"><a href="#7-2-1-查看使用情况" class="headerlink" title="7.2.1. 查看使用情况"></a>7.2.1. 查看使用情况</h3><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">show status like <span class="hljs-string">&#x27;Handler_read%&#x27;</span>;<br>show global status like <span class="hljs-string">&#x27;Handler_read%&#x27;</span>;<br></code></pre></td></tr></table></figure><h3 id="7-2-2-防止索引失效"><a href="#7-2-2-防止索引失效" class="headerlink" title="7.2.2. 防止索引失效"></a>7.2.2. 防止索引失效</h3><a href="/2023/03/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-7%E3%80%81%E7%B4%A2%E5%BC%95%E5%8E%9F%E7%90%86/" title="MySQL-7、索引原理">MySQL-7、索引原理</a><h2 id="7-3-主键优化"><a href="#7-3-主键优化" class="headerlink" title="7.3. 主键优化"></a>7.3. 主键优化</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319172923.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319172944.png" alt="image.png"></p><h3 id="7-3-1-页分裂"><a href="#7-3-1-页分裂" class="headerlink" title="7.3.1. 页分裂"></a>7.3.1. 页分裂</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230407105307.png" alt="image.png"></p><p><span style="background-color:#ff00ff">顺序插入不会出现页分裂，乱序插入会发生页分裂</span></p><p>页分裂过程如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230407105722.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230407105747.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230407105824.png" alt="image.png"></p><h3 id="7-3-2-页合并"><a href="#7-3-2-页合并" class="headerlink" title="7.3.2. 页合并"></a>7.3.2. 页合并</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319173430.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319173449.png" alt="image.png"></p><h3 id="7-3-3-主键设计原则"><a href="#7-3-3-主键设计原则" class="headerlink" title="7.3.3. 主键设计原则"></a>7.3.3. 主键设计原则</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319173558.png" alt="image.png"></p><ol><li>主键要短：一般情况下，主键索引 (聚集索引) 只有 1 个，二级索引有多个。而<span style="background-color:#ff00ff">二级索引的叶子节点存储的就是主键</span>，如果主键长度很长二级索引又很多的情况下，会占据大量的磁盘空间，同时增加 IO，影响性能</li><li>主键要自增，可<span style="background-color:#ff00ff">防止页分裂</span></li><li>尽量不要使用 UUID 或者身份证这种，既不能自增又很长的字符串做主键</li><li>避免对主键进行修改</li></ol><h2 id="7-4-大批量插入数据"><a href="#7-4-大批量插入数据" class="headerlink" title="7.4. 大批量插入数据"></a>7.4. 大批量插入数据</h2><p>主键顺序插入<br>关闭唯一性校验<br>手动提交事务<br>load 命令插入<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319172715.png" alt="image.png"></p><h2 id="7-5-优化-insert-语句"><a href="#7-5-优化-insert-语句" class="headerlink" title="7.5. 优化 insert 语句"></a>7.5. 优化 insert 语句</h2><p>合并插入，<code>;</code> 加在最后<br>手动提交<br>主键顺序插入</p><h2 id="7-6-优化-update-语句"><a href="#7-6-优化-update-语句" class="headerlink" title="7.6. 优化 update 语句"></a>7.6. 优化 update 语句</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319183921.png" alt="image.png"></p><p><span style="background-color:#ff00ff">where 条件中必须有索引，且不能失效，否则就会使用表锁，影响并发效率</span></p><h2 id="7-7-优化-order-by-语句"><a href="#7-7-优化-order-by-语句" class="headerlink" title="7.7. 优化 order by 语句"></a>7.7. 优化 order by 语句</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230407111842.png" alt="image.png"></p><h3 id="7-7-1-using-filesort"><a href="#7-7-1-using-filesort" class="headerlink" title="7.7.1. using filesort"></a>7.7.1. using filesort</h3><p>通过对返回数据进行排序，也就是通常说的 filesort 排序，<span style="background-color:#ff00ff">所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序</span>。</p><h4 id="7-7-1-1-产生-与联合索引失效原因类似"><a href="#7-7-1-1-产生-与联合索引失效原因类似" class="headerlink" title="7.7.1.1. 产生 - 与联合索引失效原因类似"></a>7.7.1.1. 产生 - 与联合索引失效原因类似</h4><h5 id="7-7-1-1-1-跳过联合索引中间值"><a href="#7-7-1-1-1-跳过联合索引中间值" class="headerlink" title="7.7.1.1.1. 跳过联合索引中间值"></a>7.7.1.1.1. 跳过联合索引中间值</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230526084939.png" alt="image.png"></p><h5 id="7-7-1-1-2-索引顺序打乱"><a href="#7-7-1-1-2-索引顺序打乱" class="headerlink" title="7.7.1.1.2. 索引顺序打乱"></a>7.7.1.1.2. 索引顺序打乱</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230526085012.png" alt="image.png"></p><h5 id="7-7-1-1-3-与索引默认顺序不一致"><a href="#7-7-1-1-3-与索引默认顺序不一致" class="headerlink" title="7.7.1.1.3. 与索引默认顺序不一致"></a>7.7.1.1.3. 与索引默认顺序不一致</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230526085054.png" alt="image.png"></p><h5 id="7-7-1-1-4-多个相等条件也是范围查询"><a href="#7-7-1-1-4-多个相等条件也是范围查询" class="headerlink" title="7.7.1.1.4. 多个相等条件也是范围查询"></a>7.7.1.1.4. 多个相等条件也是范围查询</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230526085148.png" alt="image.png"></p><h4 id="7-7-1-2-分类"><a href="#7-7-1-2-分类" class="headerlink" title="7.7.1.2. 分类"></a>7.7.1.2. 分类</h4><p><a href="https://www.jianshu.com/p/cb9c2edd7f70">https://www.jianshu.com/p/cb9c2edd7f70</a></p><p><span style="background-color:#00ff00">通过创建合适的索引，能够减少 Filesort 的出现，但是在某些情况下，条件限制不能让 Filesort 消失，那就需要加快 Filesort 的排序操作</span>。对于 Filesort ， MySQL 有两种排序算法：</p><h5 id="7-7-1-2-1-双路排序-两次扫描算法"><a href="#7-7-1-2-1-双路排序-两次扫描算法" class="headerlink" title="7.7.1.2.1. 双路排序 (两次扫描算法)"></a>7.7.1.2.1. 双路排序 (两次扫描算法)</h5><ol><li>从索引 name 字段中，查询到满足条件的数据 name&#x3D;‘jarye’条件主键的 id</li><li>根据主键 id，获取排序的字段和主键 id 缓存到 sort buffer 中</li><li>重复执行 1,2 步骤流程</li><li>对 sort buffer 中的数据实现排序</li><li>根据排序好的主键 id 和 position，在从原来表中根据 id 查询数据给客户端。</li></ol><p>该操作可能会<span style="background-color:#ff00ff">导致大量随机 I&#x2F;O 操作</span></p><h5 id="7-7-1-2-2-单路排序-一次扫描算法"><a href="#7-7-1-2-2-单路排序-一次扫描算法" class="headerlink" title="7.7.1.2.2. 单路排序 (一次扫描算法)"></a>7.7.1.2.2. 单路排序 (一次扫描算法)</h5><ol><li>从索引 name 字段中，查询到满足条件的数据 name&#x3D;’jarye’ 条件主键的 id</li><li>根据主键 id 取出整行的数据，缓存到 sort buffer 中</li><li>重复执行 1,2 步骤流程</li><li>对 sort buffer 中的数据实现排序给客户端</li></ol><p>一次性取出满足条件的所有字段，然后在排序区 sort buffer 中排序后直接输出结果集。<span style="background-color:#ff00ff">排序时内存开销较大，但是排序效率比两次扫描算法要高</span>。</p><p>MySQL 通过比较系统变量 <code>max_length_for_sort_data</code> 的大小和 Query 语句取出的字段总大小， 来判定是否那种排序算法，如果 max_length_for_sort_data 更大，那么使用第二种优化之后的算法；否则使用第一种。</p><p><span style="background-color:#ff00ff">可以适当提高 sort_buffer_size 和 max_length_for_sort_data 系统变量，来增大排序区的大小，提高排序的效率。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319130934.png" alt="1556338aaa367593"></p><h4 id="7-7-1-3-对比"><a href="#7-7-1-3-对比" class="headerlink" title="7.7.1.3. 对比"></a>7.7.1.3. 对比</h4><p>从磁盘中读取查询需要的所有列，按照 order by 列在 sort_buffer(排序缓存) 缓冲区对他们进行排序，然后扫描排序后的列表输出。因为单路排序效率更快，避免了二次读取数据，把随机 IO 变成了顺序 IO，但是会使用更多的空间。</p><p>其实对比两个排序模式，单路排序会把所有需要查询的字段都放到 sort buffer 中，而双路排序只会把主键和需要排序的字段放到 sort buffer 中进行排序，然后再通过主键回到原表查询需要的字段。</p><h4 id="7-7-1-4-优化"><a href="#7-7-1-4-优化" class="headerlink" title="7.7.1.4. 优化"></a>7.7.1.4. 优化</h4><p>至于 mysql 优化器使用双路排序还是单路排序是有自己的算法判断的，如果查询的列字段大于 max_length_for_sort_data 变量，则会使用双路排序，反之则会使用单路排序，单路排序速度是更快的，不过比较占据内存，如果在内存空间允许的情况下想要使用单路排序的话，可以增加 max_length_for_sort_data 变量的大小，max_length_for_sort_data 变量默认为 1024 字节。<br>如果全部使用 sort_buffer 内存排序一般情况下效率会高于磁盘文件排序，但不能因为这个就随便增大 sort_buffer(默认 1M)，mysql 很多参数设置都是做过优化的，不要轻易调整。</p><h3 id="7-7-2-using-index"><a href="#7-7-2-using-index" class="headerlink" title="7.7.2. using index"></a>7.7.2. using index</h3><p><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230319-1307%%</span>❕ ^2wm6nm</p><p>了解了 MySQL 的排序方式，优化目标就清晰了：尽量减少额外的排序，通过索引直接返回有序数据。<span style="background-color:#ff00ff">where 条件和 Order by 使用相同的索引，并且 Order By 的顺序和索引顺序相同， 都是升序或者都是降序</span>。否则肯定需要额外的操作，这样就会出现 FileSort。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319174504.png" alt="image.png"></p><h2 id="7-8-优化-group-by-语句"><a href="#7-8-优化-group-by-语句" class="headerlink" title="7.8. 优化 group by 语句"></a>7.8. 优化 group by 语句</h2><p>由于 GROUP BY 实际上也同样会进行排序操作，而且与 ORDER BY 相比，GROUP BY 主要只是多了排序之后的分组操作。当然，如果在分组的时候还使用了其他的一些聚合函数，那么还需要一些聚合函数的计算。所以，在 GROUP BY 的实现过程中，与 ORDER BY 一样也可以利用到索引。</p><p>如果查询包含 group by 但是用户想要避免排序结果的消耗，则可以执行 order by null 禁止排序。</p><p>也可以创建索引进行优化。</p><h2 id="7-9-优化嵌套查询-使用-JOIN"><a href="#7-9-优化嵌套查询-使用-JOIN" class="headerlink" title="7.9. 优化嵌套查询 - 使用 JOIN"></a>7.9. 优化嵌套查询 - 使用 JOIN</h2><p>使用连接（JOIN）替代。连接 (Join) 查询之所以更有效率一些 ，是因为 MySQL<span style="background-color:#ff00ff">不需要在内存中创建临时表</span>来完成这个逻辑上需要两个步骤的查询工作。</p><p><span style="background-color:#ff00ff">执行计划中 type 会由 index 优化为 ref</span></p><h2 id="7-10-优化-OR-条件-使用-UNION"><a href="#7-10-优化-OR-条件-使用-UNION" class="headerlink" title="7.10. 优化 OR 条件 - 使用 UNION"></a>7.10. 优化 OR 条件 - 使用 UNION</h2><p>对于包含 OR 的查询子句，如果要利用索引，则 OR 之间的每个条件列<span style="background-color:#ff00ff">都必须有索引</span> ， <span style="background-color:#ff0000">而且不能使用到复合索引</span>；如果没有索引，则应该考虑增加索引。</p><p><span style="background-color:#ff00ff">建议使用 union 替换 or</span>，如果不排序不去重，就用 UNION ALL<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319133350.png" alt="image.png"></p><p>type 显示的是访问类型，是较为重要的一个指标，结果值从好到坏依次是：</p><p><code>system &gt; const &gt; eq_ref &gt; ref &gt; fulltext &gt; ref_or_null  &gt; index_merge &gt; unique_subquery &gt; index_subquery &gt; range &gt; index &gt; ALL</code></p><p>UNION 语句的 type 值为 ref，OR 语句的 type 值为 range，可以看到这是一个很明显的差距</p><p>UNION 语句的 ref 值为 const，OR 语句的 ref 值为 null，const 表示是常量值引用，非常快</p><p>这两项的差距就说明了 UNION 要优于 OR 。</p><h2 id="7-11-limit-分页查询优化"><a href="#7-11-limit-分页查询优化" class="headerlink" title="7.11. limit 分页查询优化"></a>7.11. limit 分页查询优化</h2><h3 id="7-11-1-优化思路一"><a href="#7-11-1-优化思路一" class="headerlink" title="7.11.1. 优化思路一"></a>7.11.1. 优化思路一</h3><p>在索引上完成排序分页操作，最后根据主键关联回原表查询所需要的其他列内容。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319133839.png" alt="image.png"></p><h3 id="7-11-2-优化思路二"><a href="#7-11-2-优化思路二" class="headerlink" title="7.11.2. 优化思路二"></a>7.11.2. 优化思路二</h3><p>该方案适用于主键自增的表，可以把 Limit 查询转换成某个位置的查询 。</p><h2 id="7-12-count-优化"><a href="#7-12-count-优化" class="headerlink" title="7.12. count 优化"></a>7.12. count 优化</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319182940.png" alt="image.png"></p><h2 id="7-13-使用-SQL-提示"><a href="#7-13-使用-SQL-提示" class="headerlink" title="7.13. 使用 SQL 提示"></a>7.13. 使用 SQL 提示</h2><h3 id="7-13-1-USE-INDEX"><a href="#7-13-1-USE-INDEX" class="headerlink" title="7.13.1. USE INDEX"></a>7.13.1. USE INDEX</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319135038.png" alt="image.png"></p><h3 id="7-13-2-IGNORE-INDEX"><a href="#7-13-2-IGNORE-INDEX" class="headerlink" title="7.13.2. IGNORE INDEX"></a>7.13.2. IGNORE INDEX</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319135107.png" alt="image.png"></p><h3 id="7-13-3-FORCE-INDEX"><a href="#7-13-3-FORCE-INDEX" class="headerlink" title="7.13.3. FORCE INDEX"></a>7.13.3. FORCE INDEX</h3><h1 id="8-Innodb-内存优化"><a href="#8-Innodb-内存优化" class="headerlink" title="8. Innodb 内存优化"></a>8. Innodb 内存优化</h1><span style="display:none">- [ ] 🚩 - 待整理 - 🏡 2023-04-12 12:09</span>#todo<h1 id="9-实战经验"><a href="#9-实战经验" class="headerlink" title="9. 实战经验"></a>9. 实战经验</h1><h2 id="打破第三范式"><a href="#打破第三范式" class="headerlink" title="打破第三范式"></a>打破第三范式</h2><p><a href="https://www.bilibili.com/video/BV1gc411s7WB?t=905.4&amp;p=24">https://www.bilibili.com/video/BV1gc411s7WB?t=905.4&amp;p=24</a></p><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><h2 id="10-1-黑马"><a href="#10-1-黑马" class="headerlink" title="10.1. 黑马"></a>10.1. 黑马</h2><h3 id="10-1-1-视频-1"><a href="#10-1-1-视频-1" class="headerlink" title="10.1.1. 视频 1"></a>10.1.1. 视频 1</h3><p><a href="https://www.bilibili.com/video/BV1P24y1b7Xg?p=2&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1P24y1b7Xg?p=2&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="10-1-2-视频-2"><a href="#10-1-2-视频-2" class="headerlink" title="10.1.2. 视频 2"></a>10.1.2. 视频 2</h3><p><a href="https://www.bilibili.com/video/BV1zJ411M7TB?p=48&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zJ411M7TB?p=48&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><a href="/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-9%E3%80%81SQL%E5%BA%94%E7%94%A8%E4%BC%98%E5%8C%96/" title="MySQL-9、SQL应用优化">MySQL-9、SQL应用优化</a><p>[[Mysql高级-day02]]</p><h2 id="10-2-小林-coding"><a href="#10-2-小林-coding" class="headerlink" title="10.2. 小林 coding"></a>10.2. 小林 coding</h2><p><a href="https://xiaolincoding.com/mysql/lock/how_to_lock.html#gap-lock">https://xiaolincoding.com/mysql/lock/how_to_lock.html#gap-lock</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Mybatis</title>
      <link href="/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Mybatis-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2023/03/18/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Mybatis-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-什么是-Mybatis"><a href="#1-什么是-Mybatis" class="headerlink" title="1. 什么是 Mybatis"></a>1. 什么是 Mybatis</h1><p><a href="https://www.bilibili.com/video/BV1MT4y1k7wZ/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1MT4y1k7wZ/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319191021.png" alt="image.png"></p><h2 id="1-2-解决的问题"><a href="#1-2-解决的问题" class="headerlink" title="1.2. 解决的问题"></a>1.2. 解决的问题</h2><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230419-1501%%</span>❕ ^f9rguv</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319191205.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319191311.png" alt="image.png"></p><h1 id="2-组成架构"><a href="#2-组成架构" class="headerlink" title="2. 组成架构"></a>2. 组成架构</h1><p><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230419-1502%%</span>❕ ^coqkl4</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216080339.png"></p><p><a href="https://github.com/zzyandzzy/mybatis-book/tree/%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98%E8%A3%85%E9%A5%B0%E8%80%85">https://github.com/zzyandzzy/mybatis-book/tree/%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98%E8%A3%85%E9%A5%B0%E8%80%85</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230418221505.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230418222631.png" alt="image-removebg-preview.png"></p><h2 id="2-1-重要组件"><a href="#2-1-重要组件" class="headerlink" title="2.1. 重要组件"></a>2.1. 重要组件</h2><ol><li>简单执行器<br>simpleExecutor，<span style="background-color:#ff00ff">每次执行 SQL 都要预编译 SQL 语句</span>。</li><li>可重用执行器<br>ReuseExecutor，同一 SQL 语句执行只需要预编译一次 SQL 语句</li><li>批处理执行器<br>BatchExecutor，只针对修改操作的 SQL 语句预编译一次，并且需要手动刷新 SQL 执行才生效。</li><li>执行器抽象类<br>BaseExecutor，执行上面 3 个执行器的重复操作，比如一级缓存、doQuery、doUpdate 方法。</li><li>二级缓存<br>CachingExecutor，与一级缓存的区别：一级缓存查询数据库操作后会直接缓存，<span style="background-color:#ff00ff">二级缓存需要当次数据库操作提交事务后才能进行缓存</span> (二级缓存跨线程处理，一级缓存不用)。</li></ol><h2 id="2-2-继承关系"><a href="#2-2-继承关系" class="headerlink" title="2.2. 继承关系"></a>2.2. 继承关系</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230418223649.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419220811.png" alt="image.png"></p><h1 id="3-工作原理"><a href="#3-工作原理" class="headerlink" title="3. 工作原理"></a>3. 工作原理</h1><h2 id="3-1-API"><a href="#3-1-API" class="headerlink" title="3.1. API"></a>3.1. API</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319204923.png" alt="image.png"></p><h2 id="3-2-架构设计"><a href="#3-2-架构设计" class="headerlink" title="3.2. 架构设计"></a>3.2. 架构设计</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230419-1458%%</span>❕ ^p2twgg</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319222113.png" alt="image.png"></p><blockquote><p>mybatis 架构四层作用是什么呢？</p></blockquote><ol><li><strong>Api 接口层</strong>：提供 API 增加、删除、修改、查询等接口，通过 API 接口对数据库进行操作。</li><li><strong>数据处理层</strong>：主要负责 SQL 的 查询、解析、执行以及结果映射的处理，主要作用解析 sql 根据调用请求完成一次数据库操作.</li><li><strong>框架支撑层</strong>：负责通用基础服务支撑，包含事务管理、连接池管理、缓存管理等共用组件的封装，为上层提供基础服务支撑.</li><li><strong>引导层</strong>：引导层是配置和启动 MyBatis 配置信息的方式</li></ol><h2 id="3-3-主要组件及其调用关系"><a href="#3-3-主要组件及其调用关系" class="headerlink" title="3.3. 主要组件及其调用关系"></a>3.3. 主要组件及其调用关系</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319223828.png" alt="image.png"><br>组件介绍：</p><ul><li><strong>SqlSession</strong>：是 Mybatis 对外暴露的核心 API，提供了对数据库的 CRUD 操作接口。</li><li><strong>Executor</strong>：执行器，由 SqlSession 调用，负责数据库操作以及 Mybatis 两级缓存的维护</li><li><strong>StatementHandler</strong>：封装了 JDBC Statement 操作，负责对 Statement 的操作，例如 PrepareStatement 参数的设置以及结果集的处理。</li><li><strong>ParameterHandler</strong>：是 StatementHandler 内部一个组件，主要负责对 ParameterStatement 参数的设置</li><li><strong>ResultSetHandler</strong>：也是 StatementHandler 内部一个组件，主要负责对 ResultSet 结果集的处理，封装成目标对象返回</li><li><strong>TypeHandler</strong>：用于 Java 类型与 JDBC 类型之间的数据转换，ParameterHandler 和 ResultSetHandler 会分别使用到它的类型转换功能</li><li><strong>MappedStatement</strong>：是对 Mapper 配置文件或 Mapper 接口方法上通过注解申明 SQL 的封装</li><li><strong>Configuration</strong>：Mybatis 所有配置都统一由 Configuration 进行管理，内部由具体对象分别管理各自的小功能模块</li></ul><h2 id="3-4-执行逻辑⭐️🔴"><a href="#3-4-执行逻辑⭐️🔴" class="headerlink" title="3.4. 执行逻辑⭐️🔴"></a>3.4. 执行逻辑⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230320111455.png" alt="image.png"></p><ol><li>读取 MyBatis 配置文件：mybatis-config.xml 为 MyBatis 的全局配置文件，配置了 MyBatis 的运行环境等信息，例如数据库连接信息。</li><li>加载映射文件。映射文件即 SQL 映射文件，该文件中配置了操作数据库的 SQL 语句，需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件，每个文件对应数据库中的一张表。</li></ol><blockquote><p>   Mybatis 将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在 Xml 映射文件中， <code>&lt;parameterMap&gt;</code> 标签会被解析为 ParameterMap 对象，其每个子元素会被解析为 ParameterMapping 对象。 <code>&lt;resultMap&gt;</code> 标签会被解析为 ResultMap 对象，其每个子元素会被解析为 ResultMapping 对象。每一个 <code>&lt;select&gt; 、 &lt;insert&gt; 、 &lt;update&gt; 、 &lt;delete&gt;</code> 标签均会被<br>解析为 MappedStatement 对象，标签内的 sql 会被解析为 BoundSql 对象。</p></blockquote><ol start="3"><li>构造会话工厂：通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。</li><li>创建会话对象：由会话工厂创建 SqlSession 对象，该对象中包含了执行 SQL 语句的所有方法。</li><li>Executor 执行器：MyBatis 底层定义了一个 Executor 接口来操作数据库，它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句，同时负责查询缓存的维护。</li><li>MappedStatement 对象：在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数，该参数是对映射信息的封装，用于存储要映射的 SQL 语句的 id、参数等信息。</li><li>输入参数映射：输入参数类型可以是 Map、List 等集合类型，也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。</li><li>输出结果映射：输出结果类型可以是 Map、 List 等集合类型，也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。</li></ol><h1 id="4-缓存机制⭐️🔴"><a href="#4-缓存机制⭐️🔴" class="headerlink" title="4. 缓存机制⭐️🔴"></a>4. 缓存机制⭐️🔴</h1><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230319-1948%%</span>❕ ^rfip5l<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522144252.png" alt="image.png"></p><h2 id="4-1-一级缓存-默认开启"><a href="#4-1-一级缓存-默认开启" class="headerlink" title="4.1. 一级缓存 - 默认开启"></a>4.1. 一级缓存 - 默认开启</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522144850.png" alt="image.png"></p><p><a href="https://www.bilibili.com/video/BV1VP4y1c7j7/?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1VP4y1c7j7/?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="4-1-1-一级缓存失效情况"><a href="#4-1-1-一级缓存失效情况" class="headerlink" title="4.1.1. 一级缓存失效情况"></a>4.1.1. 一级缓存失效情况</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522144433.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319194150.png" alt="image.png"></p><h3 id="4-1-2-key-boundSql"><a href="#4-1-2-key-boundSql" class="headerlink" title="4.1.2. key-boundSql"></a>4.1.2. key-boundSql</h3><p><a href="https://blog.csdn.net/key_768/article/details/105855925">https://blog.csdn.net/key_768/article/details/105855925</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419195218.png" alt="image.png"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210922085021374.png" alt="image-20210922085021374"></p><h3 id="4-1-3-cachekey"><a href="#4-1-3-cachekey" class="headerlink" title="4.1.3. cachekey"></a>4.1.3. cachekey</h3><p><a href="https://www.bilibili.com/video/BV1R14y1W7yS?t=731.3&amp;p=29">https://www.bilibili.com/video/BV1R14y1W7yS?t=731.3&amp;p=29</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528124658.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528125203.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528125506.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528125758.png" alt="image.png"></p><h2 id="4-2-二级缓存-默认关闭"><a href="#4-2-二级缓存-默认关闭" class="headerlink" title="4.2. 二级缓存 - 默认关闭"></a>4.2. 二级缓存 - 默认关闭</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522145106.png" alt="image.png"></p><p><span style="background-color:#ff00ff">必须在 SqlSession 关闭或提交之后才有效</span></p><p>二级缓存是 SqlSessionFactory 级别，通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存; 此后若再次执行相同的查询语句，结果就会从缓存中获取</p><h3 id="4-2-1-设计模式-【装饰器-责任链】"><a href="#4-2-1-设计模式-【装饰器-责任链】" class="headerlink" title="4.2.1. 设计模式-【装饰器+责任链】"></a>4.2.1. 设计模式-【装饰器+责任链】</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522151224.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522151843.png" alt="image.png"></p><h3 id="4-2-2-二级缓存开启的条件"><a href="#4-2-2-二级缓存开启的条件" class="headerlink" title="4.2.2. 二级缓存开启的条件"></a>4.2.2. 二级缓存开启的条件</h3><ol><li>在核心配置文件中，设置全局配置属性 cacheEnabled&#x3D;”true”，默认为 true，不需要设置</li><li>在映射文件中设置标签<cache /></li><li>二级缓存必须在 SqlSession 关闭或提交之后有效</li><li>查询的数据所转换的实体类类型必须实现序列化的接口</li></ol><h3 id="4-2-3-二级缓存失效的情况"><a href="#4-2-3-二级缓存失效的情况" class="headerlink" title="4.2.3. 二级缓存失效的情况"></a>4.2.3. 二级缓存失效的情况</h3><p>两次查询之间执行了任意的增删改，会使一级和二级缓存同时失效<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522153825.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230522153956.png" alt="image.png"></p><h2 id="4-3-缓存查询顺序⭐️🔴"><a href="#4-3-缓存查询顺序⭐️🔴" class="headerlink" title="4.3. 缓存查询顺序⭐️🔴"></a>4.3. 缓存查询顺序⭐️🔴</h2><ol><li>先查询二级缓存，因为二级缓存中可能会有其他程序已经查出来的数据，可以拿来直接使用。</li><li>如果二级缓存没有命中，再查询一级缓存</li><li>如果一级缓存也没有命中，则查询数据库</li><li>SqlSession 关闭之后，一级缓存中的数据会写入二级缓存</li></ol><h2 id="4-4-整合第三方缓存"><a href="#4-4-整合第三方缓存" class="headerlink" title="4.4. 整合第三方缓存"></a>4.4. 整合第三方缓存</h2><p><span style="background-color:#ff0000">只是替换二级缓存，一级缓存是无法替换的</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230319195611.png" alt="image.png"></p><p><a href="https://www.bilibili.com/video/BV1u64y1z7k5?p=8">https://www.bilibili.com/video/BV1u64y1z7k5?p=8</a></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210922084529550.png" alt="image-20210922084529550"></p><h2 id="4-5-不建议使用的原因"><a href="#4-5-不建议使用的原因" class="headerlink" title="4.5. 不建议使用的原因"></a>4.5. 不建议使用的原因</h2><p><a href="https://www.cnblogs.com/yerikm/p/10784339.html">https://www.cnblogs.com/yerikm/p/10784339.html</a></p><h1 id="5-Mybatis-plus"><a href="#5-Mybatis-plus" class="headerlink" title="5. Mybatis-plus"></a>5. Mybatis-plus</h1><p>MybatisPlus 全套教程入门到精通看这个视频就可以了</p><p><a href="https://www.bilibili.com/video/BV1gy4y1778k?p=7">https://www.bilibili.com/video/BV1gy4y1778k?p=7</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230320072043.png" alt="image.png"></p><h2 id="5-1-SQL-注入原理"><a href="#5-1-SQL-注入原理" class="headerlink" title="5.1. SQL 注入原理"></a>5.1. SQL 注入原理</h2><p><a href="https://www.bilibili.com/video/BV19i4y137JK?p=26&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV19i4y137JK?p=26&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="5-1-1-ISqlInjector-AbstractSqlInjector"><a href="#5-1-1-ISqlInjector-AbstractSqlInjector" class="headerlink" title="5.1.1. ISqlInjector-AbstractSqlInjector"></a>5.1.1. ISqlInjector-AbstractSqlInjector</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230320075208.png" alt="image.png"></p><h3 id="5-1-2-inspectInject"><a href="#5-1-2-inspectInject" class="headerlink" title="5.1.2. inspectInject"></a>5.1.2. inspectInject</h3><p>在 AbstractSqlInjector 中，主要是由 inspectInject() 方法进行注入的，如下:</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230320075256.png" alt="image.png"></p><h3 id="5-1-3-injectMappedStatement"><a href="#5-1-3-injectMappedStatement" class="headerlink" title="5.1.3. injectMappedStatement"></a>5.1.3. injectMappedStatement</h3><p>在 <code>inspectInject</code> 方法实现中</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">methodList.forEach(m -&gt; m.inject(builderAssistant, mapperClass,<br>modelClass, tableInfo));<br></code></pre></td></tr></table></figure><p>最终调用抽象方法 <code>injectMappedStatement</code> 进行真正的注入:</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419092418.png" alt="image.png"></p><p>查看该方法的实现:<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419092349.png" alt="image.png"></p><h3 id="5-1-4-addMappedStatement"><a href="#5-1-4-addMappedStatement" class="headerlink" title="5.1.4. addMappedStatement"></a>5.1.4. addMappedStatement</h3><p>以 SelectById 为例查看:<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230320075433.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230320075506.png" alt="image.png"></p><p>可以看到，生成了 SqlSource 对象，再将 SQL 通过 <code>addSelectMappedStatement</code> 方法添加到 meppedStatements 中。最终调用公共方法 <code>addMappedStatement</code> 合成 SQL。</p><h1 id="6-主键策略"><a href="#6-主键策略" class="headerlink" title="6. 主键策略"></a>6. 主键策略</h1><p><a href="https://www.cnblogs.com/zimug/archive/2020/07/23/13364279.html">https://www.cnblogs.com/zimug/archive/2020/07/23/13364279.html</a></p><h2 id="6-1-全局唯一-id"><a href="#6-1-全局唯一-id" class="headerlink" title="6.1. 全局唯一 id"></a>6.1. 全局唯一 id</h2><p><a href="https://www.cnblogs.com/jiangxinlingdu/p/8440413.html">https://www.cnblogs.com/jiangxinlingdu/p/8440413.html</a></p><h1 id="7-雪花算法"><a href="#7-雪花算法" class="headerlink" title="7. 雪花算法"></a>7. 雪花算法</h1><p>雪花算法是由 Twitter 公布的分布式主键生成算法，它能够保证不同表的主键的不重复性，以及相同表的主键的有序性。</p><h2 id="7-1-核心思想"><a href="#7-1-核心思想" class="headerlink" title="7.1. 核心思想"></a>7.1. 核心思想</h2><p><span style="background-color:#ff00ff">长度共 64bit（一个 long 型）</span></p><p><strong>1bit 符号位标识</strong>，由于 long 基本类型在 Java 中是带符号的，最高位是符号位，正数是 0，负数是 1，所以 id 一般是正数，最高位是 0。<br>**41bit 时间截 (毫秒级)**，存储的是时间截的差值（当前时间截 - 开始时间截)，结果约等于 69.73 年。<br><strong>10bit 作为机器的 ID</strong>（5 个 bit 是数据中心，5 个 bit 的机器 ID，可以部署在 1024 个节点）。<br><strong>12bit 作为毫秒内的流水号</strong>（意味着每个节点在每毫秒可以产生 4096 个 ID）。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230407114802.png" alt="image.png"></p><h2 id="7-2-优点"><a href="#7-2-优点" class="headerlink" title="7.2. 优点"></a>7.2. 优点</h2><p>整体上按照时间自增排序，并且整个分布式系统内不会产生 ID 碰撞，并且效率较高。Twitter 测试的峰值是 10 万个每秒。另外，美团公司开源了一个全局唯一 id 生成系统 leaf，它里面也用到了雪花算法去构建全局唯一 id 并且在高性能和高可用方面，做了很多的优化，为美团内部业务提供了每天上亿次的调用。</p><h1 id="8-多租户原理"><a href="#8-多租户原理" class="headerlink" title="8. 多租户原理"></a>8. 多租户原理</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230420-0745%%</span>❕ ^a77gfv</p><p>  1. web 部分修改：</p><p>  a.在用户登录时，在线程变量（ThreadLocal）中记录租户的 id<br>  b.修改 jdbc 的实现 ：在提交 sql 时，从 ThreadLocal 中获取租户 id,  添加 sql 注释，把租户的 schema 放到 注释中。例如：<code>/*!mycat :  schema = test_01 */ sql ;</code></p><p>  2. 在 db 前面建立 proxy 层，代理所有 web 过来的数据库请求。proxy 层是用 mycat 实现的，web 提交的 sql 过来时在注释中指定 schema, proxy 层根据指定的 schema 转发 sql 请求。</p><p><a href="https://www.bilibili.com/video/BV1ia4y1K7s1/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ia4y1K7s1/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.bilibili.com/video/BV1Ve4y1g7Bk/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Ve4y1g7Bk/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="9-防-SQL-注入原理⭐️🔴"><a href="#9-防-SQL-注入原理⭐️🔴" class="headerlink" title="9. 防 SQL 注入原理⭐️🔴"></a>9. 防 SQL 注入原理⭐️🔴</h1><p>当我们说“预编译”的时候，其实这个功能来自于数据库的支持，它的原理是<span style="background-color:#ff00ff">先编译带有占位符的 SQL 模板，然后再传入参数让数据库自动替换 SQL 中占位符并执行。在这个过程中，由于预编译好的 SQL 模板本身语法已经定死，因此后续所有参数都会被视为不可执行的非 SQL 片段被转义，因此能够防止 SQL 注入。</span><br>八股文常提到的“<code>Mybatis</code> 的 <code>#&#123;&#125;</code> 相比 <code>$&#123;&#125;</code> 可以防止 SQL 注入”这一点，本质上是因为 <code>#&#123;&#125;</code> 占位符会被解析为 SQL 模板中的 <code>?</code> 占位符，而 <code>$&#123;&#125;</code> 占位符会被直接解析为 SQL 模板的一部分导致的。</p><p><a href="https://www.cnblogs.com/Createsequence/p/16963891.html">https://www.cnblogs.com/Createsequence/p/16963891.html</a></p><h1 id="10-面试题"><a href="#10-面试题" class="headerlink" title="10. 面试题"></a>10. 面试题</h1><p>[[MyBatis面试题 37道.pdf]]</p><h2 id="10-1-Mybatis-是如何进行分页的-分页插件的原理是什么"><a href="#10-1-Mybatis-是如何进行分页的-分页插件的原理是什么" class="headerlink" title="10.1. Mybatis 是如何进行分页的?分页插件的原理是什么?"></a>10.1. Mybatis 是如何进行分页的?分页插件的原理是什么?</h2><h1 id="11-插件原理"><a href="#11-插件原理" class="headerlink" title="11. 插件原理"></a>11. 插件原理</h1><h2 id="11-1-插件机制"><a href="#11-1-插件机制" class="headerlink" title="11.1. 插件机制"></a>11.1. 插件机制</h2><ol><li>插件机制:<br>Mybatis 通过插件 (Interceptor) 可以做到拦截四大对象相关方法的执行,根据需求，完 成相关数据的动态改变。</li></ol><p>Executor<br>StatementHandler<br>ParameterHandler<br>ResultSetHandler</p><ol start="2"><li>插件原理<br><span style="background-color:#ff00ff">四大对象的每个对象在创建时，都会执行 interceptorChain.pluginAll()</span>，会经过每个插件对象的 plugin() 方法，目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行，因为要执行四大对象的方法需要经过代理.</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230420142631.png" alt="image.png"></p><h2 id="11-2-如何编写插件"><a href="#11-2-如何编写插件" class="headerlink" title="11.2. 如何编写插件"></a>11.2. 如何编写插件</h2><p>Mybatis 仅 可 以 编 写 针 对 ParameterHandler 、 ResultSetHandler 、 StatementHandler、Executor 这 4 种接口的插件，Mybatis 使用 JDK 的动态代理， 为需要拦截的接口生成代理对象以实现接口方法拦截功能，每当执行这 4 种接口对象 的方法时，就会进入拦截方法，具体就是 InvocationHandler 的 invoke() 方法，当 然，只会拦截那些你指定需要拦截的方法。实现 Mybatis 的 Interceptor 接口并复写 intercept() 方法，然后在给插件编写注解，指定要拦截哪一个接口的哪些方法即可， 记住，还需要在配置文件中配置你编写的插件。</p><h2 id="11-3-分页插件"><a href="#11-3-分页插件" class="headerlink" title="11.3. 分页插件"></a>11.3. 分页插件</h2><h1 id="12-Mapper-代理开发"><a href="#12-Mapper-代理开发" class="headerlink" title="12. Mapper 代理开发"></a>12. Mapper 代理开发</h1><h2 id="12-1-使用方法"><a href="#12-1-使用方法" class="headerlink" title="12.1. 使用方法"></a>12.1. 使用方法</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419113546.png" alt="image.png"></p><h2 id="12-2-注解原理⭐️🔴"><a href="#12-2-注解原理⭐️🔴" class="headerlink" title="12.2. 注解原理⭐️🔴"></a>12.2. 注解原理⭐️🔴</h2><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-6%E3%80%81%E6%95%B4%E5%90%88Mybatis-@MapperScan/" title="Spring-6、整合Mybatis-@MapperScan">Spring-6、整合Mybatis-@MapperScan</a><h2 id="12-3-idea-新增目录"><a href="#12-3-idea-新增目录" class="headerlink" title="12.3. idea 新增目录"></a>12.3. idea 新增目录</h2><p>不能使用 <code>.</code> 分割，要用 <code>/</code> 分割，否则不会分层</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419075343.png" alt="image.png"></p><h1 id="13-实战经验"><a href="#13-实战经验" class="headerlink" title="13. 实战经验"></a>13. 实战经验</h1><h1 id="14-参考与感谢"><a href="#14-参考与感谢" class="headerlink" title="14. 参考与感谢"></a>14. 参考与感谢</h1><h2 id="14-1-Mybatis"><a href="#14-1-Mybatis" class="headerlink" title="14.1. Mybatis"></a>14.1. Mybatis</h2><h3 id="14-1-1-博学谷⭐️✅"><a href="#14-1-1-博学谷⭐️✅" class="headerlink" title="14.1.1. 博学谷⭐️✅"></a>14.1.1. 博学谷⭐️✅</h3><h4 id="14-1-1-1-视频-源码"><a href="#14-1-1-1-视频-源码" class="headerlink" title="14.1.1.1. 视频 - 源码"></a>14.1.1.1. 视频 - 源码</h4><p><a href="https://www.bilibili.com/video/BV1R14y1W7yS?p=18&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1R14y1W7yS?p=18&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h4 id="14-1-1-2-资料"><a href="#14-1-1-2-资料" class="headerlink" title="14.1.1.2. 资料"></a>14.1.1.2. 资料</h4><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">003</span>-Mybatis/《源码系列-主流框架&amp;中间件》-博学谷-MyBatis资料<br></code></pre></td></tr></table></figure><h3 id="14-1-2-尚硅谷⭐️✅"><a href="#14-1-2-尚硅谷⭐️✅" class="headerlink" title="14.1.2. 尚硅谷⭐️✅"></a>14.1.2. 尚硅谷⭐️✅</h3><h4 id="14-1-2-1-视频-原理-演示"><a href="#14-1-2-1-视频-原理-演示" class="headerlink" title="14.1.2.1. 视频 - 原理 + 演示"></a>14.1.2.1. 视频 - 原理 + 演示</h4><p><a href="https://www.bilibili.com/video/BV1VP4y1c7j7/?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1VP4y1c7j7/?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h4 id="14-1-2-2-资料"><a href="#14-1-2-2-资料" class="headerlink" title="14.1.2.2. 资料"></a>14.1.2.2. 资料</h4><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">003</span>-Mybatis/【尚硅谷】MyBatis零基础入门教程<br></code></pre></td></tr></table></figure><h3 id="14-1-3-鲁班大叔"><a href="#14-1-3-鲁班大叔" class="headerlink" title="14.1.3. 鲁班大叔"></a>14.1.3. 鲁班大叔</h3><h4 id="14-1-3-1-视频-源码"><a href="#14-1-3-1-视频-源码" class="headerlink" title="14.1.3.1. 视频 - 源码"></a>14.1.3.1. 视频 - 源码</h4><p><a href="https://www.bilibili.com/video/BV1Tp4y1X7FM?p=1&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Tp4y1X7FM?p=1&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="14-1-4-黑马"><a href="#14-1-4-黑马" class="headerlink" title="14.1.4. 黑马"></a>14.1.4. 黑马</h3><h4 id="14-1-4-1-视频-实战"><a href="#14-1-4-1-视频-实战" class="headerlink" title="14.1.4.1. 视频 - 实战"></a>14.1.4.1. 视频 - 实战</h4><p><a href="https://www.bilibili.com/video/BV1MT4y1k7wZ/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1MT4y1k7wZ/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h4 id="14-1-4-2-资料"><a href="#14-1-4-2-资料" class="headerlink" title="14.1.4.2. 资料"></a>14.1.4.2. 资料</h4><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">003</span>-Mybatis/黑马mybatis教程全套视频教程-<span class="hljs-number">2</span>天Mybatis框架从入门到精通<br></code></pre></td></tr></table></figure><h2 id="14-2-Mybatis-Plus"><a href="#14-2-Mybatis-Plus" class="headerlink" title="14.2. Mybatis-Plus"></a>14.2. Mybatis-Plus</h2><h3 id="14-2-1-尚硅谷"><a href="#14-2-1-尚硅谷" class="headerlink" title="14.2.1. 尚硅谷"></a>14.2.1. 尚硅谷</h3><h4 id="14-2-1-1-视频-原理"><a href="#14-2-1-1-视频-原理" class="headerlink" title="14.2.1.1. 视频 - 原理"></a>14.2.1.1. 视频 - 原理</h4><p><a href="https://www.bilibili.com/video/BV1Wb411V71s?p=30&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Wb411V71s?p=30&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h4 id="14-2-1-2-资料"><a href="#14-2-1-2-资料" class="headerlink" title="14.2.1.2. 资料"></a>14.2.1.2. 资料</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/002-框架源码专题/003-Mybatis/MyBatisPlus-尚硅谷<br></code></pre></td></tr></table></figure><h3 id="14-2-2-黑马"><a href="#14-2-2-黑马" class="headerlink" title="14.2.2. 黑马"></a>14.2.2. 黑马</h3><h4 id="14-2-2-1-视频-实战"><a href="#14-2-2-1-视频-实战" class="headerlink" title="14.2.2.1. 视频 - 实战"></a>14.2.2.1. 视频 - 实战</h4><p><a href="https://www.bilibili.com/video/BV1oK4y177YN/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1oK4y177YN/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h4 id="14-2-2-2-资料"><a href="#14-2-2-2-资料" class="headerlink" title="14.2.2.2. 资料"></a>14.2.2.2. 资料</h4><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">003</span>-Mybatis/资料-全面学习Mybatis插件之Mybatis-Plus<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Redis-6、缓存穿透-缓存击穿-缓存雪崩</title>
      <link href="/2023/03/16/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-4%E3%80%81%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F-%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF-%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9/"/>
      <url>/2023/03/16/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-4%E3%80%81%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F-%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF-%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-缓存穿透"><a href="#1-缓存穿透" class="headerlink" title="1. 缓存穿透"></a>1. 缓存穿透</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222173300.png"></p><p>一个一定不存在缓存及查询不到的数据，由于缓存是不命中时被动写的，并且出于容错考虑，如果从存储层查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到存储层去查询，失去了缓存的意义。</p><h2 id="1-1-产生原因"><a href="#1-1-产生原因" class="headerlink" title="1.1. 产生原因"></a>1.1. 产生原因</h2><p><span style="background-color:#ff00ff">Key 在缓存和数据库中都不存在</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223162852.png"></p><h2 id="1-2-解决方案"><a href="#1-2-解决方案" class="headerlink" title="1.2. 解决方案"></a>1.2. 解决方案</h2><h3 id="1-2-1-对空值进行缓存"><a href="#1-2-1-对空值进行缓存" class="headerlink" title="1.2.1. 对空值进行缓存"></a>1.2.1. 对空值进行缓存</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230502163612.png" alt="image.png"></p><p>如果一个查询返回的数据为空（不管是数据是否不存在），我们仍然把这个空结果（null）进行缓存，设置空结果的过期时间会很短，最长不超过五分钟</p><h3 id="1-2-2-设置白名单-bitmap-过滤恶意请求"><a href="#1-2-2-设置白名单-bitmap-过滤恶意请求" class="headerlink" title="1.2.2. 设置白名单 -bitmap-过滤恶意请求"></a>1.2.2. 设置白名单 -bitmap-过滤恶意请求</h3><p>使用 bitmaps 类型定义一个可以访问的名单，名单 id 作为 bitmaps 的偏移量，每次访问和 bitmap 里面的 id 进行比较，如果访问 id 不在 bitmaps 里面，进行拦截，不允许访问。</p><a href="/2022/12/21/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="缓存-Redis-1、基本原理">缓存-Redis-1、基本原理</a><h3 id="1-2-3-采用布隆过滤器-过滤对不存在的数据的请求"><a href="#1-2-3-采用布隆过滤器-过滤对不存在的数据的请求" class="headerlink" title="1.2.3. 采用布隆过滤器-过滤对不存在的数据的请求"></a>1.2.3. 采用布隆过滤器-过滤对不存在的数据的请求</h3><a href="/2023/03/15/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-7%E3%80%81%E6%A1%88%E4%BE%8B%E8%90%BD%E5%9C%B0%E5%AE%9E%E6%88%98/" title="缓存-Redis-7、案例落地实战">缓存-Redis-7、案例落地实战</a><p>(布隆过滤器（Bloom Filter）是 1970 年由布隆提出的。它实际上是一个很长的二进制向量 (位图) 和一系列随机映射函数（哈希函数）。</p><p>布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法，缺点是<span style="background-color:#00ff00">有一定的误识别率</span>和删除困难。)</p><p>将所有可能存在的数据哈希到一个<span style="background:#d3f8b6">足够大的 bitmaps</span>中，一个一定不存在的数据会被这个 bitmaps 拦截掉，从而避免了对底层存储系统的查询压力。</p><h3 id="1-2-4-进行实时监控"><a href="#1-2-4-进行实时监控" class="headerlink" title="1.2.4. 进行实时监控"></a>1.2.4. 进行实时监控</h3><p>当发现 Redis 的命中率开始急速降低，需要排查访问对象和访问的数据，和运维人员配合，可以设置黑名单限制服务</p><h1 id="2-缓存雪崩"><a href="#2-缓存雪崩" class="headerlink" title="2. 缓存雪崩"></a>2. 缓存雪崩</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222174201.png"></p><p>缓存失效时的雪崩效应对底层系统的冲击非常可怕！</p><h2 id="2-1-产生原因"><a href="#2-1-产生原因" class="headerlink" title="2.1. 产生原因"></a>2.1. 产生原因</h2><p><span style="background-color:#ff00ff">同一时间大量 Key 过期</span><br><span style="background-color:#ff00ff">Redis 服务宕机</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223163510.png"></p><h2 id="2-2-解决方案"><a href="#2-2-解决方案" class="headerlink" title="2.2. 解决方案"></a>2.2. 解决方案</h2><h3 id="2-2-1-将缓存失效时间分散开"><a href="#2-2-1-将缓存失效时间分散开" class="headerlink" title="2.2.1. 将缓存失效时间分散开"></a>2.2.1. 将缓存失效时间分散开</h3><p>比如我们可以在原有的失效时间基础上增加一个随机值，<font color="#ff0000">比如 1-5 分钟随机</font>，这样每一个缓存的过期时间的重复率就会降低，就很难引发集体失效的事件。</p><h3 id="2-2-2-设置过期标志更新缓存-job-扫描"><a href="#2-2-2-设置过期标志更新缓存-job-扫描" class="headerlink" title="2.2.2. 设置过期标志更新缓存-job 扫描"></a>2.2.2. 设置过期标志更新缓存-job 扫描</h3><p>记录缓存数据是否过期（设置提前量），如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。</p><h3 id="2-2-3-构建多级缓存架构"><a href="#2-2-3-构建多级缓存架构" class="headerlink" title="2.2.3. 构建多级缓存架构"></a>2.2.3. 构建多级缓存架构</h3><p>nginx 缓存 + redis 缓存 + 其他缓存（ehcache 等）</p><a href="/2023/02/28/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-4%E3%80%81%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98/" title="分布式专题-4、多级缓存">分布式专题-4、多级缓存</a><h3 id="2-2-4-避免-Redis-宕机"><a href="#2-2-4-避免-Redis-宕机" class="headerlink" title="2.2.4. 避免 Redis 宕机"></a>2.2.4. 避免 Redis 宕机</h3><p>使用 Redis 集群</p><h3 id="2-2-5-给缓存业务增加降级限流策略"><a href="#2-2-5-给缓存业务增加降级限流策略" class="headerlink" title="2.2.5. 给缓存业务增加降级限流策略"></a>2.2.5. 给缓存业务增加降级限流策略</h3><h1 id="3-缓存击穿"><a href="#3-缓存击穿" class="headerlink" title="3. 缓存击穿"></a>3. 缓存击穿</h1><p>[[02-Redis企业实战.pptx]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222173827.png"></p><p>key 可能会在某些时间点被<span style="background-color:#00ff00">超高并发地访问</span>，是一种非常“热点”的数据。这个时候，需要考虑一个问题：缓存被“击穿”的问题。</p><h2 id="3-1-产生原因"><a href="#3-1-产生原因" class="headerlink" title="3.1. 产生原因"></a>3.1. 产生原因</h2><p><span style="background-color:#ff00ff">缓存雪崩是大量 Key 同时过期，而缓存击穿是部分 Key 过期，但这些不是普通 Key，是热点 Key。</span>经常被高并发访问，并且往往重建业务比较复杂且耗时比较久。在重建期间被高并发访问，就会将压力打到数据库上。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316115928.png" alt="image.png"></p><h2 id="3-2-解决方案"><a href="#3-2-解决方案" class="headerlink" title="3.2. 解决方案"></a>3.2. 解决方案</h2><p><span style="display:none">%%<br>▶11.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-1920%%</span>❕ ^ce1aw6</p><h3 id="3-2-1-预先设置热门数据"><a href="#3-2-1-预先设置热门数据" class="headerlink" title="3.2.1. 预先设置热门数据"></a>3.2.1. 预先设置热门数据</h3><p>在 redis 高峰访问之前，把一些热门数据提前存入到 redis 里面，加大这些热门数据 key 的时长</p><h3 id="3-2-2-实时调整"><a href="#3-2-2-实时调整" class="headerlink" title="3.2.2. 实时调整"></a>3.2.2. 实时调整</h3><p>现场监控哪些数据热门，实时调整 key 的过期时长</p><h3 id="3-2-3-使用锁"><a href="#3-2-3-使用锁" class="headerlink" title="3.2.3. 使用锁"></a>3.2.3. 使用锁</h3><p>不推荐</p><h3 id="3-2-4-逻辑过期"><a href="#3-2-4-逻辑过期" class="headerlink" title="3.2.4. 逻辑过期"></a>3.2.4. 逻辑过期</h3><p>推荐</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316121010.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316132451.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316190335.png" alt="image.png"></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><h2 id="5-1-黑马"><a href="#5-1-黑马" class="headerlink" title="5.1. 黑马"></a>5.1. 黑马</h2><h3 id="5-1-1-视频"><a href="#5-1-1-视频" class="headerlink" title="5.1.1. 视频"></a>5.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?p=43&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1cr4y1671t?p=43&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Redis-5、案例落地实战</title>
      <link href="/2023/03/15/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-7%E3%80%81%E6%A1%88%E4%BE%8B%E8%90%BD%E5%9C%B0%E5%AE%9E%E6%88%98/"/>
      <url>/2023/03/15/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-7%E3%80%81%E6%A1%88%E4%BE%8B%E8%90%BD%E5%9C%B0%E5%AE%9E%E6%88%98/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-键值设计"><a href="#1-键值设计" class="headerlink" title="1. 键值设计"></a>1. 键值设计</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317191705.png" alt="image.png"></p><p>[[Redis高级篇之最佳实践.md]]</p><h1 id="2-布隆过滤器-BloomFilter"><a href="#2-布隆过滤器-BloomFilter" class="headerlink" title="2. 布隆过滤器 -BloomFilter"></a>2. 布隆过滤器 -BloomFilter</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230502-1633%%</span>❕ ^o440ad</p><h2 id="2-1-是什么"><a href="#2-1-是什么" class="headerlink" title="2.1. 是什么"></a>2.1. 是什么</h2><p>布隆过滤器（Bloom Filter）是 1970 年由布隆提出的。它实际上是一个很长的 <strong>二进制向量</strong> 和 <strong>一系列随机映射函数</strong> 。布隆过滤器可以用于 <strong>检索一个元素是否在一个集合中</strong> 。它的优点是 <strong>空间效率和查询时间都比一般的算法要好的多</strong> ，缺点是有一定的 <strong>误识别率和删除困难</strong> 。</p><p>可以理解为：有个二进制的 <strong>集合</strong> ，里面存放的 0 和 1，0 代表不存在，1 代表存在，可以通过一些定义好的 <strong>方法</strong> 快速判断元素是否在集合中。内部逻辑如下图展示</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107113632.png"></p><h2 id="2-2-作用"><a href="#2-2-作用" class="headerlink" title="2.2. 作用"></a>2.2. 作用</h2><blockquote><p>由于布隆过滤器的特性，能够<span style="background-color:#ff00ff">判断一个数据 <strong>可能在集合中</strong> ，和一个数据 <strong>绝对不在集合中</strong></span> ，所以他可以用于以下场景</p></blockquote><ol><li>网页 URL 的去重（爬虫，避免爬取相同的 URL 地址）</li><li>垃圾邮件的判别</li><li>集合重复元素的判别</li><li>查询加速（比如基于 key-value 的存储系统）</li><li>解决缓存穿透，使用 BloomFilter 来减少不存在的行或列的磁盘查找。温故知新：[[框架源码专题-Redis-1、基本原理#5.1. 缓存穿透]]</li></ol><p>目的：减少内存占用<br>方式：不保存数据信息，只是在内存中做一个是否存在的标记 flag</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316174102.png" alt="image.png"></p><h2 id="2-3-原理"><a href="#2-3-原理" class="headerlink" title="2.3. 原理"></a>2.3. 原理</h2><p>布隆过滤器 (Bloom Filter) 是一种专门用来解决去重问题的高级数据结构。</p><p>实质就是一个大型 <code>位数组</code> 和几个不同的无偏 hash 函数 (无偏表示分布均匀)。由一个初值都为零的 bit 数组和多个哈希函数构成，用来快速判断某个数据是否存在。但是跟 HyperLogLog 一样，它也一样有那么一点点不精确，也存在一定的误判概率</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316174333.png" alt="image.png"></p><h3 id="2-3-1-添加-key"><a href="#2-3-1-添加-key" class="headerlink" title="2.3.1. 添加 key"></a>2.3.1. 添加 key</h3><h4 id="2-3-1-1-添加-key-时"><a href="#2-3-1-1-添加-key-时" class="headerlink" title="2.3.1.1. 添加 key 时"></a>2.3.1.1. 添加 key 时</h4><p>使用<span style="background-color:#ff00ff">多个 hash 函数</span>对 key 进行 hash 运算得到一个整数索引值，对位数组长度进行取模运算得到一个位置，<span style="background-color:#ff00ff">每个 hash 函数都会得到一个不同的位置，将这几个位置都置 1</span>就完成了 add 操作。</p><h4 id="2-3-1-2-查询-key-时⭐️🔴"><a href="#2-3-1-2-查询-key-时⭐️🔴" class="headerlink" title="2.3.1.2. 查询 key 时⭐️🔴"></a>2.3.1.2. 查询 key 时⭐️🔴</h4><p><span style="background-color:#ff00ff">只要有其中一位是 0 就表示这个 key 不存在，但如果都是 1，则不一定存在对应的 key。结论：有，是可能有；无，是肯定无</span></p><h3 id="2-3-2-为什么不精确"><a href="#2-3-2-为什么不精确" class="headerlink" title="2.3.2. 为什么不精确"></a>2.3.2. 为什么不精确</h3><p>当有变量被加入集合时，通过 N 个映射函数将这个变量映射成位图中的 N 个点，<br>把它们置为 1（假定有两个变量都通过 3 个映射函数）。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316174751.png" alt="image.png"></p><p>查询某个变量的时候我们只要看看这些点是不是都是 1，就可以大概率知道集合中有没有它了</p><p>如果这些点，有任何一个为零则被查询变量<span style="background-color:#ff00ff">一定不在</span>，<br>如果都是 1，则被查询变量<span style="background-color:#ff00ff">很可能存在</span>，</p><p>为什么说是可能存在，而不是一定存在呢？那是因为映射函数本身就是散列函数，散列函数是会有碰撞的。（见上图 3 号坑两个对象都 1）</p><h2 id="2-4-布隆过滤器的用法"><a href="#2-4-布隆过滤器的用法" class="headerlink" title="2.4. 布隆过滤器的用法"></a>2.4. 布隆过滤器的用法</h2><p>基于别的轮子<br>以下轮子可以使用</p><ul><li>Guava 示例 <a href="https://gitee.com/bulkall/bulk-demo/blob/master/spring-boot-bloom-filter/src/test/java/top/bulk/bloom/demo/GuavaBloomFilterTest.java">GuavaBloomFilterTest</a></li><li>HuTool</li><li>Redisson 示例 <a href="https://gitee.com/bulkall/bulk-demo/blob/master/spring-boot-bloom-filter/src/test/java/top/bulk/bloom/demo/RedissonDemoTest.java">RedissonDemoTest</a></li><li>自己实现一个示例 <a href="https://gitee.com/bulkall/bulk-demo/blob/master/spring-boot-bloom-filter/src/main/java/top/bulk/bloom/bloom/BulkBloomFilter.java">BulkBloomFilter</a></li></ul><h2 id="2-5-如何解决布隆过滤器无法删除数据的问题"><a href="#2-5-如何解决布隆过滤器无法删除数据的问题" class="headerlink" title="2.5. 如何解决布隆过滤器无法删除数据的问题"></a>2.5. 如何解决布隆过滤器无法删除数据的问题</h2><ol><li>升级版的布隆过滤器（Counting Bloom Filter） 其原理为：</li></ol><ul><li>原理就是把位图的位升级为计数器 (Counter)</li><li>添加元素, 就给对应的 Counter 分别 +1</li><li>删除元素, 就给对应的 Counter 分别减一</li><li>用多出几倍存储空间的代价, 来实现删除功能</li></ul><ol start="2"><li>布谷鸟过滤器（Cuckoo filter）</li></ol><h2 id="2-6-使用场景"><a href="#2-6-使用场景" class="headerlink" title="2.6. 使用场景"></a>2.6. 使用场景</h2><h3 id="2-6-1-解决缓存穿透的问题"><a href="#2-6-1-解决缓存穿透的问题" class="headerlink" title="2.6.1. 解决缓存穿透的问题"></a>2.6.1. 解决缓存穿透的问题</h3><p><span style="display:none">%%<br>▶8.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-1749%%</span>❕ ^w0pylf</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316174815.png" alt="image.png"></p><p>把已存在数据的 key 存在布隆过滤器中，相当于 redis 前面挡着一个布隆过滤器。<br>当有新的请求时，先到布隆过滤器中查询是否存在：<br>如果布隆过滤器中不存在该条数据则直接返回；</p><p>如果布隆过滤器中已存在，才去查询缓存 redis，如果 redis 里没查询到则再查询 Mysql 数据库<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316175540.png" alt="image.png"></p><h3 id="2-6-2-黑名单校验"><a href="#2-6-2-黑名单校验" class="headerlink" title="2.6.2. 黑名单校验"></a>2.6.2. 黑名单校验</h3><p>发现存在黑名单中的，就执行特定操作。比如：识别垃圾邮件，只要是邮箱在黑名单中的邮件，就识别为垃圾邮件。</p><p>假设黑名单的数量是数以亿计的，存放起来就是非常耗费存储空间的，布隆过滤器则是一个较好的解决方案。</p><p>把所有黑名单都放在布隆过滤器中，在收到邮件时，判断邮件地址是否在布隆过滤器中即可。</p><h3 id="2-6-3-安全网站判断"><a href="#2-6-3-安全网站判断" class="headerlink" title="2.6.3. 安全网站判断"></a>2.6.3. 安全网站判断</h3><h2 id="2-7-优缺点"><a href="#2-7-优缺点" class="headerlink" title="2.7. 优缺点"></a>2.7. 优缺点</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316184402.png" alt="image.png"></p><h2 id="2-8-与-bitmap-的区别"><a href="#2-8-与-bitmap-的区别" class="headerlink" title="2.8. 与 bitmap 的区别"></a>2.8. 与 bitmap 的区别</h2><p><a href="https://developer.aliyun.com/article/930143#slide-6">https://developer.aliyun.com/article/930143#slide-6</a><br><a href="https://juejin.cn/s/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E5%92%8Cbitmap%E5%8C%BA%E5%88%AB">https://juejin.cn/s/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E5%92%8Cbitmap%E5%8C%BA%E5%88%AB</a></p><h1 id="3-缓存击穿-高并发聚划算案例"><a href="#3-缓存击穿-高并发聚划算案例" class="headerlink" title="3. 缓存击穿 - 高并发聚划算案例"></a>3. 缓存击穿 - 高并发聚划算案例</h1><p>[[框架源码专题-Redis-1、基本原理#^ce1aw6]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316191517.png" alt="image.png"></p><h2 id="3-1-互斥检查锁"><a href="#3-1-互斥检查锁" class="headerlink" title="3.1. 互斥检查锁"></a>3.1. 互斥检查锁</h2><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-1918%%</span>❕ ^2kv736</p><p>多个线程同时去查询数据库的这条数据，那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。</p><p>其他的线程走到这一步拿不到锁就等着，等第一个线程查询到了数据，然后做缓存。后面的线程进来发现已经有缓存了，就直接走缓存。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316191657.png" alt="image.png"></p><h2 id="3-2-差异化失效时间"><a href="#3-2-差异化失效时间" class="headerlink" title="3.2. 差异化失效时间"></a>3.2. 差异化失效时间</h2><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-1919%%</span>❕ ^f2c38u</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316191926.png" alt="image.png"></p><h1 id="4-BigKey"><a href="#4-BigKey" class="headerlink" title="4. BigKey"></a>4. BigKey</h1><h2 id="4-1-MoreKey"><a href="#4-1-MoreKey" class="headerlink" title="4.1. MoreKey"></a>4.1. MoreKey</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517065304.png" alt="image.png"></p><h3 id="4-1-1-禁用-keys-、flashdb、flushall"><a href="#4-1-1-禁用-keys-、flashdb、flushall" class="headerlink" title="4.1.1. 禁用 keys *、flashdb、flushall"></a>4.1.1. 禁用 keys <code>*</code>、flashdb、flushall</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200036.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200047.png" alt="image.png"></p><blockquote><p>说明：对于 FLUSHALL 命令，需要设置配置文件中 appendonly no，否则服务器无法启动。rename-command 命名无法直接对线上集群生效。如果需要使用 rename-command，必须重启集群。所以建议一开始，就将该配置配置好。</p></blockquote><h3 id="4-1-2-scan-命令"><a href="#4-1-2-scan-命令" class="headerlink" title="4.1.2. scan 命令"></a>4.1.2. scan 命令</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200503.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200518.png" alt="image.png"></p><p>SCAN 命令是一个基于游标的迭代器，每次被调用之后，都会向用户返回一个新的游标，用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数，以此来延续之前的迭代过程。</p><p>SCAN 返回一个包含两个元素的数组， </p><p>第一个元素是用于进行下一次迭代的新游标， <br>第二个元素则是一个数组，这个数组中包含了所有被迭代的元素。如果新游标返回零表示迭代已结束。</p><p>SCAN 的遍历顺序</p><p>非常特别，它不是从第一维数组的第零位一直遍历到末尾，而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历，是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。</p><h2 id="4-2-BigKey⭐️🔴"><a href="#4-2-BigKey⭐️🔴" class="headerlink" title="4.2. BigKey⭐️🔴"></a>4.2. BigKey⭐️🔴</h2><h3 id="4-2-1-多大算-big"><a href="#4-2-1-多大算-big" class="headerlink" title="4.2.1. 多大算 big"></a>4.2.1. 多大算 big</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200625.png" alt="image.png"></p><h3 id="4-2-2-哪些危害"><a href="#4-2-2-哪些危害" class="headerlink" title="4.2.2. 哪些危害"></a>4.2.2. 哪些危害</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200924.png" alt="image.png"></p><h3 id="4-2-3-如何产生"><a href="#4-2-3-如何产生" class="headerlink" title="4.2.3. 如何产生"></a>4.2.3. 如何产生</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315200954.png" alt="image.png"></p><h3 id="4-2-4-如何发现"><a href="#4-2-4-如何发现" class="headerlink" title="4.2.4. 如何发现"></a>4.2.4. 如何发现</h3><h4 id="4-2-4-1-redis-cli-–bigkeys"><a href="#4-2-4-1-redis-cli-–bigkeys" class="headerlink" title="4.2.4.1. redis-cli –bigkeys"></a>4.2.4.1. redis-cli –bigkeys</h4><p><code>redis-cli --bigkeys -a 111111 </code><br><code>redis-cli -h 127.0.0.1 -p 6379 -a 111111 --bigkeys</code></p><p>每隔 100 条 scan 指令就会休眠 0.1s，ops 就不会剧烈抬升，但是扫描的时间会变长<br><code>redis-cli -h 127.0.0.1 -p 7001 –-bigkeys -i 0.1</code></p><h4 id="4-2-4-2-MEMORY-USAGE-key"><a href="#4-2-4-2-MEMORY-USAGE-key" class="headerlink" title="4.2.4.2. MEMORY USAGE key"></a>4.2.4.2. MEMORY USAGE key</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201151.png" alt="image.png"></p><h3 id="4-2-5-如何删除"><a href="#4-2-5-如何删除" class="headerlink" title="4.2.5. 如何删除"></a>4.2.5. 如何删除</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201351.png" alt="image.png"></p><h4 id="4-2-5-1-String"><a href="#4-2-5-1-String" class="headerlink" title="4.2.5.1. String"></a>4.2.5.1. String</h4><p>一般用 del，过于庞大用 unlink</p><h4 id="4-2-5-2-hash"><a href="#4-2-5-2-hash" class="headerlink" title="4.2.5.2. hash"></a>4.2.5.2. hash</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201546.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201558.png" alt="image.png"></p><h4 id="4-2-5-3-list"><a href="#4-2-5-3-list" class="headerlink" title="4.2.5.3. list"></a>4.2.5.3. list</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201611.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201622.png" alt="image.png"></p><h4 id="4-2-5-4-set"><a href="#4-2-5-4-set" class="headerlink" title="4.2.5.4. set"></a>4.2.5.4. set</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201634.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201642.png" alt="image.png"></p><h4 id="4-2-5-5-zset"><a href="#4-2-5-5-zset" class="headerlink" title="4.2.5.5. zset"></a>4.2.5.5. zset</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201652.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201701.png" alt="image.png"></p><h3 id="4-2-6-BigKey-生产调优"><a href="#4-2-6-BigKey-生产调优" class="headerlink" title="4.2.6. BigKey 生产调优"></a>4.2.6. BigKey 生产调优</h3><p>redis.conf 中配置 LAZY FREEING</p><h4 id="4-2-6-1-阻塞和非阻塞删除命令"><a href="#4-2-6-1-阻塞和非阻塞删除命令" class="headerlink" title="4.2.6.1. 阻塞和非阻塞删除命令"></a>4.2.6.1. 阻塞和非阻塞删除命令</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315201828.png" alt="image.png"></p><h4 id="4-2-6-2-优化配置"><a href="#4-2-6-2-优化配置" class="headerlink" title="4.2.6.2. 优化配置"></a>4.2.6.2. 优化配置</h4><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-1050%%</span>❕ ^o2bbla</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315202032.png" alt="image.png"></p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-散装-java-公众号"><a href="#6-1-散装-java-公众号" class="headerlink" title="6.1. 散装 java 公众号"></a>6.1. 散装 java 公众号</h2><p><a href="https://doc.bulkall.top/">https://doc.bulkall.top/</a><br><a href="https://gitee.com/bulkall/bulk-demo.git">https://gitee.com/bulkall/bulk-demo.git</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Redis-4、原理进阶</title>
      <link href="/2023/03/14/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-6%E3%80%81%E5%8E%9F%E7%90%86%E8%BF%9B%E9%98%B6-%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E2%99%A8%EF%B8%8F/"/>
      <url>/2023/03/14/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-6%E3%80%81%E5%8E%9F%E7%90%86%E8%BF%9B%E9%98%B6-%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E2%99%A8%EF%B8%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Redis-使用单线程含义"><a href="#1-Redis-使用单线程含义" class="headerlink" title="1. Redis 使用单线程含义"></a>1. Redis 使用单线程含义</h1><p>主要是指 Redis 的<span style="background-color:#ff00ff">网络 IO 和键值对读写</span>是由一个线程来完成的，Redis 在处理客户端的请求时包括<span style="background-color:#00ff00">获取 (socket 读)、解析、执行、内容返回 (socket 写) 等</span>都<span style="background-color:#ff00ff">由一个顺序串行的主线程处理</span>，这就是所谓的“单线程”。这也是 Redis 对外提供键值存储服务的主要流程。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315151215.png" alt="image.png"></p><p>但 Redis 的其他功能，比如<span style="background-color:#00ff00">持久化 RDB、AOF、异步删除、集群数据同步</span>等等，其实是由额外的线程执行的。</p><p><span style="background-color:#ff00ff">Redis 命令工作线程是单线程的，但是，整个 Redis 来说，是多线程的；</span></p><h1 id="2-单多线程演进变化"><a href="#2-单多线程演进变化" class="headerlink" title="2. 单多线程演进变化"></a>2. 单多线程演进变化</h1><h2 id="2-1-单多线程版本发展历史"><a href="#2-1-单多线程版本发展历史" class="headerlink" title="2.1. 单多线程版本发展历史"></a>2.1. 单多线程版本发展历史</h2><p>Redis 的版本很多 3.x、4.x、6.x，版本不同架构也是不同的，不限定版本问是否单线程也不太严谨。</p><ol><li>版本 3.x ，最早版本，也就是大家口口相传的 redis 是单线程。</li><li>版本 4.x，严格意义来说也不是单线程，而是负责处理客户端请求的线程是单线程，但是开始加了点多线程的东西 (<span style="background-color:#00ff00">异步删除</span>)。</li><li>2020 年 5 月版本的 6.0.x 后及 2022 年出的 7.0 版本后，告别了大家印象中的单线程，用一种全新的多线程来解决问题。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315151028.png" alt="image.png"></p><h2 id="2-2-Redis3-X-单线程但性能依旧很快"><a href="#2-2-Redis3-X-单线程但性能依旧很快" class="headerlink" title="2.2. Redis3.X 单线程但性能依旧很快"></a>2.2. Redis3.X 单线程但性能依旧很快</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230315151605.png" alt="image.png"></p><h2 id="2-3-Redis4-0-之前一直采用单线程的主要原因"><a href="#2-3-Redis4-0-之前一直采用单线程的主要原因" class="headerlink" title="2.3. Redis4.0 之前一直采用单线程的主要原因"></a>2.3. Redis4.0 之前一直采用单线程的主要原因</h2><ol><li>使用单线程模型是 Redis 的开发和维护更简单，因为单线程模型方便开发和调试；</li><li>即使使用单线程模型<span style="background-color:#ff00ff">也能并发的处理多客户端的请求，主要使用的是 IO 多路复用和非阻塞 IO</span>；</li><li>对于 Redis 系统来说，主要的性能瓶颈<span style="background-color:#ff00ff">是内存或者网络带宽而并非 CPU</span>。</li></ol><h2 id="2-4-为什么又采用了多线程"><a href="#2-4-为什么又采用了多线程" class="headerlink" title="2.4. 为什么又采用了多线程"></a>2.4. 为什么又采用了多线程</h2><h3 id="2-4-1-网络硬件性能提升"><a href="#2-4-1-网络硬件性能提升" class="headerlink" title="2.4.1. 网络硬件性能提升"></a>2.4.1. 网络硬件性能提升</h3><p>在 Redis6、7 之前，Redis 一直被大家熟知的就是它的单线程架构，虽然有些命令操作可以用后台线程或子进程执行（比如数据删除、快照生成、AOF 重写）。但是，<span style="background-color:#00ff00">从网络 IO 处理到实际的读写命令处理，都是由单个线程完成的。</span></p><p>然而，随着网络硬件的性能提升，Redis 的性能瓶颈有时会出现在网络 IO 的处理上，也就是说，<span style="background-color:#ff00ff">单个主线程处理网络请求的速度跟不上底层网络硬件的速度</span>，为了应对这个问题: 采用多个 IO 线程来处理网络请求，提高网络请求处理的并行度，Redis6&#x2F;7 就是采用的这种方法。</p><p>但是，<span style="background-color:#ff00ff">Redis 的多 IO 线程只是用来处理网络请求的，对于读写操作命令 Redis 仍然使用单线程来处理</span>。这是因为，Redis 处理请求时，网络处理经常是瓶颈，通过多个 IO 线程并行处理网络操作，可以提升实例的整体处理性能。而继续使用单线程执行命令操作，就<span style="background-color:#ffff00">不用为了保证 Lua 脚本、事务的原子性，额外开发多线程互斥加锁机制了 (不管加锁操作处理)</span>，这样一来，Redis 线程模型实现就简单了</p><h3 id="2-4-2-单线程其他问题-删除数据效率比较低"><a href="#2-4-2-单线程其他问题-删除数据效率比较低" class="headerlink" title="2.4.2. 单线程其他问题 - 删除数据效率比较低"></a>2.4.2. 单线程其他问题 - 删除数据效率比较低</h3><p>比如当我（Redis）需要删除一个很大的数据时，因为是单线程原子命令操作，这就会导致 Redis 服务卡顿，于是在 Redis 4.0 中就新增了多线程的模块，当然此版本中的多线程主要是为了解决删除数据效率比较低的问题的。</p><p><code>unlink key</code><br><code>flushdb async</code><br><code>flushall async</code></p><p>把删除工作交给了后台的小弟（子线程）异步来删除数据了。</p><h2 id="2-5-主线程和-IO-多线程协作关系⭐️🔴"><a href="#2-5-主线程和-IO-多线程协作关系⭐️🔴" class="headerlink" title="2.5. 主线程和 IO 多线程协作关系⭐️🔴"></a>2.5. 主线程和 IO 多线程协作关系⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316104414.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316104449.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316104428.png" alt="image.png"></p><h2 id="2-6-如何开启多线程"><a href="#2-6-如何开启多线程" class="headerlink" title="2.6. 如何开启多线程"></a>2.6. 如何开启多线程</h2><p>Redis7 将所有数据放在内存中，内存的响应时长大约为 100 纳秒，对于小数据包，Redis 服务器可以处理 8W 到 10W 的 QPS，这也是 Redis 处理的极限了，对于 80% 的公司来说，单线程的 Redis 已经足够使用了。<br>在 Redis6.0 及 7 后，<span style="background-color:#ff00ff">多线程机制默认是关闭的</span>，如果需要使用多线程功能，需要在 redis.conf 中完成两个设置</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316104634.png" alt="image.png"></p><ol><li>设置 io-thread-do-reads 配置项为 yes，表示启动多线程。</li><li>设置线程个数。关于线程数的设置，官方的建议是如果为 4 核的 CPU，建议线程数设置为 2 或 3，如果为 8 核 CPU 建议线程数设置为 6，线程数一定要小于机器核数，线程数并不是越大越好。</li></ol><h1 id="3-Linux-网络模型"><a href="#3-Linux-网络模型" class="headerlink" title="3. Linux 网络模型"></a>3. Linux 网络模型</h1><a href="/2023/05/27/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6-2%E3%80%81IO%E6%A8%A1%E5%9E%8B/" title="并发进阶-2、IO模型">并发进阶-2、IO模型</a><h1 id="4-Redis-网络模型⭐️🔴"><a href="#4-Redis-网络模型⭐️🔴" class="headerlink" title="4. Redis 网络模型⭐️🔴"></a>4. Redis 网络模型⭐️🔴</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317224456.png" alt="image.png"></p><h2 id="4-1-单线程"><a href="#4-1-单线程" class="headerlink" title="4.1. 单线程"></a>4.1. 单线程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230318073757.png" alt="image.png"></p><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?t=2430.1&amp;p=171">https://www.bilibili.com/video/BV1cr4y1671t?t=2430.1&amp;p=171</a></p><h2 id="4-2-多线程"><a href="#4-2-多线程" class="headerlink" title="4.2. 多线程"></a>4.2. 多线程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230318074050.png" alt="image.png"></p><h1 id="5-Redis-为什么这么快⭐️🔴"><a href="#5-Redis-为什么这么快⭐️🔴" class="headerlink" title="5. Redis 为什么这么快⭐️🔴"></a>5. Redis 为什么这么快⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230606-0857%%</span>❕ ^tl4ngs</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230606092441.png" alt="image.png"></p><p>决定 Redis 请求效率的因素主要是三个方面，分别是网络、cpu、内存。</p><h2 id="5-1-网络"><a href="#5-1-网络" class="headerlink" title="5.1. 网络"></a>5.1. 网络</h2><p>在网络层面，Redis 采用多路复用的设计，提升了并发处理的连接数，不过这个阶段， ｛如图｝Server 端的所有 IO 操作，都是由同一个主线程处理的这个时候 IO 的瓶颈就会影响到 Redis 端的整体处理性能。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230605073957.png" alt="image.png"></p><p>所以从 Redis6.0 开始｛如图｝，在多路复用及层面增加了多线程的处理，来优化 IO 处理的能力。 不过，具体的数据操作仍然是由主线程来处理的，所以我们可以认为 Redis 对于数据 IO 的处理依然是单线程。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230605074051.png" alt="image.png"></p><h2 id="5-2-CPU"><a href="#5-2-CPU" class="headerlink" title="5.2. CPU"></a>5.2. CPU</h2><p>从 CPU 层面来说，Redis 只需要采用单线程即可，原因有两个。如果采用多线程，对于 Redis 中的数据操作，都需要通过同步的方式来保证线程安全性，这反而会影响到 redis 的性能<br>在 Linux 系统上 Redis 通过 pipelining 可以处理 100w 个请求每秒，而应用程序的计算复杂度主要是 O(N) 或 O(log(N)) ，不会消耗太多 CPU</p><h2 id="5-3-内存"><a href="#5-3-内存" class="headerlink" title="5.3. 内存"></a>5.3. 内存</h2><p>从内存层面来说，Redis 本身就是一个内存数据库，内存的 IO 速度本身就很快，所以内存的瓶颈只是受限于内存大小。最后，Redis 本身的数据结构也做了很多的优化，比如压缩表、跳跃表等方式降低了时间复杂读，同时还提供了不同时间复杂度的数据类型。使得开发人员能够有更多合适的选择</p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230528-0844%%</span>❕ ^5oujxq</p><h2 id="7-1-尚硅谷-周阳"><a href="#7-1-尚硅谷-周阳" class="headerlink" title="7.1. 尚硅谷 - 周阳"></a>7.1. 尚硅谷 - 周阳</h2><h3 id="7-1-1-视频"><a href="#7-1-1-视频" class="headerlink" title="7.1.1. 视频"></a>7.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV13R4y1v7sP?p=101&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13R4y1v7sP?p=101&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-1-2-脑图"><a href="#7-1-2-脑图" class="headerlink" title="7.1.2. 脑图"></a>7.1.2. 脑图</h3><p>file:&#x2F;&#x2F;&#x2F;Users&#x2F;taylor&#x2F;Nutstore%20Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98&#x2F;002-DB&#x2F;001-Redis&#x2F;%E5%B0%9A%E7%A1%85%E8%B0%B7Redis7&#x2F;%E8%84%91%E5%9B%BE&#x2F;Redis%E8%84%91%E5%9B%BE%EF%BC%88%E5%9F%BA%E7%A1%80%E7%AF%87+%E9%AB%98%E7%BA%A7%E7%AF%87%EF%BC%89.html</p><h2 id="7-2-黑马程序员"><a href="#7-2-黑马程序员" class="headerlink" title="7.2. 黑马程序员"></a>7.2. 黑马程序员</h2><h3 id="7-2-1-视频"><a href="#7-2-1-视频" class="headerlink" title="7.2.1. 视频"></a>7.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?p=161&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1cr4y1671t?p=161&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-2-2-资料"><a href="#7-2-2-资料" class="headerlink" title="7.2.2. 资料"></a>7.2.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/002-框架源码专题/002-DB/001-Redis/Redis-笔记资料/04-原理篇<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-11、ELK-ES</title>
      <link href="/2023/03/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/NoSQL-1%E3%80%81ELK-ES/"/>
      <url>/2023/03/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/NoSQL-1%E3%80%81ELK-ES/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-什么是-elasticsearch"><a href="#1-什么是-elasticsearch" class="headerlink" title="1. 什么是 elasticsearch"></a>1. 什么是 elasticsearch</h1><p>elasticsearch 是一个开源的分布式搜索引擎，可以用来实现搜索、日志统计、分析、系统监控等功能，底层是基于 <strong>lucene</strong> 来实现的。<br><strong>Lucene</strong> 是一个 Java 语言的搜索引擎类库，是 Apache 公司的顶级项目。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313153552.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313153606.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313143941.png" alt="image.png"></p><h2 id="1-1-特性"><a href="#1-1-特性" class="headerlink" title="1.1. 特性"></a>1.1. 特性</h2><ol><li>第一、采用 Master-slave 架构，实现数据的分片和备份</li><li>第二、使用 Java 编写，并对 Lucene 进行封装，隐藏了 Lucene 的复杂性</li><li>第三、能胜任上百个服务节点的扩展，并支持 PB 级别的结构化或者非结构化数据</li><li>第四、ES 提供的 Restful API，不仅简化了 ES 的操作，还支持任何语言的客户端提供 API 接口，另外 Restful API 的风格还实现了 CURD 操作、创建索引，删除索引等功能。</li></ol><h2 id="1-2-什么是-elastic-stack（ELK）"><a href="#1-2-什么是-elastic-stack（ELK）" class="headerlink" title="1.2. 什么是 elastic stack（ELK）"></a>1.2. 什么是 elastic stack（ELK）</h2><ul><li>是以 elasticsearch 为核心的技术栈，包括 beats、Logstash、kibana、elasticsearch</li></ul><h1 id="2-倒排索引"><a href="#2-倒排索引" class="headerlink" title="2. 倒排索引"></a>2. 倒排索引</h1><p>倒排索引的概念是基于 MySQL 这样的正向索引而言的。</p><h2 id="2-1-正向索引"><a href="#2-1-正向索引" class="headerlink" title="2.1. 正向索引"></a>2.1. 正向索引</h2><p>那么什么是正向索引呢？例如给下表（tb_goods）中的 id 创建索引：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313154017.png" alt="image-20210720195531539"></p><p>如果是根据 id 查询，那么直接走索引，查询速度非常快。</p><p>但如果是基于 title 做模糊查询，只能是逐行扫描数据，流程如下：</p><p>1）用户搜索数据，条件是 title 符合 <code>&quot;%手机%&quot;</code><br>2）逐行获取数据，比如 id 为 1 的数据<br>3）判断数据中的 title 是否符合用户搜索条件<br>4）如果符合则放入结果集，不符合则丢弃。回到步骤 1</p><p><span style="background-color:#ffff00">逐行扫描，也就是全表扫描，随着数据量增加，其查询效率也会越来越低。当数据量达到数百万时，就是一场灾难。</span></p><h2 id="2-2-倒排索引"><a href="#2-2-倒排索引" class="headerlink" title="2.2. 倒排索引"></a>2.2. 倒排索引</h2><p>倒排索引中有两个非常重要的概念：</p><ul><li>文档（<code>Document</code>）：用来搜索的数据，其中的<span style="background-color:#ff00ff">每一条数据就是一个文档</span>。例如一个网页、一个商品信息</li><li>词条（<code>Term</code>）：对文档数据或用户搜索数据，<span style="background-color:#ff00ff">利用某种算法分词，得到的具备含义的词语就是词条</span>。例如：我是中国人，就可以分为：我、是、中国人、中国、国人这样的几个词条</li></ul><h3 id="2-2-1-创建倒排索引"><a href="#2-2-1-创建倒排索引" class="headerlink" title="2.2.1. 创建倒排索引"></a>2.2.1. 创建倒排索引</h3><p><strong>创建倒排索引</strong> 是对正向索引的一种特殊处理，流程如下：<br><span style="display:none">%%<br>▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1542%%</span>❕ ^km4l5x</p><ul><li>将每一个文档的数据利用算法分词，得到一个个词条</li><li>创建表，每行数据包括词条、词条所在文档 id、位置等信息</li><li>因为词条唯一性，可以给词条创建索引，例如<span style="background-color:#ffff00">hash 表结构索引</span></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313154314.png" alt="image-20210720200457207"></p><h3 id="2-2-2-使用倒排索引"><a href="#2-2-2-使用倒排索引" class="headerlink" title="2.2.2. 使用倒排索引"></a>2.2.2. 使用倒排索引</h3><p>倒排索引的 <strong>搜索流程</strong> 如下（以搜索 “ 华为手机 “ 为例）：</p><p>1）用户输入条件 <code>&quot;华为手机&quot;</code> 进行搜索。<br>2）对用户输入内容 <strong>分词</strong>，得到词条：<code>华为</code>、<code>手机</code>。<br>3）拿着词条在倒排索引中查找，可以得到包含词条的文档 id：1、2、3。<br>4）拿着文档 id 到正向索引中查找具体文档。</p><p>如图：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313154349.png" alt="image-20210720201115192"></p><h3 id="2-2-3-为什么快"><a href="#2-2-3-为什么快" class="headerlink" title="2.2.3. 为什么快"></a>2.2.3. 为什么快</h3><p><span style="background-color:#ff00ff">虽然要先查询倒排索引，再查询正向索引，但是无论是词条、还是文档 id 都建立了索引，查询速度非常快！无需全表扫描。</span></p><h2 id="2-3-正向和倒排"><a href="#2-3-正向和倒排" class="headerlink" title="2.3. 正向和倒排"></a>2.3. 正向和倒排</h2><p>那么为什么一个叫做正向索引，一个叫做倒排索引呢？</p><ul><li><strong>正向索引</strong> 是最传统的，根据 id 索引的方式。但根据词条查询时，必须<span style="background-color:#00ff00">先逐条获取每个文档，然后判断文档中是否包含所需要的词条</span>，是 <strong>根据文档找词条的过程</strong>。</li><li>而 <strong>倒排索引</strong> 则相反，是<span style="background-color:#ff00ff">先找到用户要搜索的词条，根据词条得到保护词条的文档的 id，然后根据 id 获取文档</span>。是 <strong>根据词条找文档的过程</strong>。</li></ul><p><strong>正向索引</strong>：</p><ul><li>优点：<ul><li>可以给多个字段创建索引</li><li>根据索引字段搜索、排序速度非常快</li></ul></li><li>缺点：<ul><li><span style="background-color:#ff00ff">根据非索引字段，或者索引字段中的部分词条查找时，只能全表扫描。</span></li></ul></li></ul><p><strong>倒排索引</strong>：<br><span style="display:none">%%<br>▶13.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1547%%</span>❕ ^3zq0wh</p><ul><li>优点：<ul><li>根据词条搜索、模糊搜索时，速度非常快</li></ul></li><li>缺点：<ul><li><span style="background-color:#ff0000">只能给词条创建索引，而不是字段</span></li><li><span style="background-color:#ff0000">无法根据字段做排序</span></li></ul></li></ul><h1 id="3-相关概念"><a href="#3-相关概念" class="headerlink" title="3. 相关概念"></a>3. 相关概念</h1><h2 id="3-1-文档和字段"><a href="#3-1-文档和字段" class="headerlink" title="3.1. 文档和字段"></a>3.1. 文档和字段</h2><p>elasticsearch 是面向**文档（Document）存储的，可以是数据库中的一条商品数据，一个订单信息。文档数据会被序列化为 json 格式后存储在 elasticsearch 中：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313154931.png" alt="image-20210720202707797"></p><p>而 Json 文档中往往包含很多的 <strong>字段（Field）</strong>，类似于数据库中的列。</p><h2 id="3-2-索引和映射"><a href="#3-2-索引和映射" class="headerlink" title="3.2. 索引和映射"></a>3.2. 索引和映射</h2><p><strong>索引（Index）</strong>，就是<span style="background-color:#ff00ff">相同类型的文档的集合</span>。</p><p>例如：</p><ul><li>所有用户文档，就可以组织在一起，称为用户的索引；</li><li>所有商品的文档，可以组织在一起，称为商品的索引；</li><li>所有订单的文档，可以组织在一起，称为订单的索引；<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313160430.png" alt="image-20210720203022172"><br><span style="background-color:#ff0000">因此，我们可以把索引当做是数据库中的表。</span></li></ul><p>数据库的表会有约束信息，用来定义表的结构、字段的名称、类型等信息。因此，索引库中就有 <strong>映射（mapping）</strong>，是索引中文档的字段约束信息，类似表的结构约束。</p><h2 id="3-3-MySQL-与-Elasticsearch"><a href="#3-3-MySQL-与-Elasticsearch" class="headerlink" title="3.3. MySQL 与 Elasticsearch"></a>3.3. MySQL 与 Elasticsearch</h2><p>我们统一的把 mysql 与 elasticsearch 的概念做一下对比：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313162250.png" alt="image.png"></p><p>是不是说，我们学习了 elasticsearch 就不再需要 mysql 了呢？</p><p>并不是如此，两者各自有自己的擅长支出：</p><ul><li>Mysql：擅长事务类型操作，可以确保数据的安全和一致性</li><li>Elasticsearch：擅长海量数据的搜索、分析、计算</li></ul><p>因此在企业中，往往是两者结合使用：</p><ul><li>对安全性要求较高的写操作，使用 mysql 实现</li><li>对查询性能要求较高的搜索需求，使用 elasticsearch 实现</li><li>两者再基于某种方式，实现数据的同步，保证一致性</li></ul><h2 id="MongoDB-与Elasticsearcharch"><a href="#MongoDB-与Elasticsearcharch" class="headerlink" title="MongoDB 与Elasticsearcharch"></a>MongoDB 与Elasticsearcharch</h2><p><a href="https://www.cnblogs.com/xuzhujack/p/15798610.html">https://www.cnblogs.com/xuzhujack/p/15798610.html</a></p><h1 id="4-使用方法"><a href="#4-使用方法" class="headerlink" title="4. 使用方法"></a>4. 使用方法</h1><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=91&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=91&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="5-ES-集群⭐️🔴"><a href="#5-ES-集群⭐️🔴" class="headerlink" title="5. ES 集群⭐️🔴"></a>5. ES 集群⭐️🔴</h1><h2 id="5-1-选主算法"><a href="#5-1-选主算法" class="headerlink" title="5.1. 选主算法"></a>5.1. 选主算法</h2><p><a href="https://juejin.cn/post/7051761366591340574">https://juejin.cn/post/7051761366591340574</a><br>在 7.x 版本前后，Elasticsearch 采用了不同的选主算法。7.x 之前基于 Bully 算法选主，之后基于 Raft 算法。</p><h1 id="6-面试题"><a href="#6-面试题" class="headerlink" title="6. 面试题"></a>6. 面试题</h1><h2 id="6-1-谈谈对-ES-的理解"><a href="#6-1-谈谈对-ES-的理解" class="headerlink" title="6.1. 谈谈对 ES 的理解"></a>6.1. 谈谈对 ES 的理解</h2><p>ES 全称是 Elastic Search，它是一个建立在全文搜索引擎库 Lucene 基础上的开源搜索和分析引擎。ES 它本身具 有分布式存储、检索速度快的特性。所以，我们经常会用它来实现全文检索的功能。 Elastic 官网对 ES 的定义已经不再是 ElasticSearch 这一个组件，而是指 Elastic Stack 生态。<br>而 Elastic Stack 主要包括 ElasticSearch、Logstash、Kibana，这三个经典组合也称之为 ELK。ElasticSearch 主 要用来做数据存储、Logstash 主要用来做数据采集，Kibana 主要用来做数据可视化展示。</p><h2 id="6-2-为什么这么快⭐️🔴"><a href="#6-2-为什么这么快⭐️🔴" class="headerlink" title="6.2. 为什么这么快⭐️🔴"></a>6.2. 为什么这么快⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230601134623.png" alt="image.png"></p><p>1、ES 是基于 Lucene 开发的一个全文搜索引擎，一方面 Lucene 是擅长管理大量的索引数据；另外一方面，它会对数据进行分词以后再保存索引。这样，能够去提升数据的检索效率。<br>2、ES 采用了倒排索引。所谓倒排索引就是通过属性值来确定数据记录位置的索引，从而避免全表扫描的问题。<br>3、ES 存储数据采用了分片机制。<br>4、ES 扩展性很好，支持通过水平扩展的方式来动态增加节点，从而提升 ES 的处理性能。能够支持上百台服务器节点的扩展，并且支持 TB 级别的结构化数据和非结构化数据<br>5、ES 内部提供的<span style="background-color:#ff00ff">数据汇总和索引生命周期管理</span>的功能，更加便于高效地存储和检索数据</p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><ol><li>es 里面复杂的关联查询尽量别用，一旦用了性能都不太好。最好是先在 Java 系统 里就完成关联，将关联好的数据直接写入 ES 中</li><li>避免一些太复杂的操作，比如 join&#x2F;nested&#x2F;parent-child 搜索，不然容易出现性 能问题。</li><li>避免深分页查询，ES 集群的分页查询支持 from 和 size 参数，查询的时候，每个 分片必须构造一个长度为 from+size 的优先队列，然后回传到网关节点，网关节 点<span style="background-color:#ff00ff">再对这些优先队列进行排序</span>找到正确的 size 文档。当 from 足够大的时候，就算 不发生 OOM，也会影响到 CPU 和带宽等，从而影响到整个集群的性能。</li></ol><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-黑马"><a href="#8-1-黑马" class="headerlink" title="8.1. 黑马"></a>8.1. 黑马</h2><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4/?p=91&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4/?p=91&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Redis-3、缓存过期删除与淘汰策略</title>
      <link href="/2023/03/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-2%E3%80%81%E8%BF%87%E6%9C%9F%E5%88%A0%E9%99%A4%E4%B8%8E%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5/"/>
      <url>/2023/03/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-2%E3%80%81%E8%BF%87%E6%9C%9F%E5%88%A0%E9%99%A4%E4%B8%8E%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-删除策略"><a href="#1-删除策略" class="headerlink" title="1. 删除策略"></a>1. 删除策略</h1><h2 id="1-1-定时删除-自动过期"><a href="#1-1-定时删除-自动过期" class="headerlink" title="1.1. 定时删除 (自动过期)"></a>1.1. 定时删除 (自动过期)</h2><p><span style="background-color:#ff00ff">单单依靠使用设置的过期时间，到期立即删除。</span>可以理解为<span style="background-color:#ffff00">立即删除策略</span></p><blockquote><p> Redis 不可能时时刻刻遍历所有被设置了生存时间的 key，来检测数据是否已经到达过期时间，然后对它进行删除。立即删除能保证内存中数据的最大新鲜度，因为它保证过期键值会在过期后马上被删除，其所占用的内存也会随之释放。但是立即删除对 cpu 是最不友好的。因为删除操作会占用 cpu 的时间，如果刚好碰上了 cpu 很忙的时候，比如正在做交集或排序等计算的时候，就会给 cpu 造成额外的压力，让 CPU 心累，时时需要删除，忙死。。。。。。这会产生大量的性能消耗，同时也会影响数据的读取操作。</p></blockquote><p>总结：对 CPU 不友好，用处理器性能换取存储空间（拿时间换空间）</p><h2 id="1-2-惰性删除-被动检查"><a href="#1-2-惰性删除-被动检查" class="headerlink" title="1.2. 惰性删除 (被动检查)"></a>1.2. 惰性删除 (被动检查)</h2><blockquote><p> <span style="background-color:#ff00ff">数据到达过期时间，不做处理。</span>等下次访问该数据时，如果未过期，返回数据； 发现已过期，删除，返回不存在。 惰性删除策略的缺点是，它对内存是最不友好的。 如果一个键已经过期，而这个键又仍然保留在数据库中，那么只要这个过期键不被删除，它所占用的内存就不会释放。在使用惰性删除策略时，如果数据库中有非常多的过期键，而这些过期键又恰好没有被访问到的话，那么它们也许永远也不会被删除 (除非用户手动执行 FLUSHDB)，我们甚至可以将这种情况看作是一种内存泄漏–无用的垃圾数据占用了大量的内存，而服务器却不会自己去释放它们，这对于运行状态非常依赖于内存的 Redis 服务器来说,肯定不是一个好消息</p></blockquote><p>总结：对 memory 不友好，用存储空间换取处理器性能（拿空间换时间)</p><h2 id="1-3-定期删除-轮询随机"><a href="#1-3-定期删除-轮询随机" class="headerlink" title="1.3. 定期删除 (轮询随机)"></a>1.3. 定期删除 (轮询随机)</h2><blockquote><p>定期删除策略是前两种策略的折中: 定期删除策略每隔一段时间执行一次删除过期键操作，并通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。<span style="background-color:#ff00ff">周期性轮询 redis 库中的时效性数据，采用随机抽取的策略</span>，利用过期数据占比的方式控制删除频度 特点 1：CPU 性能占用设置有峰值，检测频度可自定义设置 特点 2：内存压力不是很大，长期占用内存的冷数据会被持续清理总结: 周期性抽查存储空间（随机抽查，重点抽查) 举例：<span style="background-color:#ff00ff">redis 默认每个 100ms 检查，是否有过期的 key，有过期 key 则删除。注意：redis 不是每隔 100ms 将所有的 key 检查一次而是随机抽取进行检查</span>(如果每隔 100ms,全部 key 进行检查，redis 直接进去 ICU)。因此，如果只采用定期删除策略，会导致很多 key 到时间没有删除。定期删除策略的难点是确定删除操作执行的时长和频率：如果删除操作执行得太频繁，或者执行的时间太长，定期删除策略就会退化成定时删除策略，以至于将 CPU 时间过多地消耗在删除过期键上面。如果删除操作执行得太少，或者执行的时间太短，定期删除策略又会和惰性删除束略一样，出现浪费内存的情况。因此，如果采用定期删除策略的话，服务器必须根据情况，合理地设置删除操作的执行时长和执行频率。</p></blockquote><p>定期抽样 key，判断是否过期 依旧有漏网之鱼</p><h2 id="1-4-总有漏网之鱼"><a href="#1-4-总有漏网之鱼" class="headerlink" title="1.4. 总有漏网之鱼"></a>1.4. 总有漏网之鱼</h2><p>1 定期删除时，从来没有被抽查到<br>2 惰性删除时，也从来没有被点中使用过     <br>上述 2 步骤&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&gt; 大量过期的 key 堆积在内存中，导致 redis 内存空间紧张或者很快耗尽必须要有一个更好的兜底方案……</p><h1 id="2-淘汰策略"><a href="#2-淘汰策略" class="headerlink" title="2. 淘汰策略"></a>2. 淘汰策略</h1><p>Redis 支持 8 种不同策略来选择要删除的 key：</p><ol><li>noeviction： <span style="background-color:#00ff00">不淘汰任何 key，但是内存满时不允许写入新数据</span>，<span style="background-color:#ff00ff">默认就是这种策略</span>。</li><li>volatile-ttl： 对设置了 TTL 的 key，比较 key 的剩余 TTL 值，TTL 越小越先被淘汰</li><li>allkeys-random：对全体 key ，随机进行淘汰。也就是直接从 db-&gt;dict 中随机挑选</li><li>volatile-random：对设置了 TTL 的 key ，随机进行淘汰。也就是从 db-&gt;expires 中随机挑选。</li><li>allkeys-lru： 对全体 key，基于 LRU 算法进行淘汰</li><li>volatile-lru： 对设置了 TTL 的 key，基于 LRU 算法进行淘汰</li><li>allkeys-lfu： 对全体 key，基于 LFU 算法进行淘汰</li><li>volatile-lfu： 对设置了 TTL 的 key，基于 LFI 算法进行淘汰</li></ol><h2 id="2-1-设置方法"><a href="#2-1-设置方法" class="headerlink" title="2.1. 设置方法"></a>2.1. 设置方法</h2><p>config set maxmemory-policy allkeys-lru</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314081830.png" alt="image.png"></p><h1 id="3-LRU-和-LFU"><a href="#3-LRU-和-LFU" class="headerlink" title="3. LRU 和 LFU"></a>3. LRU 和 LFU</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314081326.png" alt="image.png"></p><p>比较容易混淆的有两个：</p><ul><li>LRU（Least Recently Used），最少最近使用。用当前时间减去最后一次访问时间，这个值越大则淘汰优先级越高。</li><li>LFU（Least Frequently Used），最少频率使用。会统计每个 key 的访问频率，值越小淘汰优先级越高。</li></ul><h1 id="4-Redis-的内存淘汰算法和原理是什么"><a href="#4-Redis-的内存淘汰算法和原理是什么" class="headerlink" title="4. Redis 的内存淘汰算法和原理是什么"></a>4. Redis 的内存淘汰算法和原理是什么</h1><p>Redis 里面的内存淘汰策略，是指内存的使用率达到 maxmemory 上限的时候的一种内存释放的行为。 Redis 里面提供了很多种内存淘汰算法，归纳起来主要就四种</p><ol><li>Random 算法，随机移除某个 key</li><li>TTL 算法，在设置了过期时间的键中，把更早过期时间的 key 有限移除</li><li>LRU 算法，移除最近很少使用的 key</li><li>LFU 算法，移除最近很少使用的 key</li></ol><p>LRU 是比较常见的一种内存淘汰算法，在 Redis 里面会维护一个大小为 16 的侯选池，这个侯选池里面的数据会根据时间进行排序，然后每一次随机取出 5 个 key 放入到这个侯选池里面，当侯选池满了以后，访问的时间间隔最大的 key 就会从侯选池里面取出来淘汰掉。通过这样的一个设计，就可以把真实的最少访问的 key 从内存中淘汰掉。但是这样的一种 LRU 算法还是存在一个问题，假如一个 key 的访问频率很低，但是最近一次偶尔被访问到，那么 LRU 就会认为这是一个热点 Key，不会被淘汰。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528182103.png" alt="image.png"></p><p>所以在 Redis4 里面，增加了一个 LFU 的算法，相比于 LRU，LFU 增加了访问频率这个维度来统计数据的热点情况。 （如图）LFU 的主要设计是，使用了两个双向链表形成一个二维双向链表，一个用来保存访问频率，另一个用来保存访问频率相同的所有元素。</p><p>当添加元素的时候，访问次数默认为 1，于是找到相同访问频次的节点，然后添加到相同频率节点对应的双向链表头部。当元素被访问的时候，就会增加对应 key 的访问频次，并且把当前访问的节点移动到下一个频次节点。有可能出现某个数据前期访问次数很多，然后后续就一直不用了，如果单纯按照访问频率，这个 key 就很难被淘汰，所以在 LFU 中通过使用频率和上次访问时间来标记数据的热度，如果有有读写，就增加访问频率，如果一段时间内没有读写，就减少访问频率。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528182217.png" alt="image.png"></p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314081925.png" alt="image.png"></p><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-尚硅谷-周阳"><a href="#6-1-尚硅谷-周阳" class="headerlink" title="6.1. 尚硅谷 - 周阳"></a>6.1. 尚硅谷 - 周阳</h2><p><a href="https://www.bilibili.com/video/BV13R4y1v7sP?p=145&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13R4y1v7sP?p=145&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>经验专题-Bean拷贝-1、原理对比</title>
      <link href="/2023/03/12/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-Bean%E6%8B%B7%E8%B4%9D-1%E3%80%81%E5%8E%9F%E7%90%86%E5%AF%B9%E6%AF%94/"/>
      <url>/2023/03/12/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-Bean%E6%8B%B7%E8%B4%9D-1%E3%80%81%E5%8E%9F%E7%90%86%E5%AF%B9%E6%AF%94/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-特性"><a href="#1-特性" class="headerlink" title="1. 特性"></a>1. 特性</h1><p><a href="https://segmentfault.com/a/1190000040106844">https://segmentfault.com/a/1190000040106844</a></p><p><a href="https://cloud.tencent.com/developer/article/1624695">https://cloud.tencent.com/developer/article/1624695</a></p><p>Spring.BeanUtils：是浅拷贝，不支持深拷贝</p><p>Apache.BeanUtils：是浅拷贝，不支持深拷贝</p><p>BeanCopier：是浅拷贝，不支持深拷贝。BeanCopier 的实现原理跟 BeanUtils 截然不同，它不是利用反射对属性进行赋值，而是直接使用 cglib 来生成带有的 get&#x2F;set 方法的 class 类，然后执行。由于是直接生成字节码执行，所以 BeanCopier 的性能接近手写</p><p>BeanUtils 和 BeanCopier 都是功能比较简单的，需要属性名称一样，甚至类型也要一样。</p><p>Dozer：是深拷贝。Dozer 的实现原理本质上还是用反射&#x2F;Introspector 那套</p><p>Orika：是深拷贝。底层使用 <code>Javassist</code> 生成字节码</p><p>JMapper：未知</p><p>MapStruct：是浅拷贝，不支持深拷贝。MapStruct 是一个自动生成 bean mapper 类的代码生成器</p><p>ModelMapper：是浅拷贝，不支持深拷贝</p><h2 id="1-1-选择"><a href="#1-1-选择" class="headerlink" title="1.1. 选择"></a>1.1. 选择</h2><p><a href="http://18965050.github.io/work/orika">http://18965050.github.io/work/orika</a></p><p>如果不需要深拷贝，就用 MapStruct</p><p>如果需要深拷贝，就用 Orika 或者用插件生成（如 coding-wizard）</p><h1 id="2-原理对比"><a href="#2-原理对比" class="headerlink" title="2. 原理对比"></a>2. 原理对比</h1><p>目前常用的属性拷贝工具，包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct。</p><h2 id="2-1-Apache-BeanUtils-BeanUtils"><a href="#2-1-Apache-BeanUtils-BeanUtils" class="headerlink" title="2.1. Apache BeanUtils:BeanUtils"></a>2.1. Apache BeanUtils:BeanUtils</h2><p> Apache commons 组件里面的成员，由 Apache 提供的一套开源 api，用于简化对 javaBean 的操作，能够对基本类型自动转换。</p><p>  循环遍历源对象的每个属性，对于每个属性，拷贝流程为:</p><ul><li>校验来源类的字段是否可读 isReadable</li><li>校验目标类的字段是否可写 isWriteable</li><li>获取来源类的字段属性值 getSimpleProperty</li><li>获取目标类字段的类型 type，并进行类型转换</li><li>设置目标类字段的值</li></ul><p><span style="background-color:#ff00ff">由于单字段拷贝时每个阶段都会调用 PropertyUtilsBean.getPropertyDescriptor 获取属性配置,而该方法通过 for 循环获取类的字段属性，严重影响拷贝效率</span>。</p><h2 id="2-2-Spring-BeanUtils"><a href="#2-2-Spring-BeanUtils" class="headerlink" title="2.2. Spring BeanUtils"></a>2.2. Spring BeanUtils</h2><p>BeanUtils 是 spring 框架下自带的工具，在 org.springframework.beans 包下， spring 项目可以直接使用。</p><h2 id="2-3-Cglib-BeanCopier"><a href="#2-3-Cglib-BeanCopier" class="headerlink" title="2.3. Cglib BeanCopier"></a>2.3. Cglib BeanCopier</h2><p>cglib（Code Generation Library）是一个强大的、高性能、高质量的代码生成类库，BeanCopier 依托于 cglib 的字节码增强能力，动态生成实现类，完成对象的拷贝。</p><h2 id="2-4-mapstruct"><a href="#2-4-mapstruct" class="headerlink" title="2.4. mapstruct"></a>2.4. mapstruct</h2><p>mapstruct 是一个 Java 注释处理器，用于生成类型安全的 bean 映射类，在构建时，根据注解生成实现类，完成对象拷贝。</p><a href="/2023/03/04/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-2%E3%80%81%E7%B3%BB%E7%BB%9F%E8%B0%83%E4%BC%98%E6%A1%88%E4%BE%8B%E2%99%A8%EF%B8%8F/" title="性能调优-进阶-2、系统调优案例♨️">性能调优-进阶-2、系统调优案例♨️</a><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><p><a href="https://www.51cto.com/article/741833.html">https://www.51cto.com/article/741833.html</a></p><p><a href="https://zhuanlan.zhihu.com/p/586539335">https://zhuanlan.zhihu.com/p/586539335</a></p><p><a href="https://www.freebuf.com/news/350650.html">https://www.freebuf.com/news/350650.html</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-7、远程调用-F&#39;ei&#39;g&#39;n</title>
      <link href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-7%E3%80%81%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8-Feign%E4%B8%8EOpenFeign/"/>
      <url>/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-7%E3%80%81%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8-Feign%E4%B8%8EOpenFeign/</url>
      
        <content type="html"><![CDATA[<hr><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210925134648168.png" alt="image-20210925134648168"></p><h1 id="1-Feign-与-OpenFeign-与-Ribbon"><a href="#1-Feign-与-OpenFeign-与-Ribbon" class="headerlink" title="1. Feign 与 OpenFeign 与 Ribbon"></a>1. Feign 与 OpenFeign 与 Ribbon</h1><p><span style="background-color:#ff00ff">Feign 内置了 Ribbon 作为负载均衡实现</span><br><span style="background-color:#ff00ff">OpenFeign 基于 Feign 增加了对 SpringMVC 注解的支持</span></p><p>a、他们底层都是内置了 Ribbon，去调用注册中心的服务。<br>b、Feign 是 Netflix 公司写的，是 SpringCloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端，是 SpringCloud 中的第一代负载均衡客户端。<br>OpenFeign 是 SpringCloud 自己研发的，在 Feign 的基础上支持了 Spring MVC 的注解，如@RequesMapping 等等。是 SpringCloud 中的第二代负载均衡客户端。<br>c、Feign 本身不支持 Spring MVC 的注解，使用 Feign 的注解定义接口，调用这个接口，就可以调用服务注册中心的服务</p><p><span style="background-color:#ff00ff">OpenFeign 的@FeignClient 可以解析 SpringMVC 的@RequestMapping 注解下的接口，并通过动态代理的方式产生实现类，实现类中做负载均衡并调用其他服务。</span></p><h1 id="2-Feign-与-RestTemplate"><a href="#2-Feign-与-RestTemplate" class="headerlink" title="2. Feign 与 RestTemplate"></a>2. Feign 与 RestTemplate</h1><p>先来看我们以前利用 RestTemplate 发起远程调用的代码：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312183524.png" alt="image-20210714174814204"></p><p>存在下面的问题：</p><p><span style="background-color:#ffff00">•代码可读性差，编程体验不统一</span><br><span style="background-color:#ffff00">•参数复杂 URL 难以维护</span></p><p>Feign 是一个<span style="background-color:#ff00ff">声明式的 http 客户端</span>，官方地址：<a href="https://github.com/OpenFeign/feign">https://github.com/OpenFeign/feign</a></p><p>其作用就是帮助我们优雅的实现 http 请求的发送，解决上面提到的问题。</p><h1 id="3-Feign-使用"><a href="#3-Feign-使用" class="headerlink" title="3. Feign 使用"></a>3. Feign 使用</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312183740.png" alt="image.png"></p><h1 id="4-性能优化"><a href="#4-性能优化" class="headerlink" title="4. 性能优化"></a>4. 性能优化</h1><p>Feign 底层发起 http 请求，依赖于其它的框架。其底层客户端实现包括：</p><p>•URLConnection：<span style="background-color:#ff00ff">默认实现，不支持连接池</span><br>•Apache HttpClient ：支持连接池<br>•OKHttp：支持连接池</p><p>可以更改为 HttpClient 或者 OKHttp，这里我们用 Apache 的 HttpClient 来演示</p><p><strong>1）引入依赖</strong></p><p>在 order-service 的 pom 文件中引入 Apache 的 HttpClient 依赖：</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></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-comment">&lt;!--httpClient的依赖 --&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>io.github.openfeign<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>feign-httpclient<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></code></pre></td></tr></table></figure><p><strong>2）配置连接池</strong></p><p>在 order-service 的 application.yml 中添加配置：</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></pre></td><td class="code"><pre><code class="hljs yaml"><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> <span class="hljs-comment"># default全局的配置</span><br>        <span class="hljs-attr">loggerLevel:</span> <span class="hljs-string">BASIC</span> <span class="hljs-comment"># 日志级别，BASIC就是基本的请求和响应信息</span><br>  <span class="hljs-attr">httpclient:</span><br>    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 开启feign对HttpClient的支持</span><br>    <span class="hljs-attr">max-connections:</span> <span class="hljs-number">200</span> <span class="hljs-comment"># 最大的连接数</span><br>    <span class="hljs-attr">max-connections-per-route:</span> <span class="hljs-number">50</span> <span class="hljs-comment"># 每个路径的最大连接数</span><br></code></pre></td></tr></table></figure><p>接下来，在 FeignClientFactoryBean 中的 loadBalance 方法中打断点：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312184720.png" alt="image-20210714185925910"></p><p>Debug 方式启动 order-service 服务，可以看到这里的 client，底层就是 Apache HttpClient：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312184717.png" alt="image-20210714190041542"></p><p>总结，Feign 的优化：</p><p>1.日志级别尽量用 basic<br>2.使用 HttpClient 或 OKHttp 代替 URLConnection</p><p>① 引入 feign-httpClient 依赖<br>② 配置文件开启 httpClient 功能，设置连接池参数</p><h2 id="4-1-最佳实践-使用连接池⭐️🔴"><a href="#4-1-最佳实践-使用连接池⭐️🔴" class="headerlink" title="4.1. 最佳实践-使用连接池⭐️🔴"></a>4.1. 最佳实践-使用连接池⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312200803.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312200845.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312201254.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312201430.png" alt="image.png"></p><h1 id="5-openFeign"><a href="#5-openFeign" class="headerlink" title="5. openFeign"></a>5. openFeign</h1><h2 id="5-1-是什么"><a href="#5-1-是什么" class="headerlink" title="5.1. 是什么"></a>5.1. 是什么</h2><h2 id="5-2-使用-OpenFeign-时程序执行流程"><a href="#5-2-使用-OpenFeign-时程序执行流程" class="headerlink" title="5.2. 使用 OpenFeign 时程序执行流程"></a>5.2. 使用 OpenFeign 时程序执行流程</h2><p>   OpenFeign 代替之前的 RestTemplate 代码。也是写在 Application Client 中。把 OpenFeign 接口单独放在 feign 包中，表示服务调用层。当需要调用其他服务时，直接注入 OpenFeign 接口对象就可以像调用本地方法一样调用远程服务。</p><p>整体流程说明：</p><ol><li>ApplicationService 向 Eureka Server 注册服务。</li><li>Application Client 从 Eureka Server 中发现服务信息。</li><li>在 Application Client 中调用 OpenFeign 接口中方法</li><li>Application Client 中 OpenFeign 通过应用程序名调用 Application Service</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416212959.png" alt="image.png"></p><h2 id="5-3-超时时间配置"><a href="#5-3-超时时间配置" class="headerlink" title="5.3. 超时时间配置"></a>5.3. 超时时间配置</h2><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></pre></td><td class="code"><pre><code class="hljs yaml"><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-comment">#建立连接所用的时间，适用于网络状况正常的情况下，两端连接所需要的时间,</span><br>        <span class="hljs-attr">ConnectTimeOut:</span> <span class="hljs-number">5000</span><br>        <span class="hljs-comment">#指建立连接后从服务端读取到可用资源所用的时间,默认为1s</span><br>        <span class="hljs-attr">ReadTimeOut:</span> <span class="hljs-number">5000</span><br></code></pre></td></tr></table></figure><a href="/2023/04/15/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4-1%E3%80%81%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/" title="经验专题-超时时间-1、超时问题汇总">经验专题-超时时间-1、超时问题汇总</a><h2 id="5-4-Spring-Feign-序列化机制"><a href="#5-4-Spring-Feign-序列化机制" class="headerlink" title="5.4. Spring Feign 序列化机制"></a>5.4. Spring Feign 序列化机制</h2><h2 id="5-5-OpenFeign-通讯优化"><a href="#5-5-OpenFeign-通讯优化" class="headerlink" title="5.5. OpenFeign 通讯优化"></a>5.5. OpenFeign 通讯优化</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416212431.png" alt="image.png"></p><p>在交互数据量级不够的时候，看不到压缩内容。</p><p>      这里只开启 Feign 请求 - 应答过程中的 GZIP，也就是浏览器 -Application Client 之间的请求应答不开启 GZIP 压缩。</p><p>      在全局配置文件中，使用下述配置来实现 OpenFeign 请求 - 应答的 GZIP 压缩</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416212552.png" alt="image.png"></p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/" title="面试专题-6、分布式组件">面试专题-6、分布式组件</a><h2 id="7-1-黑马程序员"><a href="#7-1-黑马程序员" class="headerlink" title="7.1. 黑马程序员"></a>7.1. 黑马程序员</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程<br></code></pre></td></tr></table></figure><h2 id="7-2-尚学堂"><a href="#7-2-尚学堂" class="headerlink" title="7.2. 尚学堂"></a>7.2. 尚学堂</h2><h3 id="7-2-1-视频"><a href="#7-2-1-视频" class="headerlink" title="7.2.1. 视频"></a>7.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1hq4y177iu/?vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1hq4y177iu/?vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-2-2-资料"><a href="#7-2-2-资料" class="headerlink" title="7.2.2. 资料"></a>7.2.2. 资料</h3><p><a href="https://www.bilibili.com/read/cv11226358/">https://www.bilibili.com/read/cv11226358/</a><br><a href="https://www.cnblogs.com/duanxz/p/15933313.html">https://www.cnblogs.com/duanxz/p/15933313.html</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-8、网关-GateWay</title>
      <link href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-8%E3%80%81%E7%BD%91%E5%85%B3-GateWay/"/>
      <url>/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-8%E3%80%81%E7%BD%91%E5%85%B3-GateWay/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-分类"><a href="#1-分类" class="headerlink" title="1. 分类"></a>1. 分类</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230227213832.png" alt="image.png"></p><p>面对互联网复杂的业务系统，基本可以将服务网关分成两类：流量网关和业务网关。<br><strong>流量网关</strong>：跟具体的后端业务系统和服务完全无关的部分，比如安全策略、全局性流控策略、流量分发策略等。<br><strong>业务网关</strong>：针对具体的后端业务系统，或者是服务和业务有一定关联性的部分，并且一般被直接部署在业务服务的前面。业务网关一般部署在流量网关之后，业务系统之前，比流量网关更靠近系统。我们大部分情况下说的 API 网关，狭义上指的是业务网关。并且如果系统的规模不大，我们也会将两者合二为一，使用一个网关来处理所有的工作</p><h1 id="2-作用"><a href="#2-作用" class="headerlink" title="2. 作用"></a>2. 作用</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312211609.png" alt="image.png"></p><h1 id="3-跨域问题"><a href="#3-跨域问题" class="headerlink" title="3. 跨域问题"></a>3. 跨域问题</h1><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><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">gateway:</span><br>      <span class="hljs-comment"># 。。。</span><br>      <span class="hljs-attr">globalcors:</span> <span class="hljs-comment"># 全局的跨域处理</span><br>        <span class="hljs-attr">add-to-simple-url-handler-mapping:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 解决options请求被拦截问题</span><br>        <span class="hljs-attr">corsConfigurations:</span><br>          <span class="hljs-string">&#x27;[/**]&#x27;</span><span class="hljs-string">:</span><br>            <span class="hljs-attr">allowedOrigins:</span> <span class="hljs-comment"># 允许哪些网站的跨域请求 </span><br>              <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;http://localhost:8090&quot;</span><br>            <span class="hljs-attr">allowedMethods:</span> <span class="hljs-comment"># 允许的跨域ajax的请求方式</span><br>              <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;GET&quot;</span><br>              <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;POST&quot;</span><br>              <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;DELETE&quot;</span><br>              <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;PUT&quot;</span><br>              <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;OPTIONS&quot;</span><br>            <span class="hljs-attr">allowedHeaders:</span> <span class="hljs-string">&quot;*&quot;</span> <span class="hljs-comment"># 允许在请求中携带的头信息</span><br>            <span class="hljs-attr">allowCredentials:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 是否允许携带cookie</span><br>            <span class="hljs-attr">maxAge:</span> <span class="hljs-number">360000</span> <span class="hljs-comment"># 这次跨域检测的有效期</span><br></code></pre></td></tr></table></figure><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/" title="面试专题-6、分布式组件">面试专题-6、分布式组件</a><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/Enterprise/0003-Architecture/005-分布式专题/1、微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程/实用篇/学习资料<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-9、容器技术-Docker</title>
      <link href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%AE%B9%E5%99%A8%E6%8A%80%E6%9C%AF-9%E3%80%81Docker/"/>
      <url>/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%AE%B9%E5%99%A8%E6%8A%80%E6%9C%AF-9%E3%80%81Docker/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p>Docker 是一种<span style="background-color:#ff00ff">轻量级的虚拟化技术</span>，同时是一个开源的应用容器运行环境搭建平台，可以让开发者以便捷方式打包应用到一个可移植的容器中，然后安装至任何运行 Linux 或 Windows 等系统的服务器上。相较于传统虚拟机，Docker 容器提供轻量化的虚拟化方式、安装便捷、启停速度快。<br>Docker 是基于 Go 语言实现的云开源项目。<br>Docker 的主要目标是“Build，Ship and Run Any App,Anywhere”，也就是通过<span style="background-color:#ff00ff">对应用组件的封装、分发、部署、运行等生命周期的管理</span>，使用户的 APP（可以是一个 WEB 应用或数据库应用等等）及其运行环境能够做到“<span style="background-color:#ff00ff">一次镜像，处处运行</span>”。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230530145250.png" alt="image.png"></p><h1 id="2-运行原理-解决问题"><a href="#2-运行原理-解决问题" class="headerlink" title="2. 运行原理 (解决问题)"></a>2. 运行原理 (解决问题)</h1><h2 id="2-1-大型项目众多应用组件间依赖和配置不兼容"><a href="#2-1-大型项目众多应用组件间依赖和配置不兼容" class="headerlink" title="2.1. 大型项目众多应用组件间依赖和配置不兼容"></a>2.1. 大型项目众多应用组件间依赖和配置不兼容</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416160913.png" alt="image-20210731142219735"><br>Docker 为了解决依赖的兼容问题的，采用了两个手段：</p><p><span style="background-color:#ff00ff">- 将应用的 Libs（函数库）、Deps（依赖）、配置与应用一起打包，形成可移植镜像</span><br><span style="background-color:#ff00ff">- 将每个应用放到一个隔离 <strong>容器</strong> 去运行，使用沙箱机制进行隔离，避免互相干扰</span></p><p>这样打包好的应用包中，既包含应用本身，也保护应用所需要的 Libs、Deps，无需再操作系统上安装这些，自然就不存在不同应用之间的兼容问题了。</p><p>虽然解决了不同应用的兼容问题，但是开发、测试等环境会存在差异，操作系统版本也会有差异，怎么解决这些问题呢？</p><h2 id="2-2-应用部署环境及系统存在差异"><a href="#2-2-应用部署环境及系统存在差异" class="headerlink" title="2.2. 应用部署环境及系统存在差异"></a>2.2. 应用部署环境及系统存在差异</h2><h3 id="2-2-1-系统应用与系统交互逻辑"><a href="#2-2-1-系统应用与系统交互逻辑" class="headerlink" title="2.2.1. 系统应用与系统交互逻辑"></a>2.2.1. 系统应用与系统交互逻辑</h3><p>要解决不同操作系统环境差异问题，必须先了解操作系统结构。以一个 Ubuntu 操作系统为例，结构如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416161035.png" alt="image-20210731143401460"></p><p>从下往上依次来看，结构包括：</p><ul><li>计算机硬件：例如 CPU、内存、磁盘等</li><li>系统内核：所有 Linux 发行版的内核都是 Linux，例如 CentOS、Ubuntu、Fedora 等。内核可以与计算机硬件交互，对外提供 <strong>内核指令</strong>，用于操作计算机硬件。</li><li>系统应用：操作系统本身提供的应用、函数库。这些函数库是对内核指令的封装，使用更加方便。</li></ul><p>应用与计算机交互的流程如下：</p><p>1）<span style="background-color:#ff00ff">应用调用操作系统应用（函数库）</span>，实现各种功能<br>2）<span style="background-color:#ff00ff">系统函数库是对内核指令集的封装，会调用内核指令</span><br>3）内核指令操作计算机硬件</p><h3 id="2-2-2-Docker-如何解决不同系统环境的问题"><a href="#2-2-2-Docker-如何解决不同系统环境的问题" class="headerlink" title="2.2.2. Docker 如何解决不同系统环境的问题"></a>2.2.2. Docker 如何解决不同系统环境的问题</h3><ul><li><span style="background-color:#ff00ff">Docker 将用户程序与所需要调用的系统 (比如 Ubuntu) 函数库一起打包</span></li><li>Docker 运行到不同操作系统时，直接基于打包的函数库，借助于操作系统的 Linux 内核来运行<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416161427.png" alt="image-20210731144820638"></li></ul><h2 id="2-3-优点"><a href="#2-3-优点" class="headerlink" title="2.3. 优点"></a>2.3. 优点</h2><p>Docker 是一个快速交付应用、运行应用的技术，具备下列优势：</p><ul><li>可以将程序及其依赖、运行环境一起打包为一个镜像，可以迁移到任意 Linux 操作系统</li><li>运行时利用沙箱机制形成隔离容器，各个应用互不干扰</li><li>启动、移除都可以通过一行命令完成，方便快捷</li></ul><h1 id="3-与虚拟机区别"><a href="#3-与虚拟机区别" class="headerlink" title="3. 与虚拟机区别"></a>3. 与虚拟机区别</h1><h2 id="3-1-优势分析"><a href="#3-1-优势分析" class="headerlink" title="3.1. 优势分析"></a>3.1. 优势分析</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312214931.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312214955.png" alt="image.png"></p><h3 id="3-1-1-资源占用小"><a href="#3-1-1-资源占用小" class="headerlink" title="3.1.1. 资源占用小"></a>3.1.1. 资源占用小</h3><p>由于容器不需要进行硬件虚拟，也不需要运行完整操作系统等额外的资源开销，使得 Docker 对系统资源的利用率更高，无论是应用执行速度还是文件存储速度,都要比传统虚拟机技术更高效,内存消 耗更少 。</p><h3 id="3-1-2-启动速度快"><a href="#3-1-2-启动速度快" class="headerlink" title="3.1.2. 启动速度快"></a>3.1.2. 启动速度快</h3><p>传统的虚拟机技术启动应用服务往往需要较长时间，而 Docker 容器应用，由于直接运行于宿主内核，无需启动完整的操作系统，因此可以做到秒级，甚至毫秒级的启动时间，大大的节约了开发，测试，部署的时间。</p><h3 id="3-1-3-迁移更轻松"><a href="#3-1-3-迁移更轻松" class="headerlink" title="3.1.3. 迁移更轻松"></a>3.1.3. 迁移更轻松</h3><p>由于 Docker 确保了执行环境的一致性，使得应用的迁移更加容易， Docker 可以在很多平台上运行，无论是物理机，虚拟机，公有云，私有云，它们的运行结果是一致的，因此用户可以很轻易的将一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行这类的问题。</p><h3 id="3-1-4-维护和拓展更轻松"><a href="#3-1-4-维护和拓展更轻松" class="headerlink" title="3.1.4. 维护和拓展更轻松"></a>3.1.4. 维护和拓展更轻松</h3><p>docker 使用的分层存储和镜像技术，让应用重复部分的复用更容易，也让应用的维护更新更简单，基于基础镜像进一步扩展镜像也变得十分简单。另外，docker 团队和各个开源项目团队一起维护了一大批高质量的官网镜像，既可以直接在生产环境使用，又可以作为基础进一步定制，大大降低了应用服务的镜像制作成本。</p><h3 id="3-1-5-运行环境一致"><a href="#3-1-5-运行环境一致" class="headerlink" title="3.1.5. 运行环境一致"></a>3.1.5. 运行环境一致</h3><p>开发过程中一个常见的问题是环境一致性问题，由于开发环境，测试环境，生产环境不一致，导致有些 bug 并未在开发过程中被发现，而 Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性。</p><h3 id="3-1-6-持续交付和部署"><a href="#3-1-6-持续交付和部署" class="headerlink" title="3.1.6. 持续交付和部署"></a>3.1.6. 持续交付和部署</h3><p>使用 Docker 可以通过定制应用镜像来实现持续集成，持续交付，部署。开发人员可以 通过 Dockerfile 来进行镜像构建， 并结合持续集成系统进行集成测试，而运维人员则可以在生产环境中快速部署该镜像， 甚至结合持续部署系统进行自动部署</p><h2 id="3-2-优势原因"><a href="#3-2-优势原因" class="headerlink" title="3.2. 优势原因"></a>3.2. 优势原因</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230313111810.png" alt="image.png"></p><h1 id="4-镜像和容器"><a href="#4-镜像和容器" class="headerlink" title="4. 镜像和容器"></a>4. 镜像和容器</h1><h2 id="4-1-镜像和容器"><a href="#4-1-镜像和容器" class="headerlink" title="4.1. 镜像和容器"></a>4.1. 镜像和容器</h2><p><strong>镜像（Image）</strong>：Docker 将<span style="background-color:#ff00ff">应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起</span>，称为镜像。<br><strong>容器（Container）</strong>：镜像中的应用程序<span style="background-color:#ff00ff">运行后形成的 [进程]</span>就是 <strong>容器</strong>，只是 Docker 会给容器进程做隔离，对外不可见。</p><p>一切应用最终都是代码组成，都是硬盘中的一个个的字节形成的 <strong>文件</strong>。只有运行时，才会加载到内存，形成进程。<br>而 <strong>镜像</strong>，就是把一个应用在硬盘上的文件、及其运行环境、部分系统函数库文件一起打包形成的文件包。这个文件包是只读的。<br><strong>容器</strong> 呢，就是将这些文件中编写的程序、函数加载到内存中运行，形成进程，只不过要隔离起来。因此一个镜像可以启动多次，形成多个容器进程。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416164304.png" alt="image-20210731153059464"></p><h2 id="4-2-虚悬镜像"><a href="#4-2-虚悬镜像" class="headerlink" title="4.2. 虚悬镜像"></a>4.2. 虚悬镜像</h2><p>仓库名、标签都是 <code>none</code> 的镜像，俗称虚悬镜像 dangling image</p><h3 id="4-2-1-产生原因"><a href="#4-2-1-产生原因" class="headerlink" title="4.2.1. 产生原因"></a>4.2.1. 产生原因</h3><p>构建或者删除镜像时出错导致</p><h3 id="4-2-2-如何处理"><a href="#4-2-2-如何处理" class="headerlink" title="4.2.2. 如何处理"></a>4.2.2. 如何处理</h3><p>查看： <code>docker image ls -f dangling=true</code><br>删除： <code>docker image prune</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230318224829.png" alt="image.png"></p><h1 id="5-数据卷"><a href="#5-数据卷" class="headerlink" title="5. 数据卷"></a>5. 数据卷</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312222809.png" alt="image.png"></p><h1 id="6-Dockerfile"><a href="#6-Dockerfile" class="headerlink" title="6. Dockerfile"></a>6. Dockerfile</h1><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230318-2215%%</span>❕ ^dnlslf</p><h2 id="6-1-镜像结构"><a href="#6-1-镜像结构" class="headerlink" title="6.1. 镜像结构"></a>6.1. 镜像结构</h2><p>镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416165624.png" alt="image-20210731175806273"></p><p>简单来说，镜像就是在系统函数库、运行环境基础上，添加应用程序文件、配置文件、依赖文件等组合，然后编写好启动脚本打包在一起形成的文件。</p><p>我们要构建镜像，其实就是实现上述打包的过程。</p><h2 id="6-2-Dockerfile-命令"><a href="#6-2-Dockerfile-命令" class="headerlink" title="6.2. Dockerfile 命令"></a>6.2. Dockerfile 命令</h2><p>构建自定义的镜像时，并不需要一个个文件去拷贝，打包。</p><p>我们只需要告诉 Docker，我们的镜像的组成，需要哪些 BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么，将来 Docker 会帮助我们构建镜像。</p><p>而描述上述信息的文件就是 Dockerfile 文件。</p><p><strong>Dockerfile</strong> 就是一个文本文件，其中包含一个个的 **指令 (Instruction)**，用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层 Layer。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230416165730.png"></p><h2 id="6-3-继承分层"><a href="#6-3-继承分层" class="headerlink" title="6.3. 继承分层"></a>6.3. 继承分层</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312224524.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312224549.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312224654.png" alt="image.png"></p><h2 id="6-4-ENTRYPOINT-与-CMD"><a href="#6-4-ENTRYPOINT-与-CMD" class="headerlink" title="6.4. ENTRYPOINT 与 CMD"></a>6.4. ENTRYPOINT 与 CMD</h2><p><a href="https://blog.csdn.net/u010900754/article/details/78526443">https://blog.csdn.net/u010900754/article/details/78526443</a><br><a href="https://cloud.tencent.com/developer/article/1906538">https://cloud.tencent.com/developer/article/1906538</a><br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230416-1721%%</span>❕ ^hhynot</p><h3 id="6-4-1-CMD-可以被覆盖的来历"><a href="#6-4-1-CMD-可以被覆盖的来历" class="headerlink" title="6.4.1. CMD 可以被覆盖的来历"></a>6.4.1. CMD 可以被覆盖的来历</h3><blockquote><p>The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well.</p></blockquote><p>意思是，cmd 给出的是一个容器的默认的可执行体。也就是容器启动以后，默认的执行的命令。重点就是这个“默认”。意味着，如果 docker run 没有指定任何的执行命令或者 dockerfile 里面也没有 entrypoint，那么，就会使用 cmd 指定的默认的执行命令执行。同时也从侧面说明了 entrypoint 的含义，它才是真正的容器启动以后要执行命令。<br>所以这句话就给出了 cmd 命令的一个角色定位，它主要作用是默认的容器启动执行命令。（注意不是“全部”作用）<br>这也是为什么大多数网上博客论坛说的“cmd 会被覆盖”，其实为什么会覆盖？因为 cmd 的角色定位就是默认，如果你不额外指定，那么就执行 cmd 的命令，否则呢？只要你指定了，那么就不会执行 cmd，也就是 cmd 会被覆盖。</p><h3 id="6-4-2-最佳实践"><a href="#6-4-2-最佳实践" class="headerlink" title="6.4.2. 最佳实践"></a>6.4.2. 最佳实践</h3><p>一般使用 entrypoint 的中括号形式作为 docker 容器启动以后的默认执行命令，里面放的是不变的部分，可变部分比如命令参数可以使用 cmd 的形式提供默认版本，也就是 run 里面没有任何参数时使用的默认参数。如果我们想用默认参数，就直接 run，否则想用其他参数，就 run 里面加参数。</p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-黑马"><a href="#8-1-黑马" class="headerlink" title="8.1. 黑马"></a>8.1. 黑马</h2><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=57&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=57&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><p>[[Docker实用篇]]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程/day03-Docker/讲义<br></code></pre></td></tr></table></figure><h2 id="8-2-尚硅谷-周阳"><a href="#8-2-尚硅谷-周阳" class="headerlink" title="8.2. 尚硅谷 - 周阳"></a>8.2. 尚硅谷 - 周阳</h2><h3 id="8-2-1-视频"><a href="#8-2-1-视频" class="headerlink" title="8.2.1. 视频"></a>8.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1gr4y1U7CY?p=8&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1gr4y1U7CY?p=8&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-2-2-资料"><a href="#8-2-2-资料" class="headerlink" title="8.2.2. 资料"></a>8.2.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/Docker2022-周阳脑图笔记<br></code></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-6、服务注册与发现-Nacos</title>
      <link href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-6%E3%80%81Nacos/"/>
      <url>/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-6%E3%80%81Nacos/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-注解原理"><a href="#1-注解原理" class="headerlink" title="1. 注解原理"></a>1. 注解原理</h1><p><a href="https://www.cnblogs.com/lm970585581/p/13066729.html">https://www.cnblogs.com/lm970585581/p/13066729.html</a></p><p>从 Edgware 版本开始,可以不加@EnableDiscoveryClient 注解<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312173104.png" alt="image.png"></p><h1 id="2-注册中心"><a href="#2-注册中心" class="headerlink" title="2. 注册中心"></a>2. 注册中心</h1><p><strong>服务注册</strong>： 当服务启动通过 Rest 请求的方式向 Nacos Server 注册自己的服务<br><strong>服务心跳</strong>：Nacos Client 会维护一个定时心跳持续通知 Nacos Server , <span style="background-color:#ff00ff">默认 5s 一次</span>，如果 Nacos  Server<span style="background-color:#ffff00">超过了 15 秒</span>没有接收心跳，会将服务健康状态设置 false（<span style="background-color:#ffff00">拉取的时候会忽略</span>），如果<span style="background-color:#ff00ff">超过了 30 秒</span>没有接收心跳剔除服务。<br><strong>服务发现</strong>：Nacos Client  会<span style="background-color:#ff00ff">有一个定时任务</span>，实时去 Nacos Server 拉取健康服务，并缓存在本地一份。如果有变更，Nacos Server 也会主动推送。<br><strong>服务停止</strong>：Nacos Client 会主动通过 Rest 请求 Nacos Server 发送一个注销的请求</p><h2 id="2-1-注册流程"><a href="#2-1-注册流程" class="headerlink" title="2.1. 注册流程"></a>2.1. 注册流程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312173958.png" alt="image.png"></p><h2 id="2-2-有拉有推"><a href="#2-2-有拉有推" class="headerlink" title="2.2. 有拉有推"></a>2.2. 有拉有推</h2><h1 id="3-配置中心"><a href="#3-配置中心" class="headerlink" title="3. 配置中心"></a>3. 配置中心</h1><h2 id="3-1-优点"><a href="#3-1-优点" class="headerlink" title="3.1. 优点"></a>3.1. 优点</h2><p>集中管理服务的配置、提高维护性、时效性、安全性</p><h2 id="3-2-配置内容"><a href="#3-2-配置内容" class="headerlink" title="3.2. 配置内容"></a>3.2. 配置内容</h2><p>有哪些东西可以作为配置？<br>比方说，数据库连接 Url，缓存连接 url 字符串，数据库的用户名，密码都可以作为配置的字符串，除此之外，还有一些可以&#x3D;&#x3D;动态调整的参数&#x3D;&#x3D;，比方说，客户端的超时设置<span style="background-color:#00ff00">限流规则和降级开关</span>，<span style="background-color:#00ff00">流量的动态调度</span>，比方说某个功能只是针对某个地区用户，还有某个功能只在大促的时段开放，如果这种需要通过静态的方式去配置或者发布的方式去配置，那么响应速度是非常慢，可能对业务存在风险，如果有一套集中式的配置中心，只需要相关人员在配置中心动态去调整参数，就基本上可以实时或准实时去调整相关对应的业务。所以配置中心在微服务中算是一个举足轻重的组件。</p><h2 id="3-3-配置读取"><a href="#3-3-配置读取" class="headerlink" title="3.3. 配置读取"></a>3.3. 配置读取</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312161350.png" alt="image.png"></p><h2 id="3-4-配置更新"><a href="#3-4-配置更新" class="headerlink" title="3.4. 配置更新"></a>3.4. 配置更新</h2><p><a href="https://ost.51cto.com/posts/13172">https://ost.51cto.com/posts/13172</a><br><a href="https://www.cnkirito.moe/nacos-and-longpolling/">https://www.cnkirito.moe/nacos-and-longpolling/</a></p><h3 id="3-4-1-推与拉模型"><a href="#3-4-1-推与拉模型" class="headerlink" title="3.4.1. 推与拉模型"></a>3.4.1. 推与拉模型</h3><p>客户端与配置中心的数据交互方式其实无非就两种，要么推 <code>push</code>，要么拉 <code>pull</code></p><h4 id="3-4-1-1-推模型"><a href="#3-4-1-1-推模型" class="headerlink" title="3.4.1.1. 推模型"></a>3.4.1.1. <strong>推模型</strong></h4><p>客户端与服务端建立 <code>TCP</code> 长连接，当服务端配置数据有变动，立刻通过建立的长连接将数据推送给客户端。</p><p>优势：长链接的优点是实时性，一旦数据变动，立即推送变更数据给客户端，而且对于客户端而言，这种方式更为简单，只建立连接接收数据，并不需要关心是否有数据变更这类逻辑的处理。</p><p>弊端：长连接可能会因为网络问题，导致不可用，也就是俗称的 <code>假死</code>。连接状态正常，但实际上已无法通信，所以要有的心跳机制 <code>KeepAlive</code> 来保证连接的可用性，才可以保证配置数据的成功推送。</p><h4 id="3-4-1-2-拉模型"><a href="#3-4-1-2-拉模型" class="headerlink" title="3.4.1.2. 拉模型"></a>3.4.1.2. <strong>拉模型</strong></h4><p>客户端主动的向服务端发请求拉配置数据，常见的方式就是轮询，比如每 3s 向服务端请求一次配置数据。</p><p>轮询的优点是实现比较简单。但弊端也显而易见，轮询无法保证数据的实时性，什么时候请求？间隔多长时间请求一次？都是不得不考虑的问题，而且轮询方式对服务端还会产生不小的压力。</p><h3 id="3-4-2-长轮询与轮询"><a href="#3-4-2-长轮询与轮询" class="headerlink" title="3.4.2. 长轮询与轮询"></a>3.4.2. 长轮询与轮询</h3><p><a href="https://www.cnkirito.moe/nacos-and-longpolling/">https://www.cnkirito.moe/nacos-and-longpolling/</a></p><p>长轮训典型的场景有： 扫码登录、扫码支付。<br><a href="https://juejin.cn/post/6844904019278692360">https://juejin.cn/post/6844904019278692360</a></p><h3 id="3-4-3-长轮询-拉取⭐️🔴"><a href="#3-4-3-长轮询-拉取⭐️🔴" class="headerlink" title="3.4.3. 长轮询 + 拉取⭐️🔴"></a>3.4.3. 长轮询 + 拉取⭐️🔴</h3><p>首先，Nacos 是采用<span style="background-color:#ff00ff">长轮询的方式</span>向 Nacos Server 端发起配置更新查询的功能。所谓长轮询就是客户端发起一次轮训请求到服务端，当服务端配置没有任何变更的时候，这个连接一直打开。<span style="background-color:#ff00ff">直到服务端有配置或者连接超时后返回</span>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230604195114.png" alt="image.png"></p><p>Nacos Client 端需要获取服务端变更的配置，前提是要有一个比较，也就是拿客户端本地的配置信息和服务端的配置信息进行比较。一旦发现和服务端的配置有差异，就表示服务端配置有更新，于是把更新的配置拉到本地。<br>在这个过程中，有可能因为客户端配置比较多，导致比较的时间较长，使得配置同步较慢的问题。于是 Nacos 针对这个场景，做了两个方面的优化。</p><h4 id="减少网络通信的数据量"><a href="#减少网络通信的数据量" class="headerlink" title="减少网络通信的数据量"></a>减少网络通信的数据量</h4><p>客户端把需要进行比较的配置进行分片，<span style="background-color:#ff00ff">每一个分片大小是 3000</span>，也就是说，<span style="background-color:#ff00ff">每次最多拿 3000 个配置去 Nacos Server 端进行比较</span>。</p><h4 id="分阶段进行比较和更新"><a href="#分阶段进行比较和更新" class="headerlink" title="分阶段进行比较和更新"></a>分阶段进行比较和更新</h4><p>第一阶段，客户端把这 3000 个配置的 key 以及对应的 value 值的 md5 拼接成一个字符串，然后发送到 Nacos Server 端进行判断，服务端会逐个比较这些配置中 md5 不同的 key，把存在更新的 key 返回给客户端。<br>第二阶段，客户端拿到这些变更的 key，循环逐个去调用服务单获取这些 key 的 value 值。<br>这两个优化，核心目的是减少网络通信数据包的大小，把一次大的数据包通信拆分成了多次小的数据包通信。虽然会增加网络通信次数，但是对整体的性能有较大的提升。最后，再采用长连接这种方式，既减少了 pull 轮询次数，又利用了长连接的优势，很好的实现了配置的动态更新同步功能。</p><p>源码： <a href="http://blog.itpub.net/69908605/viewspace-2657617/">http://blog.itpub.net/69908605/viewspace-2657617/</a></p><h1 id="4-NacosRule-负载均衡"><a href="#4-NacosRule-负载均衡" class="headerlink" title="4. NacosRule 负载均衡"></a>4. NacosRule 负载均衡</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312152151.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312152305.png" alt="image.png"></p><h1 id="5-CP-or-AP"><a href="#5-CP-or-AP" class="headerlink" title="5. CP or AP"></a>5. CP or AP</h1><p>Nacos 集群默认采用 AP 方式，当集群中存在非临时实例时，采用 CP 模式；Eureka 采用 AP 方式</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230505084723.png" alt="image.png"></p><h1 id="6-环境隔离"><a href="#6-环境隔离" class="headerlink" title="6. 环境隔离"></a>6. 环境隔离</h1><h2 id="6-1-配置隔离"><a href="#6-1-配置隔离" class="headerlink" title="6.1. 配置隔离"></a>6.1. 配置隔离</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312154028.png" alt="image.png"></p><p><span style="background-color:#ff00ff">以命名空间区分各个微服务，以分组区分不同的开发环境</span></p><h2 id="6-2-注册隔离"><a href="#6-2-注册隔离" class="headerlink" title="6.2. 注册隔离"></a>6.2. 注册隔离</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312154415.png" alt="image.png"></p><h1 id="7-面试题"><a href="#7-面试题" class="headerlink" title="7. 面试题"></a>7. 面试题</h1><h2 id="7-1-Nacos-的服务注册表结构是怎样的⭐️🔴"><a href="#7-1-Nacos-的服务注册表结构是怎样的⭐️🔴" class="headerlink" title="7.1. Nacos 的服务注册表结构是怎样的⭐️🔴"></a>7.1. Nacos 的服务注册表结构是怎样的⭐️🔴</h2><p><span style="display:none">%%<br>▶21.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230228-2248%%</span><br>Nacos 采用了&#x3D;&#x3D;数据的分级存储模型&#x3D;&#x3D;，最外层是 Namespace，用来<span style="background-color:#00ff00">隔离环境</span>。然后是 Group，用来<span style="background-color:#ff00ff">对服务分组</span>。接下来就是服务（Service）了，&#x3D;&#x3D;一个服务包含多个实例，但是可能处于不同机房，因此 Service 下有多个集群（Cluster）&#x3D;&#x3D;，Cluster 下是不同的实例（Instance）。 ^i099uh</p><p>对应到 Java 代码中，Nacos 采用了一个多层的 Map 来表示。结构为 Map&lt;String, Map&lt;String, Service&gt;&gt;，其中最外层 Map 的 key 就是 namespaceId，值是一个 Map。内层 Map 的 key 是 <code>group 拼接 serviceName</code>，值是 <code>Service 对象</code>。Service 对象内部又是一个 Map，key 是 <code>集群名称</code>，值是 <code>Cluster 对象</code>。而 Cluster 对象内部维护了 <code>Instance 的集合</code>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228224515.png" alt="image-20210925215305446"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312173416.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312174333.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312174210.png" alt="image.png"></p><h2 id="7-2-Nacos-如何支撑阿里内部数十万服务注册压力？"><a href="#7-2-Nacos-如何支撑阿里内部数十万服务注册压力？" class="headerlink" title="7.2. Nacos 如何支撑阿里内部数十万服务注册压力？"></a>7.2. Nacos 如何支撑阿里内部数十万服务注册压力？</h2><p>Nacos 内部接收到注册的请求时，不会立即写数据，而是将服务注册的任务<span style="background-color:#ff00ff">放入一个阻塞队列</span>就立即响应给客户端。<span style="background-color:#00ff00">然后利用线程池读取阻塞队列中的任务，异步来完成实例更新</span>，从而提高并发写能力。</p><h2 id="7-3-Nacos-如何避免并发读写冲突问题？"><a href="#7-3-Nacos-如何避免并发读写冲突问题？" class="headerlink" title="7.3. Nacos 如何避免并发读写冲突问题？"></a>7.3. Nacos 如何避免并发读写冲突问题？</h2><p>Nacos 在更新实例列表时，会采用 <code>CopyOnWrite</code> 技术，首先将旧的实例列表拷贝一份，然后更新拷贝的实例列表，再用更新后的实例列表来覆盖旧的实例列表。<br>这样在更新的过程中，就不会对读实例列表的请求产生影响，也不会出现脏读问题了。</p><h2 id="7-4-Nacos-如何保证并发写的安全性？"><a href="#7-4-Nacos-如何保证并发写的安全性？" class="headerlink" title="7.4. Nacos 如何保证并发写的安全性？"></a>7.4. Nacos 如何保证并发写的安全性？</h2><p>首先，<span style="background-color:#ff00ff">在注册实例时，会对 service 加锁</span>，不同 service 之间本身就不存在并发写问题，互不影响。相同 service 时通过锁来互斥。并且，在更新实例列表时，是基于<span style="background-color:#ff00ff">异步的线程池来完成，而线程池的线程数量为 1</span>.</p><h2 id="7-5-Nacos-与-Eureka-的区别有哪些？"><a href="#7-5-Nacos-与-Eureka-的区别有哪些？" class="headerlink" title="7.5. Nacos 与 Eureka 的区别有哪些？"></a>7.5. Nacos 与 Eureka 的区别有哪些？</h2><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=23&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=23&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>Nacos 与 Eureka 有相同点，也有不同之处，可以从以下几点来描述：</p><ul><li><strong>接口方式</strong>：Nacos 与 Eureka 都对外暴露了 Rest 风格的 API 接口，用来实现服务注册、发现等功能</li><li><strong>实例类型</strong>：Nacos 的实例有永久和临时实例之分；而 Eureka 只支持临时实例</li><li><strong>健康检测</strong>：Nacos 对临时实例采用心跳模式检测，对永久实例采用主动请求来检测；Eureka 只支持心跳模式</li><li><strong>服务发现</strong>：Nacos 支持<span style="background-color:#ff00ff">定时拉取</span>和<span style="background-color:#ff00ff">订阅推送</span>两种模式；Eureka 只支持定时拉取模式</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312124719.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312124735.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312154934.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312155210.png" alt="image.png"></p><p><span style="background-color:#ff00ff">每 30 秒向 EurekaServer 发送心跳</span></p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/" title="面试专题-6、分布式组件">面试专题-6、分布式组件</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>经验专题-分布式微服务-1、重构</title>
      <link href="/2023/03/11/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%88%86%E5%B8%83%E5%BC%8F%E5%BE%AE%E6%9C%8D%E5%8A%A1-1%E3%80%81%E6%9C%8D%E5%8A%A1%E6%8B%86%E5%88%86/"/>
      <url>/2023/03/11/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E5%88%86%E5%B8%83%E5%BC%8F%E5%BE%AE%E6%9C%8D%E5%8A%A1-1%E3%80%81%E6%9C%8D%E5%8A%A1%E6%8B%86%E5%88%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-领域模型"><a href="#1-领域模型" class="headerlink" title="1. 领域模型"></a>1. 领域模型</h1><a href="/2023/05/08/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95-%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BB%BA%E6%A8%A1-DDD/" title="内功心法-领域驱动建模-DDD">内功心法-领域驱动建模-DDD</a><h1 id="2-微服务划分"><a href="#2-微服务划分" class="headerlink" title="2. 微服务划分"></a>2. 微服务划分</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312115042.png" alt="image.png"></p><h1 id="3-业务场景"><a href="#3-业务场景" class="headerlink" title="3. 业务场景"></a>3. 业务场景</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312123809.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312123929.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230312124207.png" alt="image.png"></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><h2 id="5-1-服务拆分"><a href="#5-1-服务拆分" class="headerlink" title="5.1. 服务拆分"></a>5.1. 服务拆分</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/Enterprise/0003-Architecture/000-微服务/服务拆分<br></code></pre></td></tr></table></figure><h2 id="5-2-黑马程序员-AA"><a href="#5-2-黑马程序员-AA" class="headerlink" title="5.2. 黑马程序员 AA"></a>5.2. 黑马程序员 AA</h2><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/" title="面试专题-6、分布式组件">面试专题-6、分布式组件</a><h2 id="5-3-慕课"><a href="#5-3-慕课" class="headerlink" title="5.3. 慕课"></a>5.3. 慕课</h2><p><a href="https://www.bilibili.com/video/BV1LJ41187ZH?p=56&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LJ41187ZH?p=56&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-12、GC-三色标记算法</title>
      <link href="/2023/03/10/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-12%E3%80%81GC-%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E7%AE%97%E6%B3%95/"/>
      <url>/2023/03/10/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-12%E3%80%81GC-%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E7%AE%97%E6%B3%95/</url>
      
        <content type="html"><![CDATA[<hr><p><a href="https://www.bilibili.com/video/BV1fi4y1U76z?p=21&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1fi4y1U76z?p=21&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://abcgao.com/archives/%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B3%95">https://abcgao.com/archives/%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B3%95</a></p><h1 id="1-概念定位"><a href="#1-概念定位" class="headerlink" title="1. 概念定位"></a>1. 概念定位</h1><p>垃圾回收算法主要有有两种，<strong>可达性分析算法(追踪式垃圾回收算法)</strong> 和 <strong>引用计数法</strong>（ Reference counting ）。**<a href="https://www.jiangguo.net/tag/w306.html">三色标记法</a> 与复制、标清、标整一样，也属于追踪式垃圾回收算法**。<span style="background-color:#ff00ff">三色标记法用来帮助完成并发场景下的垃圾标记判断工作。如果不需要并发能力，则没必要使用三色标记算法。三色标记算法解决了标记清除算法不能并发执行的问题</span></p><p><span style="background-color:#00ff00">标记 - 整理算法是并行垃圾回收算法，主打高吞吐，不涉及并发。而标记 - 清除算法追求低延迟，所以有并发需求。所以要引入三色标记法来完成并发的目标。</span>CMS 和 G1 都引入了三色标记法。</p><p>追踪式算法的核心思想是判断一个对象是否可触达，因为一旦对象不可触达就应该被 <a href="https://www.jiangguo.net/tag/73y5.html">GC</a> 回收了。那么如何得知一个对象是否可达呢？具体如下：</p><ol><li>找出所有的全局变量和当前函数栈里的变量，标记为可达。</li><li>从已标记的数据开始，进一步标记它们可访问的变量，以此类推，也就是我们经常提到的 <strong>传递闭包</strong>。</li></ol><p>标记法中有个算法叫 <strong>Mark-And-Sweep</strong> （<span style="background-color:#ff00ff">标记清除</span>），该算法是严格按照追踪式算法的思路实现的。首先为所有对象标记为 0，如果发现对象是可达的就会置为 1，一步步下去就会呈现一个类似树状的结果。待标记的步骤完成后，会将未被标记的对象统一清理，再次把所有的标记位设置成 0 方便下次清理。</p><p>这个算法最大的问题是 <a href="https://www.jiangguo.net/tag/73y5.html">GC</a> 执行期间需要把整个程序完全暂停，<span style="background-color:#ff00ff">不能异步进行 GC 操作</span>。因为在不同阶段标记清扫法的标志位 0 和 1 有不同的含义，那么新增的对象无论标记为什么都有可能意外删除这个对象。对实时性要求高的系统来说，这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决 <span style="background-color:#ff00ff">GC 运行时程序长时间挂起的问题</span>。</p><h1 id="2-标记过程"><a href="#2-标记过程" class="headerlink" title="2. 标记过程"></a>2. 标记过程</h1><p><a href="https://www.jiangguo.net/tag/w306.html">三色标记法</a> 就是为了解决这个问题而出现的。它将对象标记为黑、白、灰三种颜色，黑色对象为可达对象，应该保留，白色对象为不可达对象，应该被清除，灰色作为中间过度态。如下图：<br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230312-0648%%</span>❕ ^44bdy0</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310155214.gif" alt="三色标记图.gif"></p><p>假设现在有白、灰、黑三个集合（表示当前对象的颜色），其遍历访问过程为：</p><ol><li>初始时，所有对象都在 【白色集合】中；</li><li>将 <span style="background-color:#ff00ff">GC Roots 直接引用</span>到的对象挪到 【灰色集合】中；</li><li>从灰色集合中获取对象：<br> 3.1. 将本对象<span style="background-color:#ff00ff">直接引用到的</span>其他对象全部挪到 【灰色集合】中；<br> 3.2. 将本对象挪到 【黑色集合】里面。</li><li>重复步骤 3，直至【灰色集合】为空时结束。</li><li>结束后，仍在【白色集合】的对象即为 GC Roots 不可达，可以进行回收。</li></ol><h1 id="3-优点特性"><a href="#3-优点特性" class="headerlink" title="3. 优点特性"></a>3. 优点特性</h1><ol><li>因为 <a href="https://www.jiangguo.net/tag/w306.html">三色标记法</a> 多了一个白色的状态来存放不确定的对象，所以<span style="background-color:#00ff00">可以异步地执行</span>。</li></ol><blockquote><p>当然异步执行的代价是可能会造成一些遗漏，因为那些早先被标记为黑色的对象可能目前已经是不可达的了。所以三色标记法是一个 false negative（假阴性）的算法。</p></blockquote><ol start="2"><li>除了异步标记的优点，<a href="https://www.jiangguo.net/tag/w306.html">三色标记法</a> 掌握了更多当前内存的信息，因此<span style="background-color:#ff00ff">可以更加精确地按需调度</span>，而不用像标记清扫法那样只能定时执行。</li></ol><h1 id="4-存在问题"><a href="#4-存在问题" class="headerlink" title="4. 存在问题"></a>4. 存在问题</h1><p>当 Stop The World （以下简称 STW）时，对象间的引用是不会发生变化的，可以轻松完成标记。<br>而当<span style="background-color:#ff00ff">需要支持并发标记时</span>，即标记期间应用线程还在继续跑，对象间的引用可能发生变化，多标和漏标的情况就有可能发生。</p><h2 id="4-1-浮动垃圾-多标"><a href="#4-1-浮动垃圾-多标" class="headerlink" title="4.1. 浮动垃圾 (多标)"></a>4.1. 浮动垃圾 (多标)</h2><p>本来应该为白色是要回收的，而误标了颜色，使其本次 GC 阶段无法回收，即为多标。<span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230312-0655%%</span>❕ ^ctng6t<br>浮动垃圾 (多标)：将原本应该被清除的对象，误标记为存活对象。后果是垃圾回收不彻底，不过影响不大，可以在下个周期被回收；<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310155950.png" alt="image.png"></p><p>产生原因在于 GC 线程和应用线程的并发执行，比如 GC 线程先遍历到 E（已经变为灰色了），接下来应用执行了 objD.fieldE &#x3D; null 又导致 D -&gt; E 的引用断开<br><span style="background-color:#ff00ff">黑删灰</span><br>此刻之后，对象 E&#x2F;F&#x2F;G 是“应该”被回收的。然而因为 E 已经变为灰色了，其仍会被当作存活对象继续遍历下去。最终的结果是：这部分对象仍会被标记为存活，即本轮 GC 不会回收这部分内存。</p><p><span style="background-color:#ff00ff">浮动垃圾 -2 种来源</span> ^u9321a</p><ol><li>这部分本应该回收但是没有回收到的内存，类似<span style="background-color:#ff00ff">黑删灰</span>的情况，被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性，只是需要等到下一轮垃圾回收中才被清除。</li><li>另外，针对<span style="background-color:#ff00ff">并发标记开始后的新对象</span>，<span style="background-color:#00ff00">通常的做法是直接全部当成黑色，本轮不会进行清除。这部分对象期间可能会变为垃圾，这也算是浮动垃圾的一部分</span>。</li></ol><h2 id="4-2-对象消失-漏标"><a href="#4-2-对象消失-漏标" class="headerlink" title="4.2. 对象消失 (漏标)"></a>4.2. 对象消失 (漏标)</h2><p><span style="background-color:#ff0000">对象消失 (漏标)：将原本应该存活的对象，最终没有被标记为黑色，还是初始的白色，而被回收了。后果很严重，影响程序运行，是不可容忍的</span>。</p><p>假设 GC 线程已经遍历到 E（变为灰色了），此时应用线程先执行了：</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-comment">// Step1 读操作；对象E的成员变量fieldG的引用值，即对象G</span><br><span class="hljs-type">var</span> <span class="hljs-variable">G</span> <span class="hljs-operator">=</span> objE.fieldG; <br><span class="hljs-comment">// Step2 写操作；灰色E 断开引用 白色G</span><br>objE.fieldG = <span class="hljs-literal">null</span>;<br><span class="hljs-comment">// Step3 写操作；黑色D 引用 白色G</span><br>objD.fieldG = G;<br><br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310173445.png" alt="image.png"></p><p>E -&gt; G 断开，D 引用 G</p><p>此时切回 GC 线程继续跑，因为 E 已经没有对 G 的引用了，所以不会将 G 放到灰色集合；尽管因为 D 重新引用了 G，<span style="background-color:#ff0000">但因为 D 已经是黑色了，不会再重新做遍历处理</span>。<br>最终导致的结果是：G 会一直停留在白色集合中，最后被当作垃圾进行清除。这直接影响到了应用程序的正确性，是不可接受的。</p><h2 id="4-3-漏标解决方案"><a href="#4-3-漏标解决方案" class="headerlink" title="4.3. 漏标解决方案"></a>4.3. 漏标解决方案</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-0834%%</span>❕ ^kkx1uo</p><p>漏标必须要同时满足以下两个条件：<span style="background-color:#ff0000">黑增灰删</span></p><p><span style="background-color:#ff00ff">1. 赋值器插入了一条或者多条从黑色对象到白色对象的新引用；</span><br><span style="background-color:#ff00ff">2. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。</span></p><p>这两个条件必须全部满足，才会出现对象消失的问题。那么我们只需要对上面条件进行破坏，破坏其中的任意一个，都可以防止对象消失问题的产生。这样就产生了两种解决方案：</p><p>增量更新：Incremental Update<br>原始快照：Snapshot At The Beginning(SATB)</p><p>增量更新破坏的是第一个条件，<span style="background-color:#ff00ff">当黑色对象插入新的指向白色对象的引用时，就将这个新加入的引用记录下来</span>，待并发标记完成后，重新对这种新增的引用记录进行扫描；<br>原始快照破坏的是第二个条件，<span style="background-color:#ff00ff">当灰色对象要删除指向白色对象的引用关系时，就将白色对象的引用记录下来</span>，待并发标记完成后，对该记录进行重新扫描。</p><p>HotSpot 虚拟机中，不管是新增还是删除，这种记录的操作都是通过写屏障实现的。我们可以将写屏障理解为 JVM 对引用修改操作的一层 AOP，注意它与内存屏障是两个不同的东西。</p><p>对于读写屏障，以 Java HotSpot VM 为例，其并发标记时对漏标的处理方案如下：<br>CMS：写屏障 + 增量更新<br>G1：    写屏障 + SATB（原始快照）<br>ZGC： 读屏障</p><h3 id="4-3-1-增量更新"><a href="#4-3-1-增量更新" class="headerlink" title="4.3.1. 增量更新"></a>4.3.1. 增量更新</h3><p>增量更新破坏的是第一个条件，在新增一条引用时，将该记录保存。实际的实现中，通常是将引用相关的节点进行重新标记。考虑下图中的例子：</p><p><span style="background-color:#ff00ff">A、B、C 分别为黑、灰、白，起初 A→B、B→C，后来新增 A→C，而 B→C 断开</span><br>增量更新的方案是：将 A→C 这条新增的引用关系中 C 的引用保存起来，在并发扫描完成后，重新对这种新增的引用记录进行扫描。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311084002.png" alt="image.png"></p><p>上面就是一次引用关系修改导致的对象消失问题。增量更新进行的处理，就是<span style="background-color:#ff00ff">将由 A 到 C 的这条新增的引用关系进行保存。</span>首先看下 Dijkstra 等人提出的方式：</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">write_barrier(obj, field, newobj) &#123;<br>    <span class="hljs-keyword">if</span> (newobj.mark == FALSE) &#123;<br>        newobj.mark = TRUE;<br>        push(newobj, $mark_stack);<br>    &#125;<br>    *field = newobj;<br>&#125;<br></code></pre></td></tr></table></figure><p>如果新引用的对象 newobj 没有被标记，那么就将其标记后堆到标记栈里。换句话说，<span style="background-color:#ff00ff">如果 newobj 是白色对象，就把它涂成灰色</span>。这样操作后的结果如下图所示：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311084136.png" alt="image.png"><br>此时 C 被<span style="background-color:#ff00ff">涂成了灰色，它将在后续被重新扫描，阻止了对象消失。</span></p><p>Steele 提出了一种更严厉的方法，它相比 Dijkstra 的方法，可以减少错误标记的对象数量。就是直接把 A 涂成灰色，从根上再捋一遍</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">write_barrier(obj, field, newobj) &#123;<br>    <span class="hljs-keyword">if</span>($gc_phase == GC_MARK &amp;&amp; obj.mark == TRUE &amp;&amp; newobj.mark == FALSE) &#123;<br>        obj.mark = FALSE;<br>        push(obj, $mark_stack);<br>    &#125;<br>    *field = newobj;<br>&#125;<br></code></pre></td></tr></table></figure><p>如果在标记过程中发出引用的对象是黑色对象，且新的引用的目标对象为灰色或白色，那么我们就把发出引用的对象涂成灰色。这样操作后的结果如下图：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311084255.png" alt="image.png"><br>此时 A 由原来的黑色变成了灰色，将在后续被重新扫描。</p><h3 id="4-3-2-原始快照"><a href="#4-3-2-原始快照" class="headerlink" title="4.3.2. 原始快照"></a>4.3.2. 原始快照</h3><p>原始快照破坏的是第二个条件，当灰色对象要删除指向白色对象的引用关系时，就<span style="background-color:#ff00ff">将这个要删除的引用记录下来</span>，并发扫描结束后，在将这些记录重新扫描一次。</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">write_barrier(obj, field, newobj) &#123;<br>    oldobj = *field;<br>    <span class="hljs-keyword">if</span>(gc_phase == GC_MARK &amp;&amp; oldobj.mark == FALSE) &#123;<br>        oldobj.mark = TRUE;<br>        push(oldobj, $mark_stack);<br>    &#125;<br>    *field = newobj;<br>&#125;<br></code></pre></td></tr></table></figure><p>当 GC 进入到标记阶段且 oldobj 没被标记时，则标记 oldobj，并将其记录。也就是说，在标记阶段中如果指针更新前引用的 oldobj 是白色对象，就将其涂成灰色。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311084347.png" alt="image.png"><br>上图依旧是对象消失的例子。a 到 b 中，产生了一条由 A 到 C 的引用关系，这里并没有像增量更新那样将 A 或者 C 标为灰色，相反原始快照中允许出现从黑色指向白色的引用。而在从 b 到 c 中，删除了由 B 到 C 的引用关系。这时候就需要进行处理，将 C 涂为灰色。</p><h3 id="4-3-3-读写屏障"><a href="#4-3-3-读写屏障" class="headerlink" title="4.3.3. 读写屏障"></a>4.3.3. 读写屏障</h3><p>下面我们再来看看防止对象的漏标具体做法，不难分析，漏标只有同时满足以下两个条件时才会发生：</p><p>条件一：灰色对象断开了白色对象的引用（直接或间接的引用）；即灰色对象原来成员变量的引用发生了变化。<br>条件二：黑色对象重新引用了该白色对象；即黑色对象成员变量增加了新的引用。<br>从代码的角度看：</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-type">var</span> <span class="hljs-variable">G</span> <span class="hljs-operator">=</span> objE.fieldG; <span class="hljs-comment">// 1.读</span><br>objE.fieldG = <span class="hljs-literal">null</span>;  <span class="hljs-comment">// 2.写</span><br>objD.fieldG = G;     <span class="hljs-comment">// 3.写</span><br></code></pre></td></tr></table></figure><ol><li>读取对象 E 的成员变量 fieldG 的引用值，即对象 G；</li><li>对象 E 往其成员变量 fieldG，写入 null 值。</li><li>对象 D 往其成员变量 fieldG，写入对象 G ；</li></ol><p>我们只要在上面这三步中的任意一步中做一些“手脚”，将对象 G 记录起来，然后作为灰色对象再进行遍历即可。比如放到一个特定的集合，等初始的 GC Roots 遍历完（并发标记），该集合的对象遍历即可（重新标记）。</p><p>注意: 重新标记通常是需要 STW 的，因为应用程序一直在跑的话，该集合可能会一直增加新的对象，导致永远都跑不完。当然，并发标记期间也可以将该集合中的大部分先跑了，从而缩短重新标记 STW 的时间，这个是优化问题了。</p><p>读屏障则是拦截第一步，写屏障用于拦截第二和第三步。它们的拦截的目的很简单：<span style="background-color:#ff00ff">就是在读写前后，将对象 G 给记录下来</span>。</p><h4 id="4-3-3-1-读屏障（Load-Barrier）"><a href="#4-3-3-1-读屏障（Load-Barrier）" class="headerlink" title="4.3.3.1. 读屏障（Load Barrier）"></a>4.3.3.1. 读屏障（Load Barrier）</h4><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">oop <span class="hljs-title function_">oop_field_load</span><span class="hljs-params">(oop* field)</span> &#123;<br>     pre_load_barrier(field); <span class="hljs-comment">// 读屏障-读取前操作</span><br>     <span class="hljs-keyword">return</span> *field;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>读屏障是直接针对第一步：var G &#x3D; objE.fieldG; ，当读取成员变量时，一律记录下来：</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">void</span> <span class="hljs-title function_">pre_load_barrier</span><span class="hljs-params">(oop* field, oop old_value)</span> &#123;  <br>     <span class="hljs-keyword">if</span>($gc_phase == GC_CONCURRENT_MARK &amp;&amp; !isMarkd(field)) &#123;<br>          <span class="hljs-type">oop</span> <span class="hljs-variable">old_value</span> <span class="hljs-operator">=</span> *field;<br>          remark_set.add(old_value); <span class="hljs-comment">// 记录读取到的对象</span><br>     &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>这种做法是保守的，但也是安全的。因为条件二中【黑色对象重新引用了该白色对象】，重新引用的前提是：得获取到该白色对象，此时已经读屏障就发挥作用了。</p><h4 id="4-3-3-2-写屏障（Store-Barrier）"><a href="#4-3-3-2-写屏障（Store-Barrier）" class="headerlink" title="4.3.3.2. 写屏障（Store Barrier）"></a>4.3.3.2. 写屏障（Store Barrier）</h4><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> field 某对象的成员变量，如 D.fieldG</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> new_value 新值，如 null</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">oop_field_store</span><span class="hljs-params">(oop* field, oop new_value)</span> &#123; <br>     *field = new_value; <span class="hljs-comment">// 赋值操作</span><br>&#125; <br><br></code></pre></td></tr></table></figure><p>所谓的写屏障，其实就是指在赋值操作前后，加入一些处理（类似于 AOP 的概念）：</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">void</span> <span class="hljs-title function_">oop_field_store</span><span class="hljs-params">(oop* field, oop new_value)</span> &#123;  <br>      pre_write_barrier(field); <span class="hljs-comment">// 写屏障-写前操作</span><br>      *field = new_value; <br>      post_write_barrier(field, value);  <span class="hljs-comment">// 写屏障-写后操作</span><br>&#125;<br><br></code></pre></td></tr></table></figure><h3 id="4-3-4-解决方案"><a href="#4-3-4-解决方案" class="headerlink" title="4.3.4. 解决方案"></a>4.3.4. 解决方案</h3><h4 id="4-3-4-1-写屏障-SATB"><a href="#4-3-4-1-写屏障-SATB" class="headerlink" title="4.3.4.1. 写屏障 + SATB"></a>4.3.4.1. 写屏障 + SATB</h4><h4 id="4-3-4-2-写屏障-增量更新"><a href="#4-3-4-2-写屏障-增量更新" class="headerlink" title="4.3.4.2. 写屏障 + 增量更新"></a>4.3.4.2. 写屏障 + 增量更新</h4><h2 id="4-4-其他补充"><a href="#4-4-其他补充" class="headerlink" title="4.4. 其他补充"></a>4.4. 其他补充</h2><h3 id="4-4-1-为什么-G1-用-SATB？CMS-用增量更新？"><a href="#4-4-1-为什么-G1-用-SATB？CMS-用增量更新？" class="headerlink" title="4.4.1. 为什么 G1 用 SATB？CMS 用增量更新？"></a>4.4.1. 为什么 G1 用 SATB？CMS 用增量更新？</h3><p>增量更新：黑色对象新增一条指向白色对象的引用，那么要进行深入扫描白色对象及它的引用对象。</p><p>原始快照：灰色对象删除了一条指向白色对象的引用，实际上就产生了浮动垃圾，好处是不需要像 CMS 那样 remark，再走一遍 root trace 这种相当耗时的流程。</p><h3 id="4-4-2-ZGC-只使用了读屏障"><a href="#4-4-2-ZGC-只使用了读屏障" class="headerlink" title="4.4.2. ZGC 只使用了读屏障"></a>4.4.2. ZGC 只使用了读屏障</h3><p>而 ZGC 有一个标志性的设计是它采用的染色指针技术，染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量，设置内存屏障，尤其是写屏障的目的通常是为了记录对象引用的变动情况，如果讲这些信息直接维护在指针中，显然可以省去一些专门的记录操作。而 ZGC 没有使用写屏障，只使用了读屏障，显然对性能大有裨益的。</p><h1 id="5-记忆集-写屏障-卡表"><a href="#5-记忆集-写屏障-卡表" class="headerlink" title="5. 记忆集 - 写屏障 - 卡表"></a>5. 记忆集 - 写屏障 - 卡表</h1><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1058%%</span>❕ ^1uy99j</p><h2 id="5-1-记忆集"><a href="#5-1-记忆集" class="headerlink" title="5.1. 记忆集"></a>5.1. 记忆集</h2><blockquote><p>一个对象被不同区域引用的问题<br>一个 Region 不可能是孤立的，一个 Region 中的对象可能被其他任意 Region 中对象引用，判断对象存活时，是否需要扫描整个 Java 堆才能保证准确<br>在其他的分代收集器，也存在这样的问题<span style="background-color:#ff00ff">（而 G1 更突出）回收新生代也不得不同时扫描老年代</span><br>这样的话会降低 MinorGC 的效率；</p></blockquote><p>记忆集也叫 rememberSet，垃圾收集器<span style="background-color:#ff0000">在新生代中</span>建立了记忆集这样的数据结构，<span style="background-color:#00ff00">用来避免把整个老年代加入到 GC ROOTS 的扫描范围中</span>。对于记忆集来说，我们可以理解为他是一个抽象类，那么具体实现它的方法将由子类去完成。这里我们简单列举一下实现记忆集的三种方式：</p><p>1.字长精度<br>2.对象精度<br>3.卡精度（卡表）</p><h2 id="5-2-写屏障"><a href="#5-2-写屏障" class="headerlink" title="5.2. 写屏障"></a>5.2. 写屏障</h2><p><span style="display:none">%%<br>▶14.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230312-0917%%</span>❕ ^l180q9</p><p>每次 Reference 类型数据写操作时，都会产生一个 <strong>Write Barrier</strong> 暂时中断操作；然后检查将要写入的<span style="background-color:#ff00ff">引用指向的对象是否和该 Reference 类型数据在不同的 Region</span>（其他收集器：检查老年代对象是否引用了新生代对象）；<br>如果不同，<span style="background-color:#ff00ff">通过 Card Table(卡表) </span>把相关引用信息记录到<span style="background-color:#00ff00">引用指向对象的所在 Region 对应的 Remembered Set 中</span>；<br>当进行垃圾收集时，在 GC 根节点的枚举范围加入 Remembered Set；就可以保证不进行全局扫描，也不会有遗漏。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307215115.png" alt="image.png"></p><h2 id="5-3-卡表"><a href="#5-3-卡表" class="headerlink" title="5.3. 卡表"></a>5.3. 卡表</h2><p><span style="display:none">%%<br>▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230312-0917%%</span>❕ ^qpslqo</p><p>卡表 (Card Table) 是一种对记忆集的具体实现。主要定义了记忆集的记录精度、与堆内存的映射关系等。卡表中的每一个元素都对应着一块特定大小的内存块，这个内存块我们称之为卡页（card page），当存在跨带引用的时候，它会将卡页标记为 dirty。那么 JVM 对于卡页的维护也是通过写屏障的方式，这也就是为什么刚刚我们跟进写屏障操作到最后会发现它会对卡表进行一系列的操作。</p><h2 id="5-4-关系"><a href="#5-4-关系" class="headerlink" title="5.4. 关系"></a>5.4. 关系</h2><p>逻辑上说每个 Region 都有一个 RSet，<span style="background-color:#ff00ff">RSet 记录了其他 Region 中的对象引用本 Region 中对象的关系</span>，属于 points-into 结构（<span style="background-color:#ff00ff">谁引用了我的对象</span>）。<br>而 Card Table 则是一种 points-out（我引用了谁的对象）的结构，每个 Card 覆盖一定范围的 Heap（一般为 512Bytes）。<br>G1 的 RSet 是在 Card Table 的基础上实现的：每个 Region 会记录下别的 Region 有指向自己的指针，并标记这些指针分别在哪些 Card 的范围内。 这个 RSet 其实是一个 Hash Table，Key 是别的 Region 的起始地址，Value 是一个集合，里面的元素是 Card Table 的 Index。</p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p><a href="https://tech.meituan.com/2016/09/23/g1.html">https://tech.meituan.com/2016/09/23/g1.html</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-11、7大垃圾回收器</title>
      <link href="/2023/03/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-11%E3%80%817%E5%A4%A7%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/"/>
      <url>/2023/03/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-11%E3%80%817%E5%A4%A7%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/</url>
      
        <content type="html"><![CDATA[<hr><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307114308.png" alt="image.png"></p><h1 id="1-默认回收器"><a href="#1-默认回收器" class="headerlink" title="1. 默认回收器"></a>1. 默认回收器</h1><p>● 1999 年随 JDK1.3.1 一起来的是串行方式的 serialGc，它是第一款 GC。ParNew 垃圾收集器是 Serial 收集器的多线程版本<br>● 2002 年 2 月 26 日，Parallel GC 和 Concurrent Mark Sweep GC 跟随 JDK1.4.2 一起发布·<br>● Parallel GC 在 JDK6 之后成为 HotSpot 默认 GC。<br>● <span style="background-color:#00ff00">2012 年，在 JDK1.7u4 版本中，G1 可用。</span><br>● <span style="background-color:#ff00ff">2017 年，JDK9 中 G1 变成默认的垃圾收集器，以替代 CMS。</span><br>● 2018 年 3 月，JDK10 中 G1 垃圾回收器的并行完整垃圾回收，实现并行性来改善最坏情况下的延迟。<br>● 2018 年 9 月，JDK11 发布。引入 Epsilon 垃圾回收器，又被称为 “No-Op(无操作)“ 回收器。同时，引入 ZGC：可伸缩的低延迟垃圾回收器（Experimental）<br>● 2019 年 3 月，JDK12 发布。增强 G1，自动返回未用堆内存给操作系统。同时，引入 Shenandoah GC：低停顿时间的 GC（Experimental）。·<br>● 2019 年 9 月，JDK13 发布。增强 ZGC，自动返回未用堆内存给操作系统。<br>● 2020 年 3 月，JDK14 发布。<span style="background-color:#ff0000">删除 CMS 垃圾回收器</span>。扩展 ZGC 在 macos 和 Windows 上的应用</p><h2 id="1-1-发展历史"><a href="#1-1-发展历史" class="headerlink" title="1.1. 发展历史"></a>1.1. 发展历史</h2><p>UseParallelGC: 并行垃圾收集器</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173944.jpg" alt="image-20200410104929436"></p><h2 id="1-2-回收器分类"><a href="#1-2-回收器分类" class="headerlink" title="1.2. 回收器分类"></a>1.2. 回收器分类</h2><h3 id="1-2-1-按线程数分"><a href="#1-2-1-按线程数分" class="headerlink" title="1.2.1. 按线程数分"></a>1.2.1. 按线程数分</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307150020.png" alt="image.png"></p><p>串行回收指的是在同一时间段内<span style="background-color:#ff00ff">只允许有一个 CPU 用于执行垃圾回收操作</span>，此时工作线程被暂停，直至垃圾收集工作结束。比如早期的 Serial GC 和 Serial Old GC。</p><ul><li>在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合，串行回收器的性能表现可以超过并行回收器和并发回收器。所以，串行回收默认被应用在客户端的 Client 模式下的 JVM 中</li><li>在并发能力比较强的 CPU 上，并行回收器产生的停顿时间要短于串行回收器。</li></ul><p>和串行回收相反，并行收集可以运用多个 CPU 同时执行垃圾回收，因此提升了应用的吞吐量，不过并行回收仍然与串行回收一样，采用独占式，使用了“Stop-the-World”机制。比如 ParNew、Parallel Scavenge GC、Parallel Old GC</p><h3 id="1-2-2-按工作模式分"><a href="#1-2-2-按工作模式分" class="headerlink" title="1.2.2. 按工作模式分"></a>1.2.2. 按工作模式分</h3><p>可以分为并发式垃圾回收器和独占式垃圾回收器。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307150311.png" alt="image.png"></p><ul><li>并发式垃圾回收器与应用程序线程交替工作，以尽可能减少应用程序的停顿时间。</li><li>独占式垃圾回收器（Stop the world）一旦运行，就停止应用程序中的所有用户线程，直到垃圾回收过程完全结束</li></ul><h3 id="1-2-3-按碎片处理方式分"><a href="#1-2-3-按碎片处理方式分" class="headerlink" title="1.2.3. 按碎片处理方式分"></a>1.2.3. 按碎片处理方式分</h3><p>可分为压缩式垃圾回收器和非压缩式垃圾回收器。</p><ul><li>压缩式垃圾回收器会在回收完成后，对存活对象进行压缩整理，消除回收后的碎片。</li><li>非压缩式的垃圾回收器不进行这步操作。</li></ul><h3 id="1-2-4-按工作内存区间分"><a href="#1-2-4-按工作内存区间分" class="headerlink" title="1.2.4. 按工作内存区间分"></a>1.2.4. 按工作内存区间分</h3><p>可分为年轻代垃圾回收器和老年代垃圾回收器。</p><h3 id="1-2-5-按分代模型分"><a href="#1-2-5-按分代模型分" class="headerlink" title="1.2.5. 按分代模型分"></a>1.2.5. 按分代模型分</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309191744.png" alt="image.png"></p><h2 id="1-3-查看方法"><a href="#1-3-查看方法" class="headerlink" title="1.3. 查看方法"></a>1.3. 查看方法</h2><p>java -XX：+PrintCommandLineFlags -version</p><h2 id="1-4-组合方式⭐️🔴"><a href="#1-4-组合方式⭐️🔴" class="headerlink" title="1.4. 组合方式⭐️🔴"></a>1.4. 组合方式⭐️🔴</h2><p><span style="display:none">%%<br>▶20.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1729%%</span>❕ ^m3ftuh</p><p><a href="https://www.bilibili.com/video/av75859780?p=984">https://www.bilibili.com/video/av75859780?p=984</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173954.jpg" alt="image-20200324205304809"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307114308.png" alt="image.png"></p><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【JDK8 的默认垃圾回收器是 Parallel Scavenge GC + Parallel Old GC】◼️⭐️-point-20230307-1144%%</span>❕ ^w7k0dt</p><p><span style="background-color:#ff00ff">由于 CMS 在 JDK14 后被移除，所以可用组合只剩蓝色①和③了</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305163450.png" alt="image.png"></p><p>搭配疑问❓CMS 为何不能跟 Parallel Scavenge GC 进行搭配<br><span style="background-color:#ff00ff">是因为 Parallel Scavenge 没有使用 HotSpot 原本通用的 GC 框架，所以不能跟使用了通用 GC 框架的 CMS 搭配使用。</span></p><h1 id="2-Serial"><a href="#2-Serial" class="headerlink" title="2. Serial"></a>2. Serial</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305205633.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174105.jpg" alt="image-20200324211250674"></p><p>Serial 收集器采用<span style="background-color:#00ff00">复制算法</span>、串行回收和 “stop-the-World” 机制的方式执行内存回收。<br>除了年轻代之外，Serial 收集器还提供用于执行老年代垃圾收集的 Serial Old 收集器。Serial Old 收集器同样也采用了串行回收和 “Stop the World” 机制，只不过内存回收算法使用的是<span style="background-color:#00ff00">标记 - 压缩算法</span>。</p><ul><li>Serial old 是运行在 Client 模式下默认的老年代的垃圾回收器</li><li>Serial 0ld 在 Server 模式下主要有两个用途：<br>  ① 与新生代的 Parallel scavenge 配合使用<br>  ② 作为老年代 CMS 收集器的后备垃圾收集方案</li></ul><h1 id="3-Parnew"><a href="#3-Parnew" class="headerlink" title="3. Parnew"></a>3. Parnew</h1><p>如果说 Serial GC 是年轻代中的单线程垃圾收集器，那么 ParNew 收集器则是 Serial 收集器的多线程版本。Par 是 Parallel 的缩写，<span style="background-color:#ff00ff">New：只能处理的是新生代</span>。可以组合 CMS 和 SerialOld，CMS 在 JDK14 中已经删除，而 SerialOld 适合单核场景，所以 ParNew 基本边缘化了。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174114.jpg" alt="image-20200324211645935"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174123.jpg" alt="image-20200324212106542"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305205403.png" alt="image.png"></p><h1 id="4-Parallel-Scavenge-【高吞吐】"><a href="#4-Parallel-Scavenge-【高吞吐】" class="headerlink" title="4. Parallel Scavenge- 【高吞吐】"></a>4. Parallel Scavenge- 【高吞吐】</h1><p>高性能场景下，是串行收集器在新生代和老年代的并行化。所有新生代都是复制算法，PreallelScavenge 也不例外。老年代配合 ParallelOldGC，也可以配合 SerialOldGC，2 个都是标记整理算法。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230305205331.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174141.jpg" alt="image-20200324213249966"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208145803.png"></p><p>HotSpot 的年轻代中除了拥有 ParNew 收集器是基于并行回收的以外，Parallel Scavenge 收集器同样也采用了复制算法、并行回收和 “Stop the World” 机制。</p><p>那么 Parallel 收集器的出现是否多此一举？</p><p>● 和 ParNew 收集器不同，ParallelScavenge 收集器的目标则是达到<span style="background-color:#ff00ff">一个可控制的吞吐量（Throughput），它也被称为吞吐量优先的垃圾收集器。</span><br>● 自适应调节策略也是 Parallel Scavenge 与 ParNew 一个重要区别。</p><p>高吞吐量则可以<span style="background-color:#00ff00">高效率地利用 CPU 时间，尽快完成程序的运算任务</span>，主要适合在后台运算而不需要太多交互的任务。因此，常见在服务器环境中使用。例如，那些<span style="background-color:#00ff00">执行批量处理、订单处理、工资支付、科学计算的应用程序</span>。</p><p>Parallel 收集器在 JDK1.6 时提供了用于执行老年代垃圾收集的 Parallel Old 收集器，用来代替老年代的 Serial Old 收集器。<br>Parallel Old 收集器采用了标记 - 压缩算法，但同样也是基于并行回收和 “Stop-the-World” 机制。</p><h2 id="4-1-参数配置"><a href="#4-1-参数配置" class="headerlink" title="4.1. 参数配置"></a>4.1. 参数配置</h2><p><span style="display:none">%%<br>▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-0712%%</span>❕ ^e8ztve</p><p>●  -XX:+UseParallelGC 手动指定年轻代使用 Parallel 并行收集器执行内存回收任务。<br>●  -XX:+UseParallelOldGC 手动指定老年代都是使用并行回收收集器。<br>  ○ 分别适用于新生代和老年代。默认 jdk8 是开启的。<br>  ○ 上面两个参数，默认开启一个，另一个也会被开启。（互相激活）<br>●  <code>-XX:ParallelGCThreads</code> <span style="background-color:#ff00ff">设置年轻代并行收集器的线程数</span>。一般地，最好与 CPU 数量相等，以避免过多的线程数影响垃圾收集性能。</p><p>$ParallelGCThreads &#x3D;  \begin{cases}  CPU_Count &amp; \text (CPU_Count &lt;&#x3D; 8) \  3 + (5 * CPU＿Count &#x2F; 8) &amp; \text (CPU_Count &gt; 8)  \end{cases}$</p><p><span style="background-color:#00ff00">大于 8，比如 12 个，得出结果为 10 个</span>  <span style="display:none">%%<br>▶8.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230308-0729%%</span>❕ ^qrcauu</p><p>●  <code>-XX:MaxGCPauseMillis</code> 设置垃圾收集器最大停顿时间（即 STW 的时间）。单位是毫秒。<br>  ○ 为了尽可能地把停顿时间控制在 MaxGCPauseMills 以内，收集器在工作时会调整 Java 堆大小或者其他一些参数。<br>  ○ 对于用户来讲，停顿时间越短体验越好。但是在服务器端，我们注重高并发，整体的吞吐量。所以服务器端适合 Parallel，进行控制。<br>  ○ <span style="background-color:#ff0000">-XX:MaxGCPauseMillis 使用需谨慎。</span><br>●  <code>-XX:GCTimeRatio</code> 垃圾收集时间占总时间的比例（&#x3D;1&#x2F;（N+1））。用于衡量吞吐量的大小。<br>  ○ 取值范围（0, 100）。<span style="background-color:#ff00ff">默认值 99，也就是垃圾回收时间不超过 1%。</span><br>  ○ 与前一个 -XX:MaxGCPauseMillis 参数有一定矛盾性。暂停时间越长，Radio 参数就容易超过设定的比例。<br>●  <code>-XX:+UseAdaptivesizePolicy</code> 设置 Parallel Scavenge 收集器具有自适应调节策略<br>  ○ 在这种模式下，年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会被自动调整，已达到在堆大小、吞吐量和停顿时间之间的平衡点。<br>  ○ 在手动调优比较困难的场合，可以直接使用这种自适应的方式，仅指定虚拟机的最大堆、目标的吞吐量（GCTimeRatio）和停顿时间（MaxGCPauseMills），让虚拟机自己完成调优工作。</p><h1 id="5-CMS-【低延迟】"><a href="#5-CMS-【低延迟】" class="headerlink" title="5. CMS- 【低延迟】"></a>5. CMS- 【低延迟】</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208153949.png"></p><h2 id="5-1-特点"><a href="#5-1-特点" class="headerlink" title="5.1. 特点"></a>5.1. 特点</h2><p>在 JDK1.5 时期，Hotspot 推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器：CMS（Concurrent-Mark-Sweep）收集器，这款收集器是 HotSpot 虚拟机中第一款<span style="background-color:#ff00ff">真正意义上的并发收集器</span>，<span style="background-color:#ff00ff">它第一次实现了让垃圾收集线程与用户线程同时工作。</span></p><p>CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短（低延迟）就越适合与用户交互的程序，良好的响应速度能提升用户体验。</p><p>● 目前很大一部分的 Java 应用集中在互联网站或者 B&#x2F;S 系统的服务端上，这类应用尤其重视服务的响应速度，希望系统停顿时间最短，以给用户带来较好的体验。CMS 收集器就非常符合这类应用的需求。</p><p>CMS 的垃圾收集算法采用<span style="background-color:#ff00ff">标记 - 清除算法</span>，并且也会 “Stop-the-World”</p><p>不幸的是，CMS 作为老年代的收集器，却无法与 JDK1.4.0 中已经存在的新生代收集器 Parallel Scavenge 配合工作，所以在 JDK1.5 中使用 CMS 来收集老年代的时候，新生代只能选择 ParNew 或者 Serial 收集器中的一个。</p><p>在 G1 出现之前，CMS 使用还是非常广泛的。一直到今天，仍然有很多系统使用 CMS GC。</p><h2 id="5-2-回收逻辑"><a href="#5-2-回收逻辑" class="headerlink" title="5.2. 回收逻辑"></a>5.2. 回收逻辑</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174200.jpg" alt="image-20200324221224514"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174209.jpg" alt="image-20200324221611386"></p><p>CMS 整个过程比之前的收集器要复杂，整个过程分为 4 个主要阶段，即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段</p><p>● 初始标记（Initial-Mark）阶段：在这个阶段中，程序中所有的工作线程都将<span style="background-color:#ffff00">会因为“Stop-the-World”机制而出现短暂的暂停</span>，这个阶段的主要任务仅仅只是标记出 <span style="background-color:#ff0000">GCRoots 能直接关联到的对象</span>。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小，<span style="background-color:#ffff00">所以这里的速度非常快</span>。<br>● 并发标记（Concurrent-Mark）阶段：从 GC Roots 的直接关联对象开始遍历整个对象图的过程，这个过程耗时较长但是<span style="background-color:#ff00ff">不需要停顿用户线程</span>，可以与垃圾收集线程一起并发运行。<br>● 重新标记（Remark）阶段：由于在并发标记阶段中，程序的工作线程会和垃圾收集线程同时运行或者交叉运行，因此<span style="background-color:#00ff00">为了修正并发标记期间，因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录</span>，<span style="background-color:#ffff00">这个阶段的“Stop-the-World”停顿时间通常会比初始标记阶段稍长一些，但也远比并发标记阶段的时间短。</span><br>● 并发清除（Concurrent-Sweep）阶段：此阶段清理删除掉标记阶段判断的已经死亡的对象，释放内存空间。由于不需要移动存活对象，所以这个阶段也是可以与用户线程同时并发的</p><h2 id="5-3-优缺点"><a href="#5-3-优缺点" class="headerlink" title="5.3. 优缺点"></a>5.3. 优缺点</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-0559%%</span>❕ ^d6te2p</p><h3 id="5-3-1-优点"><a href="#5-3-1-优点" class="headerlink" title="5.3.1. 优点"></a>5.3.1. 优点</h3><p>停顿短，<span style="background-color:#00ff00">由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作，所以整体的回收是低停顿的。</span></p><p>尽管 CMS 收集器采用的是并发回收（非独占式），<span style="background-color:#ff0000">但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop-the-World”机制暂停程序中的工作线程，不过暂停时间并不会太长，因此可以说明目前所有的垃圾收集器都做不到完全不需要“stop-the-World”，只是尽可能地缩短暂停时间。</span></p><p>另外，由于在垃圾收集阶段用户线程没有中断，所以在 CMS 回收过程中，<span style="background-color:#ff00ff">还应该确保应用程序用户线程有足够的内存可用</span>。因此，CMS 收集器<span style="background-color:#00ff00">不能像其他收集器那样等到老年代几乎完全被填满了再进行收集，而是当堆内存使用率达到某一阈值时，便开始进行回收，以确保应用程序在 CMS 工作过程中依然有足够的空间支持应用程序运行。</span> <span style="background-color:#ff0000">要是 CMS 运行期间预留的内存无法满足程序需要，就会出现一次“Concurrent Mode Failure” 失败，这时虚拟机将启动后备预案：临时启用 Serial Old 收集器来重新进行老年代的垃圾收集，这样停顿时间就很长了。</span></p><h3 id="5-3-2-缺点"><a href="#5-3-2-缺点" class="headerlink" title="5.3.2. 缺点"></a>5.3.2. 缺点</h3><p><span style="display:none">%%<br>▶70.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230307-1623%%</span>❕</p><p>● <span style="background-color:#ff00ff">会产生内存碎片</span>，导致并发清除后，用户线程可用的空间不足。在无法分配大对象的情况下，不得不提前触发 FullGC。<br>● CMS 收集器对 CPU 资源非常敏感。在并发阶段，它虽然不会导致用户停顿，但是会因为占用了一部分线程而导致应用程序变慢，<span style="background-color:#ff00ff">总吞吐量会降低</span>。<br>● <span style="background-color:#ff00ff">CMS 收集器无法处理浮动垃圾</span>。可能出现“Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的，那么<span style="background-color:#ff0000">在并发标记阶段如果产生新的垃圾对象，CMS 将无法对这些垃圾对象进行标记，最终会导致这些新产生的垃圾对象没有被及时回收，从而只能在下一次执行 GC 时释放这些之前未被回收的内存空间</span>。❕<span style="display:none">%%<br>▶69.🏡⭐️◼️浮动垃圾 ?🔜MSTM📝 在并发阶段产生的垃圾，在重新标记阶段无法识别出来，只能等到下一次 GC 才能被清除◼️⭐️-point-20230307-1548%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174219.jpg" alt="image-20200324221406359"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174231.jpg" alt="image-20200324221443257"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208153827.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208160301.png"></p><h2 id="5-4-参数设置⭐️🔴"><a href="#5-4-参数设置⭐️🔴" class="headerlink" title="5.4. 参数设置⭐️🔴"></a>5.4. 参数设置⭐️🔴</h2><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230427-1826%%</span>❕ ^yfbdrc</p><p>●  <code>-XX:+UseConcMarkSweepGC </code> 手动指定使用 CMS 收集器执行内存回收任务。<br>开启该参数后会自动将 -xx:+UseParNewGC 打开。即：<span style="background-color:#ff00ff">ParNew（Young 区用）+CMS（Old 区用）+ Serial Old 的组合</span>。<br>●  <code>-XX:CMSInitiatingOccupancyFraction</code> <span style="background-color:#ff00ff">设置堆内存使用率的阈值，一旦达到该阈值，便开始进行回收。 </span><br><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1110%%</span>❕ ^eklrte<br>  ○ JDK5 及以前版本的默认值为 68，即<span style="background-color:#ff0000">当老年代的空间使用率</span>达到<span style="background-color:#ff00ff">68%</span>时，会执行一次 CMS 回收。JDK6 及以上版本默认值为<span style="background-color:#ff00ff">92%</span><br>  ○ <span style="background-color:#00ff00">如果内存增长缓慢，则可以设置一个稍大的值，大的阀值可以有效降低 CMS 的触发频率，减少老年代回收的次数可以较为明显地改善应用程序性能。反之，如果应用程序内存使用率增长很快，则应该降低这个阈值，以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低 FullGC 的执行次数。</span><br>●  <code>-XX:+UseCMSCompactAtFullCollection</code> 用于指定在执行完 Full GC 后对内存空间进行压缩整理，以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行，所带来的问题就是停顿时间变得更长了。<br>●  <code>-XX:CMSFullGCsBeforeCompaction</code> 设置在执行多少次 Full GC 后对内存空间进行压缩整理。<br>●  -XX:ParallelcMSThreads 设置 CMS 的线程数量。<br>  ○ CMS 默认启动的线程数是（ParallelGCThreads+3）&#x2F;4，ParallelGCThreads 是年轻代并行收集器的线程数。当 CPU 资源比较紧张时，受到 CMS 收集器线程的影响，应用程序的性能在垃圾回收阶段可能会非常糟糕。</p><h2 id="5-5-详细阶段"><a href="#5-5-详细阶段" class="headerlink" title="5.5. 详细阶段"></a>5.5. 详细阶段</h2><p>CMS 垃圾回收的 6 个重要阶段</p><p>initial-mark 初始标记（CMS 的第一个 STW 阶段），标记 GC Root 直接引用的对象，GC Root 直接引用的对象不多，所以很快。<br>concurrent-mark 并发标记阶段，由第一阶段标记过的对象出发，所有可达的对象都在本阶段标记。<br><strong>concurrent-preclean 并发预清理阶段</strong>，也是一个并发执行的阶段。在本阶段，会查找前一阶段执行过程中, 从新生代晋升或新分配或被更新的对象。通过并发地重新扫描这些对象，预清理阶段可以减少下一个 stop-the-world<br>重新标记阶段的工作量。<br><strong>concurrent-abortable-preclean 并发可中止的预清理阶段</strong>。这个阶段其实跟上一个阶段做的东西一样，也是为了减少下一个 STW 重新标记阶段的工作量。增加这一阶段是为了让我们可以控制这个阶段的结束时机，比如扫描多长时间（默认 5 秒）或者 Eden 区使用占比达到期望比例（默认 50%）就结束本阶段。<br>remark 重标记阶段（CMS 的第二个 STW 阶段），暂停所有用户线程，从 GC Root 开始重新扫描整堆，标记存活的对象。需要注意的是，虽然 CMS 只回收老年代的垃圾对象，但是这个阶段依然需要扫描新生代，因为很多 GC<br>Root 都在新生代，而这些 GC Root 指向的对象又在老年代，这称为“跨代引用”。<br>concurrent-sweep ，并发清理。原文链接： <a href="https://blog.csdn.net/flysqrlboy/article/details/88679457">https://blog.csdn.net/flysqrlboy/article/details/88679457</a><br>-XX:CMSMaxAbortablePrecleanTime&#x3D;5000 ，默认值 5s，代表该阶段最大的持续时间（第四阶段）</p><h1 id="6-G1"><a href="#6-G1" class="headerlink" title="6. G1"></a>6. G1</h1><h2 id="6-1-特点"><a href="#6-1-特点" class="headerlink" title="6.1. 特点"></a>6.1. 特点</h2><h3 id="6-1-1-并行与并发"><a href="#6-1-1-并行与并发" class="headerlink" title="6.1.1. 并行与并发"></a>6.1.1. 并行与并发</h3><ul><li>并行性：G1 在回收期间，可以有多个 GC 线程同时工作，有效利用多核计算能力。此时用户线程 STW</li><li>并发性：G1 拥有与应用程序交替执行的能力，部分工作可以和应用程序同时执行，因此，一般来说，不会在整个回收阶段发生完全阻塞应用程序的情况</li></ul><h3 id="6-1-2-也是分代型垃圾回收器"><a href="#6-1-2-也是分代型垃圾回收器" class="headerlink" title="6.1.2. 也是分代型垃圾回收器"></a>6.1.2. 也是分代型垃圾回收器</h3><ul><li>从分代上看，G1 依然属于分代型垃圾回收器，它会区分年轻代和老年代，年轻代依然有 Eden 区和 Survivor 区。但从堆的结构上看，<span style="background-color:#ff00ff">它不要求整个 Eden 区、年轻代或者老年代都是连续的，也不再坚持固定大小和固定数量</span>。</li><li>将堆空间分为若干个区域（Region），这些区域中包含了逻辑上的年轻代和老年代。</li><li>和之前的各类回收器不同，它同时兼顾年轻代和老年代。对比其他回收器，或者工作在年轻代，或者工作在老年代；</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307172542.png" alt="image.png"></p><h3 id="6-1-3-空间整合"><a href="#6-1-3-空间整合" class="headerlink" title="6.1.3. 空间整合"></a>6.1.3. 空间整合</h3><ul><li>CMS：“标记 - 清除”算法、内存碎片、若干次 GC 后进行一次碎片整理</li><li>G1 将内存划分为一个个的 region。内存的回收是以 region 作为基本单位的。</li></ul><p>  <span style="background-color:#ff00ff">Region 之间是复制算法，但整体上实际可看作是标记 - 压缩（Mark-Compact）算法，两种算法都可以避免内存碎片。这种特性有利于程序长时间运行，分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。尤其是当 Java 堆非常大的时候，G1 的优势更加明显。</span></p><h3 id="6-1-4-可预测的停顿时间模型（软实时-soft-real-time）"><a href="#6-1-4-可预测的停顿时间模型（软实时-soft-real-time）" class="headerlink" title="6.1.4. 可预测的停顿时间模型（软实时 soft real-time）"></a>6.1.4. 可预测的停顿时间模型（软实时 soft real-time）</h3><p>这是 G1 相对于 CMS 的另一大优势，G1 除了追求低停顿外，还能建立可预测的停顿时间模型，能让使用者明确指定在一个长度为 M 毫秒的时间片段内，消耗在垃圾收集上的时间不得超过 N 毫秒。</p><ul><li>由于分区的原因，G1 可以只选取部分区域进行内存回收，这样缩小了回收的范围，因此对于全局停顿情况的发生也能得到较好的控制。</li><li>G1 跟踪各个 Region 里面的垃圾堆积的价值大小（回收所获得的空间大小以及回收所需时间的经验值），在后台维护一个优先列表，每次根据允许的收集时间，优先回收价值最大的 Region。保证了 G1 收集器在有限的时间内可以获取尽可能高的收集效率。</li><li>相比于 CMS，G1 未必能做到 CMS 在最好情况下的延时停顿，但是最差情况要好很多。</li></ul><h2 id="6-2-CMS-对比"><a href="#6-2-CMS-对比" class="headerlink" title="6.2. CMS 对比"></a>6.2. CMS 对比</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174308.jpg" alt="image-20200402112557923"></p><h2 id="6-3-优点"><a href="#6-3-优点" class="headerlink" title="6.3. 优点"></a>6.3. 优点</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174316.jpg" alt="image-20200324223211928"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174324.jpg" alt="image-20200402111808637"></p><h2 id="6-4-缺点"><a href="#6-4-缺点" class="headerlink" title="6.4. 缺点"></a>6.4. 缺点</h2><p>相较于 CMS，G1 还不具备全方位、压倒性优势。比如在用户程序运行过程中，G1 无论是为了垃圾收集产生的内存占用（Footprint）还是程序运行时的额外执行负载（Overload）都要比 CMS 要高。</p><p><span style="background-color:#ff00ff">从经验上来说，在小内存应用上 CMS 的表现大概率会优于 G1，而 G1 在大内存应用上则发挥其优势。平衡点在 6-8GB 之间。</span></p><h2 id="6-5-参数设置⭐️🔴"><a href="#6-5-参数设置⭐️🔴" class="headerlink" title="6.5. 参数设置⭐️🔴"></a>6.5. 参数设置⭐️🔴</h2><ul><li><code>-XX:+UseG1GC</code>：手动指定使用 G1 垃圾收集器执行内存回收任务</li><li><code>-XX:G1HeapRegionSize</code> 设置每个 Region 的大小。值是 2 的幂，范围是 1MB 到 32MB 之间，目标是根据最小的 Java 堆大小划分出约 2048 个区域。<span style="background-color:#ff00ff">默认是堆内存的 1&#x2F;2000。</span>  ^pcgqi8</li><li><code>-XX:MaxGCPauseMillis</code> 设置期望达到的最大 GC 停顿时间指标（JVM 会尽力实现，但不保证达到）。<span style="background-color:#ff00ff">默认值是 200ms（人的平均反应速度）</span></li><li><code>-XX:+ParallelGCThread</code> 设置 STW 工作线程数的值。最多设置为 8（上面说过 Parallel 回收器的线程计算公式，当 CPU_Count &gt; 8 时，ParallelGCThreads 也会大于 8）</li><li><code>-XX:ConcGCThreads</code> 设置并发标记的线程数。将 n 设置为并行垃圾回收线程数（ParallelGCThreads）的 1&#x2F;4 左右。</li><li><code>-XX:InitiatingHeapOccupancyPercent</code> 设置触发并发 GC 周期的 <span style="background-color:#ff00ff">Java 堆占用率阈值</span>。超过此值，就触发 GC。默认值是 **45%**。CMS 中占用率阈值是看<span style="background-color:#ff00ff">老年代的占用 </span>JDK5 是 68%，JDK6 及以上为 92%。 ^6qnxn7</li></ul><h3 id="6-5-1-到底是整堆还是老年代⭐️🔴"><a href="#6-5-1-到底是整堆还是老年代⭐️🔴" class="headerlink" title="6.5.1. 到底是整堆还是老年代⭐️🔴"></a>6.5.1. 到底是整堆还是老年代⭐️🔴</h3><p><a href="https://doudaxia.club/index.php/archives/212/">https://doudaxia.club/index.php/archives/212/</a></p><p>如果你使用的 JDK 版本在 8b12 之前，那么文章开头的第一种说法是正确的，即 XX:InitiatingHeapOccupancyPercent 是<span style="background-color:#ff00ff">整堆使用量</span>与堆总体容量的比值；<br>如果你使用的 JDK 版本在 8b12 之后（包括大版本 9、10、11….），那么文章开头第二种说法是正确的，即 XX:InitiatingHeapOccupancyPercent 是<span style="background-color:#ff0000">老年代大小</span>与堆总体容量的比值。</p><p>CMS 的老年代阈值 <code>-XX:CMSInitiatingOccupancyFraction</code> 没有争议，就是老年代的使用率超过该阈值就触发</p><h2 id="6-6-回收过程⭐️🔴"><a href="#6-6-回收过程⭐️🔴" class="headerlink" title="6.6. 回收过程⭐️🔴"></a>6.6. 回收过程⭐️🔴</h2><p>G1GC 的垃圾回收过程主要包括如下三个环节：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310085733.png" alt="image.png"></p><p><span style="background-color:#00ff00">- 年轻代 GC（Young GC）</span><br><span style="background-color:#00ff00">- 老年代并发标记过程（Concurrent Marking）</span><br><span style="background-color:#00ff00">- 混合回收（Mixed GC）</span><br>    <span style="background-color:#ff0000">（如果需要，单线程、独占式、高强度的 Full GC 还是继续存在的。它针对 GC 的评估失败提供了一种失败保护机制，即强力回收。）</span></p><p>顺时针，Young gc -&gt; Young gc + Concurrent mark-&gt;Mixed GC 顺序，进行垃圾回收。</p><p>应用程序分配内存，当年轻代的 Eden 区用尽时开始年轻代回收过程；G1 的年轻代收集阶段是一个<span style="background-color:#ff00ff">并行的独占式收集器</span>。<span style="background-color:#00ff00">在年轻代回收期，G1 GC 暂停所有应用程序线程，启动多线程执行年轻代回收。</span>然后从年轻代区间移动存活对象到 Survivor 区间或者老年区间，也有可能是两个区间都会涉及。</p><p>当<span style="background-color:#ff00ff">堆内存</span>使用达到一定值（<span style="background-color:#00ff00">默认 45%</span>）时，开始老年代<span style="background-color:#ff00ff">并发标记过程</span>。<span style="background-color:#ff0000">并且标记完成马上开始混合回收过程。</span></p><p>对于一个混合回收期，G1 GC 从老年区间移动存活对象到空闲区间，这些空闲区间也就成为了老年代的一部分。和年轻代不同，老年代的 G1 回收器和其他 GC 不同，G1 的<span style="background-color:#ff00ff">老年代回收器不需要整个老年代被回收</span>，一次只需要扫描&#x2F;回收一小部分老年代的 Region 就可以了。同时，这个老年代 Region 是和年轻代一起被回收的。</p><p>举个例子：一个 Web 服务器，Java 进程最大堆内存为 4G，每分钟响应 1500 个请求，每 45 秒钟会新分配大约 2G 的内存。G1 会每 45 秒钟进行一次年轻代回收，每 31 个小时整个堆的使用率会达到 45%，会开始老年代并发标记过程，标记完成后开始四到五次的混合回收。</p><h3 id="6-6-1-年轻代-GC-STW-所有-ES-区"><a href="#6-6-1-年轻代-GC-STW-所有-ES-区" class="headerlink" title="6.6.1. 年轻代 GC-STW-所有 ES 区"></a>6.6.1. 年轻代 GC-STW-所有 ES 区</h3><p>JVM 启动时，G1 先准备好 Eden 区，程序在运行过程中不断创建对象到 Eden 区，<span style="background-color:#ff00ff">当 Eden 空间耗尽时</span>，G1 会启动一次年轻代垃圾回收过程。</p><p>年轻代垃圾回收只会回收 Eden 区和 Survivor 区。</p><p><span style="background-color:#ff00ff">首先 G1 停止应用程序的执行（Stop-The-World）</span>，G1 创建回收集（Collection Set），回收集是指需要被回收的内存分段的集合，年轻代回收过程的回收集包含年轻代 Eden 区和 Survivor 区所有的内存分段。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310090813.png" alt="image.png"></p><h3 id="6-6-2-年轻代-GC-老年代并发标记"><a href="#6-6-2-年轻代-GC-老年代并发标记" class="headerlink" title="6.6.2. 年轻代 GC+ 老年代并发标记"></a>6.6.2. 年轻代 GC+ 老年代并发标记</h3><ol><li>初始标记阶段：标记从根节点直接可达的对象。这个阶段是 STW 的，并且会触发一次年轻代 GC。</li><li>根区域扫描（Root Region Scanning）：G1 GC 扫描 Survivor 区直接可达的老年代区域对象，并标记被引用的对象。这一过程必须在 YoungGC 之前完成。</li><li>并发标记（Concurrent Marking）：在整个堆中进行并发标记（和应用程序并发执行），此过程可能被 YoungGC 中断。在并发标记阶段，若发现区域对象中的所有对象都是垃圾，那这个区域会被立即回收。同时，并发标记过程中，会计算每个区域的对象活性（区域中存活对象的比例）。</li><li>再次标记（Remark）：由于应用程序持续进行，需要修正上一次的标记结果。是 STW 的。G1 中采用了比 CMS 更快的初始快照算法：snapshot-at-the-beginning（SATB）。</li><li>独占清理（cleanup，STW）：计算各个区域的存活对象和 GC 回收比例，并进行排序，识别可以混合回收的区域。为下阶段做铺垫。是 STW 的。<span style="background-color:#ff0000">这个阶段并不会实际上去做垃圾的收集</span></li><li>并发清理阶段：识别并清理完全空闲的区域。</li></ol><h3 id="6-6-3-混合回收"><a href="#6-6-3-混合回收" class="headerlink" title="6.6.3. 混合回收"></a>6.6.3. 混合回收</h3><p>当越来越多的对象晋升到老年代 old region 时，为了避免堆内存被耗尽，虚拟机会触发一个混合的垃圾收集器，即 Mixed GC，<span style="background-color:#ff00ff">该算法并不是一个 Old GC，除了回收整个 Young Region</span>，还会<span style="background-color:#ff0000">回收一部分的 Old Region</span>。这里需要注意：是一部分老年代，而不是全部老年代。可以选择哪些 Old Region 进行收集，从而可以对垃圾回收的耗时时间进行控制。也要注意的是 Mixed GC 并不是 Full GC。</p><h2 id="6-7-补充逻辑"><a href="#6-7-补充逻辑" class="headerlink" title="6.7. 补充逻辑"></a>6.7. 补充逻辑</h2><h3 id="6-7-1-内存碎片"><a href="#6-7-1-内存碎片" class="headerlink" title="6.7.1. 内存碎片"></a>6.7.1. 内存碎片</h3><p><strong>内存碎片解决办法：小区域收集 + 形成连续的内存块</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174344.jpg" alt="image-20200402112031488"></p><h3 id="6-7-2-记忆集和写屏障"><a href="#6-7-2-记忆集和写屏障" class="headerlink" title="6.7.2. 记忆集和写屏障"></a>6.7.2. 记忆集和写屏障</h3><p><span style="display:none">%%<br>▶32.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨♨️⭐️】◼️⭐️-point-20230310-1739%%</span>❕ ^y2fepk</p><a href="/2023/03/10/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-12%E3%80%81GC-%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E7%AE%97%E6%B3%95/" title="性能调优-基础-12、GC-三色标记算法">性能调优-基础-12、GC-三色标记算法</a><h2 id="6-8-开启方法"><a href="#6-8-开启方法" class="headerlink" title="6.8. 开启方法"></a>6.8. 开启方法</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174402.jpg" alt="image-20200324234009052"></p><h2 id="6-9-优化特性"><a href="#6-9-优化特性" class="headerlink" title="6.9. 优化特性"></a>6.9. 优化特性</h2><p><span style="display:none">%%<br>▶35.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1749%%</span>❕ ^d8zp7j</p><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP?p=79">https://www.bilibili.com/video/BV1yE411Z7AP?p=79</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174412.jpg" alt="image-20200402132839496"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174420.jpg" alt="image-20200402133232905"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174429.jpg" alt="image-20200402133425862"></p><h2 id="6-10-G1-收集器的适用场景"><a href="#6-10-G1-收集器的适用场景" class="headerlink" title="6.10. G1 收集器的适用场景"></a>6.10. G1 收集器的适用场景</h2><p>面向服务端应用，针对具有大内存、多处理器的机器。（在普通大小的堆里表现并不惊喜）<br>最主要的应用是<span style="background-color:#ff00ff">需要低 GC 延迟，并具有大堆的应用程序</span>提供解决方案；如：在堆大小约 6GB 或更大时，可预测的暂停时间可以低于 0.5 秒；（G1 通过每次只清理一部分而不是全部的 Region 的增量式清理来保证每次 GC 停顿时间不会过长）。</p><p>用来替换掉 JDK1.5 中的 CMS 收集器；在下面的情况时，使用 G1 可能比 CMS 好：</p><ul><li>超过 50% 的 Java 堆被活动数据占用；</li><li>对象分配频率或年代提升频率变化很大；</li><li>GC 停顿时间过长（长于 0.5 至 1 秒）</li></ul><p>HotSpot 垃圾收集器里，除了 G1 以外，其他的垃圾收集器使用内置的 JVM 线程执行 GC 的多线程操作，而 G1 GC 可以采用应用线程承担后台运行的 GC 工作，即当 JVM 的 GC 线程处理速度慢时，系统会调用应用程序线程帮助加速垃圾回收过程。</p><h2 id="6-11-调优建议⭐️🔴"><a href="#6-11-调优建议⭐️🔴" class="headerlink" title="6.11. 调优建议⭐️🔴"></a>6.11. 调优建议⭐️🔴</h2><h3 id="6-11-1-年轻代大小"><a href="#6-11-1-年轻代大小" class="headerlink" title="6.11.1. 年轻代大小"></a>6.11.1. 年轻代大小</h3><p>● 避免使用 -Xmn 或 -XX:NewRatio 等相关选项显式设置年轻代大小<br>● <span style="background-color:#ff0000">固定年轻代的大小会覆盖暂停时间目标</span></p><h3 id="6-11-2-暂停时间目标不要太过严苛"><a href="#6-11-2-暂停时间目标不要太过严苛" class="headerlink" title="6.11.2. 暂停时间目标不要太过严苛"></a>6.11.2. 暂停时间目标不要太过严苛</h3><p>● G1 GC 的吞吐量目标是 90% 的应用程序时间和 10% 的垃圾回收时间<br>● <span style="background-color:#ff0000">评估 G1 GC 的吞吐量时，暂停时间目标不要太严苛。目标太过严苛表示你愿意承受更多的垃圾回收开销，而这些会直接影响到吞吐量。</span></p><h3 id="6-11-3-G1-会不会进行-Full-GC"><a href="#6-11-3-G1-会不会进行-Full-GC" class="headerlink" title="6.11.3. G1 会不会进行 Full GC"></a>6.11.3. G1 会不会进行 Full GC</h3><p><span style="display:none">%%<br>▶36.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1825%%</span>❕ ^oq6j20</p><p>会，当内存满了的时候就会进行 <code>Full GC</code>；<span style="background-color:#ff00ff">且 <code>JDK10</code> 之前的 <code>Full GC</code>，为单线程的</span>，所以使用 G1 需要避免 <code>Full GC</code> 的产生。</p><p>G1 的初衷就是要避免 Full GC 的出现。但是如果上述方式不能正常工作，G1 会停止应用程序的执行（Stop-The-World），使用单线程的内存回收算法进行垃圾回收，性能会非常差，应用程序停顿时间会很长。</p><p>要避免 Full GC 的发生，一旦发生需要进行调整。什么时候会发生 Full GC 呢？<span style="background-color:#ff0000">比如堆内存太小</span>，当 G1 在复制存活对象的时候没有空的内存分段可用，则会回退到 Full GC，这种情况可以通过增大内存解决。</p><h4 id="6-11-3-1-导致原因"><a href="#6-11-3-1-导致原因" class="headerlink" title="6.11.3.1. 导致原因"></a>6.11.3.1. 导致原因</h4><p>导致 G1 Full GC 的原因可能有两个：</p><p><span style="background-color:#ff00ff">● Evacuation 的时候没有足够的 to-space 来存放晋升的对象；</span><br><span style="background-color:#ff00ff">● 并发处理过程完成之前空间耗尽。</span></p><h4 id="6-11-3-2-解决方案"><a href="#6-11-3-2-解决方案" class="headerlink" title="6.11.3.2. 解决方案"></a>6.11.3.2. 解决方案</h4><ul><li>加大内存；</li><li>提高 CPU 性能，加快 GC 回收速度，而对象增加速度赶不上回收速度，则 Full GC 可以避免；</li><li><span style="background-color:#ff00ff">降低老年代并发标记触发的阈值（默认堆使用率为 45%），让 Mixed GC 提早发生</span></li></ul><h3 id="6-11-4-其他案例"><a href="#6-11-4-其他案例" class="headerlink" title="6.11.4. 其他案例"></a>6.11.4. 其他案例</h3><p>首先，建议尽量升级到较新的 JDK 版本，从上面介绍的改进就可以看到，很多人们常常讨论的问题，其实升级 JDK 就可以解决了。</p><p>第二，掌握 GC 调优信息收集途径。掌握尽量全面、详细、准确的信息，是各种调优的基础，不仅仅是 GC 调优。我们来看看打开 GC 日志，这似乎是很简单的事情，可是你确定真的掌握了吗?</p><p>   除了常用的两个选项，</p><p><code> -XX:+PrintGCDetails </code> -XX:+PrintGCDateStamps&#96;</p><p>还有一些非常有用的日志选项，很多特定问题的诊断都是要依赖这些选项:</p><blockquote><p><code>-XX:+PrintAdaptiveSizePolicy</code> &#x2F;&#x2F; 打印 G1 Ergonomics 相关信息</p></blockquote><p>   我们知道 GC 内部一些行为是适应性的触发的，利用 PrintAdaptiveSizePolicy，我们就可以知道为什么 JVM 做出了一些可能我们不希望发生的动作。例如，G1 调优的一个基本建议就是避免进行大量的 Humongous 对象分配，如果 Ergonomics 信息说明发生了这一 点，那么就可以考虑要么增大堆的大小，要么直接将 region 大小提高。</p><p>如果是怀疑出现引用清理不及时的情况，则可以打开下面选项，掌握到底是哪里出现了堆积。</p><blockquote><p><code> -XX:+PrintReferenceGC</code></p></blockquote><p>另外，建议开启选项下面的选项进行并行引用处理。</p><blockquote><p><code>-XX:+ParallelRefProcEnabled</code></p></blockquote><p>   需要注意的一点是，JDK 9 中 JVM 和 GC 日志机构进行了重构，其实我前面提到的 PrintGCDetails 已经被标记为废弃，而 PrintGCDateStamps 已经被移除，指定它会导致 JVM 无法启动。可以使用下面的命令查询新的配置参数。</p><blockquote><p> <code>java -Xlog:help</code></p></blockquote><p> 最后，来看一些通用实践，理解了我前面介绍的内部结构和机制，很多结论就一目了然了，例如:</p><h4 id="6-11-4-1-Young-GC-非常耗时"><a href="#6-11-4-1-Young-GC-非常耗时" class="headerlink" title="6.11.4.1. Young GC 非常耗时"></a>6.11.4.1. Young GC 非常耗时</h4><p>这很可能就是因为新生代太大了，我们可以考虑减小新生代的最小比例。</p><blockquote><p><code> -XX:G1NewSizePercent</code></p></blockquote><p>降低其最大值同样对降低 Young GC 延迟有帮助。</p><blockquote><p><code>-XX:G1MaxNewSizePercent</code></p></blockquote><p>   如果我们直接为 G1 设置较小的延迟目标值，也会起到减小新生代的效果，虽然会影响吞吐量。</p><h4 id="6-11-4-2-Mixed-GC-延迟较长"><a href="#6-11-4-2-Mixed-GC-延迟较长" class="headerlink" title="6.11.4.2. Mixed GC 延迟较长"></a>6.11.4.2. Mixed GC 延迟较长</h4><p>还记得前面说的，部分 Old region 会被包含进 Mixed GC，减少一次处理的 region 个数，就是个直接的选择之一。</p><p><code>G1OldCSetRegionThresholdPercent</code>：一次 Mixed GC 中能被选入 CSet 的最多 old generation region 数量。**- 减少个数**</p><p>我在上面已经介绍了 G1OldCSetRegionThresholdPercent 控制其最大值，还可以利用下面参数提高 Mixed GC 的次数，当前默认值是 8，Mixed GC 数量增多，意味着每次被包含的 region 减少，那么数量少了延迟就会相应减少。**- 增加次数**</p><blockquote><p> <code>-XX:G1MixedGCCountTarget</code></p></blockquote><h1 id="7-总结-回收器选择"><a href="#7-总结-回收器选择" class="headerlink" title="7. 总结 - 回收器选择"></a>7. 总结 - 回收器选择</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174440.jpg" alt="image-20200324222211602"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108174447.jpg" alt="image-20200324222135032"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208185015.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307221529.png" alt="image.png"></p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>架构实战专题-基础-1、架构演进</title>
      <link href="/2023/03/08/006-%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98%E4%B8%93%E9%A2%98/%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-1%E3%80%81%E6%9E%B6%E6%9E%84%E6%BC%94%E8%BF%9B/"/>
      <url>/2023/03/08/006-%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98%E4%B8%93%E9%A2%98/%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-1%E3%80%81%E6%9E%B6%E6%9E%84%E6%BC%94%E8%BF%9B/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1><p><a href="https://cloud.fynote.com/share/d/3035#4-%E5%8E%8B%E6%B5%8B%E5%9B%BE----_69">https://cloud.fynote.com/share/d/3035#4-%E5%8E%8B%E6%B5%8B%E5%9B%BE----_69</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-JVM监控及调优-1、内存泄露</title>
      <link href="/2023/03/08/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/JVM%E7%9B%91%E6%8E%A7%E5%8F%8A%E8%B0%83%E4%BC%98-1%E3%80%81%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E4%B8%8E%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/"/>
      <url>/2023/03/08/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/JVM%E7%9B%91%E6%8E%A7%E5%8F%8A%E8%B0%83%E4%BC%98-1%E3%80%81%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E4%B8%8E%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-什么是内存泄露"><a href="#1-什么是内存泄露" class="headerlink" title="1. 什么是内存泄露"></a>1. 什么是内存泄露</h1><p>可达性分析算法来判断对象是否是不再使用的对象，本质都是判断一个对象是否还被引用。那么对于这种情况下，由于代码的实现不同就会出现很多种内存泄漏问题</p><p><strong>＞</strong> 是否还被使用？是<br><strong>＞</strong> 是否还被需要？否</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309071258.png" alt="image.png"></p><h2 id="1-1-内存泄露-memory-leak"><a href="#1-1-内存泄露-memory-leak" class="headerlink" title="1.1. 内存泄露 (memory leak)"></a>1.1. 内存泄露 (memory leak)</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230309-0722%%</span>❕ ^gvkdgy</p><p>严格来说，只有对象不会再被程序用到了，但是 GC 又不能回收他们的情况，才叫内存泄漏。但实际情况很多时候一些不太好的实践（或疏忽）会导致对象的生命周期变得很长甚至导致 00M，也可以叫做宽泛意义上的“内存泄漏”。</p><p>如下图，当 Y 生命周期结束的时候，X 依然引用着 Y，这时候，垃圾回收期是不会回收对象 Y 的；如果对象 X 还引用着生命周期比较短的 A、B、C，对象 A 又引用着对象 a、b、c，这样就可能造成大量无用的对象不能被回收，进而占据了内存资源，造成内存泄漏，直到内存溢出。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308215232.png" alt="image.png"></p><h2 id="1-2-内存溢出"><a href="#1-2-内存溢出" class="headerlink" title="1.2. 内存溢出"></a>1.2. 内存溢出</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308215301.png" alt="image.png"></p><h1 id="2-内存泄露的-8-种情况"><a href="#2-内存泄露的-8-种情况" class="headerlink" title="2. 内存泄露的 8 种情况"></a>2. 内存泄露的 8 种情况</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230426-1806%%</span>❕ ^rglrwj</p><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=336&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ?p=336&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308215714.png" alt="image.png"></p><h2 id="2-1-各种连接，如数据库连接、网络连接和-IO-连接等"><a href="#2-1-各种连接，如数据库连接、网络连接和-IO-连接等" class="headerlink" title="2.1. 各种连接，如数据库连接、网络连接和 IO 连接等"></a>2.1. 各种连接，如数据库连接、网络连接和 IO 连接等</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308220330.png" alt="image.png"></p><h2 id="2-2-变量不合理的作用域"><a href="#2-2-变量不合理的作用域" class="headerlink" title="2.2. 变量不合理的作用域"></a>2.2. 变量不合理的作用域</h2><p><span style="background-color:#ff00ff">没必要做成员变量做了成员变量</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308220428.png" alt="image.png"></p><h2 id="2-3-更改-参与哈希值计算的属性"><a href="#2-3-更改-参与哈希值计算的属性" class="headerlink" title="2.3. 更改 参与哈希值计算的属性"></a>2.3. 更改 参与哈希值计算的属性</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308220537.png" alt="image.png"></p><h2 id="2-4-缓存泄露-使用-WeakHashMap⭐️🔴"><a href="#2-4-缓存泄露-使用-WeakHashMap⭐️🔴" class="headerlink" title="2.4. 缓存泄露 - 使用 WeakHashMap⭐️🔴"></a>2.4. 缓存泄露 - 使用 WeakHashMap⭐️🔴</h2><p>内存泄漏的另一个常见来源是缓存，一旦你把对象引用放入到缓存中，他就很容易遗忘。比如：之前项目在一次上线的时候，应用启动奇慢直到夯死，就是因为代码中会加载一个表中的数据到缓存（内存）中，测试环境只有几百条数据，但是生产环境有几百万的数据。</p><p>对于这个问题，可以使用<span style="background-color:#ff00ff">WeakHashMap</span>代表缓存，此种 Map 的特点是，当除了自身有对 key 的引用外，此 key 没有其他引用那么此 map 会自动丢弃此值。</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></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_">MapTest</span> &#123;<br>    <span class="hljs-keyword">static</span> <span class="hljs-type">Map</span> <span class="hljs-variable">wMap</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakHashMap</span>();<br>    <span class="hljs-keyword">static</span> <span class="hljs-type">Map</span> <span class="hljs-variable">map</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</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>        init();<br>        testWeakHashMap();<br>        testHashMap();<br>    &#125;<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_">init</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">ref1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;obejct1&quot;</span>);<br>        <span class="hljs-type">String</span> <span class="hljs-variable">ref2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;obejct2&quot;</span>);<br>        <span class="hljs-type">String</span> <span class="hljs-variable">ref3</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;obejct3&quot;</span>);<br>        <span class="hljs-type">String</span> <span class="hljs-variable">ref4</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;obejct4&quot;</span>);<br>        wMap.put(ref1, <span class="hljs-string">&quot;cacheObject1&quot;</span>);<br>        wMap.put(ref2, <span class="hljs-string">&quot;cacheObject2&quot;</span>);<br>        map.put(ref3, <span class="hljs-string">&quot;cacheObject3&quot;</span>);<br>        map.put(ref4, <span class="hljs-string">&quot;cacheObject4&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;String引用ref1，ref2，ref3，ref4 消失&quot;</span>);<br><br>    &#125;<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_">testWeakHashMap</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;WeakHashMap GC之前&quot;</span>);<br>        <span class="hljs-keyword">for</span> (Object o : wMap.entrySet()) &#123;<br>            System.out.println(o);<br>        &#125;<br>        <span class="hljs-keyword">try</span> &#123;<br>            System.gc();<br>            TimeUnit.SECONDS.sleep(<span class="hljs-number">5</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>        System.out.println(<span class="hljs-string">&quot;WeakHashMap GC之后&quot;</span>);<br>        <span class="hljs-keyword">for</span> (Object o : wMap.entrySet()) &#123;<br>            System.out.println(o);<br>        &#125;<br>    &#125;<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_">testHashMap</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;HashMap GC之前&quot;</span>);<br>        <span class="hljs-keyword">for</span> (Object o : map.entrySet()) &#123;<br>            System.out.println(o);<br>        &#125;<br>        <span class="hljs-keyword">try</span> &#123;<br>            System.gc();<br>            TimeUnit.SECONDS.sleep(<span class="hljs-number">5</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>        System.out.println(<span class="hljs-string">&quot;HashMap GC之后&quot;</span>);<br>        <span class="hljs-keyword">for</span> (Object o : map.entrySet()) &#123;<br>            System.out.println(o);<br>        &#125;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>上面代码和图示主演演示 WeakHashMap 如何自动释放缓存对象，当 init 函数执行完成后，局部变量字符串引用 weakd1，weakd2，d1，d2 都会消失，此时只有静态 map 中保存中对字符串对象的引用，可以看到，调用 gc 之后，HashMap 的没有被回收，而 WeakHashMap 里面的缓存被回收了。</p><h2 id="2-5-静态集合类"><a href="#2-5-静态集合类" class="headerlink" title="2.5. 静态集合类"></a>2.5. 静态集合类</h2><p>静态集合类，如 HashMap、LinkedList 等等。如果这些容器为静态的，那么它们的生命周期与 JVM 程序一致，则容器中的对象在程序结束之前将不能被释放，从而造成内存泄漏。简单而言，<span style="background-color:#ff00ff">长生命周期的对象持有短生命周期对象的引用，尽管短生命周期的对象不再使用，但是因为长生命周期对象持有它的引用而导致不能被回收。</span></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-keyword">class</span> <span class="hljs-title class_">MemoryLeak</span> &#123;<br>    <span class="hljs-keyword">static</span> <span class="hljs-type">List</span> <span class="hljs-variable">list</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>();<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">oomTests</span><span class="hljs-params">()</span>&#123;<br>        Object obj＝<span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>();<span class="hljs-comment">//局部变量</span><br>        list.add(obj);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="2-6-单例模式"><a href="#2-6-单例模式" class="headerlink" title="2.6. 单例模式"></a>2.6. 单例模式</h2><p><span style="background-color:#ff00ff">单例对象如果持有外部对象的引用，那么这个外部对象也不会被回收</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308220026.png" alt="image.png"></p><h2 id="2-7-内部类持有外部类"><a href="#2-7-内部类持有外部类" class="headerlink" title="2.7. 内部类持有外部类"></a>2.7. 内部类持有外部类</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309075430.png" alt="image.png"></p><h2 id="2-8-监听器和其他回调"><a href="#2-8-监听器和其他回调" class="headerlink" title="2.8. 监听器和其他回调"></a>2.8. 监听器和其他回调</h2><p>内存泄漏第三个常见来源是监听器和其他回调，如果客户端在你实现的 API 中注册回调，却没有显示的取消，那么就会积聚。</p><p>需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用，例如将他们保存成为<span style="background-color:#ff00ff">WeakHashMap</span>中的键。</p><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-只进未出"><a href="#3-1-只进未出" class="headerlink" title="3.1. 只进未出"></a>3.1. 只进未出</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309073820.png" alt="image.png"></p><h2 id="3-2-匿名线程"><a href="#3-2-匿名线程" class="headerlink" title="3.2. 匿名线程"></a>3.2. 匿名线程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309074134.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309074110.png" alt="image.png"></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-JVM监控及诊断工具-1、Arthas</title>
      <link href="/2023/03/08/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-JVM%E7%9B%91%E6%8E%A7%E5%8F%8A%E8%AF%8A%E6%96%AD%E5%B7%A5%E5%85%B7-1%E3%80%81Arthas/"/>
      <url>/2023/03/08/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-JVM%E7%9B%91%E6%8E%A7%E5%8F%8A%E8%AF%8A%E6%96%AD%E5%B7%A5%E5%85%B7-1%E3%80%81Arthas/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="常用功能"><a href="#常用功能" class="headerlink" title="常用功能"></a>常用功能</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310222353.png" alt="image.png"></p><h1 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h1><p><span style="display:none">%%<br>▶41.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-2242%%</span>❕ ^vdhxtb</p><p>jad</p><p>watch</p><p>trace</p><p>stack</p><p>monitor</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310223120.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310223728.png" alt="image.png"></p><p>请求回放<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310224028.png" alt="image.png"></p><p>手动请求，抓取请求</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310224135.png" alt="image.png"></p><p>tt -i xxxx<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230310224217.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309224312.png" alt="image.png"></p><h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1><p><a href="https://www.bilibili.com/video/BV16B4y1V7hv/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV16B4y1V7hv/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-RabbitMQ-1、基本原理</title>
      <link href="/2023/03/08/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RabbitMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2023/03/08/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RabbitMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-消息可靠性"><a href="#1-消息可靠性" class="headerlink" title="1. 消息可靠性"></a>1. 消息可靠性</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304191042.png" alt="image.png"></p><h2 id="1-1-生产者消息确认"><a href="#1-1-生产者消息确认" class="headerlink" title="1.1. 生产者消息确认"></a>1.1. 生产者消息确认</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304192143.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304192315.png" alt="image-20210718161707992"></p><h3 id="1-1-1-修改配置"><a href="#1-1-1-修改配置" class="headerlink" title="1.1.1. 修改配置"></a>1.1.1. 修改配置</h3><p>首先，修改 publisher 服务中的 application.yml 文件，添加下面的内容：</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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">rabbitmq:</span><br>    <span class="hljs-attr">publisher-confirm-type:</span> <span class="hljs-string">correlated</span><br>    <span class="hljs-attr">publisher-returns:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-attr">template:</span><br>      <span class="hljs-attr">mandatory:</span> <span class="hljs-literal">true</span><br></code></pre></td></tr></table></figure><p>说明：</p><ul><li><code>publish-confirm-type</code>：开启 publisher-confirm，这里支持两种类型：<ul><li><code>simple</code>：同步等待 confirm 结果，直到超时</li><li><code>correlated</code>：<span style="background-color:#ff00ff">异步回调，定义 ConfirmCallback，MQ 返回结果时会回调这个 ConfirmCallback</span></li></ul></li><li><code>publish-returns</code>：开启 publish-return 功能，同样是基于 callback 机制，不过是定义 ReturnCallback</li><li><code>template.mandatory</code>：<span style="background-color:#ff0000">定义消息路由失败时的策略。true，则调用 ReturnCallback；false：则直接丢弃消息</span></li></ul><h3 id="1-1-2-定义-ReturnCallback-ApplicationContextAware"><a href="#1-1-2-定义-ReturnCallback-ApplicationContextAware" class="headerlink" title="1.1.2. 定义 ReturnCallback -ApplicationContextAware"></a>1.1.2. 定义 ReturnCallback -ApplicationContextAware</h3><p><span style="background-color:#ff00ff">每个 RabbitTemplate 只能配置一个 ReturnCallback</span>，因此需要在项目加载时配置：<br>修改 publisher 服务，添加一个：</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">@Slf4j</span><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_">CommonConfig</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">ApplicationContextAware</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_">setApplicationContext</span><span class="hljs-params">(ApplicationContext applicationContext)</span> <span class="hljs-keyword">throws</span> BeansException &#123;<br>        <span class="hljs-comment">// 获取RabbitTemplate</span><br>        <span class="hljs-type">RabbitTemplate</span> <span class="hljs-variable">rabbitTemplate</span> <span class="hljs-operator">=</span> applicationContext.getBean(RabbitTemplate.class);<br>        <span class="hljs-comment">// 设置ReturnCallback</span><br>        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -&gt; &#123;<br>            <span class="hljs-comment">// 投递失败，记录日志</span><br>            log.info(<span class="hljs-string">&quot;消息发送失败，应答码&#123;&#125;，原因&#123;&#125;，交换机&#123;&#125;，路由键&#123;&#125;,消息&#123;&#125;&quot;</span>,<br>                     replyCode, replyText, exchange, routingKey, message.toString());<br>            <span class="hljs-comment">// 如果有业务需要，可以重发消息</span><br>        &#125;);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="1-1-3-定义-ConfirmCallback"><a href="#1-1-3-定义-ConfirmCallback" class="headerlink" title="1.1.3. 定义 ConfirmCallback"></a>1.1.3. 定义 ConfirmCallback</h3><p>ConfirmCallback 可以在发送消息时指定，因为每个业务处理 confirm 成功或失败的逻辑不一定相同。</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></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_">testSendMessage2SimpleQueue</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>    <span class="hljs-comment">// 1.消息体</span><br>    <span class="hljs-type">String</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;hello, spring amqp!&quot;</span>;<br>    <span class="hljs-comment">// 2.全局唯一的消息ID，需要封装到CorrelationData中</span><br>    <span class="hljs-type">CorrelationData</span> <span class="hljs-variable">correlationData</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CorrelationData</span>(UUID.randomUUID().toString());<br>    <span class="hljs-comment">// 3.添加callback</span><br>    correlationData.getFuture().addCallback(<br>        result -&gt; &#123;<br>            <span class="hljs-keyword">if</span>(result.isAck())&#123;<br>                <span class="hljs-comment">// 3.1.ack，消息成功</span><br>                log.debug(<span class="hljs-string">&quot;消息发送成功, ID:&#123;&#125;&quot;</span>, correlationData.getId());<br>            &#125;<span class="hljs-keyword">else</span>&#123;<br>                <span class="hljs-comment">// 3.2.nack，消息失败</span><br>                log.error(<span class="hljs-string">&quot;消息发送失败, ID:&#123;&#125;, 原因&#123;&#125;&quot;</span>,correlationData.getId(), result.getReason());<br>            &#125;<br>        &#125;,<br>        ex -&gt; log.error(<span class="hljs-string">&quot;消息发送异常, ID:&#123;&#125;, 原因&#123;&#125;&quot;</span>,correlationData.getId(),ex.getMessage())<br>    );<br>    <span class="hljs-comment">// 4.发送消息</span><br>    rabbitTemplate.convertAndSend(<span class="hljs-string">&quot;task.direct&quot;</span>, <span class="hljs-string">&quot;task&quot;</span>, message, correlationData);<br><br>    <span class="hljs-comment">// 休眠一会儿，等待ack回执</span><br>    Thread.sleep(<span class="hljs-number">2000</span>);<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="1-2-消息持久化"><a href="#1-2-消息持久化" class="headerlink" title="1.2. 消息持久化"></a>1.2. 消息持久化</h2><p>生产者确认可以确保消息投递到 RabbitMQ 的队列中，但是消息发送到 RabbitMQ 以后，如果突然宕机，也可能导致消息丢失。</p><p>要想确保消息在 RabbitMQ 中安全保存，必须开启消息持久化机制。</p><ul><li>交换机持久化</li><li>队列持久化</li><li>消息持久化</li></ul><p>默认情况下，由 SpringAMQP 声明的<span style="background-color:#ff00ff">交换机、队列、消息都是持久化的</span>。</p><h2 id="1-3-消费者消息确认"><a href="#1-3-消费者消息确认" class="headerlink" title="1.3. 消费者消息确认"></a>1.3. 消费者消息确认</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304194042.png" alt="image.png"></p><h2 id="1-4-消费失败重试机制⭐️🔴"><a href="#1-4-消费失败重试机制⭐️🔴" class="headerlink" title="1.4. 消费失败重试机制⭐️🔴"></a>1.4. 消费失败重试机制⭐️🔴</h2><p>当消费者出现异常后，消息会不断 requeue（重入队）到队列，再重新发送给消费者，然后再次异常，再次 requeue，无限循环，导致 mq 的消息处理飙升，带来不必要的压力</p><h3 id="1-4-1-本地重试"><a href="#1-4-1-本地重试" class="headerlink" title="1.4.1. 本地重试"></a>1.4.1. 本地重试</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304194552.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304194701.png" alt="image.png"></p><p>结论：</p><ul><li>开启本地重试时，消息处理过程中抛出异常，不会 requeue 到队列，而是在消费者本地重试</li><li>重试达到最大次数后，Spring 会返回 ack，消息会被丢弃</li></ul><h3 id="1-4-2-失败策略"><a href="#1-4-2-失败策略" class="headerlink" title="1.4.2. 失败策略"></a>1.4.2. 失败策略</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304195333.png" alt="image.png"></p><p>在之前的测试中，达到最大重试次数后，消息会被丢弃，这是由 Spring 内部机制决定的。<br>在开启重试模式后，重试次数耗尽，如果消息依然失败，<span style="background-color:#ff00ff">则需要有 MessageRecovery 接口来处理</span>，它包含三种不同的实现：</p><ul><li>RejectAndDontRequeueRecoverer：重试耗尽后，直接 reject，丢弃消息。&#x3D;&#x3D;默认就是这种方式&#x3D;&#x3D;</li><li>ImmediateRequeueMessageRecoverer：重试耗尽后，返回 nack，消息重新入队</li><li><span style="background-color:#ff00ff"> RepublishMessageRecoverer：重试耗尽后，将失败消息投递到指定的交换机</span></li></ul><p><span style="background-color:#ff00ff">比较优雅的一种处理方案是 RepublishMessageRecoverer，失败后将消息投递到一个指定的，专门存放异常消息的队列，后续由人工集中处理。</span></p><p><span style="background-color:#ff0000">兜底方案</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307085038.png" alt="image.png"></p><h2 id="1-5-如何确保-RabbitMQ-消息的可靠性⭐️🔴"><a href="#1-5-如何确保-RabbitMQ-消息的可靠性⭐️🔴" class="headerlink" title="1.5. 如何确保 RabbitMQ 消息的可靠性⭐️🔴"></a>1.5. 如何确保 RabbitMQ 消息的可靠性⭐️🔴</h2><p><span style="display:none">%%<br>▶61.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-2008%%</span>❕ ^yw0uls<br><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230402-1954%%</span>❕ ^g43mqv</p><ul><li>开启&#x3D;&#x3D;生产者确认机制&#x3D;&#x3D;，确保生产者的消息能到达队列</li><li>开启&#x3D;&#x3D;持久化&#x3D;&#x3D;功能，确保消息未消费前在队列中不会丢失</li><li>开启&#x3D;&#x3D;消费者确认机制为 auto&#x3D;&#x3D;，由 spring 确认消息处理成功后完成 ack</li><li>开启&#x3D;&#x3D;消费者失败重试机制&#x3D;&#x3D;，并设置 MessageRecoverer，多次重试失败后将消息投递到异常交换机，交由人工处理</li></ul><h3 id="1-5-1-详细说明"><a href="#1-5-1-详细说明" class="headerlink" title="1.5.1. 详细说明"></a>1.5.1. 详细说明</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;005-分布式专题&#x2F;黑马-面试题&#x2F;讲义&#x2F;微服务常见面试题#^fzknz2]]</p><h1 id="2-死信交换机"><a href="#2-死信交换机" class="headerlink" title="2. 死信交换机"></a>2. 死信交换机</h1><h2 id="2-1-什么是死信"><a href="#2-1-什么是死信" class="headerlink" title="2.1. 什么是死信"></a>2.1. 什么是死信</h2><p>当一个队列中的消息满足下列情况之一时，可以成为死信（dead letter）：</p><ul><li>消费者使用 basic.reject 或 basic.nack 声明消费失败，并且消息的 requeue 参数设置为 false</li><li>消息是一个过期消息，超时无人消费</li><li>要投递的队列消息满了，无法投递</li></ul><h2 id="2-2-死信交换机"><a href="#2-2-死信交换机" class="headerlink" title="2.2. 死信交换机"></a>2.2. 死信交换机</h2><p>如果这个包含死信的队列配置了 <code>dead-letter-exchange</code> 属性，指定了一个交换机，那么队列中的死信就会投递到这个交换机中，而这个交换机称为 <strong>死信交换机</strong>（Dead Letter Exchange，检查 DLX）。<br>如图，一个消息被消费者拒绝了，变成了死信：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307080252.png" alt="image-20210718174328383"></p><p>因为 simple.queue 绑定了死信交换机 dl.direct，因此死信会投递给这个交换机：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307080320.png" alt="image-20210718174416160"></p><p>如果这个死信交换机也绑定了一个队列，则消息最终会进入这个存放死信的队列：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307080343.png" alt="image-20210718174506856"></p><p>另外，队列将死信投递给死信交换机时，必须知道两个信息：</p><ul><li>死信交换机名称： <code>dead-letter-exchange</code></li><li>死信交换机与死信队列绑定的 RoutingKey： <code>dead-letter-routing-key</code></li></ul><p>这样才能确保投递的消息能到达死信交换机，并且正确的路由到死信队列。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307080415.png" alt="image-20210821073801398"></p><h2 id="2-3-默认兜底方案与死信队列区别"><a href="#2-3-默认兜底方案与死信队列区别" class="headerlink" title="2.3. 默认兜底方案与死信队列区别"></a>2.3. 默认兜底方案与死信队列区别</h2><p>在失败重试策略中，<span style="background-color:#ff00ff">默认的 RejectAndDontRequeueRecoverer </span>会在本地重试次数耗尽后，发送 reject 给 RabbitMQ，消息变成死信，被丢弃。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304202050.png" alt="image.png"></p><h2 id="2-4-总结"><a href="#2-4-总结" class="headerlink" title="2.4. 总结"></a>2.4. 总结</h2><p><strong>什么样的消息会成为死信？</strong><br><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230307-0809%%</span>❕ ^auuhua</p><ul><li>消息被消费者 reject 或者返回 nack</li><li>消息超时未消费</li><li>队列满了</li></ul><p><strong>死信交换机的使用场景是什么？</strong><br>在失败重试策略中，<span style="background-color:#ff00ff">默认的 RejectAndDontRequeueRecoverer </span>会在本地重试次数耗尽后，发送 reject 给 RabbitMQ，消息变成死信，被丢弃。</p><p>我们可以<span style="background-color:#ff00ff">给 simple.queue 添加一个死信交换机，给死信交换机绑定一个队列</span>。这样消息变成死信后也不会丢弃，而是最终投递到死信交换机，路由到与死信交换机绑定的队列。</p><ul><li>如果队列绑定了死信交换机，死信会投递到死信交换机；</li><li>可以利用死信交换机收集所有消费者处理失败的消息（死信），交由人工处理，进一步提高消息队列的可靠性。</li></ul><h1 id="3-延时队列"><a href="#3-延时队列" class="headerlink" title="3. 延时队列"></a>3. 延时队列</h1><h2 id="3-1-TTL"><a href="#3-1-TTL" class="headerlink" title="3.1. TTL"></a>3.1. TTL</h2><p>一个队列中的消息如果超时未消费，则会变为死信，超时分为两种情况：</p><ul><li>消息所在的队列设置了超时时间</li><li>消息本身设置了超时时间</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304202547.png" alt="image.png"></p><h2 id="3-2-延迟队列⭐️🔴"><a href="#3-2-延迟队列⭐️🔴" class="headerlink" title="3.2. 延迟队列⭐️🔴"></a>3.2. 延迟队列⭐️🔴</h2><p><span style="background-color:#ff00ff">利用 TTL 结合死信交换机</span>，我们实现了消息发出后，消费者延迟收到消息的效果。这种消息模式就称为延迟队列（Delay Queue）模式。</p><p>延迟队列的使用场景包括：</p><ul><li>延迟发送短信</li><li>用户下单，如果用户在 15 分钟内未支付，则自动取消</li><li>预约工作会议，20 分钟后自动通知所有参会人员</li></ul><p>因为延迟队列的需求非常多，所以 RabbitMQ 的官方也推出了一个插件，原生支持延迟队列效果。这个插件就是 DelayExchange 插件。参考 RabbitMQ 的插件列表页面：<a href="https://www.rabbitmq.com/community-plugins.html">https://www.rabbitmq.com/community-plugins.html</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307083410.png" alt="image-20210718192529342"></p><h1 id="4-惰性队列"><a href="#4-惰性队列" class="headerlink" title="4. 惰性队列"></a>4. 惰性队列</h1><h2 id="4-1-消息堆积问题"><a href="#4-1-消息堆积问题" class="headerlink" title="4.1. 消息堆积问题"></a>4.1. 消息堆积问题</h2><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230307-0852%%</span>❕ ^4y1a0a</p><p>当生产者发送消息的速度超过了消费者处理消息的速度，就会导致队列中的消息堆积，直到队列存储消息达到上限。之后发送的消息就会成为死信，可能会被丢弃，这就是消息堆积问题。<br>解决消息堆积有两种思路：</p><ul><li>增加更多消费者，提高消费速度。也就是我们之前说的 work queue 模式</li><li>扩大队列容积，提高堆积上限</li></ul><p>要提升队列容积，把消息保存在内存中显然是不行的。</p><h2 id="4-2-惰性队列"><a href="#4-2-惰性队列" class="headerlink" title="4.2. 惰性队列"></a>4.2. 惰性队列</h2><p><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230307-0909%%</span>❕ ^k34tgi</p><p>从 RabbitMQ 的 3.6.0 版本开始，就增加了 Lazy Queues 的概念，也就是惰性队列。惰性队列的特征如下：</p><ul><li><span style="background-color:#ff00ff">接收到消息后直接存入磁盘而非内存</span></li><li><span style="background-color:#00ff00">消费者要消费消息时才会从磁盘中读取并加载到内存</span></li><li>支持数百万条的消息存储</li></ul><p>消息堆积问题的解决方案？</p><ul><li>队列上绑定多个消费者，提高消费速度</li><li><span style="background-color:#ff00ff">使用惰性队列，可以在 mq 中保存更多消息</span></li></ul><p>惰性队列的优点有哪些？</p><ul><li>基于磁盘存储，消息上限高</li><li>没有间歇性的 page-out，性能比较稳定</li></ul><p>惰性队列的缺点有哪些？</p><ul><li>基于磁盘存储，消息时效性会降低</li><li>性能受限于磁盘的 IO</li></ul><h1 id="5-高可用方案"><a href="#5-高可用方案" class="headerlink" title="5. 高可用方案"></a>5. 高可用方案</h1><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230402-1811%%</span>❕ ^g8fwqy</p><h2 id="5-1-普通集群"><a href="#5-1-普通集群" class="headerlink" title="5.1. 普通集群"></a>5.1. 普通集群</h2><p>RabbitMQ 提供了两种集群模式： 普通集群模式 镜像集群模式 先来看普通集群 这种集群模式下，<span style="background-color:#ff00ff">各个节点只同步元数据，不同步队列中的消息</span>。 其中元数据包含<span style="background-color:#00ff00">队列的名称、交换机名称及属性、交换机与队列的绑定关系</span>等。 当我们发送消息和消费消息的时候，不管请求发送到 RabbitMQ 集群的哪个节点。 <span style="background-color:#ff00ff">最终都会通过元数据定位到队列所在的节点去存储以及拉取数据</span>。 很显然，这种集群方式并不能保证 Queue 的高可用，因为一旦 Queue 所在的节 点挂了，那么这个 Queue 的消息就没办法访问了。 它的好处是通过多个节点分担了流量的压力，提升了消息的吞吐能力。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402180603.png" alt="image.png"></p><h2 id="5-2-镜像集群"><a href="#5-2-镜像集群" class="headerlink" title="5.2. 镜像集群"></a>5.2. 镜像集群</h2><p>它和普通集群的区别在于，镜像集群中 Queue 的数据会在 RabbitMQ 集群的每个节点存储一份。 一旦任意一个节点发生故障，其他节点仍然可以继续提供服务。 所以这种集群模式实现了真正意义上的高可用。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402180800.png" alt="image.png"></p><p>最后，在镜像集群的模式下，我们可以通过 Keepalived+HAProxy 来实现 RabbitMQ 集群的负载均衡。其中： HAProxy 是一个能支持四层和七层的负载均衡器，可以实现对 RabbitMQ 集群的负载均衡同时为了避免 HAProxy 的单点故障，可以再<span style="background-color:#ff00ff">增加 Keepalived 实现 HAProxy 的主备</span>，如果 HAProxy 主节点出现故障那么备份节点就会接管主节点提供服务。 Keepalived 提供了一个虚拟 IP，业务只需要连接到虚拟 IP 即可。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402180900.png" alt="image.png"></p><h1 id="6-面试题"><a href="#6-面试题" class="headerlink" title="6. 面试题"></a>6. 面试题</h1><h2 id="6-1-RabbitMQ-实现高可用"><a href="#6-1-RabbitMQ-实现高可用" class="headerlink" title="6.1. RabbitMQ 实现高可用"></a>6.1. RabbitMQ 实现高可用</h2><p><a href="https://www.bilibili.com/video/BV16d4y1P7ZF/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV16d4y1P7ZF/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="6-2-RabbitMQ-实现可靠消息传递"><a href="#6-2-RabbitMQ-实现可靠消息传递" class="headerlink" title="6.2. RabbitMQ 实现可靠消息传递"></a>6.2. RabbitMQ 实现可靠消息传递</h2><p><a href="https://www.bilibili.com/video/BV14N4y1g7pV/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV14N4y1g7pV/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><a href="/2023/03/08/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RabbitMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="分布式专题-MQ-RabbitMQ-1、基本原理">分布式专题-MQ-RabbitMQ-1、基本原理</a><h2 id="6-3-RabbitMQ-的消息如何实现路由"><a href="#6-3-RabbitMQ-的消息如何实现路由" class="headerlink" title="6.3. RabbitMQ 的消息如何实现路由"></a>6.3. RabbitMQ 的消息如何实现路由</h2><p><a href="https://www.bilibili.com/video/BV1uG411x7r4/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1uG411x7r4/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>RabbitMQ 是一个基于高级消息队列协议 AMQP(<strong>Advanced Message Queuing Protocol</strong>) 协议实现的分布式消息中间件。AMQP 的具体工作机制是，生产者把消息发送到 RabbitMQ Broker 上的 Exchange 交换机上。 Exchange 交换机把收到的消息根据路由规则发给绑定的队列（Queue）。最后再把消息投递给订阅了这个队列的消费者，从而完成消息的异步通讯。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402195709.png" alt="image.png"></p><p>其中，Exchange 是一个消息交换机，它里面定义了消息路由的规则，也就是这个消息路由到那个队列。 然后 Queue 表示消息的载体，每个消息可以根据路由规则路由到一个或者多个队列里面。 而关于消息的路由机制，核心的组件是 Exchange。 它负责接收生产者的消息然后把消息路由到消息队列，而消息的路由规则由 ExchangeType 和 Binding 决定。 <span style="background-color:#ff00ff">Binding 表示建立 Queue 和 Exchange 之间的绑定关系，每一个绑定关系会存在一个 BindingKey。 通过这种方式相当于在 Exchange 中建立了一个路由关系表</span>。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402195732.png" alt="image.png"><br>生产者发送消息的时候，需要声明一个 routingKey（路由键），Exchange 拿到 routingKey 之后，根据 RoutingKey 和路由表里面的 BindingKey 进行匹配，而匹配的规则是通过 ExchangeType 来决定的。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402200026.png" alt="image.png"></p><p>在 RabbitMQ 中，有三种类型的 Exchange：direct ，fanout 和 topic。 direct： 完整匹配方式，也就是 Routing key 和 Binding Key 完全一致，相当于点对点的发送。 fanout： 广播机制，这种方式不会基于 Routing key 来匹配，而是把消息广播给绑定到当前 Exchange 上的所有队列上。 topic： 正则表达式匹配，根据 Routing Key 使用正则表达式进行匹配，符合匹配规则的 Queue 都会收到这个消息</p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-视频"><a href="#8-1-视频" class="headerlink" title="8.1. 视频"></a>8.1. 视频</h2><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4/?p=158&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4/?p=158&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="8-2-资料"><a href="#8-2-资料" class="headerlink" title="8.2. 资料"></a>8.2. 资料</h2><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;002- 框架源码专题&#x2F;001-MQ&#x2F;黑马 MQ 讲义&#x2F;day05-MQ 高级&#x2F;讲义&#x2F;RabbitMQ- 高级篇.md</p>]]></content>
      
      
      <categories>
          
          <category> MQ </category>
          
      </categories>
      
      
        <tags>
            
            <tag> RocketMQ </tag>
            
            <tag> MQ </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-JVM监控及诊断工具-2、GUI篇</title>
      <link href="/2023/03/07/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-JVM%E7%9B%91%E6%8E%A7%E5%8F%8A%E8%AF%8A%E6%96%AD%E5%B7%A5%E5%85%B7-2%E3%80%81GUI%E7%AF%87/"/>
      <url>/2023/03/07/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-JVM%E7%9B%91%E6%8E%A7%E5%8F%8A%E8%AF%8A%E6%96%AD%E5%B7%A5%E5%85%B7-2%E3%80%81GUI%E7%AF%87/</url>
      
        <content type="html"><![CDATA[<hr><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230308191917.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309224231.png" alt="image.png"></p><h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><p><a href="https://www.yuque.com/u21195183/jvm/nkq31c">https://www.yuque.com/u21195183/jvm/nkq31c</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-4、多级缓存</title>
      <link href="/2023/02/28/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-4%E3%80%81%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98/"/>
      <url>/2023/02/28/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-4%E3%80%81%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-什么是多级缓存"><a href="#1-什么是多级缓存" class="headerlink" title="1. 什么是多级缓存"></a>1. 什么是多级缓存</h1><p>黑马资料：[[多级缓存]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301201423.png" alt="image.png"></p><p><a href="https://www.bilibili.com/video/BV1LQ4y127n4?p=157&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1LQ4y127n4?p=157&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301224724.png" alt="image.png"></p><h1 id="2-JVM-进程缓存"><a href="#2-JVM-进程缓存" class="headerlink" title="2. JVM 进程缓存"></a>2. JVM 进程缓存</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301213945.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301214112.png" alt="image.png"></p><h1 id="3-Lua-语法"><a href="#3-Lua-语法" class="headerlink" title="3. Lua 语法"></a>3. Lua 语法</h1><h1 id="4-多级缓存"><a href="#4-多级缓存" class="headerlink" title="4. 多级缓存"></a>4. 多级缓存</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301214112.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301214850.png" alt="image.png"></p><h1 id="5-缓存同步"><a href="#5-缓存同步" class="headerlink" title="5. 缓存同步"></a>5. 缓存同步</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301221517.png" alt="image.png"></p><h2 id="5-1-canal"><a href="#5-1-canal" class="headerlink" title="5.1. canal"></a>5.1. canal</h2><p>[[安装Canal]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230503122655.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230503122729.png" alt="image.png"></p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><h2 id="7-1-黑马程序员"><a href="#7-1-黑马程序员" class="headerlink" title="7.1. 黑马程序员"></a>7.1. 黑马程序员</h2><a href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-5%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1-Ribbon/" title="分布式专题-5、负载均衡-Ribbon">分布式专题-5、负载均衡-Ribbon</a>]]></content>
      
      
      <categories>
          
          <category> 缓存 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 缓存 - 分布式 - 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-10、</title>
      <link href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-10%E3%80%81@Autowired/"/>
      <url>/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-10%E3%80%81@Autowired/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-注册-BPP-的-BD"><a href="#1-注册-BPP-的-BD" class="headerlink" title="1. 注册 BPP 的 BD"></a>1. 注册 BPP 的 BD</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204135249.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204135315.png" alt="image.png"></p><h1 id="2-创建-BPP-Bean"><a href="#2-创建-BPP-Bean" class="headerlink" title="2. 创建 BPP Bean"></a>2. 创建 BPP Bean</h1><p>在 refresh () 的 <code>6. registerBeanPostProcessors ()</code> ，将所有 BeanPostProcessor 注册到容器中。<span style="background-color:#ffff00">注意：只是注册但并未执行</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204140052.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204140008.png" alt="image.png"></p><h1 id="3-预解析"><a href="#3-预解析" class="headerlink" title="3. 预解析"></a>3. 预解析</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205131741.png" alt="image.png"><br>❕<span style="display:none">%%<br>▶1. 🏡⭐️◼️Autowired 注解完成自动装配功能所需要的方法和执行时机是什么 ?🔜MSTM📝 Autowired 自动装配功能是依靠 AutowiredAnnotationBeanPostProcessor 来完成的，这个 BPP 分别实现了 MergedBeanDefinitionPostProcessor 的 postProcessBeanDefinitionRegistry 方法和 InstantiationAwareBeanPostProcessor 的 postProcessProperties 方法，分别完成注解的预解析和装配动作。◼️⭐️-point-20230223-1834%%</span><br>在 refresh () 的 <code>11. finishBeanFactoryInitialization()</code>，在<span style="background-color:#00ff00">实例化之后属性赋值之前</span>，在 <code>MergedBeanDefinitionPostProcessor</code> 的子实现接口 <code>AutowiredAnnotationBeanPostProcessor</code> 的 <code>postProcessMergedBeanDefinition</code> 方法中，解析@ Autowired@Value 转换 InjectionMetadata 并缓存在 <code>injectionMetadataCache</code> 中</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204132524.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204142554.png" alt="image.png"></p><p>findAutowiringMetadata -&gt; buildAutowiringMetadata -&gt;<br>findAutowiredAnnotation-&gt; &#x3D;&#x3D;List&lt;InjectionMetadata.InjectedElement&gt;&#x3D;&#x3D;.add(new AutowiredFieldElement(<span style="background-color:#00ff00">field</span>, required)) ❕<span style="display:none">%%<br>0627-🏡⭐️◼️预解析动作 ?🔜MSTM📝 最终封装的是带有 Autowired 注解的属性的 field 和它的 required 属性，封装为 injectionMetadata 放到缓存中备用◼️⭐️-point-202302100627%%</span></p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;lianpengju&#x2F;spring-beans&#x2F;src&#x2F;main&#x2F;java&#x2F;org&#x2F;springframework&#x2F;beans&#x2F;factory&#x2F;annotation&#x2F;AutowiredAnnotationBeanPostProcessor.java]]</p><h1 id="4-自动装配"><a href="#4-自动装配" class="headerlink" title="4. 自动装配"></a>4. 自动装配</h1><p>在 refresh () 的 <code>11. finishBeanFactoryInitialization()</code> 中，<code>applyPropertyValues</code> <span style="background-color:#ffff00">真正赋值之前</span>，在 <code>InstantiationAwareBeanPostProcessor</code> 的子实现接口 <code>AutowiredAnnotationBeanPostProcessor</code> 的 (<del><code>postProcessPropertyValues</code></del>) <code>postProcessProperties</code> 方法中完成 bean 中@Autowired，@Inject，@Value 注解的解析并注入：</p><blockquote><p>但我们发现被废弃的方法 <code>postProcessPropertyValues</code> 并没有被执行，这个方法是完全没有被使用了吗？其实不是的，当 <code>postProcessProperties</code> 方法的返回值为 <code>null</code> 时，被废弃的方法 <code>postProcessPropertyValues</code> 就会被执行。</p></blockquote><p>postProcessProperties-&gt;findAutowiringMetadata-&gt;metadata.inject(bean, beanName, pvs)-&gt;beanFactory.resolveDependency</p><ol><li>获取依赖的 value 值的工作最终还是委托给 <code>beanFactory.resolveDependency()</code> 去完成的</li><li>这个接口方法由 AutowireCapableBeanFactory 提供，它提供了从 bean 工厂里获取依赖值的能力</li><li>resolveDependency 方法<span style="background-color:#ff00ff">根据 descriptor 的依赖类型解析出与 descriptor 所包装的对象匹配的候选 Bean 对象</span><br>❕<span style="display:none">%%<br>▶2. 🏡⭐️◼️为什么 Autowired 默认是按类型匹配的 ?🔜MSTM📝 因为在获取属性时使用的方法是 resolveDependency，这个方法是按类型从容器中获取对象的方法◼️⭐️-point-20230223-1830%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205125536.png" alt="image.png"></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204142632.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204143946.png" alt="image.png"></p><p><code>beanFactory.resolveDependency</code> 根据 descriptor 的<span style="background-color:#ff00ff">依赖类型解析</span>出与 descriptor 所包装的对象匹配的候选 Bean 对象 ^ac1rnt</p><p><span style="background-color:#00ff00">当容器扫描到@Autowied、@Resource 或@Inject 时，就会在 IOC 容器自动查找需要的 bean，并装配给该对象的属性</span>。在使用@Autowired 时，首先在容器中查询<span style="background-color:#ff00ff">对应类型的 bean</span>：<br>如果查询结果刚好为一个，就将该 bean 装配给@Autowired 指定的数据；<br>如果查询的结果不止一个，那么@Autowired 会<span style="background-color:#ff00ff">根据名称来查找</span>；<br>如果上述查找的结果为空，那么会抛出异常。解决方法时，使用 required&#x3D;false。<br>❕<span style="display:none">%%<br>▶6.🏡⭐️◼️Autowired 默认按类型查找并装配，如果多余 1 个，那么会按名字，如果没有就报错◼️⭐️-point-20230223-1951%%</span></p><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/" title="1、Spring-基础">1、Spring-基础</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204135000.png" alt="image.png"></p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> Autowired </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>1</title>
      <link href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-11%E3%80%81@PostConstruct/"/>
      <url>/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-11%E3%80%81@PostConstruct/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-注册-BPP-的-BD"><a href="#1-注册-BPP-的-BD" class="headerlink" title="1. 注册 BPP 的 BD"></a>1. 注册 BPP 的 BD</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204135249.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205134327.png" alt="image.png"></p><h1 id="2-创建-BPP"><a href="#2-创建-BPP" class="headerlink" title="2. 创建 BPP"></a>2. 创建 BPP</h1><p>在 refresh () 的 <code>6. registerBeanPostProcessors ()</code> ，将所有 BeanPostProcessor 注册到容器中。<span style="background-color:#ffff00">注意：只是注册但并未执行</span> ❕<span style="display:none">%%<br>1926-🏡⭐️◼️CommonAnnotationBeanPostProcessor 与 AutowiredAnnotationBeanPostProcessor ?🔜MSTM📝 都实现了 MergedBeanDefinitionPostProcessor，在 invokeBeanPostProcessor 中创建 BPP，并执行 postProcessMergedBeanDefinition 方法◼️⭐️-point-202302061926%%</span></p><h1 id="3-LifecycleMetadata-存入"><a href="#3-LifecycleMetadata-存入" class="headerlink" title="3. LifecycleMetadata 存入"></a>3. LifecycleMetadata 存入</h1><p><span style="display:none">%%<br>▶31.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-1910%%</span>❕ ^er3o6i</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221075849.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205141537.png" alt="image.png"></p><p>执行 <code>CommonAnnotationBeanPostProcessor</code> 的方法 <code>postProcessMergedBeanDefinition</code> 实际调用了其父类 <code>InitDestroyAnnotationBeanPostProcessor</code> 的方法，在其中调用方法 <code>findLifecycleMetadata</code> 获取生命周期元数据并保存</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201162515.png" alt="image.png"><br>❕<span style="display:none">%%<br>0914-🏡⭐️◼️CommonAnnotationBeanPostProcessor 中用到了双重检查锁 ?🔜MSTM📝 在第 11 步初始化剩下的单实例 Bean 的过程中，在真正初始化方法调用之前，InitDestroyAnnotationBeanPostProcessor 继承了 BeanPostProcessor，在执行 postProcessBeforeInitialization 方法时，会查找 lifeCycleMetadataCache，这里就用到了双重检查锁。其实在存入的时候，在第三个后置处理器，MergedBeanDefinitionPostProcessor 的 postProcessMergedBeanDefinition 方法中，也是调用了同一个方法，即 findLifecycleMetadata◼️⭐️-point-202302120914%%</span></p><h1 id="4-LifecycleMetadata-使用"><a href="#4-LifecycleMetadata-使用" class="headerlink" title="4. LifecycleMetadata 使用"></a>4. LifecycleMetadata 使用</h1><p><span style="display:none">%%<br>▶34.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-2007%%</span>❕ ^0ty81n</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205141630.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201163833.png" alt="image.png"></p><p>在 <code>InitDestroyAnnotationBeanPostProcessor</code>. <code>postProcessBeforeInitialization</code></p><p><span style="background-color:#ff00ff">findLifecycleMetadata–&gt;metadata.invokeInitMethods</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201163620.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201164107.png" alt="image.png"></p><h1 id="5-执行顺序"><a href="#5-执行顺序" class="headerlink" title="5. 执行顺序"></a>5. 执行顺序</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201140155.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201140244.png" alt="image.png"></p><p><span style="background-color:#ff00ff">注解版@PostConstruct 在初始化之前，而 XML 方式是在初始化之后，AfterPropertiesSet 之后</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201140329.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201140345.png" alt="image.png"><br><a href="https://www.bilibili.com/video/BV1CS4y1e7xo/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1CS4y1e7xo/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> PostConstruct </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-4、锁</title>
      <link href="/2023/02/15/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-4%E3%80%81%E9%94%81/"/>
      <url>/2023/02/15/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-4%E3%80%81%E9%94%81/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-行锁、记录锁、临键锁、间隙锁"><a href="#1-行锁、记录锁、临键锁、间隙锁" class="headerlink" title="1. 行锁、记录锁、临键锁、间隙锁"></a>1. 行锁、记录锁、临键锁、间隙锁</h1><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230412-1210%%</span>❕ ^vafwau</p><p>记录锁、临键锁、间隙锁，都是 Mysql 里面 InnoDB 引擎下解决事务隔离性的一系列排他锁。</p><p>行级锁的种类主要有三类：</p><ul><li>Record Lock，记录锁，也就是<span style="background-color:#ff00ff">仅仅把一条记录锁上</span>；</li><li>Gap Lock，间隙锁，<span style="background-color:#ff00ff">锁定一个范围，但是不包含记录本身</span>；</li><li>Next-Key Lock：Record Lock + Gap Lock 的组合，<span style="background-color:#ff00ff">锁定一个范围，并且锁定记录本身</span>。</li></ul><h2 id="1-1-查看命令"><a href="#1-1-查看命令" class="headerlink" title="1.1. 查看命令"></a>1.1. 查看命令</h2><p>有什么命令可以分析加了什么锁</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</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> performance_schema.data_locks\G;<br></code></pre></td></tr></table></figure><p>这条语句，查看事务执行 SQL 过程中加了什么锁。</p><h2 id="1-2-记录锁-主键或者唯一索引"><a href="#1-2-记录锁-主键或者唯一索引" class="headerlink" title="1.2. 记录锁 - 主键或者唯一索引"></a>1.2. 记录锁 - 主键或者唯一索引</h2><p>当我们针对主键或者唯一索引加锁的时候，Mysql 默认会对查询的这一行数据加行锁，避免其他事务对这一行数据进行修改。<br>锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的：</p><ol><li>当一个事务对一条记录加了 S 型记录锁后，其他事务也可以继续对该记录加 S 型记录锁（S 型与 S 锁兼容），但是不可以对该记录加 X 型记录锁（S 型与 X 锁不兼容）;</li><li>当一个事务对一条记录加了 X 型记录锁后，其他事务既不可以对该记录加 S 型记录锁（S 型与 X 锁不兼容），也不可以对该记录加 X 型记录锁（X 型与 X 锁不兼容）。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412114831.png" alt="image.png"></li></ol><h2 id="1-3-间隙锁-普通索引或者唯一索引-左右开区间"><a href="#1-3-间隙锁-普通索引或者唯一索引-左右开区间" class="headerlink" title="1.3. 间隙锁 - 普通索引或者唯一索引 - 左右开区间"></a>1.3. 间隙锁 - 普通索引或者唯一索引 - 左右开区间</h2><p>顾名思义，就是锁定一个索引区间。 在<span style="background-color:#ff00ff">普通索引或者唯一索引</span>列上，由于索引是基于 B+ 树的结构存储，所以默认会存在一个索引区间。 而间隙锁，就是某个事物对索引列加锁的时候，默认锁定对应索引的左右开区间范围。</p><p>基于主键的插入操作<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412115031.png" alt="image.png"></p><h2 id="1-4-临键锁-非唯一索引-左开右闭"><a href="#1-4-临键锁-非唯一索引-左开右闭" class="headerlink" title="1.4. 临键锁 - 非唯一索引 - 左开右闭"></a>1.4. 临键锁 - 非唯一索引 - 左开右闭</h2><p>它相当于行锁 + 间隙锁的组合，也就是它的锁定范围既包含了索引记录，也包含了索引区间，它会锁定一个左开右闭区间的数据范围。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412115225.png" alt="image.png"><br>假设我们<span style="background-color:#ff00ff">使用非唯一索引列进行查询</span>的时候，默认会加一个临键锁，锁定一个<span style="background-color:#ff00ff">左开右闭</span>区间的范围。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412115350.png" alt="image.png"></p><p><span style="background-color:#ff00ff">非唯一索引和主键索引的范围查询的加锁也有所不同，不同之处在于 <strong>非唯一索引范围查询，索引的 next-key lock 不会有退化为间隙锁和记录锁的情况</strong>，也就是非唯一索引进行范围查询时，对二级索引记录加锁都是加 next-key 锁。</span></p><h2 id="1-5-加锁规则"><a href="#1-5-加锁规则" class="headerlink" title="1.5. 加锁规则"></a>1.5. 加锁规则</h2><h3 id="1-5-1-唯一索引等值查询"><a href="#1-5-1-唯一索引等值查询" class="headerlink" title="1.5.1. 唯一索引等值查询"></a>1.5.1. 唯一索引等值查询</h3><p>当我们用唯一索引进行等值查询的时候，查询的记录存不存在，加锁的规则也会不同：</p><ul><li>当查询的记录是「存在」的，在索引树上定位到这一条记录后，将该记录的索引中的 next-key lock 会 <strong>退化成「记录锁」</strong>。</li><li>当查询的记录是「不存在」的，<span style="background-color:#ff00ff">在索引树找到第一条大于该查询记录的记录</span>后，将该记录的索引中的 next-key lock 会 <strong>退化成「间隙锁」</strong>。<span style="background-color:#ff00ff">左右开区间</span></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412155337.png" alt="image.png"></p><h3 id="1-5-2-唯一索引范围查询"><a href="#1-5-2-唯一索引范围查询" class="headerlink" title="1.5.2. 唯一索引范围查询"></a>1.5.2. 唯一索引范围查询</h3><p>范围查询和等值查询的加锁规则是不同的。</p><p>当唯一索引进行范围查询时，<strong>会对每一个扫描到的索引加 next-key 锁，然后如果遇到某些情况，会退化成记录锁或者间隙锁</strong></p><h3 id="1-5-3-非唯一索引等值查询"><a href="#1-5-3-非唯一索引等值查询" class="headerlink" title="1.5.3. 非唯一索引等值查询"></a>1.5.3. 非唯一索引等值查询</h3><p>当我们用非唯一索引进行等值查询的时候，<strong>因为存在两个索引，一个是主键索引，一个是非唯一索引（二级索引），所以在加锁时，同时会对这两个索引都加锁，但是对主键索引加锁的时候，只有满足查询条件的记录才会对它们的主键索引加锁</strong>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412161320.png" alt="image.png"><br><code>35丨记一次线上SQL死锁事故：如何避免死锁</code> 例子中描述不对，待验证<br><span style="display:none"></p><ul><li><input disabled="" type="checkbox"> 🚩 - 待验证非唯一索引的等值查询的加锁逻辑 - 🏡 2023-04-12 16:14</span></li></ul><p>#todo</p><h3 id="1-5-4-非唯一索引范围查询"><a href="#1-5-4-非唯一索引范围查询" class="headerlink" title="1.5.4. 非唯一索引范围查询"></a>1.5.4. 非唯一索引范围查询</h3><p>非唯一索引和主键索引的范围查询的加锁也有所不同，不同之处在于 <strong>非唯一索引范围查询，索引的 next-key lock 不会有退化为间隙锁和记录锁的情况</strong>，<span style="background-color:#ff00ff">也就是非唯一索引进行范围查询时，对二级索引记录加锁都是加 next-key 锁</span>。</p><h3 id="1-5-5-没有加索引的查询-全表扫描"><a href="#1-5-5-没有加索引的查询-全表扫描" class="headerlink" title="1.5.5. 没有加索引的查询 - 全表扫描"></a>1.5.5. 没有加索引的查询 - 全表扫描</h3><p>前面的案例，我们的查询语句都有使用索引查询，也就是查询记录的时候，是通过索引扫描的方式查询的，然后对扫描出来的记录进行加锁。</p><p><strong>如果锁定读查询语句，没有使用索引列作为查询条件，或者查询语句没有走索引查询，导致扫描是全表扫描。那么，每一条记录的索引上都会加 next-key 锁，这样就相当于锁住的全表，这时如果其他事务对该表进行增、删、改操作的时候，都会被阻塞</strong>。</p><p>不只是锁定读查询语句不加索引才会导致这种情况，update 和 delete 语句如果查询条件不加索引，那么由于扫描的方式是全表扫描，于是就会对每一条记录的索引上都会加 next-key 锁，这样就相当于锁住的全表。</p><p>因此，<strong>在线上在执行 update、delete、select … for update 等具有加锁性质的语句，一定要检查语句是否走了索引，如果是全表扫描的话，会对每一个索引加 next-key 锁，相当于把整个表锁住了</strong>，这是挺严重的问题。</p><h2 id="1-6-总结⭐️🔴"><a href="#1-6-总结⭐️🔴" class="headerlink" title="1.6. 总结⭐️🔴"></a>1.6. 总结⭐️🔴</h2><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230412-1557%%</span>❕ ^me3j71</p><p>这次我以 <strong>MySQL 8.0.26</strong> 版本，在可重复读隔离级别之下，做了几个实验，让大家了解了唯一索引和非唯一索引的行级锁的加锁规则。</p><h3 id="1-6-1-唯一索引等值查询"><a href="#1-6-1-唯一索引等值查询" class="headerlink" title="1.6.1. 唯一索引等值查询"></a>1.6.1. 唯一索引等值查询</h3><ul><li>当查询的记录是「存在」的，在索引树上定位到这一条记录后，将该记录的索引中的 next-key lock 会 <strong>退化成「记录锁」</strong>。</li><li>当查询的记录是「不存在」的，<span style="background-color:#ff00ff">在索引树找到第一条大于该查询记录的记录后</span>，将该记录的索引中的 next-key lock 会 <strong>退化成「间隙锁」</strong>。<span style="background-color:#ff00ff">左右开区间</span></li></ul><h3 id="1-6-2-非唯一索引等值查询"><a href="#1-6-2-非唯一索引等值查询" class="headerlink" title="1.6.2. 非唯一索引等值查询"></a>1.6.2. 非唯一索引等值查询</h3><ul><li>当查询的记录<span style="background-color:#00ff00">「存在」</span>时，由于不是唯一索引，所以肯定存在索引值相同的记录，于是非唯一索引等值查询的过程是一个扫描的过程，直到扫描到第一个不符合条件的二级索引记录就停止扫描，然后 <strong>在扫描的过程中，对扫描到的二级索引记录加的是 next-key 锁，而对于第一个不符合条件的二级索引记录，该二级索引的 next-key 锁会退化成间隙锁。同时，在符合查询条件的记录的主键索引上加记录锁</strong></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412172444.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412172024.png" alt="image.png"></p><ul><li><p>当查询的记录<span style="background-color:#ffff00">「不存在」</span>时，<strong>扫描到第一条不符合条件的二级索引记录，该二级索引的 next-key 锁会退化成间隙锁。因为不存在满足查询条件的记录，所以不会对主键索引加锁</strong>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230412161539.png" alt="image.png"></p></li></ul><h3 id="1-6-3-唯一索引和非唯一索引的范围查询的加锁规则"><a href="#1-6-3-唯一索引和非唯一索引的范围查询的加锁规则" class="headerlink" title="1.6.3. 唯一索引和非唯一索引的范围查询的加锁规则"></a>1.6.3. 唯一索引和非唯一索引的范围查询的加锁规则</h3><ul><li>唯一索引在满足一些条件的时候，索引的 next-key lock 退化为间隙锁或者记录锁。</li><li>非唯一索引范围查询，索引的 next-key lock 不会退化为间隙锁和记录锁。</li></ul><p>其实理解 MySQL 为什么要这样加锁，主要要以避免幻读角度去分析，这样就很容易理解这些加锁的规则了。</p><h3 id="1-6-4-具有加锁性质的语句"><a href="#1-6-4-具有加锁性质的语句" class="headerlink" title="1.6.4. 具有加锁性质的语句"></a>1.6.4. 具有加锁性质的语句</h3><p>还有一件很重要的事情，在线上在执行 update、delete、<span style="background-color:#ffff00">select … for update</span> 等具有加锁性质的语句，<span style="background-color:#ff00ff">一定要检查语句是否走了索引</span>，<strong>如果是全表扫描的话，会对每一个索引加 next-key 锁，相当于把整个表锁住了</strong>，这是挺严重的问题。</p><p>所以总的来说，行锁、临键锁、间隙锁只是表示锁定数据的范围，最终目的是为了解决幻读的问题。而临键锁相当于行锁 + 间隙锁，因此当我们使用非唯一索引进行精准匹配的时候，会默认加临键锁，因为它需要锁定匹配的这一行数据，还需要锁定这一行数据对应的左开右闭区间。<br>因此在实际应用中，<span style="background-color:#ff00ff">尽可能使用唯一索引或者主键索引进行查询，避免大面积的 锁定造成性能影响</span>。</p><h1 id="2-意向锁⭐️🔴"><a href="#2-意向锁⭐️🔴" class="headerlink" title="2. 意向锁⭐️🔴"></a>2. 意向锁⭐️🔴</h1><p><a href="https://juejin.cn/post/6844903666332368909">https://juejin.cn/post/6844903666332368909</a></p><h2 id="2-1-是一种表锁"><a href="#2-1-是一种表锁" class="headerlink" title="2.1. 是一种表锁"></a>2.1. 是一种表锁</h2><p>InnoDB 支持 <code>多粒度锁（multiple granularity locking）</code>，它允许 <code>行级锁</code> 与 <code>表级锁</code> 共存，而 <strong>意向锁</strong> 就是其中的一种 <code>不与行级锁冲突表级锁</code>。</p><h2 id="2-2-类型：IS、IX"><a href="#2-2-类型：IS、IX" class="headerlink" title="2.2. 类型：IS、IX"></a>2.2. 类型：IS、IX</h2><p>意向锁分为两种：</p><ul><li><p><strong>意向共享锁</strong>（intention shared lock, IS）：事务有意向对表中的某些行加 <strong>共享锁</strong>（S 锁）</p><figure class="highlight sql"><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 sql"><span class="hljs-comment">-- 事务要获取某些行的 S 锁，必须先获得表的 IS 锁。</span><br><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">column</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">table</span> ... LOCK <span class="hljs-keyword">IN</span> SHARE MODE;<br></code></pre></td></tr></table></figure></li><li><p><strong>意向排他锁</strong>（intention exclusive lock, IX）：事务有意向对表中的某些行加 <strong>排他锁</strong>（X 锁）</p><figure class="highlight sql"><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 sql"><span class="hljs-comment">-- 事务要获取某些行的 X 锁，必须先获得表的 IX 锁。</span><br><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">column</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">table</span> ... <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">UPDATE</span>;<br></code></pre></td></tr></table></figure></li></ul><h2 id="2-3-谁来维护-InooDB-自动加"><a href="#2-3-谁来维护-InooDB-自动加" class="headerlink" title="2.3. 谁来维护 -InooDB 自动加"></a>2.3. 谁来维护 -InooDB 自动加</h2><p><code>意向锁是有数据引擎自己维护的，用户无法手动操作意向锁</code>。用户加锁时，InooDB 在给数据行加共享 &#x2F; 排他锁之前，<span style="background-color:#ff0000">会先获取该数据行所在数据表的对应意向锁</span>。</p><h2 id="2-4-特性-除所有行级锁及共享表锁，其他都互斥"><a href="#2-4-特性-除所有行级锁及共享表锁，其他都互斥" class="headerlink" title="2.4. 特性 - 除所有行级锁及共享表锁，其他都互斥"></a>2.4. 特性 - 除所有行级锁及共享表锁，其他都互斥</h2><ol><li>InnoDB 支持 <code>多粒度锁</code>，特定场景下，行级锁可以与表级锁共存。</li><li>意向锁之间互不排斥，但除了 IS 与 S 兼容外，<code>意向锁会与 共享锁 / 排他锁 互斥</code></li><li>IX、IS 是表级锁，不会和行级的 X、S 锁发生冲突。只会和表级的 X、S 发生冲突。</li><li>意向锁在保证并发性的前提下，实现了 <code>行锁和表锁共存</code> 且 <code>满足事务隔离性</code> 的要求。</li></ol><h2 id="2-5-作用-锁粗化提高互斥判断性能"><a href="#2-5-作用-锁粗化提高互斥判断性能" class="headerlink" title="2.5. 作用 - 锁粗化提高互斥判断性能"></a>2.5. 作用 - 锁粗化提高互斥判断性能</h2><p>事务 A 获取了某一行的排他锁，并未提交：</p><figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs n1ql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> users <span class="hljs-keyword">WHERE</span> id = <span class="hljs-number">6</span> <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">UPDATE</span>;<br></code></pre></td></tr></table></figure><p>事务 B 想要获取 <code>users</code> 表的表锁：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs pgsql"><span class="hljs-keyword">LOCK</span> <span class="hljs-keyword">TABLES</span> users <span class="hljs-keyword">READ</span>;<br></code></pre></td></tr></table></figure><p><span style="background-color:#ff00ff">因为共享锁与排他锁 <code>互斥</code>，所以事务 B 在试图对 <code>users</code> 表加共享锁的时候，必须保证：</span></p><p><span style="background-color:#ff00ff">- 当前没有其他事务持有 users 表的排他锁。</span><br><span style="background-color:#ff00ff">- 当前没有其他事务持有 users 表中任意一行的排他锁 。</span></p><p><span style="background-color:#ff00ff">为了检测是否满足第二个条件，事务 B 必须在确保 <code>users</code> 表不存在任何排他锁的前提下，去检测表中的每一行是否存在排他锁。很明显这是一个效率很差的做法！</span></p><p>但是有了 <strong>意向锁</strong> 之后，情况就不一样了：</p><p><strong>意向锁之间是互相兼容的</strong><br>虽然意向锁和自家兄弟互相兼容，但是它会与普通的 <strong>排他 &#x2F; 共享锁</strong> 互斥：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230414084819.png" alt="image.png"></p><p><strong>注意：<br>这里的排他 &#x2F; 共享锁指的都是表锁！！！<br>意向锁不会与行级的共享 &#x2F; 排他锁互斥！！！</strong></p><p>现在我们回到刚才 <code>users</code> 表的例子：<br><code>事务 A</code> 获取了某一行的排他锁，并未提交：</p><figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs n1ql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> users <span class="hljs-keyword">WHERE</span> id = <span class="hljs-number">6</span> <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">UPDATE</span>;<br></code></pre></td></tr></table></figure><p><span style="background-color:#ff00ff">此时 <code>users</code> 表存在两把锁：<code>users</code> 表上的 意向排他锁 与 id 为 6 的数据行上的 排他锁。</span></p><p>事务 B 想要获取 users <span style="background-color:#00ff00">表的共享锁</span>：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs pgsql"><span class="hljs-keyword">LOCK</span> <span class="hljs-keyword">TABLES</span> users <span class="hljs-keyword">READ</span>;<br></code></pre></td></tr></table></figure><p>此时 <code>事务 B</code> 检测事务 A 持有 <code>users</code> 表的 <strong>意向排他锁</strong>，就可以得知 <code>事务 A</code> 必然持有该表中某些数据行的 <strong>排他锁</strong>，那么 <code>事务 B</code> 对 <code>users</code> 表的加锁请求就会被排斥（阻塞），<span style="background-color:#ff00ff">而无需去检测表中的每一行数据是否存在排他锁</span>。</p><h1 id="3-插入意向锁"><a href="#3-插入意向锁" class="headerlink" title="3. 插入意向锁"></a>3. 插入意向锁</h1><p><a href="https://juejin.cn/post/6844903666856493064#comment">https://juejin.cn/post/6844903666856493064#comment</a></p><p>总结一下：</p><ol><li>插入意向锁虽然名字中有意向二字，但实际上是一个特殊的间隙锁。</li><li>插入意向锁之间不互斥。</li><li>插入意向锁和排他锁之间互斥。</li></ol><h1 id="4-SQL-死锁"><a href="#4-SQL-死锁" class="headerlink" title="4. SQL 死锁"></a>4. SQL 死锁</h1><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230415-2110%%</span>❕ ^q4zhga</p><h2 id="4-1-插入意向锁-死锁分析"><a href="#4-1-插入意向锁-死锁分析" class="headerlink" title="4.1. 插入意向锁 - 死锁分析"></a>4.1. 插入意向锁 - 死锁分析</h2><p><a href="https://toutiao.io/posts/e3xkkv/preview">https://toutiao.io/posts/e3xkkv/preview</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230523130729.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230523130836.png" alt="image.png"></p><ul><li>不带插入意向锁标记的间隙锁（不包括 next 锁）不受任何锁阻挡</li><li>间隙锁（不包括 next 锁）只阻挡插入意向锁</li><li>行锁不阻挡间隙锁（不包括 next 锁）</li><li>插入意向锁不阻挡任何锁（由于第一个 case 已经对间隙锁做过特殊处理，与第一条结论不冲突）</li></ul><h2 id="4-2-死锁案例⭐️🔴"><a href="#4-2-死锁案例⭐️🔴" class="headerlink" title="4.2. 死锁案例⭐️🔴"></a>4.2. 死锁案例⭐️🔴</h2><h3 id="4-2-1-同一张表插入语句遇到间隙锁"><a href="#4-2-1-同一张表插入语句遇到间隙锁" class="headerlink" title="4.2.1. 同一张表插入语句遇到间隙锁"></a>4.2.1. 同一张表插入语句遇到间隙锁</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230506184911.png" alt="image.png"></p><p>两个事务即使生成的间隙锁的范围是一样的，也不会发生冲突，因为间隙锁目的是为了防止其他事务插入数据，因此间隙锁与间隙锁之间是相互兼容的。</p><p>在执行插入语句时，如果插入的记录在其他事务持有间隙锁范围内，插入语句就会被阻塞，<span style="background-color:#ff00ff">因为插入语句在碰到间隙锁时，会生成一个插入意向锁，然后插入意向锁和间隙锁之间是互斥的关系</span>。</p><p>如果两个事务分别向对方持有的间隙锁范围内插入一条记录，而插入操作为了获取到插入意向锁，都在等待对方事务的间隙锁释放，于是就造成了循环等待，满足了死锁的四个条件：<strong>互斥、占有且等待、不可强占用、循环等待</strong>，因此发生了死锁。</p><p><a href="https://xiaolincoding.com/mysql/lock/show_lock.html#time-3-%E9%98%B6%E6%AE%B5%E5%8A%A0%E9%94%81%E5%88%86%E6%9E%90">字节面试：加了什么锁，导致死锁的？</a></p><h3 id="4-2-2-幂等性校验的一张表经常出现死锁异常"><a href="#4-2-2-幂等性校验的一张表经常出现死锁异常" class="headerlink" title="4.2.2. 幂等性校验的一张表经常出现死锁异常"></a>4.2.2. 幂等性校验的一张表经常出现死锁异常</h3><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">/Users/Enterprise/<span class="hljs-number">0003</span>-Architecture/架构师之路/JVM-GC-调优专题/<span class="hljs-number">78</span>-Java性能调优实战（完结）/<span class="hljs-number">07</span>-模式六.数据库性能调优/<span class="hljs-number">35</span>丨记一次线上SQL死锁事故：如何避免死锁？.html<br></code></pre></td></tr></table></figure><h3 id="4-2-3-其他案例"><a href="#4-2-3-其他案例" class="headerlink" title="4.2.3. 其他案例"></a>4.2.3. 其他案例</h3><p><a href="https://juejin.cn/post/7160649681578491940">https://juejin.cn/post/7160649681578491940</a></p><h2 id="4-3-如何定位死锁"><a href="#4-3-如何定位死锁" class="headerlink" title="4.3. 如何定位死锁"></a>4.3. 如何定位死锁</h2><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">select * from performance_schema.data_locks\G;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230506200320.png" alt="image.png"></p><p>可以看到一直是 waiting 状态</p><h2 id="4-4-防止死锁⭐️🔴"><a href="#4-4-防止死锁⭐️🔴" class="headerlink" title="4.4. 防止死锁⭐️🔴"></a>4.4. 防止死锁⭐️🔴</h2><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230412-1631%%</span>❕ ^thujzj</p><ol><li><p>整体上：在允许幻读和不可重复读的情况下，尽量使用 RC 事务隔离级别，可以避免 gap lock 导致的死锁问题；</p></li><li><p>更新顺序：</p><p>   <strong>记录：</strong><br>在编程中尽量按照固定的顺序来处理数据库记录，假设有两个更新操作，分别更新两条相同的记录，但更新顺序不一样，有可能导致死锁；</p></li></ol><p><strong>表：</strong></p><blockquote><p>   用户 A 访问表 A（锁住了表 A），然后又访问表 B；另一个用户 B 访问表 B（锁住了表 B），然后企图访问表 A；这时用户 A 由于用户 B 已经锁住表 B，它必须等待用户 B 释放表 B 才能继续，同样用户 B 要等用户 A 释放表 A 才能继续，这就死锁就产生了。</p><p>用户 A–》A 表（表锁）–》B 表（表锁）<br>用户 B–》B 表（表锁）–》A 表（表锁）</p></blockquote><ol start="3"><li>更新表时，尽量<span style="background-color:#ff00ff">使用主键更新</span>；</li><li>避免长事务，尽量将长事务拆解，可以降低与其它事务发生冲突的概率；</li><li>设置锁等待超时参数，我们可以通过 <code>innodb_lock_wait_timeout</code> 设置合理的等待超时阈值，特别是在一些高并发的业务中，我们可以尽量将该值设置得小一些，避免大量事务等待，占用系统资源，造成严重的性能开销。</li><li><span style="background-color:#ff00ff">MySQL 默认开启了死锁检测机制</span>，当检测到死锁后会选择一个最小 (锁定资源最少得事务) 的事务进行回滚。<code>innodb_deadlock_detect = on</code> 打开死锁检测</li></ol><h2 id="4-5-解除死锁"><a href="#4-5-解除死锁" class="headerlink" title="4.5. 解除死锁"></a>4.5. 解除死锁</h2><p>1.查询是否锁表</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">show OPEN TABLES where In_use &gt; 0;<br></code></pre></td></tr></table></figure><p>2.查询进程（如果您有 SUPER 权限，您可以看到所有线程。否则，您只能看到您自己的线程）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">show processlist<br></code></pre></td></tr></table></figure><p>3.杀死进程 id（就是上面命令的 id 列）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">kill id<br></code></pre></td></tr></table></figure><p>其它关于查看死锁的命令：</p><blockquote><p>1：查看当前的事务<br>SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;</p><p>2：查看当前锁定的事务</p><p>SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;</p><p>3：查看当前等锁的事务<br>SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;</p></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230523125700.png" alt="image.png"></p><p>死锁解决<br>方案如下几种选择：</p><ol><li>不采用事务包装这部分逻辑，本文实际业务场景中可以不需要事务，所以直接取消事务包装即可，采用 insert ON DUPLICATE KEY UPDATE 的方式</li><li>调整事务隔离级别为 read commit，RC 级别不会产生 gap lock</li><li>利用分布式锁</li></ol><blockquote><p>附：排查过程<br>查看死锁日志，注意这里并不会包含整个事务的相关 sql，仅仅会把等待锁的 SQL 打印出来，死锁日志内容含义参考 ： <a href="http://blog.itpub.net/22664653/viewspace-2145133/">http://blog.itpub.net/22664653/viewspace-2145133/</a><br>根据服务异常 log 定位到具体事务执行代码，找出该事务相关的 sql<br>根据积累的经验知识分析加锁、锁等待情况，找出死锁原因</p></blockquote><p><a href="https://blog.51cto.com/u_14286115/3906999">https://blog.51cto.com/u_14286115/3906999</a></p><h2 id="4-6-线程死锁"><a href="#4-6-线程死锁" class="headerlink" title="4.6. 线程死锁"></a>4.6. 线程死锁</h2><a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-9%E3%80%81Java%E5%90%84%E7%A7%8D%E9%94%81/" title="并发基础-9、Java各种锁">并发基础-9、Java各种锁</a><h1 id="5-InnoDB-的行锁优化"><a href="#5-InnoDB-的行锁优化" class="headerlink" title="5. InnoDB 的行锁优化"></a>5. InnoDB 的行锁优化</h1><p>InnoDB 存储引擎由于实现了行级锁定，虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高一些，但是在整体并发处理能力方面要远远高于 MyISAM 的表锁的。当系统并发量较高的时候，InnoDB 的整体性能和 MyISAM 相比就会有比较明显的优势。<br>但是，InnoDB 的行级锁同样也有其脆弱的一面，当我们使用不当的时候，可能会让 InnoDB 的整体性能表现不仅不能比 MyISAM 高，甚至可能会更差。</p><p>优化建议：</p><ul><li>尽可能让所有数据检索都能通过索引来完成，避免无索引行锁升级为表锁。</li><li>合理设计索引，尽量缩小锁的范围</li><li>尽可能减少索引条件，及索引范围，避免间隙锁</li><li>尽量控制事务大小，减少锁定资源量和时间长度</li><li>尽可使用低级别事务隔离（但是需要业务层面满足需求）</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213230615.jpg" alt="image-20200201102128258"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213230357.jpg" alt="image-20200201103344323"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213230407.jpg" alt="image-20200419210141756"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213230309.jpg" alt="image-20200201103633355"></p><p>唯一索引跟主键一样，如果是普通索引，只对查出的记录加锁</p><p>可重复读隔离级别，对查出的记录（包括全表查询），加了间隙锁，所以防止了幻读</p><h2 id="5-1-验证-Demo"><a href="#5-1-验证-Demo" class="headerlink" title="5.1. 验证 Demo"></a>5.1. 验证 Demo</h2><p><a href="https://segmentfault.com/a/1190000039876266?utm_source=sf-similar-article">https://segmentfault.com/a/1190000039876266?utm_source=sf-similar-article</a></p><figure class="highlight plaintext"><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 plaintext">drop table if exists test_innodb_lock;<br>CREATE TABLE test_innodb_lock (<br> a INT (11),<br> b VARCHAR (20)<br>) ENGINE INNODB DEFAULT charset = utf8;<br>insert into test_innodb_lock values (1,&#x27;a&#x27;);<br>insert into test_innodb_lock values (2,&#x27;b&#x27;);<br>insert into test_innodb_lock values (3,&#x27;c&#x27;);<br>insert into test_innodb_lock values (4,&#x27;d&#x27;);<br>insert into test_innodb_lock values (5,&#x27;e&#x27;);<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">create index idx_lock_a on test_innodb_lock(a);<br>create index idx_lock_b on test_innodb_lock(b);<br></code></pre></td></tr></table></figure><p><a href="https://segmentfault.com/a/1190000023869573">https://segmentfault.com/a/1190000023869573</a></p><figure class="highlight plaintext"><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 plaintext">CREATE TABLE users(<br>    id INT NOT NULL,<br>    name VARCHAR(20) NOT NULL,<br>    age INT NOT NULL,<br>    PRIMARY KEY(id)<br>);<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">insert into users values (1,&#x27;a&#x27;,11);<br>insert into users values (2,&#x27;a&#x27;,12);<br>insert into users values (3,&#x27;a&#x27;,13);<br>insert into users values (4,&#x27;a&#x27;,14);<br>insert into users values (5,&#x27;a&#x27;,15);<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">select ENGINE_TRANSACTION_ID,LOCK_TYPE,LOCK_MODE from performance_schema.data_locks where ENGINE_TRANSACTION_ID in (1862);<br></code></pre></td></tr></table></figure><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><h2 id="7-1-锁"><a href="#7-1-锁" class="headerlink" title="7.1. 锁"></a>7.1. 锁</h2><p><a href="https://segmentfault.com/a/1190000023869573">https://segmentfault.com/a/1190000023869573</a></p><h2 id="7-2-黑马"><a href="#7-2-黑马" class="headerlink" title="7.2. 黑马"></a>7.2. 黑马</h2><h3 id="7-2-1-视频"><a href="#7-2-1-视频" class="headerlink" title="7.2.1. 视频"></a>7.2.1. 视频</h3><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-1358%%</span>❕ ^c4nii3<br><a href="https://www.bilibili.com/video/BV1zJ411M7TB?p=70&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zJ411M7TB?p=70&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-2-2-资料"><a href="#7-2-2-资料" class="headerlink" title="7.2.2. 资料"></a>7.2.2. 资料</h3><p>[[Mysql高级-day03]]</p><h2 id="7-3-小林-coding"><a href="#7-3-小林-coding" class="headerlink" title="7.3. 小林 coding"></a>7.3. 小林 coding</h2><p><a href="https://xiaolincoding.com/mysql/lock/how_to_lock.html#gap-lock">https://xiaolincoding.com/mysql/lock/how_to_lock.html#gap-lock</a></p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 锁 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-3、redolog undolog binlog</title>
      <link href="/2023/02/14/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-3%E3%80%81redolog-undolog-binlog/"/>
      <url>/2023/02/14/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-3%E3%80%81redolog-undolog-binlog/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-日志流程图"><a href="#1-日志流程图" class="headerlink" title="1. 日志流程图"></a>1. 日志流程图</h1><ul><li><strong>逻辑日志</strong>： 可以简单理解为记录的就是 sql 语句。</li><li><strong>物理日志</strong>：<code>mysql</code> 数据最终是保存在数据页中的，物理日志记录的就是数据页变更。</li><li>只有 Redolog 是物理日志，Binlog 和 Undolog 都是逻辑日志。</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301114657.png" alt="image.png"></p><h1 id="2-Binlog"><a href="#2-Binlog" class="headerlink" title="2. Binlog"></a>2. Binlog</h1><p>逻辑日志</p><h2 id="2-1-记录内容-DDL-和-DML"><a href="#2-1-记录内容-DDL-和-DML" class="headerlink" title="2.1. 记录内容 - DDL 和 DML"></a>2.1. 记录内容 - DDL 和 DML</h2><p><code>binlog</code> 用于记录数据库执行的<span style="background-color:#ff00ff">写入性操作 (DDL 和 DML)</span>信息，以<span style="background-color:#00ff00">二进制的形式</span>保存在磁盘中。<code>binlog</code> 是 <code>mysql</code> 的<span style="background-color:#ff00ff">逻辑日志</span>，<span style="background-color:#00ff00">并且由 <code>Server</code> 层进行记录，使用任何存储引擎的 <code>mysql</code> 数据库都会记录 <code>binlog</code> 日志</span><br>比如 <code>update T set c=c+1 where ID=2;</code> 这条 SQL，redo log 中记录的是 ：<code>xx页号，xx偏移量的数据修改为xxx；</code><span style="background-color:#00ff00">binlog 中记录的是：<code>id = 2 这一行的 c 字段 +1</code></span></p><p>binlog 文件是记录了所有数据库表结构变更和表数据修改的日志，不会记录查询类的操作，比如 SELECT 和 SHOW 操作。</p><h2 id="2-2-记录时机-更新完数据页之后"><a href="#2-2-记录时机-更新完数据页之后" class="headerlink" title="2.2. 记录时机 - 更新完数据页之后"></a>2.2. 记录时机 - 更新完数据页之后</h2><p>更新完成之后，事务提交之前，先写入 binlog cache 中<br>提交之后，刷盘到 binlog file 中</p><h2 id="2-3-使用场景"><a href="#2-3-使用场景" class="headerlink" title="2.3. 使用场景"></a>2.3. 使用场景</h2><p>在实际应用中，<code>binlog</code> 的主要使用场景有两个，分别是 <strong>主从复制</strong> 和 <strong>数据恢复</strong> 。</p><ol><li><strong>主从复制</strong> ：在 <code>Master</code> 端开启 <code>binlog</code>，然后将 <code>binlog</code> 发送到各个 <code>Slave</code> 端，<code>Slave</code> 端重放 <code>binlog</code> 从而达到主从数据一致。❕<span style="display:none">%%<br>▶1.🏡⭐️◼️Canal 的使用依赖于主从同步模式，尤其是 Binlog◼️⭐️-point-20230302-0741%%</span><br><span style="background-color:#ff00ff">变形用法 : Canal</span></li><li><strong>数据恢复</strong> ：通过使用 <code>mysqlbinlog</code> 工具来恢复数据。</li></ol><h2 id="2-4-刷盘时机-cache←前-commit-后→file"><a href="#2-4-刷盘时机-cache←前-commit-后→file" class="headerlink" title="2.4. 刷盘时机 -cache←前 commit 后→file"></a>2.4. 刷盘时机 -cache←前 commit 后→file</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228111521.png" alt="image.png"></p><ul><li> 上图的 write，是指把日志写入到文件系统的 page cache，并没有把数据持久化到磁盘，所以速度比较快</li><li> 上图的 fsync，才是将数据持久化到磁盘的操作，所以频繁的 fsync 会导致磁盘的 I&#x2F;O 升高<br>❕<span style="display:none">%%<br>▶1.🏡⭐️◼️binlog 日志记录逻辑 ?🔜MSTM📝 事务发起时，先写入 binlog cache，等事务提交时才写出到文件中◼️⭐️-point-20230301-1110%%</span><br>对于 <code>InnoDB</code> 存储引擎而言，<span style="background-color:#ff00ff">事务执行过程中，先把日志写到 binlog cache，事务提交的时候，再把 binlog cache 写到 binlog 文件中</span>。那么 <code>binlog</code> 是什么时候刷到磁盘中的呢？<code>mysql</code> 通过 <code>sync_binlog</code> 参数控制 <code>binlog</code> 的刷盘时机，取值范围是 <code>0-N</code></li><li>0：不去强制要求，由系统自行判断何时写入磁盘；</li><li>1：<span style="background-color:#00ff00">每次 <code>commit</code> 的时候都要将 <code>binlog</code> 写入磁盘</span>；<span style="background-color:#ff00ff">(5.7 之后默认值)</span></li><li>N：每 N 个事务，才会将 <code>binlog</code> 写入磁盘。</li></ul><p>从上面可以看出，<code>sync_binlog</code> 最安全的是设置是 <code>1</code>，这也是 <code>MySQL 5.7.7</code><br>之后版本的默认值。但是设置一个大一些的值可以提升数据库性能，因此实际情况下也可以将值适当调大，牺牲一定的一致性来获取更好的性能</p><h2 id="2-5-日志格式"><a href="#2-5-日志格式" class="headerlink" title="2.5. 日志格式"></a>2.5. 日志格式</h2><p><code>binlog</code> 日志有三种格式，分别为 <code>STATMENT</code>、<code>ROW</code> 和 <code>MIXED</code>。</p><blockquote><p>在 <code>MySQL 5.7.7</code> 之前，默认的格式是 <code>STATEMENT</code>，<code>MySQL 5.7.7</code> 之后，默认值是 <code>ROW</code>。日志格式通过 <code>binlog-format</code> 指定。</p></blockquote><ul><li><code>ROW</code>： 基于行的复制 (<code>row-based replication, RBR</code>)，不记录每条 sql 语句的上下文信息，仅需记录哪条数据被修改了。<ul><li>优点： 不会出现某些特定情况下的存储过程、或 function、或 trigger 的调用和触发无法被正确复制的问题 ；</li><li>缺点： 会产生大量的日志，尤其是 <code>alter table</code> 的时候会让日志暴涨</li></ul></li></ul><h2 id="2-6-主从复制-主从同步"><a href="#2-6-主从复制-主从同步" class="headerlink" title="2.6. 主从复制 (主从同步)"></a>2.6. 主从复制 (主从同步)</h2><p>MySQL 的主从复制依赖于 binlog ，也就是记录 MySQL 上的所有变化并以二进制形式保存在磁盘上。复制的过程就是将 binlog 中的数据从主库传输到从库上。</p><p>这个过程一般是 <strong>异步</strong> 的，也就是主库上执行事务操作的线程不会等待复制 binlog 的线程同步完成。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406164408.png" alt="image.png"><br>MySQL 集群的主从复制过程梳理成 3 个阶段：</p><ul><li><strong>写入 Binlog</strong>：主库写 binlog 日志，提交事务，并更新本地存储数据。</li><li><strong>同步 Binlog</strong>：把 binlog 复制到所有从库上，每个从库把 binlog 写到暂存日志中。</li><li><strong>回放 Binlog</strong>：回放 binlog，并更新存储引擎中的数据。</li></ul><p>具体详细过程如下：</p><ul><li>MySQL 主库在收到客户端提交事务的请求之后，会先写入 binlog，再提交事务，更新存储引擎中的数据，事务提交完成后，返回给客户端“操作成功”的响应。</li><li>从库会创建一个专门的 I&#x2F;O 线程，连接主库的 log dump 线程，来接收主库的 binlog 日志，再把 binlog 信息写入 relay log 的中继日志里，再返回给主库“复制成功”的响应。</li><li>从库会创建一个用于回放 binlog 的线程，去读 relay log 中继日志，然后回放 binlog 更新存储引擎中的数据，最终实现主从的数据一致性。</li></ul><p>在完成主从复制之后，你就可以在写数据时只写主库，在读数据时只读从库，这样即使写请求会锁表或者锁记录，也不会影响读请求的执行。</p><blockquote><p>从库是不是越多越好？</p></blockquote><p>不是的。</p><p>因为从库数量增加，从库连接上来的 I&#x2F;O 线程也比较多，<strong>主库也要创建同样多的 log dump 线程来处理复制的请求，对主库资源消耗比较高，同时还受限于主库的网络带宽</strong>。</p><p>所以在实际使用中，一个主库一般跟 2～3 个从库（1 套数据库，1 主 2 从 1 备主），这就是一主多从的 MySQL 集群结构。</p><blockquote><p>MySQL 主从复制还有哪些模型？</p></blockquote><p>主要有三种：</p><ul><li><strong>同步复制</strong>：MySQL 主库提交事务的线程要等待所有从库的复制成功响应，才返回客户端结果。这种方式在实际项目中，基本上没法用，原因有两个：一是性能很差，因为要复制到所有节点才返回响应；二是可用性也很差，主库和所有从库任何一个数据库出问题，都会影响业务。</li><li><strong>异步复制</strong>（默认模型）：MySQL 主库提交事务的线程并不会等待 binlog 同步到各从库，就返回客户端结果。这种模式一旦主库宕机，数据就会发生丢失。</li><li><strong>半同步复制</strong>：MySQL 5.7 版本之后增加的一种复制方式，介于两者之间，事务线程不用等待所有的从库复制成功响应，只要一部分复制成功响应回来就行，比如一主二从的集群，只要数据成功复制到任意一个从库上，主库的事务线程就可以返回给客户端。这种 <strong>半同步复制的方式，兼顾了异步复制和同步复制的优点，即使出现主库宕机，至少还有一个从库有最新的数据，不存在数据丢失的风险</strong>。</li></ul><h1 id="3-Buffer-Pool"><a href="#3-Buffer-Pool" class="headerlink" title="3. Buffer Pool"></a>3. Buffer Pool</h1><a href="/2022/12/31/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="MySQL-1、基本原理">MySQL-1、基本原理</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406181445.png" alt="image.png"></p><h1 id="4-Redolog"><a href="#4-Redolog" class="headerlink" title="4. Redolog"></a>4. Redolog</h1><p><a href="https://xiaolincoding.com/mysql/log/how_update.html#redo-log-%E6%96%87%E4%BB%B6%E5%86%99%E6%BB%A1%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E">https://xiaolincoding.com/mysql/log/how_update.html#redo-log-%E6%96%87%E4%BB%B6%E5%86%99%E6%BB%A1%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E</a></p><p>物理日志</p><h2 id="4-1-为什么需要-redo-log"><a href="#4-1-为什么需要-redo-log" class="headerlink" title="4.1. 为什么需要 redo log"></a>4.1. 为什么需要 redo log</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230414-1643%%</span>❕ ^k16y1h</p><p>Buffer Pool 是提高了读写效率没错，但是问题来了，Buffer Pool 是基于内存的，而内存总是不可靠，万一断电重启，还没来得及落盘的脏页数据就会丢失。</p><p><span style="background-color:#ff00ff">为了防止断电导致数据丢失的问题</span>，当有一条记录需要更新的时候，InnoDB 引擎就会<span style="background-color:#00ff00">先更新内存（同时标记为脏页），然后将本次对这个页的修改以 redo log 的形式记录下来</span>，这个时候更新就算完成了。</p><p>后续，InnoDB 引擎会在适当的时候，由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里，这就是 <strong>WAL （Write-Ahead Logging）技术</strong>。</p><p><strong>WAL 技术指的是， MySQL 的写操作并不是立刻写到磁盘上，而是先写日志，然后在合适的时间再写到磁盘上</strong>。这里与 redolog 从 redolog buffer 到 redolog file 的落盘细节无关。WAL 说的是 DML 先写出到顺序写的日志文件再写出到随机写的数据库磁盘文件的技术思想。</p><h2 id="4-2-记录内容-DDL-和-DML"><a href="#4-2-记录内容-DDL-和-DML" class="headerlink" title="4.2. 记录内容 -DDL 和 DML"></a>4.2. 记录内容 -DDL 和 DML</h2><p>DML 和 DDL</p><h2 id="4-3-记录时机-prepare-lt-前-commit-后-gt-commit"><a href="#4-3-记录时机-prepare-lt-前-commit-后-gt-commit" class="headerlink" title="4.3. 记录时机 - prepare&lt;- 前 commit 后 -&gt;commit"></a>4.3. 记录时机 - prepare&lt;- 前 commit 后 -&gt;commit</h2><p>Buffer Pool 中的数据更改之后，写到 redolog Buffer 中，状态为 prepare<br>提交之后，状态修改为 commit。<br>提交之后有没有刷盘看参数 <code>innodb_flush_log_at_trx_commit</code> 的配置</p><h2 id="4-4-Crash-Safe-总会执行恢复"><a href="#4-4-Crash-Safe-总会执行恢复" class="headerlink" title="4.4. Crash-Safe- 总会执行恢复"></a>4.4. Crash-Safe- 总会执行恢复</h2><ul><li><strong>实现事务的持久性，让 MySQL 有 crash-safe 的能力</strong>，能够保证 MySQL 在任何时间段突然崩溃，重启后之前已提交的记录都不会丢失；</li><li><strong>将写操作从「随机写」变成了「顺序写」</strong>，提升 MySQL 写入磁盘的性能。</li></ul><ol><li>因为 <code>Innodb</code> 是以 <code>页</code> 为单位进行磁盘交互的，而一个事务很可能只修改一个数据页里面的几个字节，这个时候将完整的数据页刷到磁盘的话，太浪费资源了！</li><li>一个事务可能涉及修改多个数据页，并且这些数据页在物理上并不连续，使用随机 IO 写入性能太差！</li><li>因此 <code>mysql</code> 设计了 <code>redo log</code>， <span style="background-color:#ff00ff">具体来说就是只记录事务对数据页做了哪些修改</span><br>，这样就能完美地解决性能问题了 (相对而言文件更小并且是顺序 IO)。</li></ol><p>启动 <code>innodb</code> 的时候，<span style="background-color:#ff00ff">不管上次是正常关闭还是异常关闭，总是会进行恢复操作</span>。因为 <code>redo log</code> 记录的是数据页的物理变化，因此恢复的时候速度比逻辑日志 (如 <code>binlog</code>) 要快很多。<br>有了 redo log，InnoDB 就可以保证即使数据库发生异常重启，之前提交的记录都不会丢失，这个能力称为 crash-safe。</p><h2 id="4-5-大小及存储逻辑"><a href="#4-5-大小及存储逻辑" class="headerlink" title="4.5. 大小及存储逻辑"></a>4.5. 大小及存储逻辑</h2><p>每次更新操作都要往 redo log 中写入，如果 redo log 满了，空间不够用了怎么办？<br>InnoDB 的 redo log 文件是固定大小的，比如可以配置一组 4 个文件，每个文件大小是 1GB，那么 redo log 中可以记录 4GB 的操作，InnoDB 会从第一个文件开始写入，直到第四个文件写满了，又回到第一个文件开头循环写，如下图。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406105110.png" alt="image.png"></p><p>图中的：</p><ol><li>write pos 和 checkpoint 的移动都是顺时针方向；</li><li>write pos ～ checkpoint 之间的部分（图中的红色部分），用来记录新的更新操作；</li><li>check point ～ write pos 之间的部分（图中蓝色部分）：<span style="background-color:#ff00ff">待落盘的脏数据页记录</span>；</li></ol><p>如果 write pos 追上了 checkpoint，就意味着 <strong>redo log 文件满了，这时 MySQL 不能再执行新的更新操作，也就是说 MySQL 会被阻塞</strong>（_因此所以针对并发量大的系统，适当设置 redo log 的文件大小非常重要_），此时 <strong>会停下来将 Buffer Pool 中的脏页刷新到磁盘中，然后标记 redo log 哪些记录可以被擦除，接着对旧的 redo log 记录进行擦除，等擦除完旧记录腾出了空间，checkpoint 就会往后移动（图中顺时针）</strong>，然后 MySQL 恢复正常运行，继续执行新的更新操作。</p><p>所以，一次 checkpoint 的过程就是脏页刷新到磁盘中变成干净页，然后标记 redo log 哪些记录可以被覆盖的过程。</p><h2 id="4-6-redolog-buffer"><a href="#4-6-redolog-buffer" class="headerlink" title="4.6. redolog buffer"></a>4.6. redolog buffer</h2><p>实际上，执行一个事务的过程中，产生的 redo log 也不是直接写入磁盘的，因为这样会产生大量的 I&#x2F;O 操作，而且磁盘的运行速度远慢于内存。</p><p>所以，redo log 也有自己的缓存—— <strong>redo log buffer</strong>，每当产生一条 redo log 时，会先写入到 redo log buffer，后续在持久化到磁盘</p><p>redo log buffer 默认大小 16 MB，可以通过 <code>innodb_log_Buffer_size</code> 参数动态的调整大小，增大它的大小可以让 MySQL 处理「大事务」是不必写入磁盘，进而提升写 IO 性能。</p><h2 id="4-7-刷盘时机-redologbuffer→磁盘"><a href="#4-7-刷盘时机-redologbuffer→磁盘" class="headerlink" title="4.7. 刷盘时机-redologbuffer→磁盘"></a>4.7. 刷盘时机-redologbuffer→磁盘</h2><p><span style="background-color:#ffff00">指的是 redo log 从 redolog buffer 写出到磁盘的策略</span></p><h3 id="4-7-1-默认时机"><a href="#4-7-1-默认时机" class="headerlink" title="4.7.1. 默认时机"></a>4.7.1. 默认时机</h3><ol><li>MySQL 正常关闭时；</li><li>当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时，会触发落盘；</li><li><span style="background-color:#00ff00">InnoDB 的后台线程每隔 1 秒，将 redo log buffer 持久化到磁盘。</span></li><li>每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘（这个策略可由 <code>innodb_flush_log_at_trx_commit</code> 参数控制</li></ol><h3 id="4-7-2-提交事务时策略"><a href="#4-7-2-提交事务时策略" class="headerlink" title="4.7.2. 提交事务时策略"></a>4.7.2. 提交事务时策略</h3><p>除此之外，InnoDB 还提供了另外两种策略，由参数 <code>innodb_flush_log_at_trx_commit</code> 参数控制，可取的值有：0、1、2，默认值为 1，这三个值分别代表的策略如下：</p><ul><li>当设置该<strong>参数为 0 时</strong>，表示每次事务提交时 ，还是<strong>将 redo log 留在 redo log buffer 中</strong> ，该模式下在事务提交时不会主动触发写入磁盘的操作。</li><li>当设置该<strong>参数为 1 时</strong>，表示每次事务提交时，都<strong>将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘</strong>，这样可以保证 MySQL 异常重启之后数据不会丢失。</li><li>当设置该<strong>参数为 2 时</strong>，表示每次事务提交时，都只是缓存在 redo log buffer 里的 redo log <strong>写到 redo log 文件，注意写入到「 redo log 文件」并不意味着写入到了磁盘</strong>，因为操作系统的文件系统中有个 Page Cache（如果你想了解 Page Cache，可以看<a href="https://xiaolincoding.com/os/6_file_system/pagecache.html">这篇 (opens new window)</a>），Page Cache 是专门用来缓存文件数据的，所以写入「 redo log文件」意味着写入到了操作系统的文件缓存。</li></ul><p><code>redo log</code> 包括两部分：一个是内存中的日志缓冲 (<code>redo log buffer</code>)，另一个是磁盘上的日志文件 (<code>redo log file</code>)。<span style="background-color:#ff00ff"><code>mysql</code> 每执行一条 <code>DML</code> 语句，都会先将记录写入 <code>redo log buffer</code>（3 种策略都会先写入 Redolog Buffer）</span> ，后续某个时间点再一次性将多个操作记录写到 <code>redo log file</code>。<span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-0950%%</span>❕ ^avp3mz<br>在计算机操作系统中，用户空间 (<code>user space</code>) 下的缓冲区数据一般情况下是无法直接写入磁盘的，中间必须经过操作系统内核空间 (<code>kernel space</code>) 缓冲区 (<code>OS Buffer</code>)。因此，<code>redo log buffer</code> 写入 <code>redo log file</code> 实际上是先写入 <code>OS Buffer</code>，然后再通过系统调用 <code>fsync()</code> 将其刷到 <code>redo log file</code> 中，过程如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228092314.png" alt="image.png"></p><p><code>mysql</code> 支持三种将 <code>redo log buffer</code> 写入 <code>redo log file</code> 的时机，可以通过 <code> </code>innodb_flush_log_at_trx_commit&#96; 参数配置，各参数值含义如下：❕<span style="display:none">%%<br>▶2.🏡⭐️◼️3 种 Redolog 刷盘策略的区别 ?🔜MSTM📝 相同点：都会写入 RedologBuffer。不同点：commit 之后到达的位置 0 和 2 都需要一个缓冲，0 commit 到 RedologBuffer 作为缓冲，然后从 RedologBuffer 中每秒写入 oscache 紧接着刷入磁盘；2 commit 到 oscache 作为缓冲，然后从 oscache 中每秒刷入磁盘。而策略 1，写入 RedologBuffer 之后，紧接着写入 oscache、刷入磁盘，相当于直接 commit 到了磁盘◼️⭐️-point-20230301-1131%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406194950.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228091001.png" alt="image.png"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211010192825378.png" alt="image-20211010192825378"></p><p><a href="https://blog.csdn.net/weixin_40471676/article/details/119732738">https://blog.csdn.net/weixin_40471676&#x2F;article&#x2F;details&#x2F;119732738</a></p><p><strong>总结：</strong> <strong>由于磁盘是一种相对较慢的存储设备，内存与磁盘的交互是一个相对较慢的过程</strong> <strong>由于 innodb_log_file_size 定义的是一个相对较大的值，正常情况下，由前面两种 checkpoint 刷新脏页到磁盘，在前面两种 checkpoint 刷新脏页到磁盘之后，脏页对应的 redo log 空间随即释放，一般不会发生 Async&#x2F;Sync Flush checkpoint。同时也要意识到，为了避免频繁低发生 Async&#x2F;SyncFlush checkpoint，也应该将 innodb_log_file_size 配置的相对较大一些。</strong></p><h2 id="4-8-两个日志文件"><a href="#4-8-两个日志文件" class="headerlink" title="4.8. 两个日志文件"></a>4.8. 两个日志文件</h2><p>刚刚说的 redo log 是执行引擎层的 log 文件，我们都知道，MySQL 整体来看，分为 Server 层和引擎层，而 binlog 是 Server 层面的 log 文件，也就是所有执行引擎都有 binlog</p><blockquote><p>为什么有了 binlog， 还要有 redo log？</p></blockquote><p>这个问题跟 MySQL 的时间线有关系。</p><p>最开始 MySQL 里并没有 InnoDB 引擎，MySQL 自带的引擎是 MyISAM，但是 MyISAM 没有 crash-safe 的能力，binlog 日志只能用于归档。<br>而 InnoDB 是另一个公司以插件形式引入 MySQL 的，既然只依靠 binlog 是没有 crash-safe 能力的，所以 InnoDB 使用 redo log 来实现 crash-safe 能力。<br>❕<span style="display:none">%%<br>▶2.🏡⭐️◼️为什么 MySQL 会有 2 个日志文件 ?🔜MSTM📝 ◼️⭐️-point-20230228-1057%%</span></p><h2 id="4-9-二阶段提交⭐️🔴"><a href="#4-9-二阶段提交⭐️🔴" class="headerlink" title="4.9. 二阶段提交⭐️🔴"></a>4.9. 二阶段提交⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230527133019.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230527133112.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230527133127.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230527133203.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230527133216.png" alt="image.png"></p><p>事务提交后，redo log 和 binlog 都要持久化到磁盘，但是这两个是独立的逻辑，可能出现半成功的状态，这样就造成两份日志之间的逻辑不一致。</p><p>举个例子，假设 id &#x3D; 1 这行数据的字段 name 的值原本是 ‘jay’，然后执行 <code>UPDATE t_user SET name = &#39;xiaolin&#39; WHERE id = 1;</code> 如果在持久化 redo log 和 binlog 两个日志的过程中，出现了半成功状态，那么就有两种情况：</p><ul><li><strong>如果在将 redo log 刷入到磁盘之后， MySQL 突然宕机了，而 binlog 还没有来得及写入</strong>。MySQL 重启后，通过 redo log 能将 Buffer Pool 中 id &#x3D; 1 这行数据的 name 字段恢复到新值 xiaolin，但是 binlog 里面没有记录这条更新语句，在主从架构中，binlog 会被复制到从库，由于 binlog 丢失了这条更新语句，从库的这一行 name 字段是旧值 jay，与主库的值不一致性；</li><li><strong>如果在将 binlog 刷入到磁盘之后， MySQL 突然宕机了，而 redo log 还没有来得及写入</strong>。由于 redo log 还没写，崩溃恢复以后这个事务无效，所以 id &#x3D; 1 这行数据的 name 字段还是旧值 jay，而 binlog 里面记录了这条更新语句，在主从架构中，binlog 会被复制到从库，从库执行了这条更新语句，那么这一行 name 字段是新值 xiaolin，与主库的值不一致性；</li></ul><p>可以看到，在持久化 redo log 和 binlog 这两份日志的时候，<span style="background-color:#ff00ff">如果出现半成功的状态，就会造成主从环境的数据不一致性</span>。这是因为 redo log 影响主库的数据，binlog 影响从库的数据，所以 redo log 和 binlog 必须保持一致才能保证主从数据一致。</p><p><strong>MySQL 为了避免出现两份日志之间的逻辑不一致的问题，使用了「两阶段提交」来解决</strong>，两阶段提交其实是分布式事务一致性协议，它可以保证多个逻辑操作要不全部成功，要不全部失败，不会出现半成功的状态。</p><p><strong>两阶段提交把单个事务的提交拆分成了 2 个阶段，分别是「准备（Prepare）阶段」和「提交（Commit）阶段」</strong>，每个阶段都由协调者（Coordinator）和参与者（Participant）共同完成。注意，不要把提交（Commit）阶段和 commit 语句混淆了，commit 语句执行的时候，会包含提交（Commit）阶段。</p><h3 id="4-9-1-解析"><a href="#4-9-1-解析" class="headerlink" title="4.9.1. 解析"></a>4.9.1. 解析</h3><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-0804%%</span><br><a href="https://cloud.tencent.com/developer/article/1790507">https://cloud.tencent.com/developer/article/1790507</a></p><p><a href="https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4">https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4</a></p><p>在 MySQL 的 InnoDB 存储引擎中，开启 binlog 的情况下，MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log，为了保证这两个日志的一致性，MySQL 使用了 <strong>内部 XA 事务</strong>（是的，也有外部 XA 事务，跟本文不太相关，我就不介绍了），内部 XA 事务由<span style="background-color:#ff00ff"> binlog 作为协调者，存储引擎是参与者</span>。</p><p>当客户端执行 commit 语句或者在自动提交的情况下，MySQL 内部开启一个 XA 事务，<strong>分两阶段来完成 XA 事务的提交</strong>，如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406224609.png" alt="image.png"></p><p>从图中可看出，事务的提交过程有两个阶段，就是 **<span style="background-color:#ff0000">将 redo log 的写入拆成了两个步骤：prepare 和 commit，中间再穿插写入 binlog</span>**，具体如下：</p><ol><li><strong>prepare 阶段</strong>：将 XID（内部 XA 事务的 ID） 写入到 redo log，同时将 redo log 对应的事务状态设置为 prepare，然后将 redo log 持久化到磁盘（<code>innodb_flush_log_at_trx_commit</code> &#x3D; 1 的作用）；</li><li><strong>commit 阶段</strong>：把 XID 写入到 binlog，然后将 binlog 持久化到磁盘（<code>sync_binlog</code> &#x3D; 1 的作用），接着调用引擎的提交事务接口，将 redo log 状态设置为 commit，此时该状态并不需要持久化到磁盘，只需要 write 到文件系统的 page cache 中就够了，因为只要 binlog 写磁盘成功，就算 redo log 的状态还是 prepare 也没有关系，一样会被认为事务已经执行成功；</li></ol><h3 id="4-9-2-如何判断-binlog-和-redolog-是否达成了一致"><a href="#4-9-2-如何判断-binlog-和-redolog-是否达成了一致" class="headerlink" title="4.9.2. 如何判断 binlog 和 redolog 是否达成了一致"></a>4.9.2. 如何判断 binlog 和 redolog 是否达成了一致</h3><p><a href="https://www.modb.pro/db/425141">https://www.modb.pro/db/425141</a></p><p><span style="background-color:#ff00ff">当 MySQL 写完 redo log 并将它标记为 prepare 状态时，并且会在 redo log 中记录一个 XID，它全局唯一的标识着这个事务</span>。而当你设置 <code>sync_binlog=1</code> 时，做完了上面第一阶段写 redo log 后，mysql 就会对应 binlog 并且会直接将其刷新到磁盘中。</p><p>下图就是磁盘上的 row 格式的 binlog 记录。binlog 结束的位置上也有一个 XID。<br><span style="background-color:#ff00ff">只要这个 XID 和 redo log 中记录的 XID 是一致的，MySQL 就会认为 binlog 和 redo log 逻辑上一致。</span>就上面的场景来说就会 commit，而如果仅仅是 redo log 中记录了 XID，binlog 中没有，MySQL 就会 RollBack</p><p>对于处于 PREPARE 状态的事务，存储引擎既可以提交，也可以回滚，这取决于目前该事务对应的 binlog 是否已经写入硬盘。这时就会读取最后一个 binlog 日志文件，从日志文件中找一下有没有该 PREPARE 事务对应的 xid 记录，如果有的话，就将该事务提交，否则就回滚好了。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307073250.png" alt="image.png"></p><h3 id="4-9-3-事务没提交的时候，redo-log-会被持久化到磁盘吗"><a href="#4-9-3-事务没提交的时候，redo-log-会被持久化到磁盘吗" class="headerlink" title="4.9.3. 事务没提交的时候，redo log 会被持久化到磁盘吗"></a>4.9.3. 事务没提交的时候，redo log 会被持久化到磁盘吗</h3><p>会的！<br>事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的，这些缓存在 redo log buffer 里的 redo log 也会被「后台线程」每隔一秒一起持久化到磁盘。</p><p>也就是说，<strong>事务没提交的时候，redo log 也是可能被持久化到磁盘的</strong>。</p><p>有的同学可能会问，如果 mysql 崩溃了，还没提交事务的 redo log 已经被持久化磁盘了，mysql 重启后，数据不就不一致了？</p><p>放心，这种情况 mysql 重启会进行回滚操作，因为事务没提交的时候，binlog 是还没持久化到磁盘的。</p><p><span style="background-color:#ff0000">redo log 可以在事务没提交之前持久化到磁盘，但是 binlog 必须在事务提交之后，才可以持久化到磁盘。</span></p><h3 id="4-9-4-存在问题"><a href="#4-9-4-存在问题" class="headerlink" title="4.9.4. 存在问题"></a>4.9.4. 存在问题</h3><p>两阶段提交虽然保证了两个日志文件的数据一致性，但是性能很差，主要有两个方面的影响：</p><ul><li><strong>磁盘 I&#x2F;O 次数高</strong>：对于“双 1”配置，每个事务提交都会进行两次 fsync（刷盘），一次是 redo log 刷盘，另一次是 binlog 刷盘。</li><li><strong>锁竞争激烈</strong>：两阶段提交虽然能够保证「单事务」两个日志的内容一致，但在「多事务」的情况下，却不能保证两者的提交顺序一致，因此，<span style="background-color:#ff00ff">在两阶段提交的流程基础上，还需要加一个锁来保证提交的原子性，从而保证多事务的情况下，两个日志的提交顺序一致</span>。</li></ul><blockquote><p><strong>为什么两阶段提交的磁盘 I&#x2F;O 次数会很高？</strong></p></blockquote><p>binlog 和 redo log 在内存中都对应的缓存空间，binlog 会缓存在 binlog cache，redo log 会缓存在 redo log buffer，它们持久化到磁盘的时机分别由下面这两个参数控制。一般我们为了避免日志丢失的风险，会将这两个参数设置为 1：</p><ul><li>当 <code>sync_binlog = 1 </code> 的时候，表示每次提交事务都会将 binlog cache 里的 binlog 直接持久到磁盘；</li><li>当 <code>innodb_flush_log_at_trx_commit = 1 </code> 时，表示每次事务提交时，都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘；</li></ul><p>可以看到，如果 sync_binlog 和 当 innodb_flush_log_at_trx_commit 都设置为 1，那么在每个事务提交过程中， 都会 <strong>至少调用 2 次刷盘操作</strong>，一次是 redo log 刷盘，一次是 binlog 落盘，所以这会成为性能瓶颈。</p><blockquote><p><strong>为什么锁竞争激烈？</strong></p></blockquote><p>在早期的 MySQL 版本中，通过使用 <code>prepare_commit_mutex</code> 锁来保证事务提交的顺序，在一个事务获取到锁时才能进入 prepare 阶段，一直到 commit 阶段结束才能释放锁，下个事务才可以继续进行 prepare 操作。</p><p>通过加锁虽然完美地解决了顺序一致性的问题，但在并发量较大的时候，就会导致对锁的争用，性能不佳。</p><h2 id="4-10-redolog-与-binlog-区别⭐️🔴"><a href="#4-10-redolog-与-binlog-区别⭐️🔴" class="headerlink" title="4.10. redolog 与 binlog 区别⭐️🔴"></a>4.10. redolog 与 binlog 区别⭐️🔴</h2><p><span style="display:none">%%<br>▶14.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1228%%</span>❕ ^63ep3q</p><table><thead><tr><th></th><th>redo log</th><th>binlog</th></tr></thead><tbody><tr><td>文件大小</td><td>redo log 的大小是固定的。</td><td>binlog 可通过配置参数 max_binlog_size 设置每个 binlog 文件的大小。</td></tr><tr><td>实现方式</td><td>redo log 是 InnoDB 引擎层实现的，并不是所有引擎都有。</td><td>binlog 是 Server 层实现的，所有引擎都可以使用 binlog 日志</td></tr><tr><td>记录方式</td><td>redo log 采用循环写的方式记录，当写到结尾时，会回到开头循环写日志。</td><td>binlog 通过追加的方式记录，当文件大小大于给定值后，后续的日志会记录到新的文件上</td></tr><tr><td>适用场景</td><td>redo log 适用于崩溃恢复 (crash-safe)</td><td>binlog 适用于主从复制和数据恢复</td></tr></tbody></table><p>由 <code>binlog</code> 和 <code>redo log</code> 的区别可知：<code>binlog</code> 日志只用于归档，只依靠 <code>binlog</code> 是没有 <code>crash-safe</code> 能力的。但只有 <code>redo log</code> 也不行，因为 <code>redo log</code> 是 <code>InnoDB</code><br>特有的，且日志上的记录落盘后会被覆盖掉。因此需要 <code>binlog</code> 和 <code>redo log</code><br>二者同时记录，才能保证当数据库发生宕机重启时，数据不会丢失</p><ul><li>redo log 是 InnoDB 引擎特有的；而 binlog 是 MySQL Server 层实现的</li><li>redo log 是物理日志，记录的是“在某个数据页做了什么修改”；而 binlog 是逻辑日志，记录的是语句的原始逻辑。比如 <code>update T set c=c+1 where ID=2;</code> 这条 SQL，redo log 中记录的是 ：<code>xx页号，xx偏移量的数据修改为xxx；</code> binlog 中记录的是：<code>id = 2 这一行的 c 字段 +1</code></li><li>redo log 是循环写的，固定空间会用完；binlog 可以追加写入，一个文件写满了会切换到下一个文件写，并不会覆盖之前的记录</li><li>记录内容时间不同，redo log 记录<span style="background-color:#ff00ff">事务发起后的 DML 和 DDL 语句</span>；binlog 记录<span style="background-color:#ff00ff">commit 完成后的 DML 语句和 DDL 语句</span></li><li>作用不同，redo log 作为异常宕机或者介质故障后的数据恢复使用；binlog 作为恢复数据使用，主从复制搭建。</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304072934.png" alt="image.png"></p><h1 id="5-Undolog"><a href="#5-Undolog" class="headerlink" title="5. Undolog"></a>5. Undolog</h1><h2 id="5-1-记录内容-DDL-和-DML"><a href="#5-1-记录内容-DDL-和-DML" class="headerlink" title="5.1. 记录内容 -DDL 和 DML"></a>5.1. 记录内容 -DDL 和 DML</h2><p>DML 和 DDL</p><p>数据库事务四大特性中有一个是 <strong>原子性</strong> ，具体来说就是 <strong>原子性是指对数据库的一系列操作，要么全部成功，要么全部失败，不可能出现部分成功的情况</strong><br>实际上， <strong>原子性</strong> 底层就是通过 <code>undo log</code> 实现的。<code>undo log</code> 主要记录了数据的逻辑变化，比如一条 <code>INSERT</code> 语句，对应一条 <code>DELETE</code> 的 <code>undo log</code>，对于每个 <code>UPDATE</code> 语句，对应一条相反的 <code>UPDATE</code> 的 <code>undo log</code>，这样在发生错误时，就能回滚到事务之前的数据状态。同时，<code>undo log</code> 也是 <code>MVCC</code> (多版本并发控制) 实现的关键</p><h2 id="5-2-记录时机-commit-数据页之前"><a href="#5-2-记录时机-commit-数据页之前" class="headerlink" title="5.2. 记录时机 - commit 数据页之前"></a>5.2. 记录时机 - commit 数据页之前</h2><p>undo log 是一种用于撤销回退的日志。在事务没提交之前，<span style="background-color:#ff00ff">确切来说是在 Buffer Pool 中的数据修改之前</span>，MySQL 会先记录更新前的数据到 undo log 日志文件里面，当事务回滚时，可以利用 undo log 来进行回滚。如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406195837.png" alt="image.png"></p><h2 id="5-3-作用-回滚-MVCC"><a href="#5-3-作用-回滚-MVCC" class="headerlink" title="5.3. 作用 - 回滚 + MVCC"></a>5.3. 作用 - 回滚 + MVCC</h2><ul><li><strong>实现事务回滚，保障事务的原子性</strong>。事务处理过程中，如果出现了错误或者用户执行了 ROLLBACK 语句，MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。</li><li><strong>实现 MVCC（多版本并发控制）关键因素之一</strong>。MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据，MySQL 在执行快照读（普通 select 语句）的时候，会根据事务的 Read View 里的信息，顺着 undo log 的版本链找到满足其可见性的记录。</li></ul><p>undo log 和 redo log 也是引擎层的 log 文件，undo log 提供了回滚和多个行版本控制（MVCC）功能。在数据库修改操作时，不仅记录了 redo log，还记录了 undo log，如果因为某些原因导致事务执行失败回滚了，可以借助 undo log 进行回滚。</p><p>虽然 undo log 和 redo log 都是 InnoDB 特有的，但 undo log 记录的是逻辑日志，redo log 记录的是物理日志。对记录做变更操作（insert,update,delete）时不仅会产生 redo 记录，也会产生 undo 记录要把回滚时需要的信息都记录到 undo log 里，比如：</p><ul><li>在 <strong>插入</strong> 一条记录时，要把这条记录的主键值记下来，这样之后回滚时只需要把这个主键值对应的记录 <strong>删掉</strong> 就好了；</li><li>在 <strong>删除</strong> 一条记录时，要把这条记录中的内容都记下来，这样之后回滚时再把由这些内容组成的记录 <strong>插入</strong> 到表中就好了；</li><li>在 <strong>更新</strong> 一条记录时，要把被更新的列的旧值记下来，这样之后回滚时再把这些列 <strong>更新为旧值</strong> 就好了。</li></ul><p>而多版本并发控制（MVCC） ，也用到了 undo log ，当读取的某一行被其他事务锁定时，它可以从 undo log 中获取该行记录以前的数据是什么，从而提供该行版本信息，让用户实现非锁定一致性读取。</p><h2 id="5-4-大小和存储逻辑"><a href="#5-4-大小和存储逻辑" class="headerlink" title="5.4. 大小和存储逻辑"></a>5.4. 大小和存储逻辑</h2><p>undo 记录默认被记录到系统表空间（ibdata1）中，但是从 MySQL5.6 开始，就可以使用独立的 undo 表空间了。不用担心 undo 会把 ibdata1 文件弄大。</p><p>undo log 是采用段 (segment) 的方式来记录的，每个 undo 操作在记录的时候占用一个 undo log segment</p><p>rollback segment 称为回滚段，每个回滚段中有 1024 个 undo log segment，在以前的版本中，只支持一个 rollback segment，也就是只能记录 1024 个 undo log segment，MySQL 5.5 以后，可以支持 128 个 rollback segment，即支持 128✖️1024 个 undo 操作，还可以通过变量 <code>innodb_undo_logs</code> 自定义 rollback segment 数量，默认是 128</p><p><a href="https://www.51cto.com/article/720454.html">https://www.51cto.com/article/720454.html</a><br><a href="https://juejin.cn/post/7157956679932313608#heading-1">https://juejin.cn/post/7157956679932313608#heading-1</a></p><h2 id="5-5-redolog-和-undolog-区别"><a href="#5-5-redolog-和-undolog-区别" class="headerlink" title="5.5. redolog 和 undolog 区别"></a>5.5. redolog 和 undolog 区别</h2><p>这两种日志是属于 InnoDB 存储引擎的日志，它们的区别在于：</p><ol><li>redo log 记录了此次事务「<strong>完成后</strong>」的数据状态，记录的是 commit 更新 <strong>之后</strong> 的值；</li><li>undo log 记录了此次事务「<strong>开始前</strong>」的数据状态，记录的是 commit 更新 <strong>之前</strong> 的值；</li></ol><p>事务提交之前发生了崩溃，重启后会通过 undo log 回滚事务，事务提交之后发生了崩溃，重启后会通过 redo log 恢复事务，如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406191903.png" alt="image.png"></p><h1 id="6-update-操作流程"><a href="#6-update-操作流程" class="headerlink" title="6. update 操作流程"></a>6. update 操作流程</h1><p><code>update T set c=c+1 where ID=2;</code></p><p>1、执行器先通过引擎查询到 id &#x3D; 2 这行数据，id 是主键，直接遍历主键索引树直接插到这行数据，如果这行数据所在的数据页在内存中，就直接返回结果给执行器，否则，需要先从磁盘读入内存，然后再返回。</p><p>2、执行器拿到引擎给的行数据，把这个值 +1，得到新的一行数据，再调用引擎接口写入这行数据，<span style="background-color:#ff00ff">发起事务</span>。</p><p>3、引擎将这行数据更新到内存中，同时记录到 redo log 中，此时 redo log 处于 perpare 状态，此时就告知执行器已经更新完成了，随时可以提交事务。</p><p>4、执行器生成这个操作的 binlog，并把 binlog 写入 page cache。</p><p>5、提交事务：执行器调用引擎的提交事务接口，引擎把刚刚写入的 redo log 改成提交（commit）状态，把 binlog 写入磁盘，更新完成</p><p>如下图为 update 语句的执行流程，深色代表 MySQL 执行器中执行的，浅色代表 InnoDB 内部执行的。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228112937.png" alt="image.png"><br>❕<span style="display:none">%%<br>▶2.🏡⭐️◼️不用纠结 redolog 的 prepare 和 commit 是在 redolog Buffer 中还是在磁盘中。因为这个是通过参数可变的。关键就在于 redolog 这两种状态的变化时机和原因。按推断来讲，prepare 时还没有提交，所以应该在内存。而 commit 状态就是根据参数来确定。◼️⭐️-point-20230302-0748%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406170635.png" alt="image.png"></p><h1 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h1><p><span style="display:none">%%<br>▶57.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1846%%</span>❕ ^hyl1t6</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406155635.png" alt="image.png"></p><p>redo log 用来保证 crash-safe，binlog 用来保证可以将数据库状态恢复到任一时刻，undo log 是用来保证事务需要回滚时数据状态的回滚和 MVCC 时，记录各版本数据信息。</p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h2 id="8-1-MySQL-磁盘-I-x2F-O-很高，有什么优化的方法？"><a href="#8-1-MySQL-磁盘-I-x2F-O-很高，有什么优化的方法？" class="headerlink" title="8.1. MySQL 磁盘 I&#x2F;O 很高，有什么优化的方法？"></a>8.1. MySQL 磁盘 I&#x2F;O 很高，有什么优化的方法？</h2><p>现在我们知道事务在提交的时候，需要将 binlog 和 redo log 持久化到磁盘，那么如果出现 MySQL 磁盘 I&#x2F;O 很高的现象，我们可以通过控制以下参数，来 “延迟” binlog 和 redo log 刷盘的时机，从而降低磁盘 I&#x2F;O 的频率：</p><ul><li>设置<span style="background-color:#ff00ff">组提交</span>的两个参数： binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 参数，延迟 binlog 刷盘的时机，从而减少 binlog 的刷盘次数。这个方法是基于“额外的故意等待”来实现的，因此可能会增加语句的响应时间，但即使 MySQL 进程中途挂了，也没有丢失数据的风险，因为 binlog 早被写入到 page cache 了，只要系统没有宕机，缓存在 page cache 里的 binlog 就会被持久化到磁盘。</li><li>将 <code>sync_binlog</code> 设置为大于 1 的值（比较常见是 100~1000），表示每次提交事务都 write，但<span style="background-color:#ff00ff">累积 N 个事务后才 fsync</span>，相当于延迟了 binlog 刷盘的时机。但是这样做的风险是，主机掉电时会丢 N 个事务的 binlog 日志。</li><li>将 <code>innodb_flush_log_at_trx_commit</code> 设置为 2。表示每次事务提交时，都只是缓存在 redo log buffer 里的 redo log 写到 redo log 文件，注意写入到「 redo log 文件」并不意味着写入到了磁盘，因为操作系统的文件系统中有个 Page Cache，专门用来缓存文件数据的，所以写入「 redo log 文件」意味着写入到了操作系统的文件缓存，然后交由操作系统控制持久化到磁盘的时机。但是这样做的风险是，主机掉电的时候会丢数据。</li></ul><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><p><a href="https://segmentfault.com/a/1190000023827696">https://segmentfault.com/a/1190000023827696</a></p><p><a href="https://www.cnblogs.com/renolei/p/5325435.html">https://www.cnblogs.com/renolei/p/5325435.html</a></p><p><a href="https://www.51cto.com/article/681113.html">https://www.51cto.com/article/681113.html</a></p><p><a href="https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4%E7%9A%84%E8%BF%87%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84">https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4%E7%9A%84%E8%BF%87%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84</a></p><p><a href="https://juejin.cn/post/7157956679932313608#heading-1">https://juejin.cn/post/7157956679932313608#heading-1</a></p><p>动画： <a href="https://heapdump.cn/article/3890459">https://heapdump.cn/article/3890459</a></p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 日志文件 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-2、MVCC</title>
      <link href="/2023/02/13/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-2%E3%80%81%E4%BA%8B%E5%8A%A1-MVCC-LBCC/"/>
      <url>/2023/02/13/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-2%E3%80%81%E4%BA%8B%E5%8A%A1-MVCC-LBCC/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-隔离级别实现原理"><a href="#1-隔离级别实现原理" class="headerlink" title="1. 隔离级别实现原理"></a>1. 隔离级别实现原理</h1><a href="/2023/02/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-1%E3%80%81Spring%E4%BA%8B%E5%8A%A1(%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1)/" title="事务-1、Spring事务(本地事务)">事务-1、Spring事务(本地事务)</a><ol><li>对于「读未提交」隔离级别的事务来说，因为可以读到未提交事务修改的数据，所以直接读取<span style="background-color:#ff00ff">版本链上最新的数据</span></li><li>对于「串行化」隔离级别的事务来说，通过<span style="background-color:#ff00ff">加读写锁</span>的方式来避免并行访问；</li><li>对于「读已提交」和「可重复读」隔离级别的事务来说，它们是通过 Read View 来实现的，它们的区别在于创建 Read View 的时机不同。</li></ol><blockquote><p><span style="background-color:#ff00ff">   「读已提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View</span><br><span style="background-color:#ff00ff">   「可重复读」隔离级别是「启动事务时」生成一个 Read View，然后整个事务期间都在用这个 Read View</span></p></blockquote><h1 id="2-事务实现原理⭐️🔴"><a href="#2-事务实现原理⭐️🔴" class="headerlink" title="2. 事务实现原理⭐️🔴"></a>2. 事务实现原理⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230406-0712%%</span>❕ ^9cpr5j</p><p>Mysql 里面的事务，满足 ACID 特性。</p><ol><li>首先，A 表示 Atomic 原子性，也就是需要保证多个 DML 操作是原子的，要么都成功，要么都失败。那么，失败就意味着要对原本执行成功的数据进行回滚，所以 InnoDB 设计了一个 <code>undo log</code> 表，在事务执行的过程中，把修改之前的数据快照保存到 <code>undo log</code> 里面，一旦出现错误，就直接从 <code>undo log</code> 里面读取数据执行反向操作就行了。</li><li>其次，C 表示一致性，表示数据的<span style="background-color:#ff00ff">完整性约束没有被破坏</span>，这个更多是依赖于业务层面的保证，数据库本身也提供了一些，比如主键的唯一约束，字段长度和类型的保证等等。</li><li>接着，I 表示事物的隔离性，也就是多个并行事务对同一个数据进行操作的时候，如何避免多个事务的干扰导致数据混乱的问题。而 InnoDB 实现了 SQL92 的标准，提供了四种隔离级别的实现：<br>RU（未提交读） RC（已提交读） RR（可重复读）Serializable（串行化）<br><span style="background-color:#ff00ff">InnoDB 默认的隔离级别是 RR（可重复读），然后使用了 MVCC 机制解决了脏读和不可重复读的问题，然后使用了间隙锁的方式有效避免了幻读的问题</span>。</li><li>最后一个是 D，表示持久性，也就是只要事务提交成功，那对于这个数据的结果的影响一定是永久性的。不能因为宕机或者其他原因导致数据变更失效。理论上来说，事务提交之后直接把数据持久化到磁盘就行了</li></ol><p>但是因为随机磁盘 IO 的效率确实很低，所以 InnoDB 设计了 Buffer Pool 缓冲区来优化，也就是数据发生变更的时候先更新内存缓冲区，然后在合适的时机再持久化到磁盘。那在持久化这个过程中，如果数据库宕机，就会导致数据丢失，也就无法满足持久性了。<br>所以 InnoDB 引入了 <code>redo log</code> 文件，这个文件存储了数据被修改之后的值，当我们通过事务对数据进行变更操作的时候，除了修改内存缓冲区里面的数据以外，还会把本次修改的值追加到 <code>redo log</code> 里面。当提交事务的时候，直接把 <code>redo log</code> 日志刷到磁盘上持久化，一旦数据库出现宕机，在 Mysql 重启在以后可以直接用 <code>redo log</code> 里面保存的重写日志读取出来，再执行一遍从而保证持久性。<br>因此，事务的实现原理的核心本质就是如何满足 ACID 的，在 InnDB 里面用到了 MVCC、行锁表锁、<code>undo log</code>、<code>redo log</code> 等机制来保证。</p><h1 id="MVCC-并发控制原理"><a href="#MVCC-并发控制原理" class="headerlink" title="MVCC 并发控制原理"></a>MVCC 并发控制原理</h1><p>1、在并发读写数据库时，可以做到在读操作时不用阻塞写操作，写操作也不用阻塞读操作，从而提高数据库的并发读写的处理能力。<br>2、能实现读一致性，从而解决脏读、幻读、不可重复读等不可重复读，但是不能解决数据更新丢失的问题。<br>3、采用乐观锁或者悲观锁用来解决写和写的冲突，从而最大程度地去提高数据库的并发性能。 </p><p>在 MVCC 中，通常不需要加锁来控制并发访问。相反，每个事务都可以读取已提交的快照，而不需要获得共享锁或排它锁。<br>在写操作的时候，MVCC 会使用一种叫为<span style="background-color:#ff00ff">“写时复制”（Copy-On-Write）</span>的技术，也就是在修改数据之前先将数据复制一份，从而创建一个新的快照。当一个事务需要修改数据时，MVCC 会首先检查修改数据的快照版本号是否与该事务的快照版本一致，如果一致则表示可以修改这条数据，否则该事务需要等待其他事务完成对该数据的修改。<br>另外，这个事物在新快照之上修改的结果，不会影响原始数据，其他事务可以继续读取原始数据的快照，从而解决了脏读、不可重复度问题。<br>所以，正是有了 MVCC 机制，让多个事务对同一条数据进行读写时，不需要加锁也不会出现读写冲突。</p><h1 id="3-MVCC-隔离级别原理"><a href="#3-MVCC-隔离级别原理" class="headerlink" title="3. MVCC 隔离级别原理"></a>3. MVCC 隔离级别原理</h1><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230413-1910%%</span>❕ ^a59z9q</p><p>MVCC 全称 Multi-Version Concurrency Control，MVCC 是一种通过<span style="background-color:#00ff00">增加版本冗余数据来实现并发控制的</span>方法，一般在数据库管理系统中，实现对数据库的并发访问，在编程语言中实现事务内存。<br>❕<span style="display:none">%%<br>1103-🏡⭐️◼️MySQL 隔离级别的实现原理◼️⭐️-point-20230214-1103%%</span></p><h2 id="3-1-隐藏字段"><a href="#3-1-隐藏字段" class="headerlink" title="3.1. 隐藏字段"></a>3.1. 隐藏字段</h2><p>&#x3D;&#x3D;除了我们正常业务涉及的字段外&#x3D;&#x3D;，InnoDB 在内部向数据库表中添加三个隐藏字段:</p><ul><li>DB_TRX_ID：6-byte 的事务 ID。插入或更新行的最后一个事务的事务 ID</li><li>DB_ROLL_PTR：7-byte 的回滚指针。就是指向对应某行记录的上一个版本，在 undo log 中使用。</li><li>DB_ROW_ID：6-byte 的隐藏主键。如果数据表中没有主键也没有非 null 列，那么 InnoDB 会自动生成单调递增的隐藏主键（表中有主键或者非 NULL 的 UNIQUE 键时都不会包含 DB_ROW_ID 列）。</li></ul><p>如上面的表没有设计 primary key,其中 id&#x2F;name&#x2F;city 是我们的业务字段，那么加上隐藏字段应该如下</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230214142029.png" alt="image.png"></p><p>为方便理解可以简称如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230214110325.png" alt="image.png"></p><h2 id="3-2-undo-log-和-版本链"><a href="#3-2-undo-log-和-版本链" class="headerlink" title="3.2. undo log 和 版本链"></a>3.2. undo log 和 版本链</h2><h3 id="3-2-1-undo-log"><a href="#3-2-1-undo-log" class="headerlink" title="3.2.1. undo log"></a>3.2.1. undo log</h3><p>undo log 就是回滚日志，在 insert&#x2F;update&#x2F;delete 变更操作的时候生成的记录方便回滚。</p><h4 id="3-2-1-1-insert-undo-log"><a href="#3-2-1-1-insert-undo-log" class="headerlink" title="3.2.1.1. insert undo log"></a>3.2.1.1. insert undo log</h4><p>当进行 insert 操作的时候，产生的 undo log 只有在事务回滚的时候需求，如果不回滚在事务提交之后就会被删除。</p><h4 id="3-2-1-2-modify-undo-log"><a href="#3-2-1-2-modify-undo-log" class="headerlink" title="3.2.1.2. modify undo log"></a>3.2.1.2. modify undo log</h4><p>当进行 update 和 delete 的时候，产生的 undo log 不仅仅在事务回滚的时候需要，在快照读的时候也是需要的，所以不会立即删除，只有等不在用到这个日志的时候才会被 mysql purge 线程统一处理掉（delete 操作也只是打一个删除标记，并不是真正的删除）</p><h3 id="3-2-2-版本链"><a href="#3-2-2-版本链" class="headerlink" title="3.2.2. 版本链"></a>3.2.2. 版本链</h3><p>所谓的版本链就是<span style="background-color:#ff00ff">多个事务操作同一条记录</span>的时候<span style="background-color:#ff00ff">都会生成一些 undo 日志</span>，这些 undo 日志通过回滚指针串联在一起。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230214141409.png" alt="image.png"></p><h2 id="3-3-ReadView"><a href="#3-3-ReadView" class="headerlink" title="3.3. ReadView"></a>3.3. ReadView</h2><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230413-1916%%</span>❕ ^6sakz1</p><p>使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务，除了 readview 还需要用到上文提到的版本链，核心问题是：</p><p><span style="background-color:#00ff00">需要判断版本链中的哪个版本是允许当前事务可见的</span></p><p>ReadView 中主要包含 4 个比较重要的内容：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230414124305.png" alt="image.png"></p><ol><li><strong>m_ids</strong>：表示在生成 ReadView 时<span style="background-color:#ff00ff">当前活跃的读写事务</span>的事务 id 列表。<br><strong>“活跃事务”指的就是，启动了但还没提交的事务</strong>。</li><li><strong>min_trx_id</strong>：表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务 id，也就是 m_ids 中的最小值。(up_limit_id)</li><li><strong>max_trx_id</strong>：表示生成 ReadView 时系统中应该分配给下一个事务的 id 值，也就是全局事务中最大的事务 id 值 + 1。  (low_limit_id)</li><li><strong>creator_trx_id</strong>：表示生成该 ReadView 的事务的事务 id。</li></ol><blockquote><pre><code>注意 max_trx_id 并不是 m_ids 中的最大值，事务 id 是递增分配的。比方说现在有 id 为 1，2，3 这三个事务，之后 id 为 3 的事务提交了。那么一个新的读事务在生成 ReadView 时，m_ids 就包括 1 和 2，min_trx_id 的值就是 1，max_trx_id 的值就是 4。</code></pre></blockquote><h3 id="3-3-1-low-与-up"><a href="#3-3-1-low-与-up" class="headerlink" title="3.3.1. low 与 up"></a>3.3.1. low 与 up</h3><p><span style="display:none">%%<br>▶83.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-2245%%</span>❕ ^yncd6p</p><p>其中<span style="background-color:#ff00ff">最早的</span>事务 ID 为 <code>up_limit_id</code>，<span style="background-color:#ff00ff">最迟的</span>事务 ID 为 <code>low_limit_id</code><br>❕<span style="display:none">%%<br>1457-🏡⭐️◼️low 与 up 的理解 ?🔜MSTM📝 up 表示最小的，最早出现的那个，low 的话指后面来的◼️⭐️-point-20230214-1457%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230214145643.png" alt="image.png"></p><p><span style="background-color:#ff00ff">如何记忆：up→起得早的就排在前面，即前面提交的事务 id，数值比较小。</span> ^1xd5nv</p><h2 id="3-4-可见性判断⭐️🔴"><a href="#3-4-可见性判断⭐️🔴" class="headerlink" title="3.4. 可见性判断⭐️🔴"></a>3.4. 可见性判断⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230414123940.png" alt="image.png"></p><p><span style="background-color:#ff00ff">trx_id: 最后一次稳定事务 id，即刚刚提交的事务 id</span></p><ol><li><p>trx_id &lt; up_limit_id (即 min_id)   <span style="background-color:#00ff00">读之前已提交</span><br>   表示这个版本的记录是在创建 Read View <strong>前</strong> 已经提交的事务生成的，所以该版本的记录对当前事务 <strong>可见</strong>。跳转到步骤 5；</p></li><li><p>trx_id &gt; low_limit_id (即 max_id)   <span style="background-color:#00ff00">读之后才启动</span><br>表示这个版本的记录是在创建 Read View <strong>后</strong> 才启动的事务生成的，所以该版本的记录对当前事务 <strong>不可见</strong>。跳转到步骤 4；</p></li><li><p>up_limit_id &lt;&#x3D; trx_id &lt;&#x3D; low_limit_id</p></li></ol><blockquote><p>   需要判断 trx_id 是否在 m_ids 列表中：<br>       1. 如果记录的 trx_id 在 m_ids 列表中，表示生成该版本记录的活跃事务依然活跃着（还没提交事务），所以该版本的记录对当前事务不可见。<br>       2. 如果记录的 trx_id 不在 m_ids 列表中，表示生成该版本记录的活跃事务已经被提交，所以该版本的记录对当前事务可见。</p></blockquote><p>   从 up_limit_id 到 low_limit_id 进行遍历，如果 trx_id 等于他们之中的某个事务 id 的话，表示该记录的最后一次修改尚未保存，跳转到步骤 4。否则跳转到步骤 5；</p><ol start="4"><li>从此记录的 DB_ROLL_PTR 指针所指向的 undo log（此记录的上一次修改，即上一个版本），将 undo log 的 DB_TRX_ID 赋值给 trx_id，跳转到步骤 1 重新开始计算可见性；<br>   <span style="background-color:#ff00ff">取上一个版本进行判断，拿着 readview 从版本链上，从上往下移动判断</span> ❕<span style="display:none">%%<br>1533-🏡⭐️◼️对版本链和 readview 可见性判断的理解◼️⭐️-point-20230214-1533%%</span></li><li>如果此记录的 DELELE_BIT 为 false，说明该记录未被删除，可以返回，否则不返回。</li></ol><blockquote><p><span style="background-color:#00ff00">&gt; 一个事务只能看到第一次查询之前已经提交的事务以及当前事务的修改。</span><br><span style="background-color:#00ff00">&gt; 一个事务不能看到当前事务第一次查询之后创建的事务，以及未提交的事务。</span></p></blockquote><h1 id="4-LBCC"><a href="#4-LBCC" class="headerlink" title="4. LBCC"></a>4. LBCC</h1><p>LBCC（Lock-Base Concurrency Control）基于锁的并发控制。在前面的文章中我们学习过 MVCC，我们知道 MVCC 基于多版本控制可以提升 mysql 的并发读写能力，但是不能完全解决幻读，以及并发写的问题（出现更新丢失）</p><h2 id="4-1-读取分类"><a href="#4-1-读取分类" class="headerlink" title="4.1. 读取分类"></a>4.1. 读取分类</h2><p><a href="https://www.modb.pro/db/593096">https://www.modb.pro/db/593096</a><br>当多个事务同时操作同一份数据内容时，可以分为两种获取方式：当前读、快照读。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230214131739.png" alt="image.png"></p><h3 id="4-1-1-当前读-锁定读"><a href="#4-1-1-当前读-锁定读" class="headerlink" title="4.1.1. 当前读 (锁定读)"></a>4.1.1. 当前读 (锁定读)</h3><p>直接从磁盘或 buffer 中获取当前内容的最新数据，读到什么就是什么。根据隔离级别的不同期间会产生一些锁，防止并发场景下其他事务产生影响；</p><p>在官方叫做 Locking Reads（锁定读）：<br><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html">https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html</a></p><h3 id="4-1-2-快照读"><a href="#4-1-2-快照读" class="headerlink" title="4.1.2. 快照读"></a>4.1.2. <strong>快照读</strong></h3><p>简单的 SELECT 操作（不包括 lock in share mode,for update），根据隔离级别的不同，会在不同时机产生快照，事务读取的实际是快照内容，保证一致性的同时减少了锁之间的竞争；<br>官方叫做 Consistent Nonlocking Reads（一致性非锁定读取，也叫一致性读取）: <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html">https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html</a></p><h2 id="4-2-RR-级别下并没有完全解决幻读"><a href="#4-2-RR-级别下并没有完全解决幻读" class="headerlink" title="4.2. RR 级别下并没有完全解决幻读"></a>4.2. RR 级别下并没有完全解决幻读</h2><p> ❕<span style="display:none">%%<br>1319-🏡⭐️◼️为什么说 MySQL 的可重复读并没有完全解决幻读？◼️⭐️-point-20230214-1319%%</span></p><p>MySQL 在「可重复读」隔离级别下，可以很大程度上避免幻读现象的发生<span style="background-color:#ff0000">（注意是很大程度避免，并不是彻底避免）</span>，所以 MySQL 并不会使用「串行化」隔离级别来避免幻读现象的发生，因为使用「串行化」隔离级别会影响性能。</p><p><span style="background-color:#ff00ff">在 RR 级别下普通查询是快照读，并不会看到其他事务插入的数据。这种幻读情况只有在快照读与当前读混合使用的情况下才会出现，这部分也是争议比较多的地方。</span></p><p>然而当前读的定义就是能从 buffer 或磁盘获取到已提交数据的最新值，所以这跟事务的可见性其实并不矛盾。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230216080646.png" alt="image.png"></p><p>案例一分析：事务 B 的两次读中间 A 有提交，如果 B 第二次读是当前读，就会发生幻读。因为当前读不是通过 readview 来判断可见性，而是直接读取缓存中的最新数据</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230216080900.png" alt="image.png"></p><h3 id="4-2-1-解决方案"><a href="#4-2-1-解决方案" class="headerlink" title="4.2.1. 解决方案"></a>4.2.1. 解决方案</h3><p><span style="background-color:#ff00ff"><strong>Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题，就引出了间隙锁</strong>。</span></p><h1 id="5-幻读总结"><a href="#5-幻读总结" class="headerlink" title="5. 幻读总结"></a>5. 幻读总结</h1><p>MySQL InnoDB 引擎的可重复读隔离级别（默认隔离级），根据不同的查询方式，分别提出了避免幻读的方案：</p><ul><li>针对 <strong>快照读</strong>（普通 select 语句），是通过 MVCC 方式解决了幻读。</li><li>针对 <strong>当前读</strong>（select … for update 等语句），是通过 next-key lock（记录锁 + 间隙锁）方式解决了幻读。</li></ul><p>举例两个发生幻读场景的例子：</p><p>第一个例子：对于快照读， MVCC 并不能完全避免幻读现象。因为当事务 A 更新了一条事务 B 插入的记录，那么事务 A 前后两次查询的记录条目就不一样了，所以就发生幻读。</p><p>第二个例子：对于当前读，如果事务开启后，并没有执行当前读，而是先快照读，然后这期间如果其他事务插入了一条记录，那么事务后续使用当前读进行查询的时候，就会发现两次查询的记录条目就不一样了，所以就发生幻读。</p><p>所以，<strong>MySQL 可重复读隔离级别并没有彻底解决幻读，只是很大程度上避免了幻读现象的发生。</strong></p><p>要避免这类特殊场景下发生幻读的现象的话，就要<span style="background-color:#ff00ff">尽量在开启事务之后，马上执行 select … for update 这类当前读的语句</span>，<span style="background-color:#00ff00">因为它会对记录加 next-key lock，从而避免其他事务插入一条新记录。</span></p><p><strong>for update 案例</strong>：</p><h1 id="6-RC-与-RR-隔离级别的区别"><a href="#6-RC-与-RR-隔离级别的区别" class="headerlink" title="6. RC 与 RR 隔离级别的区别"></a>6. RC 与 RR 隔离级别的区别</h1><p>Repeatable Read 和 Read Committed 隔离级别都是基于 read view 来实现，不同之处在于：</p><ul><li><p>Repeatable Read<br>read view 是在执行事务中第一条 select 语句的瞬间创建，后续整个事务期间所有的 select 都是复用这个对象，所以能保证每次读取的一致性。（<strong>可重复读的语义</strong>）❕<span style="display:none">%%<br>1537-🏡⭐️◼️可重复读，复用第一次查询时使用的 readview。与读已提交不同的是，读已提交每次都会生成一个 readview，相当于动态维护了 m_ids 集合◼️⭐️-point-20230214-1537%%</span></p></li><li><p>Read Committed<br>事务中每条 select 语句都会创建 read view，这样就可以读取到其它事务已经提交的内容。</p></li></ul><blockquote><p>对于 InnoDB 来说，Repeatable Read 虽然比 Read Committed 隔离级别高，开销反而相对较小。</p></blockquote><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><p><a href="https://www.modb.pro/db/593096">https://www.modb.pro/db/593096</a><br><a href="https://juejin.cn/post/7020422614552150052">https://juejin.cn/post/7020422614552150052</a><br>可见性： <a href="https://developer.aliyun.com/article/1094075">https://developer.aliyun.com/article/1094075</a> ^zqsfcl<br><a href="https://juejin.cn/post/6844903774071291917">https://juejin.cn/post/6844903774071291917</a> ^033zxt</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213230544.jpg" alt="image-20200201101126173"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213230606.jpg" alt="image-20200201101626392"></p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> MVCC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-关键字和接口-7、Throwable</title>
      <link href="/2023/02/12/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-7%E3%80%81Throwable/"/>
      <url>/2023/02/12/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-7%E3%80%81Throwable/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-异常结构"><a href="#1-异常结构" class="headerlink" title="1. 异常结构"></a>1. 异常结构</h1><h2 id="1-1-Error（错误）"><a href="#1-1-Error（错误）" class="headerlink" title="1.1. Error（错误）"></a>1.1. Error（错误）</h2><p>定义：Error 类及其子类。程序中无法处理的错误，表示运行应用程序中出现了严重的错误。<br>特点：此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError（虚拟机运行错误）、<span style="background-color:#ffff00">NoClassDefFoundError（类定义错误）等。比如 OutOfMemoryError：内存不足错误；StackOverflowError：栈溢出错误</span>。此类错误发生时，JVM 将终止线程。<span style="background-color:#ff00ff">这些错误是不受检异常，非代码性错误。因此，当此类错误发生时，应用程序不应该去处理此类错误</span>。按照 Java 惯例，我们是不应该实现任何新的 Error 子类的！</p><h2 id="1-2-Exception（异常）"><a href="#1-2-Exception（异常）" class="headerlink" title="1.2. Exception（异常）"></a>1.2. Exception（异常）</h2><p>程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类：运行时异常和编译时异常。</p><h1 id="2-异常分类"><a href="#2-异常分类" class="headerlink" title="2. 异常分类"></a>2. 异常分类</h1><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230321-1604%%</span>❕ ^u110am</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213170910.png" alt="image.png"></p><h2 id="2-1-运行时异常和编译时异常"><a href="#2-1-运行时异常和编译时异常" class="headerlink" title="2.1. 运行时异常和编译时异常"></a>2.1. 运行时异常和编译时异常</h2><h3 id="2-1-1-运行时异常"><a href="#2-1-1-运行时异常" class="headerlink" title="2.1.1. 运行时异常"></a>2.1.1. 运行时异常</h3><p>定义：RuntimeException 类及其子类，表示 JVM 在运行期间可能出现的异常。<br>特点：Java 编译器不会检查它。也就是说，当程序中可能出现这类异常时，倘若既 “ 没有通过 throws 声明抛出它 “，也 “ 没有用 try-catch 语句捕获它 “，还是会编译通过。比如&#x3D;&#x3D;NullPointerException 空指针异常、ArrayIndexOutBoundException 数组下标越界异常、ClassCastException 类型转换异常、ArithmeticExecption 算术异常。&#x3D;&#x3D;此类异常属于不受检异常，一般是由程序逻辑错误引起的，在程序中可以选择捕获处理，也可以不处理。虽然 Java 编译器不会检查运行时异常，但是我们也可以通过 throws 进行声明抛出，也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常，则需要通过修改代码来进行避免。例如，若会发生除数为零的情况，则需要通过代码避免该情况的发生！<br>RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获（就算我们没写异常捕获语句运行时也会抛出错误！！），此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。</p><h3 id="2-1-2-编译时异常"><a href="#2-1-2-编译时异常" class="headerlink" title="2.1.2. 编译时异常"></a>2.1.2. 编译时异常</h3><p>定义: Exception 中除 RuntimeException 及其子类之外的异常。<br>特点: Java 编译器会检查它。如果程序中出现此类异常，比如&#x3D;&#x3D;ClassNotFoundException（没有找到指定的类异常），IOException（IO 流异常）&#x3D;&#x3D;，要么通过 throws 进行声明抛出，要么通过 trycatch 进行捕获处理，否则不能通过编译。在程序中，通常不会自定义该类异常，而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。</p><h2 id="2-2-受检异常与非受检异常"><a href="#2-2-受检异常与非受检异常" class="headerlink" title="2.2. 受检异常与非受检异常"></a>2.2. 受检异常与非受检异常</h2><p>Java 的所有异常可以分为受检异常（checked exception）和非受检异常（uncheckedexception）。</p><h3 id="2-2-1-受检异常"><a href="#2-2-1-受检异常" class="headerlink" title="2.2.1. 受检异常"></a>2.2.1. 受检异常</h3><p>编译器要求必须处理的异常。正确的程序在运行过程中，经常容易出现的、符合预期的异常情况。<br>一旦发生此类异常，就必须采用某种方式进行处理。除 RuntimeException 及其子类外，其他的 Exception 异常都属于受检异常。<span style="background-color:#00ff00">编译器会检查此类异常，也就是说当编译器检查到应用中的某处可能会此类异常时，将会提示你处理本异常</span>——<span style="background-color:#ff00ff">要么使用 try-catch 捕获，要么使用方法签名中用 throws 关键字抛出，否则编译不通过</span>。<br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;NOTE_JVM&#x2F;JVM中篇：字节码与类的加载篇&#x2F;03-类的加载过程（类的生命周期）详解&#x2F;README#^d3qqqj]]</p><h3 id="2-2-2-非受检异常⭐️🔴"><a href="#2-2-2-非受检异常⭐️🔴" class="headerlink" title="2.2.2. 非受检异常⭐️🔴"></a>2.2.2. 非受检异常⭐️🔴</h3><p>编译器不会进行检查并且不要求必须处理的异常，也就说当程序中出现此类异常时，即使我们没有 try-catch 捕获它，也没有使用 throws 抛出该异常，编译也会正常通过。该类异常包括<span style="background-color:#00ff00">运行时异常（RuntimeException 极其子类）</span>和<span style="background-color:#ff00ff">错误（Error）</span>。</p><h1 id="3-JVM-是如何处理异常的？"><a href="#3-JVM-是如何处理异常的？" class="headerlink" title="3. JVM 是如何处理异常的？"></a>3. JVM 是如何处理异常的？</h1><h2 id="3-1-异常对象"><a href="#3-1-异常对象" class="headerlink" title="3.1. 异常对象"></a>3.1. 异常对象</h2><p>在一个方法中如果发生异常，这个方法会创建一个异常对象，并转交给 JVM，该异常对象&#x3D;&#x3D;包含异常名称，异常描述以及异常发生时应用程序的状态&#x3D;&#x3D;。<span style="background-color:#00ff00">创建异常对象并转交给 JVM 的过程称为抛出异常</span>。可能有一系列的方法调用，最终才进入抛出异常的方法，这一系列方法调用的有序列表叫做调用栈。<br><span style="background-color:#00ff00">JVM 会顺着调用栈去查找看是否有可以处理异常的代码</span>，如果有，则调用异常处理代码。当 JVM 发现可以处理异常的代码时，会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块，JVM 就会将该异常转交给默认的异常处理器（默认处理器为 JVM 的一部分），默认异常处理器打印出异常信息并终止应用程序。<br>❕<span style="display:none">%%<br>0712-🏡⭐️◼️JVM 角度解释异常处理逻辑 ?🔜MSTM📝 ◼️⭐️-point-20230216-0712%%</span></p><h2 id="3-2-异常表"><a href="#3-2-异常表" class="headerlink" title="3.2. 异常表"></a>3.2. 异常表</h2><a href="/2023/02/02/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-1%E3%80%81JVM-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/" title="性能调优专题-基础-1、JVM-异常处理">性能调优专题-基础-1、JVM-异常处理</a><h1 id="4-ClassNotFoundException-和-NoClassDefFoundError-的区别"><a href="#4-ClassNotFoundException-和-NoClassDefFoundError-的区别" class="headerlink" title="4. ClassNotFoundException 和 NoClassDefFoundError 的区别"></a>4. ClassNotFoundException 和 NoClassDefFoundError 的区别</h1><p><a href="https://blog.51cto.com/mingmingruyue/3311182">https://blog.51cto.com/mingmingruyue/3311182</a></p><h1 id="5-GC-相关"><a href="#5-GC-相关" class="headerlink" title="5. GC 相关"></a>5. GC 相关</h1><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p>[[Java异常面试题 33道.pdf]]</p>]]></content>
      
      
      <categories>
          
          <category> 关键字和接口 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Throwable </tag>
            
            <tag> 异常 </tag>
            
            <tag> 错误 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-1、Spring事务(本地事务)</title>
      <link href="/2023/02/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-1%E3%80%81Spring%E4%BA%8B%E5%8A%A1(%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1)/"/>
      <url>/2023/02/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-1%E3%80%81Spring%E4%BA%8B%E5%8A%A1(%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1)/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-本地事务"><a href="#1-本地事务" class="headerlink" title="1. 本地事务"></a>1. 本地事务</h1><p>商品新增功能非常复杂，商品管理微服务在 service 层中调用保存 spu 和 sku 相关的方法，为了保证数据的一致性，必然会使用事务。</p><h2 id="1-1-基本概念"><a href="#1-1-基本概念" class="headerlink" title="1.1. 基本概念"></a>1.1. 基本概念</h2><p>事务的概念：事务是逻辑上一组操作，组成这组操作的各个逻辑单元，要么一起成功，要么一起失败。</p><p>事务的四个特性（ACID）：</p><ol><li>原子性 (atomicity)：“原子”的本意是“<strong>不可再分</strong>”，事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行，要么都不执行。</li><li>一致性 (consistency)：“一致”指的是数据的一致，具体是指：所有数据都处于满足业务规则的一致性状态。一致性原则要求：一个事务中不管涉及到多少个操作，都必须保证 <strong>事务执行之前</strong> 数据是正确的，<strong>事务执行之后</strong> 数据仍然是正确的。如果一个事务在执行的过程中，其中某一个或某几个操作失败了，则必须将其他所有操作撤销，将数据恢复到事务执行之前的状态，这就是回滚。</li><li>隔离性 (isolation)：在应用程序实际运行过程中，事务往往是并发执行的，所以很有可能有许多事务同时处理相同的数据，因此每个事务都应该与其他事务隔离开来，防止数据损坏。隔离性原则要求多个事务在 <strong>并发执行过程中不会互相干扰</strong></li><li>持久性 (durability)：持久性原则要求事务执行完成后，对数据的修改永久的保存下来，不会因各种系统错误或其他意外情况而受到影响。通常情况下，事务对数据的修改应该被写入到 <strong>持久化存储器</strong> 中。</li></ol><h3 id="1-1-1-相关命令"><a href="#1-1-1-相关命令" class="headerlink" title="1.1.1. 相关命令"></a>1.1.1. 相关命令</h3><p>查看全局事务隔离级别：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">SELECT @@global.tx_isolation<br></code></pre></td></tr></table></figure><p>设置全局事务隔离级别：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">set global transaction isolation level read committed;<br></code></pre></td></tr></table></figure><p>查看当前会话事务隔离级别：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">SELECT @@tx_isolation<br></code></pre></td></tr></table></figure><p>设置当前会话事务隔离级别：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">set session transaction isolation level read committed;<br></code></pre></td></tr></table></figure><p>查看 mysql 默认自动提交状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">select @@autocommit<br></code></pre></td></tr></table></figure><p>设置 mysql 默认自动提交状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">set autocommit = 0;【不自动提交】<br></code></pre></td></tr></table></figure><p>开启一个事务：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">start transaction;<br></code></pre></td></tr></table></figure><p>提交事务：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">commit<br></code></pre></td></tr></table></figure><p>回滚事务：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">rollback<br></code></pre></td></tr></table></figure><p>在事务中创建一个保存点：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">savepoint tx1<br></code></pre></td></tr></table></figure><p>回滚到保存点：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">rollback to tx1<br></code></pre></td></tr></table></figure><h3 id="1-1-2-事务实现原理"><a href="#1-1-2-事务实现原理" class="headerlink" title="1.1.2. 事务实现原理"></a>1.1.2. 事务实现原理</h3><a href="/2023/02/13/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-2%E3%80%81%E4%BA%8B%E5%8A%A1-MVCC-LBCC/" title="MySQL-2、事务-MVCC-LBCC">MySQL-2、事务-MVCC-LBCC</a><h2 id="1-2-隔离级别"><a href="#1-2-隔离级别" class="headerlink" title="1.2. 隔离级别"></a>1.2. 隔离级别</h2><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230413-1816%%</span>❕ ^95ef3l</p><p>事务隔离级别，是为了解决多个并行事务竞争导致的数据安全问题的一种规范。MySQL 服务端是允许多个客户端连接的，这意味着 MySQL 会出现同时处理多个事务的情况。对于整个数据库来说是并行事务，对于确定的共同操作的记录来说是并发的。<br><strong>并发写：使用 mysql 默认的锁机制（独占锁）<br>并发读产生问题：</strong></p><ul><li><strong>脏读</strong>： 一个事务可以读取另一个事务未提交的数据</li><li><strong>不可重复读</strong> ：一个事务可以读取另一个事务已提交的数据<span style="background-color:#ff0000">单条记录</span>前后不匹配<br><span style="background-color:#ff00ff">  即在当前事务中读到了其他事物 commit 的更新操作</span></li></ul><blockquote><pre><code>    **在一个事务内多次读取同一个数据，如果出现前后两次读到的数据不一样的情况，就意味着发生了「不可重复读」现象。**</code></pre></blockquote><ul><li><strong>虚读（幻读）</strong>： 一个事务可以读取另一个事务已提交的数据读取的数据前后多了点或者少了点。即在当前事务中多次读，<span style="background-color:#ff00ff">读到了其他事物 commit 的插入或删除操作</span>，导致结果集记录条数出现变化</li></ul><blockquote><pre><code>    **在一个事务内多次查询某个符合查询条件的「记录数量」，如果出现前后两次查询到的记录数量不一样的情况，就意味着发生了「幻读」现象。**</code></pre></blockquote><p><strong>解决读问题：设置事务隔离级别</strong></p><ul><li>read uncommitted(0)</li><li><strong>read committed(2)</strong></li><li><strong>repeatable read(4)</strong></li><li>Serializable(8)</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212214930.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212214942.png"></p><p>隔离级别越高，性能越低。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212215029.png" alt="image-20230101141729342"></p><p>一般情况下：脏读是不可允许的，不可重复读和幻读是可以被适当允许的。</p><h3 id="1-2-1-Spring-事务管理"><a href="#1-2-1-Spring-事务管理" class="headerlink" title="1.2.1. Spring 事务管理"></a>1.2.1. Spring 事务管理</h3><p>跟数据库的一致，但比较霸道，如果数据库跟 spring 中配置的不一样，以 spring 配置为准</p><h3 id="1-2-2-隔离级别实现原理"><a href="#1-2-2-隔离级别实现原理" class="headerlink" title="1.2.2. 隔离级别实现原理"></a>1.2.2. 隔离级别实现原理</h3><a href="/2023/02/13/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-2%E3%80%81%E4%BA%8B%E5%8A%A1-MVCC-LBCC/" title="MySQL-2、事务-MVCC-LBCC">MySQL-2、事务-MVCC-LBCC</a><h2 id="1-3-传播行为"><a href="#1-3-传播行为" class="headerlink" title="1.3. 传播行为"></a>1.3. 传播行为</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212215625.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213070707.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213070717.png"></p><p>事务的传播行为不是 jdbc 规范中的定义。传播行为主要针对实际开发中的问题</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213070725.png" alt="image-20230101141753862"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230216194100.png" alt="image.png"></p><h3 id="1-3-1-死活不要事务的-NEVER、NOT-SUPPORTED"><a href="#1-3-1-死活不要事务的-NEVER、NOT-SUPPORTED" class="headerlink" title="1.3.1. 死活不要事务的-NEVER、NOT_SUPPORTED"></a>1.3.1. 死活不要事务的-NEVER、NOT_SUPPORTED</h3><p>NEVER: 直接报异常，不存在回滚不回滚的辩论问题了</p><p>NOT_SUPPORTED:  <span style="background-color:#ff0000">外层有事务会被内层方法挂起</span>，<span style="background-color:#ff0000">内层方法以非事务运行</span>，不存在回滚，外层捕获内层的异常的话，可以达到外层回滚，但内层没事务，所以不受控制</p><h3 id="1-3-2-可有可无事务的-SUPPORTS"><a href="#1-3-2-可有可无事务的-SUPPORTS" class="headerlink" title="1.3.2. 可有可无事务的-SUPPORTS"></a>1.3.2. 可有可无事务的-SUPPORTS</h3><p>SUPPORTS：有就用外层的，跟外层一体，没有就不用，非事务运行</p><h3 id="1-3-3-必须要有事务的"><a href="#1-3-3-必须要有事务的" class="headerlink" title="1.3.3. 必须要有事务的"></a>1.3.3. 必须要有事务的</h3><h4 id="1-3-3-1-只会有-1-个事务-同进退"><a href="#1-3-3-1-只会有-1-个事务-同进退" class="headerlink" title="1.3.3.1. 只会有 1 个事务-同进退"></a>1.3.3.1. 只会有 1 个事务-同进退</h4><h5 id="1-3-3-1-1-REQUIRED-默认"><a href="#1-3-3-1-1-REQUIRED-默认" class="headerlink" title="1.3.3.1.1. REQUIRED-默认"></a>1.3.3.1.1. REQUIRED-默认</h5><p><span style="background-color:#ff0000">Spring 默认的传播方式</span>，外层有就加入外层事务，如果外层没有则新建事务。<span style="background-color:#ff00ff">内外层，不管哪层有异常，都一起回滚</span><br>外层异常，外层可以控制内层一起回滚 (就算内层没有异常)；而内层异常，由于设置了回滚标记，外层捕获异常后，内外层都要回滚。</p><h5 id="1-3-3-1-2-MANDATORY"><a href="#1-3-3-1-2-MANDATORY" class="headerlink" title="1.3.3.1.2. MANDATORY"></a>1.3.3.1.2. MANDATORY</h5><p>强制性用外层的，如果没有就抛出异常，如果有就用外层事务。内外层，不管哪层有异常，都一起回滚</p><h4 id="1-3-3-2-必会新建-1-个事务"><a href="#1-3-3-2-必会新建-1-个事务" class="headerlink" title="1.3.3.2. 必会新建 1 个事务"></a>1.3.3.2. 必会新建 1 个事务</h4><h5 id="1-3-3-2-1-REQUIRES-NEW-互不影响"><a href="#1-3-3-2-1-REQUIRES-NEW-互不影响" class="headerlink" title="1.3.3.2.1. REQUIRES_NEW-互不影响"></a>1.3.3.2.1. REQUIRES_NEW-互不影响</h5><p>存在 (1 个 or 2 个事务)：&#x3D;&#x3D;不管外层有没有，都会新建一个事务&#x3D;&#x3D;，如果外层有，则<span style="background-color:#ff0000">将原来的先挂起，直到新的事务完成。 </span><span style="background-color:#00ff00"> 而且回滚动作 2 个事务互不相关</span><br>它通常用于处理需要独立于其他事务的操作，例如记录日志或者在遇到异常时回滚该操作。<span style="background-color:#ff00ff">外层和内层互不影响，完全独立的 2 个事务</span></p><p><strong>使用场景</strong><br>常用于日志记录, 或者交易失败仍需要留痕.</p><p>还有就是 <strong>时序控制</strong>, 支付依赖于已经创建的订单, 无订单不支付. 先要有订单才能支付. 常见于事务步骤 <strong>要求时序</strong> 的情况。比如一键购、再来一单这种业务场景</p><h5 id="1-3-3-2-2-NESTED-外管内"><a href="#1-3-3-2-2-NESTED-外管内" class="headerlink" title="1.3.3.2.2. NESTED-外管内"></a>1.3.3.2.2. NESTED-外管内</h5><p>存在 (1 个 or 2 个事务)：&#x3D;&#x3D;不管外层有没有，都会新建一个事务&#x3D;&#x3D;，如果外层有，则新建一个事务嵌套在外层事务中，外层回滚控制内层一起回滚，<span style="background-color:#ff00ff">内层回滚外层可以正常提交</span>，因为内层事务把回滚标记清除了，外层事务看不到回滚标记，就只正常提交了外层自己的事务：<span style="background-color:#00ff00">外层可以控制内层一起回滚，内层回滚无法连同外层回滚</span></p><h3 id="1-3-4-业务案例"><a href="#1-3-4-业务案例" class="headerlink" title="1.3.4. 业务案例"></a>1.3.4. 业务案例</h3><p>❕<span style="display:none">%%<br>1629-🏡⭐️◼️传播行为完善内容 ?🔜MSTM📝 REQUIRES_NEW 的业务场景：相互独立的事务，且内层事务会挂起外层事务，直到内层事务完成提交或者回滚。适用于业务留痕场景、业务时序场景。比如一键购、再来一单这种业务场景，订单服务的创建订单动作为外层事务，支付服务的付款动作为内层事务，内层支付动作的事务失败不会影响外层创建订单的动作的事务。内层事务可以单独回滚，后续再发起支付即可。◼️⭐️-point-20230215-1629%%</span></p><a href="/2023/02/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-1%E3%80%81Spring%E4%BA%8B%E5%8A%A1(%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1)/" title="事务-1、Spring事务(本地事务)">事务-1、Spring事务(本地事务)</a><h4 id="1-3-4-1-REQUIRES-NEW"><a href="#1-3-4-1-REQUIRES-NEW" class="headerlink" title="1.3.4.1. REQUIRES_NEW"></a>1.3.4.1. REQUIRES_NEW</h4><p>开启一个全新的事务, 一般用于分布式事务中的一个前期步骤. 比如一键购功能. 主要步骤包括:</p><ul><li><strong>创建订单</strong><ul><li>创建订单</li><li>扣除库存</li><li>创建物流订单</li></ul></li><li><strong>发起支付</strong><ul><li><strong>对于支付,清算相关的业务流程可以参考</strong>: <a href="https://link.segmentfault.com/?enc=9cN2zRzmABPaTb8VtM80vA==.1W9atjLvw00VgxTJVjSK2FbymkvcLvrkx6oq1PhD8fxpQ6kZXEfuOnVwQJTIsNDK">深度解析：什么是支付核心？</a></li></ul></li></ul><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 一键购服务</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">BuyService</span> &#123;<br>    <span class="hljs-meta">@Transactional(propagation = Propagation.REQUIRED)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">buyDirectly</span><span class="hljs-params">()</span> &#123;<br>        Order OrderService.createOrder(OrderDto orderDto);<br>        PayService.pay(PayDto payDto);<br>    &#125;<br>&#125;<br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">OrderService</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">createOrder</span><span class="hljs-params">(OrderDto orderDto)</span>;<br>&#125;<br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">PayService</span> &#123;<br>    <span class="hljs-meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span><br>    Payment <span class="hljs-title function_">pay</span><span class="hljs-params">(PayDto payDto)</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>OrderService.createOrder</strong> 运行在外层事务中, 如果创建订单失败, 就没必要发起支付了, 直接回滚了, 根本就到不了支付这一步.</p><p>当绑定的银行卡余额不足的情况, <strong>PayService.pay();</strong> 是可以回滚的, 而不会影响 <strong>buyDirectly</strong> 整个事务, <strong>OrderService.createOrder();</strong> 成功. 向银行卡不足金额后, 可以重新发起支付, 完成购买过程.</p><blockquote><p>注意: <strong>Propagation.REQUIRES_NEW</strong> 如果作为一个子事务运行, 调用者和被调这不要在同一个服务类中 (因为 Spring AOP 动态代理的限制, 在同一个类中事务是不起作用的)</p><p><strong>Propagation.REQUIRES_NEW</strong> 的一般使用场景是作为内层事务可以单独回滚. 而不是回滚整个外层事务. 因此如果调用者和被调用者如果在一个类中, <strong>Propagation.REQUIRES_NEW</strong> 注解的方法并 <strong>不会</strong> 开启一个新的事务. 因此就达不到内层事务单独回滚的目的.</p><p><strong>归纳: 内层事务可以独立回滚, 不影响外层事务.</strong><br>前提是<span style="background-color:#ff0000">外层事务的方法不能和内层事务的方法在同一个服务类中</span><br>❕<span style="display:none">%%<br>1630-🏡⭐️◼️REQUIRES_NEW 需要注意 ?🔜MSTM📝 必须分开到不同的服务类中，否则 AOP 失效，导致变成同一个事务，而无法独立回滚◼️⭐️-point-20230215-1630%%</span></p></blockquote><h4 id="1-3-4-2-NESTED"><a href="#1-3-4-2-NESTED" class="headerlink" title="1.3.4.2. NESTED"></a>1.3.4.2. NESTED</h4><p>未使用</p><h2 id="1-4-回滚策略"><a href="#1-4-回滚策略" class="headerlink" title="1.4. 回滚策略"></a>1.4. 回滚策略</h2><a href="/2023/02/02/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-3%E3%80%81%E4%BA%8B%E5%8A%A1%E5%A4%B1%E6%95%88/" title="分布式专题-3、事务失效">分布式专题-3、事务失效</a><h2 id="1-5-事务超时"><a href="#1-5-事务超时" class="headerlink" title="1.5. 事务超时"></a>1.5. 事务超时</h2><p><a href="https://dongzl.github.io/2020/08/04/33-Spring-Transaction-Timeout/index.html">https://dongzl.github.io/2020/08/04/33-Spring-Transaction-Timeout/index.html</a></p><h3 id="1-5-1-分析总结"><a href="#1-5-1-分析总结" class="headerlink" title="1.5.1. 分析总结"></a>1.5.1. 分析总结</h3><p>分析了使用 <code>JdbcTemplate</code> 和 <code>MyBatis</code> 框架的不同执行过程，虽然代码实现不同，但是最终的效果应该说是一致的。</p><p>在一个事务当中如果执行多条 <code>SQL</code>，每次执行创建 <code>Statement</code> 对象时都会检查是否已经出现超时，如果未超时，则会设置 <code>Statement</code> 的 <code>queryTimeout</code> 属性，继续执行 <code>SQL</code>，如果本次执行一切 OK，则在执行下一条 <code>SQL</code> 语句时会重复上面逻辑，如果所有 <code>SQL</code> 语句全部执行成功，即使在最后设置一个耗时操作，也不会出现事务超时的，耗时操作并不会计入事务的超时时间判断；当然，如果某个耗时操作执行完后，还有 <code>SQL</code> 语句需要执行，那么这个耗时操作的时间是会计入到事务的超时时间当中的。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230413132916.png" alt="image.png"></p><p>在上面的博客文章中，作者总结了一个公式：</p><blockquote><p>Spring 事务超时 &#x3D; 事务开始时到最后一个 Statement 创建时时间 + 最后一个 Statement 的执行时超时时间（即其 queryTimeout）</p></blockquote><h2 id="1-6-只读事务"><a href="#1-6-只读事务" class="headerlink" title="1.6. 只读事务"></a>1.6. 只读事务</h2><p>如果一个方法标记为 readOnly&#x3D;true 事务，则代表该方法只能查询，不能增删改。readOnly 默认为 false</p><h1 id="2-什么是-Spring-事务⭐️🔴"><a href="#2-什么是-Spring-事务⭐️🔴" class="headerlink" title="2. 什么是 Spring 事务⭐️🔴"></a>2. 什么是 Spring 事务⭐️🔴</h1><p><span style="display:none">%%<br>▶7.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230412-1955%%</span>❕ ^9ocq1k</p><h2 id="2-1-与数据库事务关系"><a href="#2-1-与数据库事务关系" class="headerlink" title="2.1. 与数据库事务关系"></a>2.1. 与数据库事务关系</h2><p>之前一直觉得事务只针对于数据库当中，5 种隔离级别，7 种传播行为，后来才发现这是针对 Spring 的，对数据库来说隔离级别只有 4 种，Spring 多了一个 DEFAULT 这是一个 PlatfromTransactionManager 默认的隔离级别，使用数据库默认的事务隔离级别.</p><p>总的来说，本质上其实是同一个概念</p><p>spring 的事务是对数据库的事务的封装, 最后本质的实现还是在数据库, 假如数据库不支持事务的话,spring 的事务是没有作用的</p><p>数据库的事务说简单就只有开启, 回滚和关闭,spring 对数据库事务的包装, 原理就是拿一个数据连接, 根据 spring 的事务配置, 操作这个数据连接对数据库进行事务开启, 回滚或关闭操作. 但是 spring 除了实现这些, <span style="background-color:#ff00ff">还配合 spring 的传播行为对事务进行了更广泛的管理</span>. 其实这里还有个重要的点, 那就是事务中涉及的隔离级别, 以及 spring 如何对数据库的隔离级别进行封装. 事务与隔离级别放在一起理解会更好些.</p><h2 id="2-2-使用方法"><a href="#2-2-使用方法" class="headerlink" title="2.2. 使用方法"></a>2.2. 使用方法</h2><p>springboot 2.x 可直接使用@Transactional 玩事务，传播行为<span style="background-color:#ff00ff">默认是 REQUIRED</span></p><h2 id="2-3-方法级别事务"><a href="#2-3-方法级别事务" class="headerlink" title="2.3. 方法级别事务"></a>2.3. 方法级别事务</h2><p>同一个类中方法调用，想要设置事务生效，必须使用代理</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20201006113410383.png" alt="image-20201006113410383"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20201006112912200.png" alt="image-20201006112912200"></p><h2 id="2-4-本地事务局限"><a href="#2-4-本地事务局限" class="headerlink" title="2.4. 本地事务局限"></a>2.4. 本地事务局限</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20201006110834787.png" alt="image-20201006110834787"></p><h2 id="2-5-声明式事务"><a href="#2-5-声明式事务" class="headerlink" title="2.5. 声明式事务"></a>2.5. 声明式事务</h2><a href="/2022/12/08/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-5%E3%80%81%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1-@EnableTransactionManagement/" title="Spring-5、声明式事务-@EnableTransactionManagement">Spring-5、声明式事务-@EnableTransactionManagement</a><h2 id="2-6-实现原理"><a href="#2-6-实现原理" class="headerlink" title="2.6. 实现原理"></a>2.6. 实现原理</h2><h3 id="2-6-1-传播行为实现原理"><a href="#2-6-1-传播行为实现原理" class="headerlink" title="2.6.1. 传播行为实现原理"></a>2.6.1. 传播行为实现原理</h3><a href="/2023/02/11/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-2%E3%80%81Spring-%E4%BA%8B%E5%8A%A1/" title="面试专题-2、Spring-事务">面试专题-2、Spring-事务</a><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><p><a href="https://blog.csdn.net/f641385712/article/details/80445933">【小家java】Spring事务不生效的原因大解读</a><br><a href="https://blog.csdn.net/millery22/article/details/123567661?spm=1001.2101.3001.6650.10&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-10-123567661-blog-92797058.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-10-123567661-blog-92797058.pc_relevant_recovery_v2&utm_relevant_index=11">Spring注解@Async和@Transactional失效问题</a></p><p>&#x2F;Users&#x2F;weileluo&#x2F;001-study&#x2F;shangguigu&#x2F;谷粒商城全套视频&#x2F;分布式高级篇&#x2F;283、商城业务 - 分布式事务 - 本地事务在分布式下的问题</p><p>业务场景： <a href="https://segmentfault.com/a/1190000015794446">https://segmentfault.com/a/1190000015794446</a> ^bnb0h5</p><p>事务，事务隔离级别，spring 事务配置，spring 事务的传播特性 - 阿里巴巴面试题，面试题系列（十）</p><p><a href="https://www.bilibili.com/video/BV1EE411p7dD?from=search&seid=11104488055930606136&spm_id_from=333.337.0.0">https://www.bilibili.com/video/BV1EE411p7dD?from=search&seid=11104488055930606136&spm_id_from&#x3D;333.337.0.0</a></p>]]></content>
      
      
      <categories>
          
          <category> 事务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 事务 </tag>
            
            <tag> Spring事务 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-9、@Lazy</title>
      <link href="/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/"/>
      <url>/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-源码流程图"><a href="#1-源码流程图" class="headerlink" title="1. 源码流程图"></a>1. 源码流程图</h1><p><a href="https://www.processon.com/diagraming/63fad8c81c6b02464eef690a">https://www.processon.com/diagraming/63fad8c81c6b02464eef690a</a></p><h1 id="2-BD-中设置属性"><a href="#2-BD-中设置属性" class="headerlink" title="2. BD 中设置属性"></a>2. BD 中设置属性</h1><h2 id="2-1-使用方法"><a href="#2-1-使用方法" class="headerlink" title="2.1. 使用方法"></a>2.1. 使用方法</h2><h3 id="2-1-1-Component-类上加上-Lazy-注解"><a href="#2-1-1-Component-类上加上-Lazy-注解" class="headerlink" title="2.1.1. @Component 类上加上@Lazy 注解"></a>2.1.1. @Component 类上加上@Lazy 注解</h3><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-meta">@Lazy</span><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_">XXXX</span> &#123;<br>    ...<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-1-2-在-Configuration-类中配置-Bean-时添加-Lazy-注解"><a href="#2-1-2-在-Configuration-类中配置-Bean-时添加-Lazy-注解" class="headerlink" title="2.1.2. 在@Configuration 类中配置@Bean 时添加@Lazy 注解"></a>2.1.2. 在@Configuration 类中配置@Bean 时添加@Lazy 注解</h3><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-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">XXXX</span> &#123;<br>    <span class="hljs-meta">@Lazy</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> XXX <span class="hljs-title function_">getXXX</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_">XXX</span>();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-1-3-ComponentScan-配置延迟加载"><a href="#2-1-3-ComponentScan-配置延迟加载" class="headerlink" title="2.1.3. @ComponentScan 配置延迟加载"></a>2.1.3. @ComponentScan 配置延迟加载</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></pre></td><td class="code"><pre><code class="hljs Java"><span class="hljs-meta">@ComponentScan(value = &quot;XXX.XXX&quot;, lazyInit = true)</span><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_">XXXX</span> &#123;<br>    ...<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="2-2-设置时机⭐️🔴"><a href="#2-2-设置时机⭐️🔴" class="headerlink" title="2.2. 设置时机⭐️🔴"></a>2.2. 设置时机⭐️🔴</h2><p><span style="display:none">%%<br>▶30.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-1908%%</span>❕ ^cqh37t</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230226130018.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323190724.png" alt="image.png"></p><p>当使用 2.1 三种配置后，Spring 在扫描加载 Bean 时会读取@Lazy 和@Component 注解相应值，并设置 Bean 定义的 lazyInit 属性。读取注解配置时最终会调用 <code>ClassPathBeanDefinitionScanner</code> 及其子类实现的 doScan 方法，在这个方法中完成注解的读取配置。</p><p><a href="https://www.processon.com/diagraming/63dd8eb4b59543238fa3a6a5">https://www.processon.com/diagraming/63dd8eb4b59543238fa3a6a5</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230226184051.png" alt="image.png"></p><h1 id="3-Lazy-生效⭐️🔴"><a href="#3-Lazy-生效⭐️🔴" class="headerlink" title="3. @Lazy 生效⭐️🔴"></a>3. @Lazy 生效⭐️🔴</h1><p><span style="display:none">%%<br>▶38.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1300%%</span>❕ ^50mrkk</p><p><a href="https://www.processon.com/diagraming/63fad8c81c6b02464eef690a">https://www.processon.com/diagraming/63fad8c81c6b02464eef690a</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322221000.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230303221527.png" alt="image.png"></p><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h1><p>从上面的例子我们可以总结及延伸出两个注意点：</p><ol><li>非延迟加载的类中不能自动注入延迟加载的类，会导致延迟加载失效；</li><li>如果想要实现某个类延迟加载使用自动注入功能时需要<span style="background-color:#ff0000">调用链前都不存在</span>非延迟加载类，否则延迟加载失效。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212130611.png" alt="image.png"></p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><p><a href="https://blog.csdn.net/Peelarmy/article/details/107339547">https://blog.csdn.net/Peelarmy/article/details/107339547</a><br><a href="https://blog.csdn.net/wang489687009/article/details/120577472">https://blog.csdn.net/wang489687009/article/details/120577472</a></p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试专题-2、Spring事务</title>
      <link href="/2023/02/11/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-2%E3%80%81Spring-%E4%BA%8B%E5%8A%A1/"/>
      <url>/2023/02/11/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-2%E3%80%81Spring-%E4%BA%8B%E5%8A%A1/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-Spring-的事务传播行为实现原理"><a href="#1-Spring-的事务传播行为实现原理" class="headerlink" title="1. Spring 的事务传播行为实现原理"></a>1. Spring 的事务传播行为实现原理</h1><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230413-1415%%</span>❕ ^27ji6t</p><p>Spring 的事务信息是存在 ThreadLocal 中的，所以一个线程永远只能有一个事务<br>❕<span style="display:none">%%<br>2217-🏡⭐️◼️Spring 事务的实现原理◼️⭐️-point-20230214-2217%%</span></p><ol><li>**融入 (NESTED)**：当传播行为是融入外部事务则拿到 ThreadLocal 中的 Connection、共享一个数据库连接共同提交、回滚； </li><li>**创建新事务 (REQUIRES_NEW)**：当传播行为是创建新事务，会将嵌套新事务存入 ThreadLocal、再将外部事务暂存起来；当嵌套事务提交、回滚后，会将暂存的事务信息恢复到 ThreadLocal 中</li></ol><h2 id="1-1-融入"><a href="#1-1-融入" class="headerlink" title="1.1. 融入"></a>1.1. 融入</h2><p>try {<br>              3. 内嵌: 判断 ThreadLocal 是否已经有 Connection, 有的话就说明是一个内嵌事务, 判断当前事务的传播行为<br>                   <strong>如果是融入</strong>: &#x3D;&#x3D;不会创建 Connection, 返回事务状态信息&#x3D;&#x3D;<span style="background-color:#00ff00">(TransactionInfo.newTransaction&#x3D;false)</span><br>1、外部: 创建一个数据库连接 Connection 存入 ThreadLocal, 并且修改数据库连接的 autoCommit 属性为 false 返回事务状态信息 (TransactionInfo.newTransaction&#x3D;true)<br>2、外部: 然后执行目标方法方法 (调用了内部事务方法) 方法中会执行数据库操作 sql<br>               4. 内嵌: 然后执行目标方法方法方法中会执行数据库操作 sql<br>}<br>catch{<br>               如果出现了异常, 并且这个异常是需要回滚的就会回滚事务, 否则仍然提交事务<br>}<br>                5. 内嵌: 判断 newTransaction&#x3D;true 就将要提交事务，但因为融入 (TransactionInfo.newTransaction&#x3D;false) 所以<span style="background-color:#00ff00">暂时不会提交事务</span>。<br>6、外部: 判断 newTransaction&#x3D;true 拿到 ThreadLocal 中的 Connection 进行提交</p><h2 id="1-2-创建新事务"><a href="#1-2-创建新事务" class="headerlink" title="1.2. 创建新事务"></a>1.2. 创建新事务</h2><p>try {<br>              3. 内嵌: 判断 ThreadLocal 是否已经有 Connection, 有的话就说明是一个内嵌事务, 判断当前事务的传播行为<br>                   <strong>创建新事务</strong>:<br>                   <span style="background-color:#ff00ff">① 把外层事务相关的事务信息（Connection、隔离级别、是否只读…. ) 暂存</span>同时会把外层事务的 ThreadLocal 存储的事务信息都置空<br>                   <span style="background-color:#00ff00">② 创建自己的 Connection 放入 ThreadLocal</span>，返回事务状态信息 (TransactionInfo.newTransaction&#x3D;ture,TransactionInfo. 外部事务的信息暂存)<br>1、外部. 创建一个数据库连接 Connection 存入 ThreadLocal, 并且修改数据库连接的 autoCommit 属性为 false 返回事务状态信息 (TransactionInfo.newTransaction&#x3D;true)<br>2、外部. 然后执行目标方法方法 (调用了内部事务方法) 方法中会执行数据库操作 sql<br>               4. 内嵌: 然后执行目标方法方法方法中会执行数据库操作 sql<br>}<br>catch{<br>               如果出现了异常, 并且这个异常是需要回滚的就会回滚事务, 否则仍然提交事务<br>}<br>                5. 内嵌: 判断 newTransaction&#x3D;true 就提交事务，因为融入 (TransactionInfo.newTransaction&#x3D;true) 所以<span style="background-color:#00ff00">会提交事务，然后把暂存的事务信息回归 ThreadLocal 中</span>。<br>6、外部: 判断 newTransaction&#x3D;true 拿到 ThreadLocal 中的 Connection 进行提交</p><h1 id="2-源码分析"><a href="#2-源码分析" class="headerlink" title="2. 源码分析"></a>2. 源码分析</h1><p><a href="https://www.51cto.com/article/621225.html">https://www.51cto.com/article/621225.html</a><br><a href="https://wiyi.org/how-does-transaction-suspension-in-spring.html">https://wiyi.org/how-does-transaction-suspension-in-spring.html</a><br><a href="https://blog.csdn.net/wwh578867817/article/details/51736723">https://blog.csdn.net/wwh578867817/article/details/51736723</a><br><a href="https://segmentfault.com/a/1190000013341344">https://segmentfault.com/a/1190000013341344</a><br><a href="http://www.skjava.com/article/1396186471">http://www.skjava.com/article/1396186471</a></p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/" title="1、Spring-基础">1、Spring-基础</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>计算机基础-基本原理-1、相关名词</title>
      <link href="/2023/02/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80-1%E3%80%81%E7%9B%B8%E5%85%B3%E5%90%8D%E8%AF%8D/"/>
      <url>/2023/02/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80-1%E3%80%81%E7%9B%B8%E5%85%B3%E5%90%8D%E8%AF%8D/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-计算机基础"><a href="#1-计算机基础" class="headerlink" title="1. 计算机基础"></a>1. 计算机基础</h1><p>字与字长</p><p>机器字长：<span style="background-color:#ff00ff">CPU 一次能处理数据的位数，通常与 CPU 的寄存器位数有关</span>。<br>存储字长：存储器中一个存储单元 (存储地址) 所存储的二进制代码的位数，即存储器中的 MDR 的位数。<br>指令字长：计算机指令字的位数。<br>数据字长：计算机数据存储所占用的位数。</p><p>注：冯诺依曼机中，指令和数据同等重要，都存放在存储器中，并可按地址寻访。</p><p>通常早期计算机：存储字长 &#x3D; 指令字长 &#x3D; 数据字长。故访问一次便可取一条指令或一个数据，随着计算机应用范围的不断扩大，三者可能各不相同，但它们必须是字节的整数倍。</p><p>计算机中信息存储单位</p><p>计算机中的信息用二进制表示，常用的单位有位、字节和字。</p><p>1、位（bit）：是计算机中最小的数据单位，存放一位二进制数，即 0 或 1。它也是存储器存储信息的最小单位，通常用“b”来表示。</p><p>2、字节（Byte）：字节是计算机中表示存储容量的最常用的基本单位。一个字节由 8 位二进制数组成，通常用“B”表示。一个字符占一个字节，一个汉字占两个字节。其它常见的存储单位有：</p><p>存储容量的计量单位有字节 B、千字节 KB、兆字节 MB 以及十亿字节 GB 等。它们之间的换算关系如下：</p><p>1KB (Kilobyte 千字节)&#x3D;1024B<br>1MB (Megabyte 兆字节简称“兆”)&#x3D;1024KB<br>1GB (Gigabyte 吉字节又称“千兆”)&#x3D;1024MB<br>1TB (Trillionbyte 万亿字节太字节)&#x3D;1024GB<br>1PB（Petabyte 千万亿字节拍字节）&#x3D;1024TB<br>1EB（Exabyte 百亿亿字节艾字节）&#x3D;1024PB<br>1ZB (Zettabyte 十万亿亿字节泽字节)&#x3D; 1024 EB<br>1YB (Jottabyte 一亿亿亿字节尧字节)&#x3D; 1024 ZB<br>1BB (Brontobyte 一千亿亿亿字节)&#x3D; 1024 YB</p><p><span style="display:none">%%<br>▶40.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1953%%</span>📙❕ ^my62fg</p><p>3、<span style="background-color:#00ff00">字（Word）与字长：字是指在计算机中作为一个整体被存取、传送、处理的一组二进制数。一个字的位数（即字长）是计算机系统结构中的一个重要特性。字长是由 CPU 的类型所决定，不同的计算机系统的字长是不同的，常见的有 8 位、16 位、32 位、64 位等，字长越长，计算机一次处理的信息位就越多，精度就越高，字长是计算机性能的一个重要指标，目前主流微机正在由 32 位机向 64 位机转。</span> ❕<span style="display:none">%%<br>0616-🏡⭐️◼️字是指 ?🔜MSTM📝 在计算机中作为一个整体被存取、传送、处理的一组二进制数。◼️⭐️-point-202302090616%%</span></p><p> 注意字与字长的区别，字是单位，而字长是指标。</p><p>机器的字长会影响机器的运算速度。倘若 CPU 字长较短，又要运算位数较多的数据，那么需要经过两次或多次的运算才能完成，这样势必影响整机的运行速度。</p><p>机器的字长对硬件的造价也有较大的影响。它将直接影响加法器（或 ALU），数据总线以及存储字长的位数。所以机器字长的确不能单从精度和数的表示范围来考虑。</p><p>为了适应不同的要求及协调运算精度和硬件造价间的关系，大多数计算机均支持变字长运算，即机内可实现半字长、全字长（或单字长）和双倍字长运算。</p><h1 id="2-计算机网络"><a href="#2-计算机网络" class="headerlink" title="2. 计算机网络"></a>2. 计算机网络</h1><h2 id="2-1-对称加密与非对称加密"><a href="#2-1-对称加密与非对称加密" class="headerlink" title="2.1. 对称加密与非对称加密"></a>2.1. 对称加密与非对称加密</h2><p>对称加密：指加密和解密使用同一密钥，优点：运算速度较快，缺点：如何安全将密钥传输给另一方。常见对称加密算法有：DES、AES 等。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519101150.png" alt="image.png"></p><p>非对称加密：指加密和解密使用不同密钥（即公钥和私钥）。公钥与私钥成对存在，如果用公钥对数据进行加密，只有对应私钥才能解密。常见非对称加密算法有 RSA、DSA、ECC 等。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519101232.png" alt="image.png"></p><h2 id="2-2-TCP-与-UDP-的区别及各自的优缺点"><a href="#2-2-TCP-与-UDP-的区别及各自的优缺点" class="headerlink" title="2.2. TCP 与 UDP 的区别及各自的优缺点"></a>2.2. TCP 与 UDP 的区别及各自的优缺点</h2><p>1、TCP 面向连接 (如打电话要先拨号建立连接)，UDP 是无连接的，即发送数据之前不需要建立连接。<br>2、TCP 提供可靠的服务。通过 TCP 连接传送的数据，无差错，不丢失，不重复，且按序到达; TCP 通过校验和，重传控制，序号标识，滑动窗口、确认应答实现可靠传输。如丢包时的重发控制，还可以对次序乱掉的分包进行顺序控制。<br>UDP 尽最大努力交付，即不保证可靠交付。<br>3、UDP 具有较好的实时性，工作效率比 TCP 高，适用于对高速传输和实时性有较高的通信或广播通信。<br>4、每一条 TCP 连接只能是点到点的;UDP 支持一对一，一对多，多对一和多对多的交互通信<br>     5、TCP 对系统资源要求较多，UDP 对系统资源要求较少。</p><h3 id="2-2-1-TCP"><a href="#2-2-1-TCP" class="headerlink" title="2.2.1. TCP"></a>2.2.1. TCP</h3><h4 id="2-2-1-1-TCP-如何保证可靠性"><a href="#2-2-1-1-TCP-如何保证可靠性" class="headerlink" title="2.2.1.1. TCP 如何保证可靠性"></a>2.2.1.1. TCP 如何保证可靠性</h4><ol><li>首先，TCP 基于三次握手，而断开则需要四次挥手。确保连接和断开的可靠性。</li><li>其次，TCP 的可靠性，还体现在有状态;TCP 会记录哪些数据发送了，哪些数据被接收了，哪些没有被接收，并且保证数据包按序到达，保证数据传输不出差错。</li><li>再次，TCP 的可靠性，还体现在可控制。它有明文校验、ACK 应答、超时重传 (发送方)、失序数据重传（接收方）、丢弃重复数据、流量控制 （滑动窗口）和拥塞控制等机制。</li></ol><h3 id="2-2-2-UDP"><a href="#2-2-2-UDP" class="headerlink" title="2.2.2. UDP"></a>2.2.2. UDP</h3><p><a href="https://blog.csdn.net/qq_40732350/article/details/90902396">https://blog.csdn.net/qq_40732350/article/details/90902396</a></p><h2 id="2-3-三次握手"><a href="#2-3-三次握手" class="headerlink" title="2.3. 三次握手"></a>2.3. 三次握手</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519102809.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519102918.png" alt="image.png"></p><h2 id="2-4-四次分手"><a href="#2-4-四次分手" class="headerlink" title="2.4. 四次分手"></a>2.4. 四次分手</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519102946.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519102649.png" alt="image.png"></p><h2 id="2-5-HTTPS"><a href="#2-5-HTTPS" class="headerlink" title="2.5. HTTPS"></a>2.5. HTTPS</h2><h3 id="2-5-1-工作流程"><a href="#2-5-1-工作流程" class="headerlink" title="2.5.1. 工作流程"></a>2.5.1. 工作流程</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519094156.png" alt="image.png"></p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><p><a href="https://www.bilibili.com/video/BV1HD4y1p7YE/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1HD4y1p7YE/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://blog.51cto.com/u_14013325/2895807">https://blog.51cto.com/u_14013325/2895807</a></p>]]></content>
      
      
      <categories>
          
          <category> 计算机基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 基本原理 </tag>
            
            <tag> 相关名词 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-1、JVM-异常处理</title>
      <link href="/2023/02/02/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-1%E3%80%81JVM-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
      <url>/2023/02/02/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-1%E3%80%81JVM-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-异常表"><a href="#1-异常表" class="headerlink" title="1. 异常表"></a>1. 异常表</h1><h2 id="1-1-异常"><a href="#1-1-异常" class="headerlink" title="1.1. 异常"></a>1.1. 异常</h2><a href="/2023/02/12/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-7%E3%80%81Throwable/" title="关键字和接口-7、Throwable">关键字和接口-7、Throwable</a><h2 id="1-2-生成逻辑"><a href="#1-2-生成逻辑" class="headerlink" title="1.2. 生成逻辑"></a>1.2. 生成逻辑</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203132918.png" alt="image.png"></p><p><span style="background-color:#ff0000">没有 try-catch 或者 try-finally 的情况下，是不会生成异常表结构的</span>，比如下面 2 种情况： ❕<span style="display:none">%%<br>2133-🏡⭐️◼️异常表生成原理 ?🔜MSTM📝 一个方法加了 try-catch 或者 try-finally，才会在方法信息中生成方法表◼️⭐️-point-202302132133%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203132756.png" alt="image.png"></p><p><span style="background-color:#ff00ff">异常类也是需要 GC 的</span></p><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><h2 id="1-3-生成位置"><a href="#1-3-生成位置" class="headerlink" title="1.3. 生成位置"></a>1.3. 生成位置</h2><p><span style="background-color:#ff00ff">在方法区中的方法信息中存放</span><br>❕<span style="display:none">%%<br>2134-🏡⭐️◼️异常表的位置 ?🔜MSTM📝 方法区中的方法信息中◼️⭐️-point-202302132134%%</span></p><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-3%E3%80%81JVM-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C3%E4%B8%AA%E6%B1%A0%E5%AD%90/" title="性能调优专题-基础-3、JVM-方法区和3个池子">性能调优专题-基础-3、JVM-方法区和3个池子</a><h2 id="1-4-异常处理"><a href="#1-4-异常处理" class="headerlink" title="1.4. 异常处理"></a>1.4. 异常处理</h2><ol><li><strong>当一个异常被抛出时，JVM 会在 [当前的方法里的异常表中] 寻找一个匹配的处理，如果没有找到，这个方法会强制结束并弹出当前栈帧</strong>，并且异常会重新拋给上层调用的方法（在调用方法栈帧）。如果在所有栈帧弹出前仍然没有找到合适的异常处理，这个线程将终止。如果这个异常在最后一个非守护线程里抛出，将会导致 JVM 自己终止，比如这个线程是个 main 线程。</li><li><strong>不管什么时候抛出异常，如果异常处理最终匹配了所有异常类型，代码就会继续执行</strong>。在这种情况下，如果方法结束后没有抛出异常，仍然执行 finally 块， <span style="background-color:#ff00ff">在 return 前， 它直接跳到 finally 块来完成目标</span></li></ol><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><h2 id="3-1-尚硅谷宋红康"><a href="#3-1-尚硅谷宋红康" class="headerlink" title="3.1. 尚硅谷宋红康"></a>3.1. 尚硅谷宋红康</h2><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=264&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ?p=264&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a><h2 id="3-2-测试代码"><a href="#3-2-测试代码" class="headerlink" title="3.2. 测试代码"></a>3.2. 测试代码</h2><p>[[ExceptionTest.java]]</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> JVM </tag>
            
            <tag> 异常处理 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-3、事务失效</title>
      <link href="/2023/02/02/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-3%E3%80%81%E4%BA%8B%E5%8A%A1%E5%A4%B1%E6%95%88/"/>
      <url>/2023/02/02/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-3%E3%80%81%E4%BA%8B%E5%8A%A1%E5%A4%B1%E6%95%88/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-失效原因"><a href="#1-失效原因" class="headerlink" title="1. 失效原因"></a>1. 失效原因</h1><h2 id="1-1-Bean-是否是代理对象⭐️🔴"><a href="#1-1-Bean-是否是代理对象⭐️🔴" class="headerlink" title="1.1. Bean 是否是代理对象⭐️🔴"></a>1.1. Bean 是否是代理对象⭐️🔴</h2><h2 id="1-2-入口函数是否是-public-的⭐️🔴"><a href="#1-2-入口函数是否是-public-的⭐️🔴" class="headerlink" title="1.2. 入口函数是否是 public 的⭐️🔴"></a>1.2. 入口函数是否是 public 的⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203105149.png" alt="image.png"></p><p><strong>AbstractFallbackTransactionAttributeSource#computeTransactionAttribute</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203105455.png" alt="image.png"></p><blockquote><p>allowPublicMethodsOnly 方法由子类 AnnotationTransactionAttributeSource 实现，该子类方法中默认是 true，所以当你加了事务注解的方法不是 public 时，该方法直接返回 null</p></blockquote><h2 id="1-3-是否支持事务"><a href="#1-3-是否支持事务" class="headerlink" title="1.3. 是否支持事务"></a>1.3. 是否支持事务</h2><p>数据库是否支持事务 (Mysql 的 Mylsam 不支持事务)，行锁才支持事务</p><h2 id="1-4-切点是否配置正确"><a href="#1-4-切点是否配置正确" class="headerlink" title="1.4. 切点是否配置正确"></a>1.4. 切点是否配置正确</h2><h2 id="1-5-内部方法间调用导致事务失效⭐️🔴"><a href="#1-5-内部方法间调用导致事务失效⭐️🔴" class="headerlink" title="1.5. 内部方法间调用导致事务失效⭐️🔴"></a>1.5. 内部方法间调用导致事务失效⭐️🔴</h2><h3 id="1-5-1-解决方案-1"><a href="#1-5-1-解决方案-1" class="headerlink" title="1.5.1. 解决方案 1"></a>1.5.1. 解决方案 1</h3><p>再声明一个 service，自己注入自己，将内部调用改为外部调用</p><h3 id="1-5-2-解决方案-2"><a href="#1-5-2-解决方案-2" class="headerlink" title="1.5.2. 解决方案 2"></a>1.5.2. 解决方案 2</h3><p>因为 this 不是代理对象，可以配置 expose-proxy&#x3D;”true”，就可以通过 AopContext.currentProxy() 获取到当前类的代理对象。</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">SpuInfoService</span> <span class="hljs-variable">proxy</span> <span class="hljs-operator">=</span> (SpuInfoService) AopContext.currentProxy();<br></code></pre></td></tr></table></figure><h3 id="1-5-3-解决方案-3"><a href="#1-5-3-解决方案-3" class="headerlink" title="1.5.3. 解决方案 3"></a>1.5.3. 解决方案 3</h3><p>使用编程式事务</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203105853.png" alt="image.png"></p><h2 id="1-6-异常类型是否配置正确-回滚策略-⭐️🔴"><a href="#1-6-异常类型是否配置正确-回滚策略-⭐️🔴" class="headerlink" title="1.6. 异常类型是否配置正确 (回滚策略)⭐️🔴"></a>1.6. 异常类型是否配置正确 (回滚策略)⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213170910.png" alt="image.png"></p><a href="/2023/02/12/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-7%E3%80%81Throwable/" title="关键字和接口-7、Throwable">关键字和接口-7、Throwable</a><p>默认只支持非受检 (检查) 异常：<span style="background-color:#ff00ff">RuntimeException 和 Error</span>，不支持受检 (检查) 异常。</p><p><span style="background-color:#00ff00">想要支持检查异常需要配置 rollbackFor</span><br><strong>@Transactional(rollbackFor &#x3D; Exception.class)</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110208.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110215.png" alt="image.png"></p><p><strong>TransactionInterceptor#invoke</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110309.png" alt="image.png"><br><strong>TransactionAspectSupport#invokeWithinTransaction</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110501.png" alt="image.png"><br><strong>TransactionAspectSupport#completeTransactionAfterThrowing</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110529.png" alt="image.png"><br><strong>rollbackOn(Throwable ex)</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110544.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110645.png" alt="image.png"></p><h3 id="1-6-1-为什么默认只支持非受检异常⭐️🔴"><a href="#1-6-1-为什么默认只支持非受检异常⭐️🔴" class="headerlink" title="1.6.1. 为什么默认只支持非受检异常⭐️🔴"></a>1.6.1. 为什么默认只支持非受检异常⭐️🔴</h3><p>配置了这个，Exception 异常的事务，就会生效，如果没有配置则使用父类的默认配置逻辑如下：❕<span style="display:none">%%<br>1713-🏡⭐️◼️为什么事务回滚只支持非受检异常 ?🔜MSTM📝 因为系统重 rollbackOn 条件默认是这两种类型◼️⭐️-point-202302131713%%</span></p><p><code>DefaultTransactionAttribute</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110725.png" alt="image.png"></p><h2 id="1-7-异常被-catch-住了"><a href="#1-7-异常被-catch-住了" class="headerlink" title="1.7. 异常被 catch 住了"></a>1.7. 异常被 catch 住了</h2><p>代码中手动 catch 了异常，然后又未抛出来，此时事务就不生效了。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110859.png" alt="image.png"></p><p>解决方法：要么不 catch 需要回滚的异常，要么 catch 之后再抛出，要么手动回滚</p><h3 id="1-7-1-解决方案-1"><a href="#1-7-1-解决方案-1" class="headerlink" title="1.7.1. 解决方案 1"></a>1.7.1. 解决方案 1</h3><p>catch 之后往外抛异常<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110917.png" alt="image.png"></p><h3 id="1-7-2-解决方案-2"><a href="#1-7-2-解决方案-2" class="headerlink" title="1.7.2. 解决方案 2"></a>1.7.2. 解决方案 2</h3><p>catch 之后，设置手动回滚<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203110930.png" alt="image.png"></p><h2 id="1-8-SpringMVC"><a href="#1-8-SpringMVC" class="headerlink" title="1.8. SpringMVC"></a>1.8. SpringMVC</h2><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/" title="Spring-4、SpringMVC">Spring-4、SpringMVC</a><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><p><a href="https://www.jb51.net/article/214403.htm">https://www.jb51.net/article/214403.htm</a></p><p><a href="https://ost.51cto.com/posts/13157">https://ost.51cto.com/posts/13157</a></p>]]></content>
      
      
      <categories>
          
          <category> 事务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 事务 </tag>
            
            <tag> 事务失效 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-8、BeanDefinition</title>
      <link href="/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/"/>
      <url>/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-BD-分类"><a href="#1-BD-分类" class="headerlink" title="1. BD 分类"></a>1. BD 分类</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202132642.png" alt="image.png"></p><h1 id="2-BD-来源"><a href="#2-BD-来源" class="headerlink" title="2. BD 来源"></a>2. BD 来源</h1><h2 id="2-1-配置类及其注入的-bean"><a href="#2-1-配置类及其注入的-bean" class="headerlink" title="2.1. 配置类及其注入的 bean"></a>2.1. 配置类及其注入的 bean</h2><h2 id="2-2-指定的类路径"><a href="#2-2-指定的类路径" class="headerlink" title="2.2. 指定的类路径"></a>2.2. 指定的类路径</h2><h1 id="3-RootBeanDefinition-逻辑流程⭐️🔴"><a href="#3-RootBeanDefinition-逻辑流程⭐️🔴" class="headerlink" title="3. RootBeanDefinition 逻辑流程⭐️🔴"></a>3. RootBeanDefinition 逻辑流程⭐️🔴</h1><p>^f4b3wu</p><p><a href="https://www.bilibili.com/video/BV1Ap4y1D798/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Ap4y1D798/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.bilibili.com/video/BV1Ap4y1D798/?spm_id_from=..search-card.all.click&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204#t=2119.968117">35:19</a></p><h2 id="3-1-注册-Root-BD"><a href="#3-1-注册-Root-BD" class="headerlink" title="3.1. 注册 Root BD"></a>3.1. 注册 Root BD</h2><p>入口 ：<code>new AnnotationConfigApplicationContext(MainConfig.class)</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203170315.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204104856.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204104924.png" alt="image.png"></p><h2 id="3-2-创建-ConfigurationClassPostProcessor"><a href="#3-2-创建-ConfigurationClassPostProcessor" class="headerlink" title="3.2. 创建 ConfigurationClassPostProcessor"></a>3.2. 创建 ConfigurationClassPostProcessor</h2><p><code>刷新第5步：invokeBeanFactoryPostProcessors()</code><br><span style="background-color:#ff00ff">getBean</span> <code>ConfigurationClassPostProcessor</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202140323.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205163422.png" alt="image.png"><br>❕<span style="display:none">%%<br>0827-🏡⭐️◼️ConfigurationClassPostProcessor 继承关系 ?🔜MSTM📝 继承 BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor。与其他几个对比记忆：AutowiredAnnotationBeanPostProcessor:MI, CommonAnnotationBeanPostProcessor:MI, AbstractAutoProxyCreator: ISB◼️⭐️-point-202302120827%%</span> ^na0djy</p><h1 id="4-用户-BD-解析流程-Annotated-⭐️🔴"><a href="#4-用户-BD-解析流程-Annotated-⭐️🔴" class="headerlink" title="4. 用户 BD 解析流程 (Annotated)⭐️🔴"></a>4. 用户 BD 解析流程 (Annotated)⭐️🔴</h1><h2 id="4-1-流程图"><a href="#4-1-流程图" class="headerlink" title="4.1. 流程图"></a>4.1. 流程图</h2><p>Annotated:<a href="https://www.processon.com/diagraming/63dd8eb4b59543238fa3a6a5">https://www.processon.com/diagraming/63dd8eb4b59543238fa3a6a5</a><br>XML:<a href="https://www.processon.com/diagraming/63ddb70fc12afe0cadb59d2c">https://www.processon.com/diagraming/63ddb70fc12afe0cadb59d2c</a></p><h2 id="4-2-读取配置类-注册配置类"><a href="#4-2-读取配置类-注册配置类" class="headerlink" title="4.2. 读取配置类 (注册配置类)"></a>4.2. 读取配置类 (注册配置类)</h2><h3 id="4-2-1-BeanDefinitionReader-接口"><a href="#4-2-1-BeanDefinitionReader-接口" class="headerlink" title="4.2.1. BeanDefinitionReader 接口"></a>4.2.1. BeanDefinitionReader 接口</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202122641.png" alt="image.png"></p><p> 1. XmlBeanDefinitionReader 从 XML 配置中读取 BeanDefinition；<br> 2. PropertiesBeanDefinitionReader 从 Properties 文件读取 BeanDefinition；<br> 3. <span style="background-color:#00ff00">AnnotatedBeanDefinitionReader 对带有@Configuration 注解的</span>BeanDefinition 进行注册；</p><h3 id="4-2-2-AnnotatedBeanDefinitionReader⭐️🔴"><a href="#4-2-2-AnnotatedBeanDefinitionReader⭐️🔴" class="headerlink" title="4.2.2. AnnotatedBeanDefinitionReader⭐️🔴"></a>4.2.2. AnnotatedBeanDefinitionReader⭐️🔴</h3><p>^8skkg4</p><h4 id="4-2-2-1-注册位置"><a href="#4-2-2-1-注册位置" class="headerlink" title="4.2.2.1. 注册位置"></a>4.2.2.1. 注册位置</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230203195618.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205214736.png" alt="image.png"></p><h2 id="4-3-扫描配置类-解析配置类"><a href="#4-3-扫描配置类-解析配置类" class="headerlink" title="4.3. 扫描配置类 (解析配置类)"></a>4.3. 扫描配置类 (解析配置类)</h2><p>^zs22cl<br>作用：处理配置的 BeanDefinition 信息，把<span style="background-color:#ff00ff">配置类中所有 bean 的定义信息</span>导入进来。</p><h3 id="4-3-1-扫描入口"><a href="#4-3-1-扫描入口" class="headerlink" title="4.3.1. 扫描入口"></a>4.3.1. 扫描入口</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202140400.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205215242.png" alt="image.png"></p><h3 id="4-3-2-解析流程概览"><a href="#4-3-2-解析流程概览" class="headerlink" title="4.3.2. 解析流程概览"></a>4.3.2. 解析流程概览</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230204113836.png" alt="image.png"></p><p>执行 <code>ConfigurationClassPostProcessor</code> 的 <code>postProcessor.postProcessBeanDefinitionRegistry()</code> 方法，会把【配置类中的】所有 bean 的定义信息导入进来。<br>期间由 ConfigurationClassParser 解析每一个配置类：<br><span style="background-color:#ff00ff">获取所有已经注册的 BeanDefinition 的 beanName，遍历中筛选对应的 beanDefinition（被注解修饰的）</span>，添加到 <code>List&lt;BeanDefinitionHolder&gt; configCandidates</code> 交给 <code>ConfigurationClassBeanDefinitionReader</code> 注册到 BeanDefinitionMap 中</p><blockquote><p>   判断当前 BeanDefinition 是否是一个配置类，并为 BeanDefinition 设置属性为 lite 或者 full：如果 Configuration 显示配置 proxyBeanMethods 代理为 true 则为 full，如果加了@Bean、@Component、@ComponentScan、@Import、@ImportResource 注解，则设置为 lite</p></blockquote><h3 id="4-3-3-ConfigurationClassParser⭐️🔴"><a href="#4-3-3-ConfigurationClassParser⭐️🔴" class="headerlink" title="4.3.3. ConfigurationClassParser⭐️🔴"></a>4.3.3. ConfigurationClassParser⭐️🔴</h3><p>获取并遍历所有已经注册的 BeanDefinition，通过 <code>checkConfigurationClassCandidate</code> 方法筛选出被注解修饰的并放入 <code>configCandidates</code> 集合中</p><p>用 <span style="background-color:#ff00ff">dowhile 循环</span>遍历上面一步得到的相关的 BeanDefinitionHolder 对象集合</p><p>Spring 的工具类 <code>ConfigurationClassParser</code> 用于<span style="background-color:#00ff00">分析@Configuration 注解的配置类，产生一组 ConfigurationClass 对象</span>。它的分析过程会接受一组种子配置类 (调用者已知的配置类，通常很可能只有一个)，从这些种子配置类开始分析所有关联的配置类，分析过程主要是<span style="background-color:#ff00ff">递归分析配置类的注解@Import，配置类内部嵌套类，</span><span style="background-color:#ff00ff">找出其中所有的配置类</span>，然后返回这组配置类。❕<span style="display:none">%%<br>1119-🏡⭐️◼️配置类中@Import 导入的类有可能也是配置类 ?🔜MSTM📝 比如声明式事务中，注解@EnableTransactionManagement 通过@Import(TransactionManagementConfigurationSelector.class)，导入了一个 AutoProxyRegistrar 和一个配置类 ProxyTransactionManagementConfiguration ◼️⭐️-point-202302121119%%</span><br>这个工具类自身的逻辑并不注册 bean 定义，它的主要任务是<span style="background-color:#00ff00">发现@Configuration 注解的所有配置类并将这些配置类交给调用者</span>(调用者会通过其他方式注册其中的 bean 定义)</p><h4 id="4-3-3-1-parse-外部调用入口"><a href="#4-3-3-1-parse-外部调用入口" class="headerlink" title="4.3.3.1. parse() : 外部调用入口"></a>4.3.3.1. parse() : 外部调用入口</h4><p>^lfmh74</p><p>1.将其封装成一个 <code>ConfigurationClass</code><br>2.调用 <code>processConfigurationClass(ConfigurationClass configClass)</code></p><blockquote><p>分析过的每个配置类都被保存到属性 <code>this.configurationClasses</code> 中</p></blockquote><h4 id="4-3-3-2-doProcessConfigurationClass"><a href="#4-3-3-2-doProcessConfigurationClass" class="headerlink" title="4.3.3.2. doProcessConfigurationClass()"></a>4.3.3.2. doProcessConfigurationClass()</h4><p><span style="background-color:#ff00ff">对一个配置类执行真正的处理</span></p><ol><li><span style="background-color:#ff00ff">一个配置类的成员类 (配置类内嵌套定义的类) 也可能适配类，先遍历这些成员配置类，调用 processConfigurationClass 处理它们</span>;</li><li>处理配置类上的注解@PropertySources,@PropertySource</li><li>处理配置类上的注解@ComponentScans,@ComponentScan</li><li>处理配置类上的注解@Import</li><li>处理配置类上的注解@ImportResource</li><li><span style="background-color:#ff00ff">处理配置类中每个带有@Bean 注解的方法</span></li><li>处理配置类所实现接口的缺省方法</li><li><span style="background-color:#ff00ff">检查父类是否需要处理，如果父类需要处理返回父类，否则返回 null</span></li></ol><h3 id="4-3-4-ComponentScanAnnotationParser⭐️🔴"><a href="#4-3-4-ComponentScanAnnotationParser⭐️🔴" class="headerlink" title="4.3.4. ComponentScanAnnotationParser⭐️🔴"></a>4.3.4. ComponentScanAnnotationParser⭐️🔴</h3><p><span style="background-color:#00ff00">对于非@Configuration 注解的其他 bean 定义，比如@Component、@ComponentScans、@ComponentScan 注解的 bean 定义</span>，使用另外一个工具 <code>ComponentScanAnnotationParser</code> 扫描和注册它们。<code>ComponentScanAnnotationParser</code>  <span style="background-color:#ff00ff">在扫描到 bean 定义时会直接将其注册到容器</span>，而不是采用和 ConfigurationClassParser 类似的方式交由调用者处理。</p><h4 id="4-3-4-1-两者的关系⭐️🔴"><a href="#4-3-4-1-两者的关系⭐️🔴" class="headerlink" title="4.3.4.1. 两者的关系⭐️🔴"></a>4.3.4.1. 两者的关系⭐️🔴</h4><ol><li>在 <code>ConfigurationClassPostProcessor</code> 中使用 <code>ConfigurationClassParser</code> 解析配置类过程中，使用到了 <code>ComponentScanAnnotationParser</code></li><li><code>ComponentScanAnnotationParser</code> 处理@ComponentScans,@ComponentScan 注解。在扫描到 bean 定义时会直接将其注册到容器，而不是采用和 ConfigurationClassParser 类似的方式交由调用者处理。</li><li><code>ComponentScanAnnotationParser</code> 最终所使用的扫描器是 <code>ClassPathBeanDefinitionScanner</code></li></ol><h3 id="4-3-5-ClassPathBeanDefinitionScanner"><a href="#4-3-5-ClassPathBeanDefinitionScanner" class="headerlink" title="4.3.5. ClassPathBeanDefinitionScanner"></a>4.3.5. ClassPathBeanDefinitionScanner</h3><h4 id="4-3-5-1-概括描述"><a href="#4-3-5-1-概括描述" class="headerlink" title="4.3.5.1. 概括描述"></a>4.3.5.1. 概括描述</h4><ol><li>在 <code>ComponentScanAnnotationParser</code> 中使用了 <code>ClassPathBeanDefinitionScanner</code></li><li>ClassPathBeanDefinitionScanner 是一个从指定包内扫描所有组件 bean 定义的 Spring 工具。<br>工作时，它接收一组包的名称，然后在这些包内扫描所有的类，查找其中符合条件的 bean 组件定义并将这些 bean 组件定义注册到容器。这些 bean 定义注册到容器时具体使用的类为 <code>ScannedGenericBeanDefinition</code> ，这是 Spring bean 定义模型接口 BeanDefinition 的一个具体实现类，针对扫描得到的 bean 定义。</li></ol><h4 id="4-3-5-2-处理注解⭐️🔴"><a href="#4-3-5-2-处理注解⭐️🔴" class="headerlink" title="4.3.5.2. 处理注解⭐️🔴"></a>4.3.5.2. 处理注解⭐️🔴</h4><ul><li><code>@Component</code><ul><li><code>@Repository</code></li><li><code>@Service</code></li><li><code>@Controller</code><ul><li><code>@RestController</code></li></ul></li></ul></li><li><code>@ManagedBean</code> (<code>Java EE 6</code>)</li><li><code>@Named</code> （<code>JSR-330</code>）</li></ul><p><a href="https://blog.csdn.net/andy_zhang2007/article/details/85705001">https://blog.csdn.net/andy_zhang2007/article/details/85705001</a></p><h3 id="4-3-6-ConfigurationClassBeanDefinitionReader⭐️🔴"><a href="#4-3-6-ConfigurationClassBeanDefinitionReader⭐️🔴" class="headerlink" title="4.3.6. ConfigurationClassBeanDefinitionReader⭐️🔴"></a>4.3.6. ConfigurationClassBeanDefinitionReader⭐️🔴</h3><p><code>this.reader.loadBeanDefinitions(configClasses)</code>，<span style="background-color:#ff00ff">最后一步注册的核心方法，将完全填充好的 ConfigurationClass 实例转化为 BeanDefinition 注册入 IOC 容器 </span></p><p>该内部工具<span style="background-color:#00ff00">由 Spring BeanDefinitionRegistryPostProcessor ConfigurationClassParser 使用</span>。在容器启动过程中， ConfigurationClassParser 会在 BeanDefinitionRegistryPostProcessor 应用阶段被调用，用于发现应用中所有的配置类 ConfigurationClass，然后交给 ConfigurationClassBeanDefinitionReader 将这些配置类中的 bean 定义注册到容器。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212122558.png" alt="image.png"></p><h1 id="5-postProcessBeanFactory⭐️🔴"><a href="#5-postProcessBeanFactory⭐️🔴" class="headerlink" title="5. postProcessBeanFactory⭐️🔴"></a>5. postProcessBeanFactory⭐️🔴</h1><h2 id="5-1-方法作用"><a href="#5-1-方法作用" class="headerlink" title="5.1. 方法作用"></a>5.1. 方法作用</h2><p>❕<span style="display:none">%%<br>0726-🏡⭐️◼️refresh() 第 5 步，invokeBeanFactoryPostProcessor 中，第二个方法 postProcessBeanFactory 的作用 ?🔜MSTM📝 生成 Cglib 动态代理，以及 full 和 lite 版本的判断◼️⭐️-point-20230215-0726%%</span><br>该方法是对 <code>BeanFactory</code> 进行处理，用来干预 <code>BeanFactory</code> 的创建过程。主要干了两件事: ^mcyor3</p><ul><li>对被 <code>@Configuration</code> 标注的类，使用 CGLIB 代理生成代理对象。</li><li>向 <code>beanPostProcessors</code> 集合中添加一个 <code>ImportAwareBeanPostProcessor</code> Bean 的后置处理器</li></ul><h2 id="5-2-full-版和-lite-版⭐️🔴"><a href="#5-2-full-版和-lite-版⭐️🔴" class="headerlink" title="5.2. full 版和 lite 版⭐️🔴"></a>5.2. full 版和 lite 版⭐️🔴</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-0720%%</span>❕ ^4zhdzi</p><p><a href="https://www.processon.com/diagraming/63e37dc46330b9282f70ecc0">https://www.processon.com/diagraming/63e37dc46330b9282f70ecc0</a></p><p>processConfigBeanDefinitions → ConfigurationClassUtils.<code>checkConfigurationClassCandidate</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230208181450.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209100044.png" alt="image.png"></p><p>❕<span style="display:none">%%<br>0946-🏡⭐️◼️Configuration 注解的 full 和 lite 版本的区别和划分 ?🔜MSTM📝 除非显示的标识 Configuration 或者加了 proxyBeanMethods 为 true，其他情况都属于 lite 模式◼️⭐️-point-202302090946%%</span></p><ul><li>配置类组件之间无依赖关系用 Lite 模式，减少判断，加速容器启动过程</li><li>配置类组件之间有依赖关系，方法会被调用得到之前单实例组件，用 Full 模式<br>❕<span style="display:none">%%<br>▶7.🏡⭐️◼️lite 能加快启动速度的原因 ?🔜MSTM📝 不需要判断容器中是否已经存在需要的 Bean，直接生成新的，不需要生成代理对象◼️⭐️-point-20230226-2149%%</span><br>[[Spring的@Configuration配置类-Full和Lite模式_demon7552003的博客-CSDN博客]]<br><a href="https://juejin.cn/post/7122064723037650980">https://juejin.cn/post/7122064723037650980</a></li></ul><p><strong>对比记忆</strong><br>exposeProxy：<a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/" title="1、Spring-基础">1、Spring-基础</a><br>❕<span style="display:none">%%<br>▶8.🏡⭐️◼️为什么 Spring5.2 对应 SpringBoot2.2 之后默认 lite 模式 ?🔜MSTM📝  Spring5.2（对应 Spring Boot 2.2.0）开始，内置的几乎所有的 <code>@Configuration</code> 配置类都被修改为了 <code>@Configuration(proxyBeanMethods = false)</code>，目的何为？答：以此来降低启动时间，为 Cloud Native 继续做准备。◼️⭐️-point-20230226-2154%%</span><br>在线程中暴露代理对象 : 目标方法执行时实际上是调用的动态代理的方法，动态代理会拦截并调用 invoke(JDK) 或者 intercept(Cglib) 方法，在此期间会设置代理对象到 AopContext 中，已备获取使用。<br>JDK 动态代理：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230216104757.png" alt="image.png"><br>❕<span style="display:none">%%<br>1050-🏡⭐️◼️动态代理执行时机及动态代理对象设置到 AopContext 的逻辑清晰程度◼️⭐️-point-20230216-1050%%</span><br>Cglib 动态代理：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212081056.png" alt="image.png"><br>❕<span style="display:none">%%<br>0816-🏡⭐️◼️对比记忆 Cglib 相关配置 ?🔜MSTM📝 1. exposeProxy：是否在当前线程中暴露代理对象，如果为 true 会放到 ThreadLocal 中。2. proxyBeanMethods：是否开启 Configuration 注解的 full 模式，即是否为目标对象生成 cglib 代理对象，如果为 true 则生成代理对象。3. proxyTargetClass：是否强制使用 Cglib 代理◼️⭐️-point-202302120816%%</span>  ^uqfm1u</p><h1 id="6-processor-关系"><a href="#6-processor-关系" class="headerlink" title="6. processor 关系"></a>6. processor 关系</h1><p>BeanDefinitionRegistryPostProcessor 是 bean 定义信息注册器的后置器，<strong>它的执行时机为所有的 bean 定义信息（BeanDefinition）将要被加载到容器的时候，但此时 Bean 实例还没有被实例化</strong>；它与 BeanFactoryPostProcessor 区别在于，BeanFactoryPostProcessor 执行是在 <strong>所有的 bean 定义信息已经加载到容器的时候</strong>，因此 <strong>BeanDefinitionRegistryPostProcessor</strong> 的执行优先于 <strong>BeanFactoryPostProcessor</strong> 的执行，它们的相同点是 <strong>执行时 bean 实例都没被</strong> 实例化；</p><h1 id="7-扩展使用"><a href="#7-扩展使用" class="headerlink" title="7. 扩展使用"></a>7. 扩展使用</h1><p>如要对配置文件进行加解密，这个可以在 bean 定义信息已加载完成，但 bean 未实例化前进行处理，这个可使用 <code>BeanFactoryPostProcessor</code> 处理（如 jasypt-spring-boot-starter 就是类似这种处理的）；</p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><h2 id="9-1-拓薪教育"><a href="#9-1-拓薪教育" class="headerlink" title="9.1. 拓薪教育"></a>9.1. 拓薪教育</h2><p><a href="https://www.bilibili.com/video/BV1Ap4y1D798/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Ap4y1D798/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="9-2-网络笔记"><a href="#9-2-网络笔记" class="headerlink" title="9.2. 网络笔记"></a>9.2. 网络笔记</h2><p><a href="https://www.cnblogs.com/coder-zyc/p/14583011.html">https://www.cnblogs.com/coder-zyc/p/14583011.html</a></p><p><a href="https://blog.csdn.net/andy_zhang2007/article/details/78549773">https://blog.csdn.net/andy_zhang2007/article/details/78549773</a></p><p><a href="https://blog.csdn.net/andy_zhang2007/article/details/78579464">https://blog.csdn.net/andy_zhang2007/article/details/78579464</a></p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> IOC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-7、扩展点</title>
      <link href="/2023/01/28/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-7%E3%80%81%E6%89%A9%E5%B1%95%E7%82%B9/"/>
      <url>/2023/01/28/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-7%E3%80%81%E6%89%A9%E5%B1%95%E7%82%B9/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-扩展点分类"><a href="#1-扩展点分类" class="headerlink" title="1. 扩展点分类"></a>1. 扩展点分类</h1><h2 id="1-1-Bean定义扩展点"><a href="#1-1-Bean定义扩展点" class="headerlink" title="1.1. Bean定义扩展点"></a>1.1. Bean定义扩展点</h2><p>BeanDefinitionRegistryPostProcessor<br>BeanFactoryPostProcessor</p><h2 id="1-2-Aware类扩展点"><a href="#1-2-Aware类扩展点" class="headerlink" title="1.2. Aware类扩展点"></a>1.2. Aware类扩展点</h2><p>BeanNameAware<br>BeanClassLoaderAware<br>BeanFactoryAware</p><h2 id="1-3-生命周期的回调"><a href="#1-3-生命周期的回调" class="headerlink" title="1.3. 生命周期的回调"></a>1.3. 生命周期的回调</h2><p>PostConstruct<br>InitializingBean<br>init-method<br>PreDestroy<br>DisposableBean<br>destroy-method</p><h1 id="2-扩展点解析"><a href="#2-扩展点解析" class="headerlink" title="2. 扩展点解析"></a>2. 扩展点解析</h1><p><a href="https://www.processon.com/view/link/5f194e727d9c0835d38cc2cb">https://www.processon.com/view/link/5f194e727d9c0835d38cc2cb</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230129221633.png" alt="image.png"></p><h2 id="2-1-总体概括"><a href="#2-1-总体概括" class="headerlink" title="2.1. 总体概括"></a>2.1. 总体概括</h2><a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/1%E3%80%81Spring-%E5%9F%BA%E7%A1%80/" title="1、Spring-基础">1、Spring-基础</a><h2 id="2-2-InstantiationAwareBeanPostProcessor"><a href="#2-2-InstantiationAwareBeanPostProcessor" class="headerlink" title="2.2. InstantiationAwareBeanPostProcessor"></a>2.2. InstantiationAwareBeanPostProcessor</h2><p><a href="https://juejin.cn/post/7068511224471748645">https://juejin.cn/post/7068511224471748645</a><br><a href="https://mrbird.cc/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Spring-BeanPostProcessor-InstantiationAwareBeanPostProcessor.html">https://mrbird.cc/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Spring-BeanPostProcessor-InstantiationAwareBeanPostProcessor.html</a><br><a href="https://cloud.tencent.com/developer/article/1409273">https://cloud.tencent.com/developer/article/1409273</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201112018.png" alt="image.png"></p><h3 id="2-2-1-postProcessBeforeInstantiation"><a href="#2-2-1-postProcessBeforeInstantiation" class="headerlink" title="2.2.1. postProcessBeforeInstantiation"></a>2.2.1. postProcessBeforeInstantiation</h3><h3 id="2-2-2-postProcessAfterInstantiation"><a href="#2-2-2-postProcessAfterInstantiation" class="headerlink" title="2.2.2. postProcessAfterInstantiation"></a>2.2.2. postProcessAfterInstantiation</h3><h3 id="2-2-3-postProcessPropertyValues"><a href="#2-2-3-postProcessPropertyValues" class="headerlink" title="2.2.3. postProcessPropertyValues"></a>2.2.3. postProcessPropertyValues</h3><p>AutowiredAnnotationBeanPostProcessor</p><ol><li>InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口，它内部提供了3个方法，再加上BeanPostProcessor接口内部的2个方法，所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情，包括实例化对象的前后过程以及实例的属性设置</li><li>postProcessBeforeInstantiation方法是最先执行的方法，它在目标对象实例化之前调用，该方法的返回值类型是Object，我们可以返回任何类型的值。由于这个时候目标对象还未实例化，所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象，后续只有postProcessAfterInitialization方法会调用，其它方法不再调用；否则按照正常的流程走</li><li>postProcessAfterInstantiation方法在目标对象实例化之后调用，这个时候对象已经被实例化，但是该实例的属性还未被设置，都是null。因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素（因为还有一个因素是mbd.getDependencyCheck()）；如果该方法返回false,并且不需要check，那么postProcessPropertyValues就会被忽略不执行；如果返回true, postProcessPropertyValues就会被执行</li><li>postProcessPropertyValues方法对属性值进行修改(这个时候属性值还未被设置，但是我们可以修改原本该设置进去的属性值)。如果postProcessAfterInstantiation方法返回false，该方法可能不会被调用。可以在该方法内对属性值进行修改</li><li>父接口BeanPostProcessor的2个方法postProcessBeforeInitialization和postProcessAfterInitialization都是在目标对象被实例化之后，并且属性也被设置之后调用的</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201101039.png" alt="image.png"></p><p>postProcessBeforeInstantiation方法返回实例对象后跳过了对象的初始化操作，直接执行了postProcessAfterInitialization(该方法在自定义初始化方法执行完成之后执行)，跳过了postProcessAfterInstantiation，postProcessPropertyValues以及自定义的初始化方法(start方法)</p><h2 id="2-3-第5步invokeBeanFactoryPostProcessors"><a href="#2-3-第5步invokeBeanFactoryPostProcessors" class="headerlink" title="2.3. 第5步invokeBeanFactoryPostProcessors()"></a>2.3. 第5步invokeBeanFactoryPostProcessors()</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230129204428.png" alt="image.png"></p><h3 id="2-3-1-BeanDefinitionRegistryPostProcessor"><a href="#2-3-1-BeanDefinitionRegistryPostProcessor" class="headerlink" title="2.3.1. BeanDefinitionRegistryPostProcessor"></a>2.3.1. BeanDefinitionRegistryPostProcessor</h3><p>执行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法<br>       <strong>作用：</strong> 在注册BeanDefinition的可以对beanFactory进行扩展<br><strong>调用时机：</strong> Ioc加载时注册BeanDefinition 的时候会调用</p><h3 id="2-3-2-BeanFactoryPostProcessor"><a href="#2-3-2-BeanFactoryPostProcessor" class="headerlink" title="2.3.2. BeanFactoryPostProcessor"></a>2.3.2. BeanFactoryPostProcessor</h3><p>执行BeanFactoryPostProcessor的postProcessBeanFactory方法<br><strong>作用</strong>：动态注册BeanDefinition<br><strong>调用时机</strong>： Ioc加载时注册BeanDefinition 的时候会调用</p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> IOC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-0、设计模式总结</title>
      <link href="/2023/01/24/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-0%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%80%BB%E7%BB%93/"/>
      <url>/2023/01/24/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-0%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%80%BB%E7%BB%93/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-聚合接口或抽象类"><a href="#1-聚合接口或抽象类" class="headerlink" title="1. 聚合接口或抽象类"></a>1. 聚合接口或抽象类</h1><h2 id="1-1-可以调用其子类的方法"><a href="#1-1-可以调用其子类的方法" class="headerlink" title="1.1. 可以调用其子类的方法"></a>1.1. 可以调用其子类的方法</h2><h3 id="1-1-1-中介者模式⭐️🔴-构造导入-集合-【2-个聚合】1-对-N-型"><a href="#1-1-1-中介者模式⭐️🔴-构造导入-集合-【2-个聚合】1-对-N-型" class="headerlink" title="1.1.1. 中介者模式⭐️🔴+ 构造导入 + 集合 +【2 个聚合】1 对 N 型"></a>1.1.1. 中介者模式⭐️🔴+ 构造导入 + 集合 +【2 个聚合】1 对 N 型</h3><p>[[内功心法专题-设计模式-20、中介者模式#2 2 UML]]</p><p><span style="background-color:#ff00ff">相当于相互聚合</span></p><p>代码：<br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;mediator&#x2F;MediatorStructure.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125190323.png" alt="image.png"></p><h3 id="1-1-2-桥接模式⭐️🔴-构造导入-N-到-N-型"><a href="#1-1-2-桥接模式⭐️🔴-构造导入-N-到-N-型" class="headerlink" title="1.1.2. 桥接模式⭐️🔴+ 构造导入 - N 到 N 型"></a>1.1.2. 桥接模式⭐️🔴+ 构造导入 - N 到 N 型</h3><a href="/2023/01/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-10%E3%80%81%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/" title="设计模式-10、桥接模式">设计模式-10、桥接模式</a><p>代码：      [[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;builder&#x2F;demo2&#x2F;Phone.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125190408.png" alt="image.png"></p><h3 id="1-1-3-观察者模式⭐️🔴-集合"><a href="#1-1-3-观察者模式⭐️🔴-集合" class="headerlink" title="1.1.3. 观察者模式⭐️🔴+ 集合"></a>1.1.3. 观察者模式⭐️🔴+ 集合</h3><p>[[内功心法专题-设计模式-19、观察者模式#3 1 1 UML⭐️🔴]]</p><p>代码：<br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;observer&#x2F;SubscriptionSubject.java]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124220007.png" style="zoom:80%;" /></p><h3 id="1-1-4-状态模式⭐️🔴-【2-个聚合】-setter-导入"><a href="#1-1-4-状态模式⭐️🔴-【2-个聚合】-setter-导入" class="headerlink" title="1.1.4. 状态模式⭐️🔴+【2 个聚合】+ setter 导入"></a>1.1.4. 状态模式⭐️🔴+【2 个聚合】+ setter 导入</h3><a href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-18%E3%80%81%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/" title="设计模式-18、状态模式">设计模式-18、状态模式</a><p><span style="background-color:#ff00ff">相互聚合</span></p><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;state&#x2F;after&#x2F;LiftState.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124070226.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124081717.png" alt="image.png"></p><p><span style="background-color:#ff00ff">调来调去就相当于各个具体状态类之间切换了</span> ❕<span style="display:none">%%<br>1157-🏡⭐️◼️状态流转逻辑 ?🔜MSTM📝 Client 构造 Context 时传入初始状态，调用 Context 的 open(e.g.) 方法时，实际调用的是刚刚 setter 给 Context 的 ConcreteState 的 open 方法，在 ConcreteState 的 open 方法中，会使用父类 State 聚合的 Context，给 Context setter 另一个 ConcreteState，然后再调用这个 ConcreteState 的方法，具体什么方法就看业务逻辑了，如此达到了状态流转的目的。同时将状态流转逻辑和对象封装在一起，状态◼️⭐️-point-202302071157%%</span></p><h3 id="1-1-5-命令模式⭐️🔴-构造导入-集合-【2-个聚合】"><a href="#1-1-5-命令模式⭐️🔴-构造导入-集合-【2-个聚合】" class="headerlink" title="1.1.5. 命令模式⭐️🔴+ 构造导入 + 集合 +【2 个聚合】"></a>1.1.5. 命令模式⭐️🔴+ 构造导入 + 集合 +【2 个聚合】</h3><p>[[内功心法专题-设计模式-16、命令模式#4 2 智能家居⭐️🔴]]</p><p>代码：[[RemoteController.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125190757.png" alt="image.png"></p><h3 id="1-1-6-策略模式⭐️🔴-构造导入"><a href="#1-1-6-策略模式⭐️🔴-构造导入" class="headerlink" title="1.1.6. 策略模式⭐️🔴+ 构造导入"></a>1.1.6. 策略模式⭐️🔴+ 构造导入</h3><p>[[内功心法专题-设计模式-15、策略模式#3 1 原理图]]</p><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;strategy&#x2F;SalesMan.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125191119.png" alt="image.png"></p><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;strategy&#x2F;improve&#x2F;Duck.java]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125191220.png" alt="image.png"></p><h3 id="1-1-7-享元模式⭐️🔴-集合"><a href="#1-1-7-享元模式⭐️🔴-集合" class="headerlink" title="1.1.7. 享元模式⭐️🔴+ 集合"></a>1.1.7. 享元模式⭐️🔴+ 集合</h3><p>[[内功心法专题-设计模式-13、享元模式#2 3 UML图示⭐️🔴]]</p><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;flyweight&#x2F;BoxFactory.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117182222.png" alt="image.png"></p><h3 id="1-1-8-装饰者模式⭐️🔴-『继承』-构造导入"><a href="#1-1-8-装饰者模式⭐️🔴-『继承』-构造导入" class="headerlink" title="1.1.8. 装饰者模式⭐️🔴+『继承』+ 构造导入"></a>1.1.8. 装饰者模式⭐️🔴+『继承』+ 构造导入</h3><p>[[内功心法专题-设计模式-9、装饰者模式#2 1 ⭐️🔴 UML图示]]</p><p>代码：[[Decorator.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125191837.png" alt="image.png"></p><p>组合模式也是元素之间有 2 种关系，<span style="background-color:#ff00ff">聚合 + 继承</span></p><h3 id="1-1-9-适配器模式⭐️🔴-『继承』"><a href="#1-1-9-适配器模式⭐️🔴-『继承』" class="headerlink" title="1.1.9. 适配器模式⭐️🔴+『继承』"></a>1.1.9. 适配器模式⭐️🔴+『继承』</h3><a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;adapter&#x2F;objectadapter&#x2F;VoltageAdapter.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125192016.png" alt="image.png"></p><h3 id="1-1-10-建造者模式⭐️🔴-【2-个聚合】-构造导入"><a href="#1-1-10-建造者模式⭐️🔴-【2-个聚合】-构造导入" class="headerlink" title="1.1.10. 建造者模式⭐️🔴+【2 个聚合】+ 构造导入"></a>1.1.10. 建造者模式⭐️🔴+【2 个聚合】+ 构造导入</h3><p>[[内功心法专题-设计模式-6、建造者模式#2 2 2 实现逻辑⭐️🔴]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125193434.png" alt="image.png"></p><p>代码： [[HouseBuilder.java]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125193953.png" alt="image.png"></p><p>代码：[[Builder.java]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125193825.png" alt="image.png"></p><h2 id="1-2-可以设置为链式结构"><a href="#1-2-可以设置为链式结构" class="headerlink" title="1.2. 可以设置为链式结构"></a>1.2. 可以设置为链式结构</h2><h3 id="1-2-1-责任链模式⭐️🔴-聚合自身"><a href="#1-2-1-责任链模式⭐️🔴-聚合自身" class="headerlink" title="1.2.1. 责任链模式⭐️🔴+ 聚合自身"></a>1.2.1. 责任链模式⭐️🔴+ 聚合自身</h3><p>代码：[[Approver.java]]</p><p>[[内功心法专题-设计模式-17、责任链模式#2 2 UML⭐️🔴]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123165451.png" alt="image.png"></p><h2 id="1-3-可以组成递归结构"><a href="#1-3-可以组成递归结构" class="headerlink" title="1.3. 可以组成递归结构"></a>1.3. 可以组成递归结构</h2><h3 id="1-3-1-组合模式⭐️🔴-集合-『继承』"><a href="#1-3-1-组合模式⭐️🔴-集合-『继承』" class="headerlink" title="1.3.1. 组合模式⭐️🔴+ 集合 +『继承』"></a>1.3.1. 组合模式⭐️🔴+ 集合 +『继承』</h3><p>[[内功心法专题-设计模式-12、组合模式#2 2 ⭐️🔴UML图示]]</p><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;combination&#x2F;MenuComponent.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125200726.png" alt="image.png"></p><h3 id="1-3-2-解释器模式⭐️🔴-『继承』-多个"><a href="#1-3-2-解释器模式⭐️🔴-『继承』-多个" class="headerlink" title="1.3.2. 解释器模式⭐️🔴+『继承』+ 多个"></a>1.3.2. 解释器模式⭐️🔴+『继承』+ 多个</h3><p>[[内功心法专题-设计模式-24、解释器模式#2 2 UML⭐️🔴]]</p><p>代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;interpreter&#x2F;Minus.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128134131.png" alt="image.png"></p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128131704.png" style="zoom:80%;" /><h1 id="2-涉及-ifelse-的设计模式"><a href="#2-涉及-ifelse-的设计模式" class="headerlink" title="2. 涉及 ifelse 的设计模式"></a>2. 涉及 ifelse 的设计模式</h1><h2 id="2-1-适配器模式"><a href="#2-1-适配器模式" class="headerlink" title="2.1. 适配器模式"></a>2.1. 适配器模式</h2><a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><h2 id="2-2-模板方法模式"><a href="#2-2-模板方法模式" class="headerlink" title="2.2. 模板方法模式"></a>2.2. 模板方法模式</h2><p>[[内功心法专题-设计模式-14、模板方法模式]]</p><h2 id="2-3-策略模式"><a href="#2-3-策略模式" class="headerlink" title="2.3. 策略模式"></a>2.3. 策略模式</h2><p>[[内功心法专题-设计模式-15、策略模式]]</p><h2 id="2-4-责任链模式"><a href="#2-4-责任链模式" class="headerlink" title="2.4. 责任链模式"></a>2.4. 责任链模式</h2><p>[[内功心法专题-设计模式-17、责任链模式]]</p><h2 id="2-5-状态模式"><a href="#2-5-状态模式" class="headerlink" title="2.5. 状态模式"></a>2.5. 状态模式</h2><a href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-18%E3%80%81%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/" title="设计模式-18、状态模式">设计模式-18、状态模式</a><h1 id="3-涉及环境类-Context-的设计模式"><a href="#3-涉及环境类-Context-的设计模式" class="headerlink" title="3. 涉及环境类 Context 的设计模式"></a>3. 涉及环境类 Context 的设计模式</h1><p>策略模式<br>[[内功心法专题-设计模式-15、策略模式]]<br>状态模式</p><a href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-18%E3%80%81%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/" title="设计模式-18、状态模式">设计模式-18、状态模式</a><p>解释器模式<br>[[内功心法专题-设计模式-24、解释器模式]]</p><h1 id="4-涉及功能增强的设计模式"><a href="#4-涉及功能增强的设计模式" class="headerlink" title="4. 涉及功能增强的设计模式"></a>4. 涉及功能增强的设计模式</h1><p>装饰者模式<br>[[内功心法专题-设计模式-9、装饰者模式]]<br>代理模式</p><a href="/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7%E3%80%81%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/" title="设计模式-7、代理模式">设计模式-7、代理模式</a><p>适配器模式</p><a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><h1 id="5-涉及动态变化的设计模式"><a href="#5-涉及动态变化的设计模式" class="headerlink" title="5. 涉及动态变化的设计模式"></a>5. 涉及动态变化的设计模式</h1><p>策略模式<br>[[内功心法专题-设计模式-15、策略模式]]<br>装饰者模式<br>[[内功心法专题-设计模式-9、装饰者模式]]</p><h1 id="6-满足开闭原则的设计模式"><a href="#6-满足开闭原则的设计模式" class="headerlink" title="6. 满足开闭原则的设计模式"></a>6. 满足开闭原则的设计模式</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230522-1235%%</span>❕ ^8yg365</p><h2 id="6-1-工厂方法-将普通工程抽象"><a href="#6-1-工厂方法-将普通工程抽象" class="headerlink" title="6.1. 工厂方法 - 将普通工程抽象"></a>6.1. 工厂方法 - 将普通工程抽象</h2><p>[[内功心法专题-设计模式-4、工厂模式]]</p><h2 id="6-2-建造者模式"><a href="#6-2-建造者模式" class="headerlink" title="6.2. 建造者模式"></a>6.2. 建造者模式</h2><p>[[内功心法专题-设计模式-6、建造者模式]]</p><h2 id="6-3-装饰者模式"><a href="#6-3-装饰者模式" class="headerlink" title="6.3. 装饰者模式"></a>6.3. 装饰者模式</h2><p>[[内功心法专题-设计模式-9、装饰者模式]]</p><h2 id="6-4-组合模式"><a href="#6-4-组合模式" class="headerlink" title="6.4. 组合模式"></a>6.4. 组合模式</h2><p>[[内功心法专题-设计模式-12、组合模式]]</p><h2 id="6-5-模板方法模式"><a href="#6-5-模板方法模式" class="headerlink" title="6.5. 模板方法模式"></a>6.5. 模板方法模式</h2><p>[[内功心法专题-设计模式-14、模板方法模式]]</p><h2 id="6-6-策略模式"><a href="#6-6-策略模式" class="headerlink" title="6.6. 策略模式"></a>6.6. 策略模式</h2><p>[[内功心法专题-设计模式-15、策略模式]]</p><h2 id="6-7-命令模式"><a href="#6-7-命令模式" class="headerlink" title="6.7. 命令模式"></a>6.7. 命令模式</h2><p>[[内功心法专题-设计模式-16、命令模式]]</p><h2 id="6-8-责任链模式"><a href="#6-8-责任链模式" class="headerlink" title="6.8. 责任链模式"></a>6.8. 责任链模式</h2><p>[[内功心法专题-设计模式-17、责任链模式]]</p><h2 id="6-9-迭代器模式"><a href="#6-9-迭代器模式" class="headerlink" title="6.9. 迭代器模式"></a>6.9. 迭代器模式</h2><p>[[内功心法专题-设计模式-21、迭代器模式]]</p><h2 id="6-10-解释器模式"><a href="#6-10-解释器模式" class="headerlink" title="6.10. 解释器模式"></a>6.10. 解释器模式</h2><p>[[内功心法专题-设计模式-24、解释器模式]]</p><h1 id="7-违反开闭原则的设计模式"><a href="#7-违反开闭原则的设计模式" class="headerlink" title="7. 违反开闭原则的设计模式"></a>7. 违反开闭原则的设计模式</h1><p>命令模式 不太友好</p><p>[[内功心法专题-设计模式-22、访问者模式]]<br>访问者模式 (还违背了依赖倒转原则)<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128081459.png" alt="image.png"></p><p>[[内功心法专题-设计模式-11、外观模式]]</p><h1 id="8-1-对-1-模式-提前关联"><a href="#8-1-对-1-模式-提前关联" class="headerlink" title="8. 1 对 1 模式 (提前关联)"></a>8. 1 对 1 模式 (提前关联)</h1><h2 id="8-1-工厂方法模式"><a href="#8-1-工厂方法模式" class="headerlink" title="8.1. 工厂方法模式"></a>8.1. 工厂方法模式</h2><p>[[内功心法专题-设计模式-4、工厂模式]]</p><h2 id="8-2-适配器模式"><a href="#8-2-适配器模式" class="headerlink" title="8.2. 适配器模式"></a>8.2. 适配器模式</h2><a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><h2 id="8-3-迭代器模式"><a href="#8-3-迭代器模式" class="headerlink" title="8.3. 迭代器模式"></a>8.3. 迭代器模式</h2><p>[[内功心法专题-设计模式-21、迭代器模式]]</p><h1 id="9-N-对-N-模式"><a href="#9-N-对-N-模式" class="headerlink" title="9. N 对 N 模式"></a>9. N 对 N 模式</h1><p>[[内功心法专题-设计模式-22、访问者模式]]</p><h1 id="10-1-对-N-模式"><a href="#10-1-对-N-模式" class="headerlink" title="10. 1 对 N 模式"></a>10. 1 对 N 模式</h1><p>[[内功心法专题-设计模式-6、建造者模式]]</p><h1 id="11-包含继承关系的设计模式"><a href="#11-包含继承关系的设计模式" class="headerlink" title="11. 包含继承关系的设计模式"></a>11. 包含继承关系的设计模式</h1><h2 id="11-1-两个元素之间继承"><a href="#11-1-两个元素之间继承" class="headerlink" title="11.1. 两个元素之间继承"></a>11.1. 两个元素之间继承</h2><p>[[内功心法专题-设计模式-12、组合模式]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117133928.png" alt="image.png"></p><p>[[内功心法专题-设计模式-9、装饰者模式]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230119204836.png" alt="image.png"></p><p>[[内功心法专题-设计模式-24、解释器模式]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128131704.png" style="zoom:80%;" /></p><h2 id="11-2-涉及三个元素"><a href="#11-2-涉及三个元素" class="headerlink" title="11.2. 涉及三个元素"></a>11.2. 涉及三个元素</h2><a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230116104719.png" alt="image.png"></p><h1 id="12-变与不变之间"><a href="#12-变与不变之间" class="headerlink" title="12. 变与不变之间"></a>12. 变与不变之间</h1><p>策略模式<br>模板方法模式<br>建造者模式<br>责任链模式<br>状态模式<br>享元模式</p><h1 id="13-实战经验"><a href="#13-实战经验" class="headerlink" title="13. 实战经验"></a>13. 实战经验</h1><h1 id="14-参考与感谢"><a href="#14-参考与感谢" class="headerlink" title="14. 参考与感谢"></a>14. 参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-19、Queue</title>
      <link href="/2023/01/22/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-19%E3%80%81Queue/"/>
      <url>/2023/01/22/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-19%E3%80%81Queue/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-JDK-原生并发队列"><a href="#1-JDK-原生并发队列" class="headerlink" title="1. JDK 原生并发队列"></a>1. JDK 原生并发队列</h1><p>在介绍 Mpsc Queue 之前，我们先回顾下 JDK 原生队列的工作原理。JDK 并发队列按照实现方式可以分为阻塞队列和非阻塞队列两种类型，阻塞队列是基于锁实现的，非阻塞队列是基于 CAS 操作实现的。JDK 中包含多种阻塞和非阻塞的队列实现，如下图所示。</p><p><img src="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Netty%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%20RPC%20%E5%AE%9E%E8%B7%B5-%E5%AE%8C/assets/Cip5yF_sPcmAaDp3AAIqvvYOtM0974.png" alt="图片1.png"></p><p>队列是一种 FIFO（先进先出）的数据结构，JDK 中定义了 java.util.Queue 的队列接口，与 List、Set 接口类似，java.util.Queue 也继承于 Collection 集合接口。此外，JDK 还提供了一种双端队列接口 java.util.Deque，我们最常用的 LinkedList 就是实现了 Deque 接口。下面我们简单说说上图中的每个队列的特点，并给出一些对比和总结。</p><h2 id="1-1-阻塞队列"><a href="#1-1-阻塞队列" class="headerlink" title="1.1. 阻塞队列"></a>1.1. 阻塞队列</h2><p>阻塞队列在队列为空或者队列满时，都会发生阻塞。阻塞队列自身是线程安全的，使用者无需关心线程安全问题，降低了多线程开发难度。阻塞队列主要分为以下几种：</p><ul><li><strong>ArrayBlockingQueue</strong>：最基础且开发中最常用的阻塞队列，底层采用数组实现的有界队列，初始化需要指定队列的容量。ArrayBlockingQueue 是如何保证线程安全的呢？它内部是使用了一个重入锁 ReentrantLock，并搭配 notEmpty、notFull 两个条件变量 Condition 来控制并发访问。从队列读取数据时，如果队列为空，那么会阻塞等待，直到队列有数据了才会被唤醒。如果队列已经满了，也同样会进入阻塞状态，直到队列有空闲才会被唤醒。</li><li><strong>LinkedBlockingQueue</strong>：内部采用的数据结构是链表，队列的长度可以是有界或者无界的，初始化不需要指定队列长度，默认是 Integer.MAX_VALUE。LinkedBlockingQueue 内部使用了 takeLock、putLock 两个重入锁 ReentrantLock，以及 notEmpty、notFull 两个条件变量 Condition 来控制并发访问。采用读锁和写锁的好处是可以避免读写时相互竞争锁的现象，所以相比于 ArrayBlockingQueue，LinkedBlockingQueue 的性能要更好。</li><li><strong>PriorityBlockingQueue</strong>：采用最小堆实现的优先级队列，队列中的元素按照优先级进行排列，每次出队都是返回优先级最高的元素。PriorityBlockingQueue 内部是使用了一个 ReentrantLock 以及一个条件变量 Condition notEmpty 来控制并发访问，不需要 notFull 是因为 PriorityBlockingQueue 是无界队列，所以每次 put 都不会发生阻塞。PriorityBlockingQueue 底层的最小堆是采用数组实现的，当元素个数大于等于最大容量时会触发扩容，在扩容时会先释放锁，保证其他元素可以正常出队，然后使用 CAS 操作确保只有一个线程可以执行扩容逻辑。</li><li><strong>DelayQueue</strong>，一种支持延迟获取元素的阻塞队列，常用于缓存、定时任务调度等场景。DelayQueue 内部是采用优先级队列 PriorityQueue 存储对象。DelayQueue 中的每个对象都必须实现 Delayed 接口，并重写 compareTo 和 getDelay 方法。向队列中存放元素的时候必须指定延迟时间，只有延迟时间已满的元素才能从队列中取出。</li><li><strong>SynchronizedQueue</strong>，又称无缓冲队列。比较特别的是 SynchronizedQueue 内部不会存储元素。与 ArrayBlockingQueue、LinkedBlockingQueue 不同，SynchronizedQueue 直接使用 CAS 操作控制线程的安全访问。其中 put 和 take 操作都是阻塞的，每一个 put 操作都必须阻塞等待一个 take 操作，反之亦然。所以 SynchronizedQueue 可以理解为生产者和消费者配对的场景，双方必须互相等待，直至配对成功。在 JDK 的线程池 Executors.newCachedThreadPool 中就存在 SynchronousQueue 的运用，对于新提交的任务，如果有空闲线程，将重复利用空闲线程处理任务，否则将新建线程进行处理。</li><li><strong>LinkedTransferQueue</strong>，一种特殊的无界阻塞队列，可以看作 LinkedBlockingQueues、SynchronousQueue（公平模式）、ConcurrentLinkedQueue 的合体。与 SynchronousQueue 不同的是，LinkedTransferQueue 内部可以存储实际的数据，当执行 put 操作时，如果有等待线程，那么直接将数据交给对方，否则放入队列中。与 LinkedBlockingQueues 相比，LinkedTransferQueue 使用 CAS 无锁操作进一步提升了性能。</li></ul><h2 id="1-2-非阻塞队列"><a href="#1-2-非阻塞队列" class="headerlink" title="1.2. 非阻塞队列"></a>1.2. 非阻塞队列</h2><p>说完阻塞队列，我们再来看下非阻塞队列。非阻塞队列不需要通过加锁的方式对线程阻塞，并发性能更好。JDK 中常用的非阻塞队列有以下几种：</p><ul><li><strong>ConcurrentLinkedQueue</strong>，它是一个采用双向链表实现的无界并发非阻塞队列，它属于 LinkedQueue 的安全版本。ConcurrentLinkedQueue 内部采用 CAS 操作保证线程安全，这是非阻塞队列实现的基础，相比 ArrayBlockingQueue、LinkedBlockingQueue 具备较高的性能。</li><li><strong>ConcurrentLinkedDeque</strong>，也是一种采用双向链表结构的无界并发非阻塞队列。与 ConcurrentLinkedQueue 不同的是，ConcurrentLinkedDeque 属于双端队列，它同时支持 FIFO 和 FILO 两种模式，可以从队列的头部插入和删除数据，也可以从队列尾部插入和删除数据，适用于多生产者和多消费者的场景。</li></ul><p>至此，常见的队列类型我们已经介绍完了。我们在平时开发中使用频率最高的是 BlockingQueue。实现一个阻塞队列需要具备哪些基本功能呢？下面看 BlockingQueue 的接口，如下图所示。</p><p><img src="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Netty%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%20RPC%20%E5%AE%9E%E8%B7%B5-%E5%AE%8C/assets/Cip5yF_sPd2ANdYxAAlJC49L-1o214.png" alt="图片2.png"></p><p>我们可以通过下面一张表格，对上述 BlockingQueue 接口的具体行为进行归类。</p><p><img src="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Netty%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%20RPC%20%E5%AE%9E%E8%B7%B5-%E5%AE%8C/assets/CgpVE1_sPeSAZ3hUAAESfy4p6LY171.png" alt="图片3.png"></p><h1 id="2-第三方高性能队列"><a href="#2-第三方高性能队列" class="headerlink" title="2. 第三方高性能队列"></a>2. 第三方高性能队列</h1><p>JDK 提供的并发队列已经能够满足我们大部分的需求，但是在大规模流量的高并发系统中，如果你对性能要求严苛，JDK 的非阻塞并发队列可选择面较少且性能并不够出色。如果你还是需要一个数组 + CAS 操作实现的无锁安全队列，有没有成熟的解决方案呢？Java 强大的生态总能给我们带来惊喜，一些第三方框架提供的高性能无锁队列已经可以满足我们的需求，其中非常出名的有 Disruptor 和 JCTools。</p><h2 id="2-1-Disruptor"><a href="#2-1-Disruptor" class="headerlink" title="2.1. Disruptor"></a>2.1. Disruptor</h2><p>Disruptor 是 LMAX 公司开发的一款高性能无锁队列，我们平时常称它为 RingBuffer，其设计初衷是为了解决内存队列的延迟问题。Disruptor 内部采用环形数组和 CAS 操作实现，性能非常优越。为什么 Disruptor 的性能会比 JDK 原生的无锁队列要好呢？环形数组可以复用内存，减少分配内存和释放内存带来的性能损耗。而且数组可以设置长度为 2 的次幂，直接通过位运算加快数组下标的定位速度。此外，Disruptor 还解决了伪共享问题，对 CPU Cache 更加友好。Disruptor 已经开源，详细可查阅 Github 地址 <a href="https://github.com/LMAX-Exchange/disruptor">https://github.com/LMAX-Exchange/disruptor</a>。</p><h2 id="2-2-JCTools"><a href="#2-2-JCTools" class="headerlink" title="2.2. JCTools"></a>2.2. JCTools</h2><p>JCTools 也是一个开源项目，Github 地址为 <a href="https://github.com/JCTools/JCTools">https://github.com/JCTools/JCTools</a>。JCTools 是适用于 JVM 并发开发的工具，主要提供了一些 JDK 确实的并发数据结构，例如非阻塞 Map、非阻塞 Queue 等。其中非阻塞队列可以分为四种类型，可以根据不同的场景选择使用。</p><ul><li>Spsc 单生产者单消费者；</li><li>Mpsc 多生产者单消费者；</li><li>Spmc 单生产者多消费者；</li><li>Mpmc 多生产者多消费者。</li></ul><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-17、责任链模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-17%E3%80%81%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-17%E3%80%81%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><p>又名职责链模式，为了避免请求发送者与多个请求处理者耦合在一起，将所有请求的处理者&#x3D;&#x3D;通过前一对象记住其下一个对象的引用而连成一条链&#x3D;&#x3D;；当有请求发生时，可将请求沿着这条链传递，直到有对象处理它为止。</p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><ul><li><strong>抽象处理者（Handler）角色</strong>：定义一个处理请求的接口，包含抽象处理方法和一个后继连接。</li><li><strong>具体处理者（Concrete Handler）角色</strong>：实现抽象处理者的处理方法，判断能否处理本次请求，如果可以处理请求则处理，否则将该请求转给它的后继者。</li><li><strong>客户类（Client）角色</strong>：创建处理链，并向链头的具体处理者对象提交请求，它不关心处理细节和请求的传递过程。</li></ul><h2 id="2-2-UML⭐️🔴"><a href="#2-2-UML⭐️🔴" class="headerlink" title="2.2. UML⭐️🔴"></a>2.2. UML⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123165451.png" alt="image.png"></p><h2 id="2-3-实现逻辑⭐️🔴"><a href="#2-3-实现逻辑⭐️🔴" class="headerlink" title="2.3. 实现逻辑⭐️🔴"></a>2.3. 实现逻辑⭐️🔴</h2><ol><li>抽象处理者<span style="background-color:#ff00ff">聚合自身作为成员变量nextHandler</span>，表示后续处理者，并提供setter方法，<span style="background-color:#00ff00">由Client配置，比较灵活</span></li><li>定义处理请求的抽象方法，交给子类实现<br>❕<span style="display:none">%%<br>2215-🏡⭐️◼️责任链模式的核心逻辑？抽象处理者类，聚合自身，并提供setter方法，表示设置下一个处理者；抽象处理者类还定义一个抽象方法，表示处理逻辑，让子类实现◼️⭐️-point-202301232215%%</span></li></ol><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-请假系统"><a href="#3-1-请假系统" class="headerlink" title="3.1. 请假系统"></a>3.1. 请假系统</h2><p>现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可；请假1天到3天的假还需要部门经理同意；请求3天到7天还需要总经理同意才行。</p><h2 id="3-2-UML"><a href="#3-2-UML" class="headerlink" title="3.2. UML"></a>3.2. UML</h2><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123165311.png" style="zoom:100%;" /><h2 id="3-3-实现逻辑⭐️🔴"><a href="#3-3-实现逻辑⭐️🔴" class="headerlink" title="3.3. 实现逻辑⭐️🔴"></a>3.3. 实现逻辑⭐️🔴</h2><ol><li>抽象处理者Handler聚合自身作为成员变量nextHandler，表示后续处理者</li><li>定义处理请求的抽象方法handleLeave，交给子类实现</li><li>定义final的请求提交方法submit，让子类调用</li><li>client调用时需创建请求，并设置责任链关系</li></ol><h2 id="3-4-代码示例"><a href="#3-4-代码示例" class="headerlink" title="3.4. 代码示例"></a>3.4. 代码示例</h2><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;responsibility&#x2F;Handler.java]]</p><h1 id="4-优缺点⭐️🔴"><a href="#4-优缺点⭐️🔴" class="headerlink" title="4. 优缺点⭐️🔴"></a>4. 优缺点⭐️🔴</h1><h2 id="4-1-优点"><a href="#4-1-优点" class="headerlink" title="4.1. 优点"></a>4.1. 优点</h2><ol><li><span style="background-color:#00ff00">降低了对象之间的耦合度</span><br>该模式降低了请求发送者和接收者的耦合度。</li><li><span style="background-color:#00ff00">增强了系统的可扩展性</span><br>可以根据需要增加新的请求处理类，&#x3D;&#x3D;满足开闭原则&#x3D;&#x3D;。</li><li>&#x3D;&#x3D;增强了给对象指派职责的灵活性&#x3D;&#x3D;<br>当工作流程发生变化，可以动态地改变链内的成员或者修改它们的次序，也可动态地新增或者删除责任。</li><li><span style="background-color:#00ff00">责任链简化了对象之间的连接</span><br>一个对象只需保持一个指向其后继者的引用，不需保持其他所有处理者的引用，这避免了使用众多的 if 或者 ifelse 语句(客户端的)。❕<span style="display:none">%%<br>1731-🏡⭐️◼️简化优化 ifelse 的设计模式：责任链模式、策略模式、适配器模式、工程方法模式 (包括配置版工程方法模式)、状态模式◼️⭐️-point-202301231731%%</span></li><li>责任分担<br>每个类只需要处理自己该处理的工作，不能处理的传递给下一个对象完成，明确各类的责任范围，<span style="background-color:#00ff00">符合类的单一职责原则</span>。<a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a></li></ol><h2 id="4-2-缺点"><a href="#4-2-缺点" class="headerlink" title="4.2. 缺点"></a>4.2. 缺点</h2><ul><li><span style="background-color:#ff00ff">不能保证每个请求一定被处理</span>。由于一个请求没有明确的接收者，所以不能保证它一定会被处理，该请求可能一直传到链的末端都得不到处理。</li><li>对比较长的职责链，请求的处理可能涉及多个处理对象，<span style="background-color:#ff00ff">系统性能将受到一定影响</span>。</li><li>职责链建立的合理性要<span style="background-color:#ff00ff">靠客户端来保证，增加了客户端的复杂性</span>，可能会由于职责链的错误设置而导致系统出错，如可能会造成循环调用。</li></ul><h1 id="5-JDK源码分析"><a href="#5-JDK源码分析" class="headerlink" title="5. JDK源码分析"></a>5. JDK源码分析</h1><h2 id="5-1-HandlerExecutionChain"><a href="#5-1-HandlerExecutionChain" class="headerlink" title="5.1. HandlerExecutionChain"></a>5.1. HandlerExecutionChain</h2><p><code>SpringMVC</code>中<code>HandlerExecutionChain</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123173458.png" alt="image.png"></p><p>通过对上述<code>SpringMVC</code>的源码分析，这里进行简单的梳理总结</p><ul><li><code>SpringMVC</code>请求的流程图中，执行了拦截器相关方法，如<code>interceptor.preHandler()</code></li><li>在处理<code>SpringMVC</code>请求时，使用到<span style="background-color:#00ff00">职责链模式和适配器模式</span></li><li><code>HandlerExecutionChain</code>：主要负责请求拦截器的执行和请求处理，但是本身不处理请求，只是将请求分配给 <em>链上注册处理器</em> 执行。这是职责链实现方式，减少职责链本身与处理逻辑之间的耦合，规范了处理流程</li><li><code>HandlerExecutionChain</code>：维护了<code>Handlerlnterceptor</code>的集合，可以向其中注册相应的拦截器</li></ul><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><ul><li>1）将请求和处理分开，实现解耦，提高系统的灵活性</li><li>2）简化了对象，使对象不需要知道链的结构</li><li>3）性能会受到影响，特别是在链比较长的时候，因此需控制链中最大节点数量，一般通过在<code>Handler</code>中设置一个最大节点数量，在<code>setNext()</code>方法中判断是否已经超过阀值，超过则不允许该链建立，避免出现超长链无意识地破坏系统性能</li><li>4）调试不方便。采用了类似递归的方式，调试时逻辑可能比较复杂</li><li>5）最佳应用场景：有多个对象可以处理同一个请求时，比如：多级请求、请假 &#x2F; 加薪等审批流程、Java Web 中 Tomcat 对<code>Encoding</code>的处理、拦截器</li></ul><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-18、状态模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-18%E3%80%81%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-18%E3%80%81%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><ul><li>状态模式（State Pattern）：<span style="background-color:#00ff00">它主要用来解决对象在多种状态转换时，需要对外输出不同的行为的问题</span>。状态和行为是一一对应的，状态之间可以相互转换❕<span style="display:none">%%<br>0724-🏡⭐️◼️主要解决的问题是什么？🔜📝 对象在不同状态下转换时输出不同行为的问题◼️⭐️-point-202301250724%%</span></li><li>当一个对象的内在状态改变时，允许改变其行为，这个对象看起来像是改变了其类</li><li>对&#x3D;&#x3D;有状态的对象&#x3D;&#x3D;，<span style="background-color:#00ff00">把复杂的“判断逻辑”提取到不同的状态对象中，允许状态对象在其内部状态发生改变时改变其行为</span>。</li></ul><h1 id="2-模式结构⭐️🔴"><a href="#2-模式结构⭐️🔴" class="headerlink" title="2. 模式结构⭐️🔴"></a>2. 模式结构⭐️🔴</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><ul><li><strong>环境（Context）角色</strong>：也称为上下文，它定义了客户程序需要的接口，维护一个当前状态，并将与状态相关的操作委托给当前状态对象来处理。</li><li><strong>抽象状态（State）角色</strong>：定义一个接口，用以封装环境对象中的特定状态所对应的行为。</li><li><strong>具体状态（Concrete State）角色</strong>：实现抽象状态所对应的行为。<br>❕<span style="display:none">%%<br>0729-🏡⭐️◼️所有有 context 角色的设计模式？🔜📝 策略模式、状态模式、解释器模式◼️⭐️-point-202301250729%%</span></li></ul><h2 id="2-2-UML"><a href="#2-2-UML" class="headerlink" title="2.2. UML"></a>2.2. UML</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123181854.png" alt="image.png"></p><h2 id="2-3-实现逻辑"><a href="#2-3-实现逻辑" class="headerlink" title="2.3. 实现逻辑"></a>2.3. 实现逻辑</h2><h1 id="3-案例分析⭐️🔴"><a href="#3-案例分析⭐️🔴" class="headerlink" title="3. 案例分析⭐️🔴"></a>3. 案例分析⭐️🔴</h1><h2 id="3-1-电梯运行"><a href="#3-1-电梯运行" class="headerlink" title="3.1. 电梯运行"></a>3.1. 电梯运行</h2><p>通过按钮来控制一个电梯的状态，一个电梯有开门状态，关门状态，停止状态，运行状态。每一种状态改变，都有可能要根据其他状态来更新处理。例如，如果电梯门现在处于运行时状态，就不能进行开门操作，而如果电梯门是停止状态，就可以执行开门操作。</p><h3 id="3-1-1-传统方案"><a href="#3-1-1-传统方案" class="headerlink" title="3.1.1. 传统方案"></a>3.1.1. 传统方案</h3><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123174815.png" style="zoom:80%;" /><ul><li>会需要大量的 switch…case 这样的判断（ifelse 也是一样)，使程序的可阅读性变差。</li><li>扩展性很差。</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124171839.png" alt="image.png"></p><h3 id="3-1-2-状态模式"><a href="#3-1-2-状态模式" class="headerlink" title="3.1.2. 状态模式"></a>3.1.2. 状态模式</h3><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123211539.png" style="zoom:70%;" /><h3 id="3-1-3-实现逻辑⭐️🔴"><a href="#3-1-3-实现逻辑⭐️🔴" class="headerlink" title="3.1.3. 实现逻辑⭐️🔴"></a>3.1.3. 实现逻辑⭐️🔴</h3><p><span style="background-color:#ff0000">Context 与 ConcreteState 相互聚合 +setter 导入</span></p><ol><li>环境类 Context 聚合四种具体状态类</li><li>环境类 Context<span style="background-color:#ff00ff">聚合</span>抽象状态类 LiftState，并<span style="background-color:#ff00ff">setter 导入</span>，通过 LiftState 抽象状态类最终调用具体状态类的 4 种方法</li><li>抽象状态类 LiftState 又<span style="background-color:#ff00ff">聚合</span>了环境类 Context，并<span style="background-color:#ff00ff">setter 导入</span>，方便子类使用</li><li>Client 首先要通过 Context 的 setLiftState<span style="background-color:#ffff00">传入一个具体状态类，来设置当前状态</span></li><li>Client 中通过 Context 调用步骤 4 中传入的具体状态类实现的方法</li><li><span style="background-color:#ff00ff">在具体状态类中通过 (继承的抽象状态类LiftState中所聚合的)context 来改变 Context 中聚合的 liftState 的状态，然后通过调用 liftState 的方法，通过多态会调用刚刚设置 ConcreteState 的方法，从而达到状态流转</span><br>❕<span style="display:none">%%<br>1724-🏡⭐️◼️状态模式状态流转的逻辑关键点在哪？client 调用 context 的 setState 方法之后，调用 context 的具体方法时，调用的是 context 中聚合的抽象 state 类中的方法，而 client 调用 setState 已经初始化了状态，也就指定了具体 state 类，所以就会调用具体 state 类的某个方法 (比如 open)，然后在这个具体 state 类中，会调用 context 的 change state 方法，因为从父类中继承过来，所以能拿到 context 的调用权限。改变了状态后，具体的 state 类也就指定了，然后在 open 方法中又调用了 context 的 close 方法，最终调用的是刚刚指定的状态类的 close 方法，至此状态跳转逻辑就流转起来了。关键在于具体状态类继承抽象状态类，然后 context 和抽象状态类相互聚合。◼️⭐️-point-202301241724%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124070226.png" alt="image.png"></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124081717.png" alt="image.png"></p><h3 id="3-1-4-示例代码"><a href="#3-1-4-示例代码" class="headerlink" title="3.1.4. 示例代码"></a>3.1.4. 示例代码</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;state&#x2F;after&#x2F;LiftState.java]]</p><h2 id="3-2-APP-抽奖问题⭐️🔴"><a href="#3-2-APP-抽奖问题⭐️🔴" class="headerlink" title="3.2. APP 抽奖问题⭐️🔴"></a>3.2. APP 抽奖问题⭐️🔴</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230410-1021%%</span>❕ ^b4joft</p><ul><li>1）应用实例要求完成 APP 抽奖活动项目，使用状态模式</li><li>2）思路分析和图解（类图）<ul><li>定义出一个接口叫状态接口，每个状态都实现它</li><li>接口有扣除积分方法、抽奖方法、发放奖品方法</li></ul></li></ul><h3 id="3-2-1-UML"><a href="#3-2-1-UML" class="headerlink" title="3.2.1. UML"></a>3.2.1. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123202503.png"></p><h3 id="3-2-2-实现逻辑"><a href="#3-2-2-实现逻辑" class="headerlink" title="3.2.2. 实现逻辑"></a>3.2.2. 实现逻辑</h3><h4 id="3-2-2-1-角色分析"><a href="#3-2-2-1-角色分析" class="headerlink" title="3.2.2.1. 角色分析"></a>3.2.2.1. 角色分析</h4><p>环境角色：RaffleActivity<br>抽象状态：State<br>具体状态：4 种抽奖状态，DispenseOutState、CanRaffleState、DispenseOutState、NoRaffleState</p><h4 id="3-2-2-2-关系逻辑"><a href="#3-2-2-2-关系逻辑" class="headerlink" title="3.2.2.2. 关系逻辑"></a>3.2.2.2. 关系逻辑</h4><ol><li>环境角色 RaffleActivity 聚合了抽象状态 State，根据业务逻辑调用 State 不同实现类的方法</li><li>具体状态类【聚合 + 构造导入】环境类 RaffleActivity，并调用方法<br>   环境类 RaffleActivity 也可以放到抽象状态类中聚合，跟案例 1 一样，会更加优雅</li><li>具体状态类通过改变环境角色类 RaffleActivity 的状态，以及调用其方法来达到状态流转的目的</li></ol><h3 id="3-2-3-示例代码"><a href="#3-2-3-示例代码" class="headerlink" title="3.2.3. 示例代码"></a>3.2.3. 示例代码</h3><p>[[RaffleActivity.java]]</p><h2 id="3-3-借贷平台"><a href="#3-3-借贷平台" class="headerlink" title="3.3. 借贷平台"></a>3.3. 借贷平台</h2><p>借贷平台的订单，有审核 - 发布 - 抢单等等步骤，随着操作的不同，会改变订单的状态，项目中的这个模块实现就会使用到状态模式</p><h3 id="3-3-1-传统方案"><a href="#3-3-1-传统方案" class="headerlink" title="3.3.1. 传统方案"></a>3.3.1. 传统方案</h3><p>通常通过 <code>if/else</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span>(审核)&#123;<br>    <span class="hljs-comment">//审核逻辑</span><br>&#125;<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(发布)&#123;<br>    <span class="hljs-comment">//发布逻辑</span><br>&#125;<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(接单)&#123;<br>    <span class="hljs-comment">//接单逻辑</span><br>&#125;<br></code></pre></td></tr></table></figure><h4 id="3-3-1-1-存在问题⭐️🔴"><a href="#3-3-1-1-存在问题⭐️🔴" class="headerlink" title="3.3.1.1. 存在问题⭐️🔴"></a>3.3.1.1. 存在问题⭐️🔴</h4><p>这类代码难以应对变化，在添加一种状态时，我们需要手动添加 <code>if/else</code>，在添加一种功能时，要对所有的状态进行判断。因此代码会变得越来越臃肿，并且一旦没有处理某个状态，便会发生极其严重的 BUG，难以维护</p><h3 id="3-3-2-状态模式"><a href="#3-3-2-状态模式" class="headerlink" title="3.3.2. 状态模式"></a>3.3.2. 状态模式</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124083447.png" alt="image.png"></p><h3 id="3-3-3-实现逻辑"><a href="#3-3-3-实现逻辑" class="headerlink" title="3.3.3. 实现逻辑"></a>3.3.3. 实现逻辑</h3><p>与前面 2 个案例的不同之处在于具体状态类和抽象状态类都没有聚合 Context，而是通过调用方法时传入的，其他逻辑差不多</p><h1 id="4-适用场景⭐️🔴"><a href="#4-适用场景⭐️🔴" class="headerlink" title="4. 适用场景⭐️🔴"></a>4. 适用场景⭐️🔴</h1><ol><li>当一个对象的<span style="background-color:#00ff00">行为取决于它的状态</span>，并且它必须在运行时根据状态改变它的行为时，就可以考虑使用状态模式。</li><li>一个操作中含有庞大的分支结构，并且这些<span style="background-color:#00ff00">分支决定于对象的状态</span>时。<br>❕<span style="display:none">%%<br>0739-🏡⭐️◼️状态模式适用场景：1. 对象行为取决于其状态；2. 庞大的业务分支取决于对象状态◼️⭐️-point-202301250739%%</span></li></ol><h1 id="5-优缺点⭐️🔴"><a href="#5-优缺点⭐️🔴" class="headerlink" title="5. 优缺点⭐️🔴"></a>5. 优缺点⭐️🔴</h1><h2 id="5-1-优点"><a href="#5-1-优点" class="headerlink" title="5.1. 优点"></a>5.1. 优点</h2><ol><li>高可读性：<span style="background-color:#00ff00">将每个状态的行为封装到对应的一个类中</span>，条理清晰</li><li>易维护：将状态转换逻辑与状态对象合成一体，<span style="background-color:#00ff00">而不是某一个巨大的条件语句块</span></li></ol><h2 id="5-2-缺点"><a href="#5-2-缺点" class="headerlink" title="5.2. 缺点"></a>5.2. 缺点</h2><ol><li>状态模式的使用必然会<span style="background-color:#ffff00">增加系统类和对象的个数</span>。</li><li>状态模式的<span style="background-color:#ffff00">结构与实现都较为复杂，如果使用不当将导致程序结构和代码的混乱</span>。<span style="background-color:#ff00ff">Context 和抽象状态类相互聚合 +setter 导入</span></li><li>状态模式<span style="background-color:#ff00ff">对 “ 开闭原则 “ 的支持并不太好</span>。因为具体状态类修改或增加后，context 也要修改或增加对应状态对象，比如状态常量，对状态对象方法的调用，以及状态流转中涉及到的其他状态类也要修改</li></ol><h1 id="6-JDK-源码分析"><a href="#6-JDK-源码分析" class="headerlink" title="6. JDK 源码分析"></a>6. JDK 源码分析</h1><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-19、观察者模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-19%E3%80%81%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-19%E3%80%81%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义⭐️🔴"><a href="#1-模式定义⭐️🔴" class="headerlink" title="1. 模式定义⭐️🔴"></a>1. 模式定义⭐️🔴</h1><p>又被称为<span style="background-color:#ff00ff">发布-订阅（Publish&#x2F;Subscribe）模式</span>，它<span style="background-color:#00ff00">定义了一种一对多的依赖关系，让多个观察者对象同时监听某一个主题对象</span>。这个主题对象在状态变化时，会<span style="background-color:#ff00ff">通知</span>所有的观察者对象，使他们能够<span style="background-color:#ff00ff">自动更新</span>自己。❕<span style="display:none">%%<br>1642-🏡⭐️◼️观察者模式是什么？定义了一种一对多的依赖关系，让一组观察者对象同时监听某一个主题对象，当主题对象状态发生变更时，通知这一组观察者对象，使得他们能够自动更新状态◼️⭐️-point-202301261642%%</span></p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色⭐️🔴"><a href="#2-1-模式角色⭐️🔴" class="headerlink" title="2.1. 模式角色⭐️🔴"></a>2.1. 模式角色⭐️🔴</h2><ol><li>Subject：<strong>抽象主题</strong>（&#x3D;&#x3D;抽象被观察者&#x3D;&#x3D;），<span style="background-color:#ff00ff">抽象主题角色把所有观察者对象保存在一个集合里(如果是接口的话，则由具体主题维护这个集合)</span>，每个主题都可以有任意数量的观察者，抽象主题提供一个接口，可以增加、删除和通知观察者对象。</li><li>ConcreteSubject：<strong>具体主题</strong>（&#x3D;&#x3D;具体被观察者&#x3D;&#x3D;），该角色<span style="background-color:#ff00ff">将有关状态存入具体观察者对象</span>，在具体主题的内部状态发生改变时，给所有注册过的观察者发送通知。</li><li>Observer：<strong>抽象观察者</strong>，是观察者的抽象类，它定义了一个更新接口，使得在得到主题更改通知时更新自己。</li><li>ConcrereObserver：<strong>具体观察者</strong>，实现抽象观察者定义的更新接口，以便在得到主题更改通知时更新自身的状态。</li></ol><h2 id="2-2-UML"><a href="#2-2-UML" class="headerlink" title="2.2. UML"></a>2.2. UML</h2><p>见具体栗子</p><h2 id="2-3-实现逻辑"><a href="#2-3-实现逻辑" class="headerlink" title="2.3. 实现逻辑"></a>2.3. 实现逻辑</h2><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-微信公众号"><a href="#3-1-微信公众号" class="headerlink" title="3.1. 微信公众号"></a>3.1. 微信公众号</h2><p>在使用微信公众号时，大家都会有这样的体验，当你关注的公众号中有新内容更新的话，它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景，微信用户就是观察者，微信公众号是被观察者，有多个的微信用户关注了程序猿这个公众号。</p><h3 id="3-1-1-UML⭐️🔴"><a href="#3-1-1-UML⭐️🔴" class="headerlink" title="3.1.1. UML⭐️🔴"></a>3.1.1. UML⭐️🔴</h3><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124220007.png" style="zoom:80%;" /><h3 id="3-1-2-实现逻辑⭐️🔴"><a href="#3-1-2-实现逻辑⭐️🔴" class="headerlink" title="3.1.2. 实现逻辑⭐️🔴"></a>3.1.2. 实现逻辑⭐️🔴</h3><p>具体主题类<span style="background-color:#ff0000">聚合</span>了抽象观察者类<span style="background-color:#ff0000">并放入集合</span>中，可以<span style="background-color:#ff0000">遍历调用</span>所有的具体观察者的<span style="background-color:#ff0000">update方法</span><br>❕<span style="display:none">%%<br>1253-🏡⭐️◼️观察者模式核心逻辑是什么？抽象主题或者具体主题(如果抽象主题是接口)聚合具体观察者并放入到集合中，抽象主题中定义发布方法，该方法在具体主题中遍历观察者集合调用抽象观察者定义的更新方法，以达到发布的功能目的◼️⭐️-point-202301251253%%</span></p><h3 id="3-1-3-示例代码"><a href="#3-1-3-示例代码" class="headerlink" title="3.1.3. 示例代码"></a>3.1.3. 示例代码</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;observer&#x2F;WeiXinUser.java]]</p><h2 id="3-2-天气预报"><a href="#3-2-天气预报" class="headerlink" title="3.2. 天气预报"></a>3.2. 天气预报</h2><ul><li>1）气象站可以将每天测量到的温度，湿度，气压等等以公告的形式发布出去（比如发布到自己的网站或第三方）</li><li>2）需要设计开放型 API，便于其他第三方也能接入气象站获取数据</li><li>3）提供温度、气压和湿度的接口</li><li>4）测量数据更新时，要能实时的通知给第三方</li></ul><h3 id="3-2-1-UML⭐️🔴"><a href="#3-2-1-UML⭐️🔴" class="headerlink" title="3.2.1. UML⭐️🔴"></a>3.2.1. UML⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124215620.png" alt="image.png"></p><h3 id="3-2-2-实现逻辑"><a href="#3-2-2-实现逻辑" class="headerlink" title="3.2.2. 实现逻辑"></a>3.2.2. 实现逻辑</h3><ul><li>1）观察者模式设计后，会以集合的方式来管理用户<code>Observer</code>，包括注册、移除和通知</li><li>2）这样，我们增加观察者（这里可以理解成一个新的公告板），就不需要去修改核心类<code>WeatherData</code>不会修改代码，遵守了<code>ocp</code>原则</li></ul><p>例如，我们新增<code>SinaWebSite</code>和<code>BaiDuWebSite</code>两个三方网站，接口气象局。此时三方只需实现相应接口即可，<code>WeatherData</code>不需要有任何的改变</p><h1 id="4-适用场景⭐️🔴"><a href="#4-适用场景⭐️🔴" class="headerlink" title="4. 适用场景⭐️🔴"></a>4. 适用场景⭐️🔴</h1><ol><li>对象间存在一对多关系，一个对象的状态发生改变会影响其他对象。</li><li>当一个抽象模型有两个方面，其中一个方面依赖于另一方面时。</li></ol><h1 id="5-优缺点"><a href="#5-优缺点" class="headerlink" title="5. 优缺点"></a>5. 优缺点</h1><p><strong>1. 优点：</strong></p><ul><li>降低了目标与观察者之间的耦合关系，两者之间是抽象耦合关系。</li><li>被观察者发送通知，所有注册的观察者都会收到信息【可以实现广播机制】</li></ul><p><strong>2. 缺点：</strong></p><ul><li><span style="background-color:#ffff00">如果观察者非常多的话</span>，那么所有的观察者收到被观察者发送的通知会耗时，超大集合还会占用内存空间</li><li><span style="background-color:#ff00ff">如果被观察者有循环依赖的话</span>，那么被观察者发送通知会使观察者循环调用，会导致系统崩溃</li></ul><h1 id="6-JDK源码分析"><a href="#6-JDK源码分析" class="headerlink" title="6. JDK源码分析"></a>6. JDK源码分析</h1><p>在 Java 中，通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式，只要实现它们的子类就可以编写观察者模式实例。</p><p><strong>1. Observable类</strong></p><p>Observable 类是抽象目标类（被观察者），也是具体目标类。它有一个 Vector 集合成员变量，用于保存所有要通知的观察者对象，并且包括对观察者相关的注册、移除和通知等方法。</p><ul><li>void addObserver(Observer o) 方法：用于将新的观察者对象添加到集合中。</li><li>void notifyObservers(Object arg) 方法：调用集合中的所有观察者对象的 update方法，通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。</li><li>void setChange() 方法：用来设置一个 boolean 类型的内部标志，注明目标对象发生了变化。当它为true时，notifyObservers() 才会通知观察者。</li></ul><p><strong>2. Observer 接口</strong></p><p>Observer 接口是抽象观察者，它监视目标对象的变化，当目标对象发生变化时，观察者得到通知，并调用 update 方法，进行相应的工作。</p><h2 id="6-1-示例代码"><a href="#6-1-示例代码" class="headerlink" title="6.1. 示例代码"></a>6.1. 示例代码</h2><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></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_">Thief</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Observable</span> &#123;<br><br>    <span class="hljs-keyword">private</span> String name;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Thief</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">this</span>.name = name;<br>    &#125;<br>    <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">this</span>.name = name;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">steal</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;小偷：我偷东西了，有没有人来抓我！！！&quot;</span>);<br>        <span class="hljs-built_in">super</span>.setChanged(); <span class="hljs-comment">//changed  = true</span><br>        <span class="hljs-built_in">super</span>.notifyObservers();<br>    &#125;<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><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_">Policemen</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Observer</span> &#123;<br><br>    <span class="hljs-keyword">private</span> String name;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Policemen</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">this</span>.name = name;<br>    &#125;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">this</span>.name = name;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> name;<br>    &#125;<br><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_">update</span><span class="hljs-params">(Observable o, Object arg)</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;警察：&quot;</span> + ((Thief) o).getName() + <span class="hljs-string">&quot;，我已经盯你很久了，你可以保持沉默，但你所说的将成为呈堂证供！！！&quot;</span>);<br>    &#125;<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></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_">Client</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">Thief</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thief</span>(<span class="hljs-string">&quot;隔壁老王&quot;</span>);<br>        <span class="hljs-comment">//创建警察对象</span><br>        <span class="hljs-type">Policemen</span> <span class="hljs-variable">p</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Policemen</span>(<span class="hljs-string">&quot;小李&quot;</span>);<br>        <span class="hljs-comment">//让警察盯着小偷</span><br>        t.addObserver(p);<br>        <span class="hljs-comment">//小偷偷东西</span><br>        t.steal();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-20、中介者模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-20%E3%80%81%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-20%E3%80%81%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义⭐️🔴"><a href="#1-模式定义⭐️🔴" class="headerlink" title="1. 模式定义⭐️🔴"></a>1. 模式定义⭐️🔴</h1><p>又叫调停模式，定义一个中介者角色来<span style="background-color:#00ff00">封装一系列对象之间的交互</span>，让各个对象不需要显式地相互引用，将原有对象之间的耦合松散，且可以独立地改变它们之间的交互。❕<span style="display:none">%%<br>1737-🏡⭐️◼️用一个中介对象来封装不同对象之间的交互，使得各个不同对象无需显示的调用，耦合度降低，同时可以更加方便的维护他们之间的交互逻辑◼️⭐️-point-202301251737%%</span></p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><ul><li><strong>抽象中介者（Mediator）角色</strong>：它是中介者的接口，提供了同事对象注册与转发同事对象信息的抽象方法。</li><li><strong>具体中介者（ConcreteMediator）角色</strong>：实现中介者接口，<span style="background-color:#ff00ff">聚合抽象同事类</span>，<span style="background-color:#ff00ff">定义一个 集合(List或者HashMap) 来管理同事对象</span>，并接受某个同事对象消息，协调各个同事角色之间的交互关系，因此它依赖于同事角色。</li><li><strong>抽象同事类（Colleague）角色</strong>：定义同事类的接口，保存中介者对象，提供同事对象交互的抽象方法，实现所有相互影响的同事类的公共功能。<span style="background-color:#ff00ff">聚合抽象中介者，并构造导入，以便具体同事类能调用具体中介者类中的方法。</span></li><li><strong>具体同事类（Concrete Colleague）角色</strong>：是抽象同事类的实现者，当需要与其他同事对象交互时，由中介者对象负责后续的交互。</li></ul><h2 id="2-2-UML"><a href="#2-2-UML" class="headerlink" title="2.2. UML"></a>2.2. UML</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125190323.png" alt="image.png"></p><h2 id="2-3-实现逻辑⭐️🔴"><a href="#2-3-实现逻辑⭐️🔴" class="headerlink" title="2.3. 实现逻辑⭐️🔴"></a>2.3. 实现逻辑⭐️🔴</h2><ol><li><span style="background-color:#ff00ff">具体中介者类ConcreteMediator聚合抽象同事类Colleague</span>，并用一个集合管理它的子类</li><li><span style="background-color:#ff00ff">抽象同事类Colleague聚合抽象中介者Mediator，并构造导入</span>，使其子类可以传入Client配置的具体中介者类，从而子类可以调用具体中介者类的方法。❕<span style="display:none">%%<br>2022-🏡⭐️◼️中介者模式核心逻辑？具体中介者类聚合+集合管理抽象同事类，抽象同事类聚合+构造导入抽象中介类，这样它的子类(比如ContreteColleague)创建时就可以传入具体中介者类ContreteMediator，从而可以调用其方法◼️⭐️-point-202301252022%%</span></li></ol><h1 id="3-案例分析⭐️🔴"><a href="#3-案例分析⭐️🔴" class="headerlink" title="3. 案例分析⭐️🔴"></a>3. 案例分析⭐️🔴</h1><h2 id="3-1-智能家居-✅"><a href="#3-1-智能家居-✅" class="headerlink" title="3.1. 智能家居 ✅"></a>3.1. 智能家居 ✅</h2><ol><li>智能家庭包括各种设备，闹钟、咖啡机、电视机、窗帘 等</li><li>主人要看电视时，各个设备可以协同工作，自动完成看电视的准备工作，比如流程为：闹铃响起-&gt;咖啡机开始做咖啡-&gt;窗帘自动落下-&gt;电视机开始播放</li></ol><h3 id="3-1-1-UML"><a href="#3-1-1-UML" class="headerlink" title="3.1.1. UML"></a>3.1.1. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125175112.png" alt="image.png"></p><h3 id="3-1-2-实现逻辑⭐️🔴"><a href="#3-1-2-实现逻辑⭐️🔴" class="headerlink" title="3.1.2. 实现逻辑⭐️🔴"></a>3.1.2. 实现逻辑⭐️🔴</h3><ol><li>抽象同事类聚合抽象中介者类，并构造导入，使得抽象同事类的子类可以构造传入Client指定的具体中介者类，并调用具体中介者类中的方法</li><li>具体中介者类聚合抽象同事类，并将其子类放入集合中管理，以便在其内部完成消息的转发</li></ol><h3 id="3-1-3-示例代码"><a href="#3-1-3-示例代码" class="headerlink" title="3.1.3. 示例代码"></a>3.1.3. 示例代码</h3><p>[[ConcreteMediator.java]]</p><h2 id="3-2-租房"><a href="#3-2-租房" class="headerlink" title="3.2. 租房"></a>3.2. 租房</h2><p>现在租房基本都是通过房屋中介，房主将房屋托管给房屋中介，而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。</p><h3 id="3-2-1-UML"><a href="#3-2-1-UML" class="headerlink" title="3.2.1. UML"></a>3.2.1. UML</h3><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125164018.png" style="zoom:70%;" /><h3 id="3-2-2-实现逻辑⭐️🔴"><a href="#3-2-2-实现逻辑⭐️🔴" class="headerlink" title="3.2.2. 实现逻辑⭐️🔴"></a>3.2.2. 实现逻辑⭐️🔴</h3><ol><li>具体中介者聚合抽象同事类或者所有具体同事类，该例子中具体同事类较少，直接作为成员变量了，没有使用集合进行管理</li><li>抽象同事类聚合抽象中介类，以便能调用具体中介类中的方法 ❕<span style="display:none">%%<br>1730-🏡⭐️◼️租房案例中的核心逻辑？具体中介类聚合了2个具体同事类，没有使用集合，只作为成员变量来管理；抽象同事类聚合了抽象中介类，以便抽象同事类的子类，具体同事类调用具体中介类中的方法◼️⭐️-point-202301251730%%</span></li></ol><h3 id="3-2-3-示例代码"><a href="#3-2-3-示例代码" class="headerlink" title="3.2.3. 示例代码"></a>3.2.3. 示例代码</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;mediator&#x2F;MediatorStructure.java]]</p><h1 id="4-适用场景⭐️🔴"><a href="#4-适用场景⭐️🔴" class="headerlink" title="4. 适用场景⭐️🔴"></a>4. 适用场景⭐️🔴</h1><ul><li>系统中对象之间存在复杂的引用关系，系统结构混乱且难以理解。</li><li><span style="background-color:#ff00ff">当想创建一个运行于多个类之间的对象，又不想生成新的子类时</span>。</li></ul><h1 id="5-优缺点⭐️🔴"><a href="#5-优缺点⭐️🔴" class="headerlink" title="5. 优缺点⭐️🔴"></a>5. 优缺点⭐️🔴</h1><p><strong>1. 优点：</strong></p><ol><li><p>松散耦合<br>中介者模式通过把多个同事对象之间的交互封装到中介者对象里面，从而使得同事对象之间松散耦合，基本上可以做到互不依赖。这样一来，<span style="background-color:#00ff00">同事对象就可以独立地变化和复用，而不再像以前那样“牵一处而动全身”了。</span></p></li><li><p>集中控制交互<br>多个同事对象的交互，被封装在中介者对象里面集中管理，使得这些交互行为发生变化的时候，<span style="background-color:#00ff00">只需要修改中介者对象就可以了，当然如果是已经做好的系统，那么就扩展中介者对象，而各个同事类不需要做修改。</span></p></li><li><p><span style="background-color:#00ff00">一对多关联转变为一对一的关联</span><br>没有使用中介者模式的时候，同事对象之间的关系通常是一对多的，引入中介者对象以后，中介者对象和同事对象的关系通常变成双向的一对一，这会让对象的关系更容易理解和实现。❕<span style="display:none">%%<br>2035-🏡⭐️◼️中介模式的优缺点：优点：同事类之间松耦合，交互逻辑封装到中介者类中，不会出现牵一发事故；通过中介者模式控制交互逻辑，无需改动同事类；同事类之间一对多的关系改为双向的一对一关系，逻辑更清晰。缺点：中介者类责任越来越大，容易出现问题；越来越难维护◼️⭐️-point-202301252035%%</span></p></li></ol><p><strong>2. 缺点：</strong></p><p><span style="background-color:#ffff00">1. 当同事类太多时，中介者的职责将很大，一旦中介者出现了问题，整个系统就会受到影响.</span><br><span style="background-color:#ffff00">2. 它会变得复杂而庞大，以至于系统难以维护。</span></p><h1 id="6-JDK源码分析"><a href="#6-JDK源码分析" class="headerlink" title="6. JDK源码分析"></a>6. JDK源码分析</h1><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-21、迭代器模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-21%E3%80%81%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-21%E3%80%81%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><ul><li>1）迭代器模式（lterator Pattern）是常用的设计模式，属于行为型模式</li><li>2）如果我们的集合元素是用不同方式实现的，有数组、集合或者其他方式。当<span style="background-color:#ffff00">客户端要遍历这些集合元素的时候就要使用多种遍历方式，而且还会暴露元素的内部结构</span>，可以考虑使用迭代器模式解决</li><li>3）迭代器模式，<span style="background-color:#00ff00">提供一种遍历集合元素的统一接口，用一致的方法遍历集合元素，不需要知道集合对象的底层表示，即：不暴露其内部的结构</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126065122.png" alt="image.png"></li></ul><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><ul><li><strong>抽象聚合</strong>（Aggregate）角色：定义存储、添加、删除聚合元素以及创建迭代器对象的接口。将客户端和具体的聚合解耦</li><li><strong>具体聚合</strong>（ConcreteAggregate）角色：实现抽象聚合类，<span style="background-color:#00ff00">返回一个具体迭代器的实例</span></li><li><strong>抽象迭代器</strong>（Iterator）角色：定义访问和遍历聚合元素的接口，通常包含 hasNext()、next() 等方法。</li><li><strong>具体迭代器</strong>（Concretelterator）角色：实现抽象迭代器接口中所定义的方法，完成对聚合对象的遍历，记录遍历的当前位置。</li></ul><h2 id="2-2-UML"><a href="#2-2-UML" class="headerlink" title="2.2. UML"></a>2.2. UML</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125210551.png" alt="image.png"></p><h2 id="2-3-实现逻辑"><a href="#2-3-实现逻辑" class="headerlink" title="2.3. 实现逻辑"></a>2.3. 实现逻辑</h2><ol><li>在具体聚合类中，具体聚合类提供返回具体迭代器类的方法，<span style="background-color:#ff00ff">在该方法中new一个与之对应的迭代器来并返回</span>。具体聚合类与具体迭代器类绑定，一一对应</li><li>与1中代码一样位置一样，在new具体迭代器类的时候，<span style="background-color:#ff00ff">将需要迭代的集合或者数组传入该迭代器的构造函数</span> ❕<span style="display:none">%%<br>1605-🏡⭐️◼️迭代器模式的核心逻辑是什么？1. 具体聚合类需提供一个方法，负责返回一个具体迭代器类，在该方法中绑定了该具体聚合类对应的具体迭代器类，通过这种绑定关系，避免大量ifelse判断。类似的模式还有模板方法模式、适配器模式。2. 在该方法中具体聚合类还将自己维护的数据集合提供给具体迭代器的构造函数作为入参◼️⭐️-point-202301261605%%</span></li></ol><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-迭代器完成学校院系结构展示✅"><a href="#3-1-迭代器完成学校院系结构展示✅" class="headerlink" title="3.1. 迭代器完成学校院系结构展示✅"></a>3.1. 迭代器完成学校院系结构展示✅</h2><h3 id="3-1-1-UML"><a href="#3-1-1-UML" class="headerlink" title="3.1.1. UML"></a>3.1.1. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126070525.png" alt="image.png"></p><h3 id="3-1-2-实现逻辑⭐️🔴"><a href="#3-1-2-实现逻辑⭐️🔴" class="headerlink" title="3.1.2. 实现逻辑⭐️🔴"></a>3.1.2. 实现逻辑⭐️🔴</h3><h4 id="3-1-2-1-具体聚合类与具体迭代器类绑定-1到1模型"><a href="#3-1-2-1-具体聚合类与具体迭代器类绑定-1到1模型" class="headerlink" title="3.1.2.1. 具体聚合类与具体迭代器类绑定  1到1模型"></a>3.1.2.1. 具体聚合类与具体迭代器类绑定  1到1模型</h4><p>具体聚合类与具体迭代器类绑定配对，一一对应的方式，与适配器模式(HandlerAdapter)、工厂方法模式有点相似<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126071043.png" alt="image.png"></p><p><strong>HandlerAdapter</strong>：<a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115091216.png" alt="image.png"></p><p>工厂方法模式：[[内功心法专题-设计模式-4、工厂模式#3 3 2 UML⭐️🔴]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111201400.png"><br>❕<span style="display:none">%%<br>1436-🏡⭐️◼️1对1设计模式有哪些？🔜📝❔适配器模式、模板方法模式、迭代器模式◼️⭐️-point-202301271436%%</span></p><h4 id="3-1-2-2-具体聚合类传参给具体迭代器类"><a href="#3-1-2-2-具体聚合类传参给具体迭代器类" class="headerlink" title="3.1.2.2. 具体聚合类传参给具体迭代器类"></a>3.1.2.2. 具体聚合类传参给具体迭代器类</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126075848.png" alt="image.png"></p><h4 id="3-1-2-3-利用多态原理获取产品对应迭代器"><a href="#3-1-2-3-利用多态原理获取产品对应迭代器" class="headerlink" title="3.1.2.3. 利用多态原理获取产品对应迭代器"></a>3.1.2.3. 利用多态原理获取产品对应迭代器</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127144545.png" alt="image.png"></p><h3 id="3-1-3-示例代码"><a href="#3-1-3-示例代码" class="headerlink" title="3.1.3. 示例代码"></a>3.1.3. 示例代码</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;iterator&#x2F;College.java]]</p><h2 id="3-2-存储学生对象"><a href="#3-2-存储学生对象" class="headerlink" title="3.2. 存储学生对象"></a>3.2. 存储学生对象</h2><h3 id="3-2-1-UML"><a href="#3-2-1-UML" class="headerlink" title="3.2.1. UML"></a>3.2.1. UML</h3><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125215239.png" style="zoom:90%;" /><h3 id="3-2-2-实现逻辑"><a href="#3-2-2-实现逻辑" class="headerlink" title="3.2.2. 实现逻辑"></a>3.2.2. 实现逻辑</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126080213.png" alt="image.png"></p><ol><li>具体聚合类与具体迭代器类绑定，一一对应</li><li>具体聚合类传参给具体迭代器类，构造导入需要迭代的集合或者数组</li></ol><h3 id="3-2-3-示例代码"><a href="#3-2-3-示例代码" class="headerlink" title="3.2.3. 示例代码"></a>3.2.3. 示例代码</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;iterator&#x2F;StudentAggregate.java]]</p><h1 id="4-适用场景⭐️🔴"><a href="#4-适用场景⭐️🔴" class="headerlink" title="4. 适用场景⭐️🔴"></a>4. 适用场景⭐️🔴</h1><ul><li><span style="background-color:#00ff00">1对多</span>：当需要为聚合对象提供多种遍历方式时。</li><li><span style="background-color:#00ff00">多对1</span>：当需要为遍历不同的聚合结构提供一个统一的接口时。</li><li>当访问一个聚合对象的内容而<span style="background-color:#00ff00">无须暴露其内部细节</span>的表示时。</li></ul><h1 id="5-优缺点⭐️🔴"><a href="#5-优缺点⭐️🔴" class="headerlink" title="5. 优缺点⭐️🔴"></a>5. 优缺点⭐️🔴</h1><p><strong>1. 优点</strong></p><ol><li>提供一个<span style="background-color:#00ff00">统一的方法遍历对象</span>，客户不用再考虑聚合的类型，使用一种方法就可以遍历对象，同时可以隐藏聚合的内部结构。</li><li><span style="background-color:#00ff00">便于新增或者更改遍历算法</span>：新增一种具体迭代器，然后修改具体聚合类返回迭代器的地方即可</li><li>迭代器简化了聚合类。由于引入了迭代器，在原有的聚合对象中不需要再提供数据遍历等方法，这样可以<span style="background-color:#00ff00">简化聚合类的设计</span>。</li><li>在迭代器模式中，由于引入了抽象层，增加新的聚合类和迭代器类都很方便，无须修改原有代码，<span style="background-color:#00ff00">满足 “开闭原则” 的要求</span>。</li><li>提供了一种设计思想，就是一个类应该只有一个引起变化的原因（单一责任原则）。在聚合类中，我们把迭代器分开，就是要把<span style="background-color:#00ff00">管理对象集合和遍历对象集合的责任分开</span>，这样一来集合改变的话，只影响到聚合对象。而如果遍历方式改变的话，只影响到了迭代器</li></ol><p><strong>2. 缺点</strong></p><p>增加了类的个数，每个聚合对象都要一个迭代器，会生成多个迭代器不好管理类这在一定程度上增加了系统的复杂性。</p><h1 id="6-JDK源码分析"><a href="#6-JDK源码分析" class="headerlink" title="6. JDK源码分析"></a>6. JDK源码分析</h1><h2 id="6-1-ArrayList的iterator"><a href="#6-1-ArrayList的iterator" class="headerlink" title="6.1. ArrayList的iterator()"></a>6.1. ArrayList的iterator()</h2><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">List&lt;String&gt; list = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>Iterator&lt;String&gt; iterator = list.iterator(); <span class="hljs-comment">//list.iterator()方法返回的肯定是Iterator接口的子实现类对象</span><br><span class="hljs-keyword">while</span> (iterator.hasNext()) &#123;<br>    System.out.println(iterator.next());<br>&#125;<br></code></pre></td></tr></table></figure><ul><li><code>List</code> (<strong>聚合接口</strong>)：定义了<code>iterator()</code>方法，返回一个迭代器接口对象</li><li><code>ArrayList</code> (<strong>具体聚合实现类</strong>)：实现了抽象聚合接口List的<code>iterator()</code>方法</li><li><code>Iterator</code> (<strong>迭代器接口</strong>)：由系统提供，定义了<code>hasNext()</code>和<code>next()</code>等方法</li><li><code>Itr</code> (<strong>具体迭代器实现类</strong>)：ArrayList.iterator()方法返回值，是<code>ArrayList</code>的内部类，实现了  <code>Iterator</code>接口的<code>hasNext()</code>和<code>next()</code>等方法</li></ul><p>迭代器模式提供了一个不同集合类型（如<code>ArrayList</code>、<code>LinkedList</code>等）的统一遍历解决方案</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125215928.png" alt="image.png"></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">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ArrayList</span>&lt;E&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractList</span>&lt;E&gt;<br>        <span class="hljs-keyword">implements</span> <span class="hljs-title class_">List</span>&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable &#123;<br>    <br>    <span class="hljs-keyword">public</span> Iterator&lt;E&gt; <span class="hljs-title function_">iterator</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_">Itr</span>();<br>    &#125;<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Itr</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Iterator</span>&lt;E&gt; &#123;<br>        <span class="hljs-type">int</span> cursor;       <span class="hljs-comment">// 下一个要返回元素的索引</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">lastRet</span> <span class="hljs-operator">=</span> -<span class="hljs-number">1</span>; <span class="hljs-comment">// 上一个返回元素的索引</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">expectedModCount</span> <span class="hljs-operator">=</span> modCount;<br><br>        Itr() &#123;&#125;<br><br>        <span class="hljs-comment">//判断是否还有元素</span><br>        <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">hasNext</span><span class="hljs-params">()</span> &#123;<br>            <span class="hljs-keyword">return</span> cursor != size;<br>        &#125;<br><br>        <span class="hljs-comment">//获取下一个元素</span><br>        <span class="hljs-keyword">public</span> E <span class="hljs-title function_">next</span><span class="hljs-params">()</span> &#123;<br>            checkForComodification();<br>            <span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> cursor;<br>            <span class="hljs-keyword">if</span> (i &gt;= size)<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NoSuchElementException</span>();<br>            Object[] elementData = ArrayList.<span class="hljs-built_in">this</span>.elementData;<br>            <span class="hljs-keyword">if</span> (i &gt;= elementData.length)<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentModificationException</span>();<br>            cursor = i + <span class="hljs-number">1</span>;<br>            <span class="hljs-keyword">return</span> (E) elementData[lastRet = i];<br>        &#125;<br>        ...<br>&#125;<br></code></pre></td></tr></table></figure><p>这部分代码还是比较简单，大致就是在 <code>iterator</code> 方法中返回了一个实例化的 <code>Iterator</code> 对象。Itr是一个内部类，它实现了 <code>Iterator</code> 接口并重写了其中的抽象方法。</p><p>注意：</p><p>当我们在使用JAVA开发的时候，想使用迭代器模式的话，只要让我们自己定义的容器类实现<code>java.util.Iterable</code>并实现其中的iterator()方法使其返回一个 <code>java.util.Iterator</code> 的实现类就可以了。</p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-22、访问者模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-22%E3%80%81%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-22%E3%80%81%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><ul><li>1）访问者模式（Visitor Pattern），<span style="background-color:#ff00ff">封装一些作用于某种数据结构的各元素的操作，它可以在不改变数据结构的前提下定义作用于这些元素的新的操作</span></li><li>2）主要将<span style="background-color:#00ff00">数据结构与数据操作</span>分离，解决数据结构和操作耦合性问题</li><li>3）访问者模式的基本工作原理是：<span style="background-color:#00ff00">在被访问的类里面加一个对外提供接待访问者的接口</span> ❕<span style="display:none">%%<br>0631-🏡⭐️◼️访问者模式的概念和实现方法🔜📝 封装一些作用于数据元素的操作，还可以在不改变数据结构的前提下定义与数据元素的其他操作；主要意义在于将数据和操作分离；实现方法是在数据结构上增加一个对外访问的接口◼️⭐️-point-202301300631%%</span></li></ul><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-UML"><a href="#2-1-UML" class="headerlink" title="2.1. UML"></a>2.1. UML</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126082602.png" alt="image.png"></p><h2 id="2-2-模式角色"><a href="#2-2-模式角色" class="headerlink" title="2.2. 模式角色"></a>2.2. 模式角色</h2><ul><li><code>Visitor</code>(抽象访问者)：为该对象结构中每个<code>ConcreteElement</code>类声明一个<code>visit</code>操作，它的参数就是可以访问的元素，它的方法个数理论上来讲与元素类个数（Element的实现类个数）是一样的，从这点不难看出，<span style="background-color:#ffff00">访问者模式要求元素类的个数不能改变</span>。</li><li><code>ConcreteVisitor</code>(具体访问者)：实现<code>Visitor</code>中声明的操作，即给出对每一个元素类访问时所产生的具体行为</li><li><code>Element</code>(抽象元素)：定义一个<code>accept</code>方法，接受一个访问者对象，其意义是指，每一个元素都要可以被访问者访问。</li><li><code>ConcreteElement</code>(具体元素)：实现了<code>accept</code>方法，而这个具体的实现，通常情况下是使用访问者提供的访问该元素类的方法。</li><li><code>ObjectStructure</code>(对象结构)：能枚举它的元素，提供一个高层接口，允许访问者访问元素。定义当中所提到的对象结构，<span style="background-color:#ff00ff">对象结构是一个抽象表述，具体点可以理解为一个具有容器性质或者复合对象特性的类，它会含有一组元素（<code>Element</code>），并且可以迭代这些元素，供访问者访问。</span></li></ul><h2 id="2-3-实现逻辑⭐️🔴"><a href="#2-3-实现逻辑⭐️🔴" class="headerlink" title="2.3. 实现逻辑⭐️🔴"></a>2.3. 实现逻辑⭐️🔴</h2><ol><li><span style="background-color:#00ff00">ObjectStructure(对象结构)聚合Element(抽象元素)</span>，并用集合管理起来，用遍历的方式，在遍历中调用Element的accept方法；对外提供高层接口，入参为ConcreteVisitor(具体访问者)</li><li>ConcreteElement(具体元素)是Element的实现类，实现了accept方法，接收入参为Visitor(抽象访问者)</li><li>Client 在调用 ObjectStructure (对象结构) 提供的高层接口时，传入 ConcreteVisitor (具体访问者)，在遍历中传给 Element (抽象元素) 的 accept 方法  ❕<span style="display:none">%%<br>0651-🏡⭐️◼️访问者模式的核心逻辑是什么？🔜📝 1. ObjectStructure聚合并用集合管理Element，在遍历过程中调用Element的accept方法，方法入参为具体的Visitor，是用Client调用ObjectStructure提供的高层访问接口时传入的。2. ConcreteElement继承或实现Element，实现accept方法，接收入参为Visitor，在accept方法中调用ConcreteVisitor的feed(this)方法，入参为当前ConcreteElement◼️⭐️-point-202301300651%%</span></li></ol><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-给宠物喂食"><a href="#3-1-给宠物喂食" class="headerlink" title="3.1. 给宠物喂食"></a>3.1. 给宠物喂食</h2><ul><li>访问者角色：给宠物喂食的人</li><li>具体访问者角色：主人、其他人</li><li>抽象元素角色：动物抽象类</li><li>具体元素角色：宠物狗、宠物猫</li><li>结构对象角色：主人家</li></ul><h3 id="3-1-1-UML"><a href="#3-1-1-UML" class="headerlink" title="3.1.1. UML"></a>3.1.1. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127203010.png"></p><h3 id="3-1-2-实现逻辑"><a href="#3-1-2-实现逻辑" class="headerlink" title="3.1.2. 实现逻辑"></a>3.1.2. 实现逻辑</h3><ol><li><p>Client创建对象结构类Home聚合抽象元素类Animal，并在类用集合管理Animal的子类</p></li><li><p>Client调用Home提供的高层接口时传入具体的访问者类Owner<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230131103112.png" alt="image.png"></p></li><li><p>在对象结构类Home中遍历执行Animal的accept方法，并传入Client提供的具体访问者类Owner<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127205223.png" alt="image.png"></p></li><li><p>在具体Animal类中调用accept方法时，实际调用的是传入的具体访问者类Owner的feed方法<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128103758.png" alt="image.png"></p></li></ol><h3 id="3-1-3-Demo"><a href="#3-1-3-Demo" class="headerlink" title="3.1.3. Demo"></a>3.1.3. Demo</h3><p>design_patterns: [[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;visitor&#x2F;Home.java]]</p><h2 id="3-2-测评系统⭐️🔴"><a href="#3-2-测评系统⭐️🔴" class="headerlink" title="3.2. 测评系统⭐️🔴"></a>3.2. 测评系统⭐️🔴</h2><h3 id="3-2-1-UML"><a href="#3-2-1-UML" class="headerlink" title="3.2.1. UML"></a>3.2.1. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127183021.png" alt="image.png"></p><h3 id="3-2-2-实现逻辑"><a href="#3-2-2-实现逻辑" class="headerlink" title="3.2.2. 实现逻辑"></a>3.2.2. 实现逻辑</h3><h3 id="3-2-3-Demo"><a href="#3-2-3-Demo" class="headerlink" title="3.2.3. Demo"></a>3.2.3. Demo</h3><p>DesignPattern：[[ObjectStructure.java]]</p><h1 id="4-双分派"><a href="#4-双分派" class="headerlink" title="4. 双分派"></a>4. 双分派</h1><p>该例中我们使用到了双分派</p><ul><li><strong>第一次分派</strong>：首先在客户端程序<code>ObjectStructure</code>中，将具体状态作为参数传递<code>Woman</code>中<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127211639.png" alt="image.png"></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128103220.png" alt="image.png"></p><ul><li><strong>第二次分派</strong>：然后<code>Woman</code>类调用作为参数的具体方法<code>getWomanResult</code>，同时将自己<code>this</code>作为参数传入<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127211714.png" alt="image.png"></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128103246.png" alt="image.png"></p><p>所谓双分派是指不管类怎么变化，我们都能找到期望的方法运行<br>双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型<br>以上述实例为例，假设我们要添加一个<code>Wait</code>的状态类，考察<code>Man</code>类和<code>Woman</code>类的反应<br>由于使用了双分派，只需增加一个 Action 子类即可在客户端调用即可，不需要改动任何其他类的代码 ❕<span style="display:none">%%<br>0707-🏡⭐️◼️访问者模式的双分派是什么意思🔜📝 Client访问ObjectStructure提供的高层接口时，遍历调用Element的accept方法，此时Element会动态选择ConcreteElement，此为第一次分派；在accept方法中，ConcreteVisitor调用feed方法，会根据传入的this动态选择入参，此为第二次分派◼️⭐️-point-202301300707%%</span></p><h1 id="5-适用场景⭐️🔴"><a href="#5-适用场景⭐️🔴" class="headerlink" title="5. 适用场景⭐️🔴"></a>5. 适用场景⭐️🔴</h1><ul><li>对象结构相对稳定，但其操作算法经常变化的程序，<span style="background-color:#00ff00">比如投票系统中抽象 Action 和 Person 是相对稳定的，但是他们的实现却会不断扩展</span>。</li><li>对象结构中的对象需要提供多种不同且不相关的操作，而且要避免让这些操作的变化“污染”对象的结构，即让数据结构与数据操作分离。</li></ul><h1 id="6-优缺点"><a href="#6-优缺点" class="headerlink" title="6. 优缺点"></a>6. 优缺点</h1><p><strong>优点</strong></p><ul><li>扩展性好<br>在不修改对象结构中的元素的情况下，为对象结构中的元素添加新的功能。</li><li>复用性好<br>通过访问者来定义整个对象结构通用的功能，从而提高复用程度。</li><li>分离无关行为<br>通过访问者来分离无关的行为，把相关的行为封装在一起，构成一个访问者，这样每一个访问者的功能都比较单一</li></ul><p><strong>缺点</strong></p><ul><li>对象结构变化很困难<br>在访问者模式中，每增加一个新的元素类，都要在每一个具体访问者类中增加相应的具体操作，这<span style="background-color:#ff0000">违背了“开闭原则”</span>。</li><li><span style="background-color:#ff0000">违反了依赖倒置原则</span><br>访问者模式依赖了具体类，而没有依赖抽象类。</li></ul><h1 id="7-JDK源码分析"><a href="#7-JDK源码分析" class="headerlink" title="7. JDK源码分析"></a>7. JDK源码分析</h1><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-23、备忘录模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-23%E3%80%81%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-23%E3%80%81%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><p>备忘录模式提供了一种状态恢复的实现机制，使得用户可以方便地回到一个特定的历史步骤，当新的状态无效或者存在问题时，可以使用暂时存储起来的备忘录将状态复原，很多软件都提供了撤销（Undo）操作，如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作，使文档恢复到之前的状态；还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。</p><p><strong>定义：</strong></p><p>又叫&#x3D;&#x3D;快照模式&#x3D;&#x3D;，在不破坏封装性的前提下，捕获一个对象的内部状态，并<span style="background-color:#00ff00">在该对象之外保存这个状态</span>，以便以后当需要时能将该对象恢复到原先保存的状态。</p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><ul><li><strong>发起人（Originator）角色</strong>：记录当前时刻的内部状态信息，提供创建备忘录和恢复备忘录数据的功能，实现其他业务功能，它可以访问备忘录里的<span style="background-color:#00ff00">所有信息</span>。</li><li><strong>备忘录（Memento）角色</strong>：负责存储发起人的内部状态，在需要的时候提供这些内部状态给发起人。</li><li><strong>管理者（Caretaker）角色</strong>：对备忘录进行管理，提供保存与获取备忘录的功能，但其<span style="background-color:#00ff00">不能对备忘录的内容进行访问与修改</span>。</li></ul><p>备忘录有两个等效的接口：</p><blockquote><ul><li><p><strong>窄接口</strong>：管理者(Caretaker)对象（和其他发起人对象之外的任何对象）看到的是备忘录的窄接口(narror Interface)，这个窄接口只允许他把备忘录对象传给其他的对象。</p></li><li><p><strong>宽接口</strong>：与管理者看到的窄接口相反，发起人对象可以看到一个宽接口(wide Interface)，这个宽接口允许它读取所有的数据，以便根据这些数据恢复这个发起人对象的内部状态。</p></li></ul></blockquote><h2 id="2-2-UML"><a href="#2-2-UML" class="headerlink" title="2.2. UML"></a>2.2. UML</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127220739.png" alt="image.png"></p><h2 id="2-3-实现逻辑"><a href="#2-3-实现逻辑" class="headerlink" title="2.3. 实现逻辑"></a>2.3. 实现逻辑</h2><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-游戏挑战BOSS"><a href="#3-1-游戏挑战BOSS" class="headerlink" title="3.1. 游戏挑战BOSS"></a>3.1. 游戏挑战BOSS</h2><p>游戏中的某个场景，一游戏角色有生命力、攻击力、防御力等数据，在打Boss前和后一定会不一样的，我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。</p><p>要实现上述案例，有两种方式：</p><ul><li>“白箱”备忘录模式</li><li>“黑箱”备忘录模式</li></ul><h3 id="3-1-1-“白箱”备忘录模式"><a href="#3-1-1-“白箱”备忘录模式" class="headerlink" title="3.1.1. “白箱”备忘录模式"></a>3.1.1. “白箱”备忘录模式</h3><p>备忘录角色对任何对象都提供一个接口，即宽接口，备忘录角色的内部所存储的状态就对所有对象公开。类图如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127221223.png" style="zoom:80%;" /></p><h4 id="3-1-1-1-Demo"><a href="#3-1-1-1-Demo" class="headerlink" title="3.1.1.1. Demo"></a>3.1.1.1. Demo</h4><p>黑马：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;memento&#x2F;white_box&#x2F;RoleStateCaretaker.java]]</p><h3 id="3-1-2-“黑箱”备忘录模式"><a href="#3-1-2-“黑箱”备忘录模式" class="headerlink" title="3.1.2. “黑箱”备忘录模式"></a>3.1.2. “黑箱”备忘录模式</h3><p>备忘录角色对发起人对象提供一个宽接口，而为其他对象提供一个窄接口。在Java语言中，实现双重接口的办法就是将<strong>备忘录类</strong>设计成<strong>发起人类</strong>的内部成员类。</p><p>将 <code>RoleStateMemento</code> 设为 <code>GameRole</code> 的内部类，从而将 <code>RoleStateMemento</code> 对象封装在 <code>GameRole</code> 里面；在外面提供一个标识接口 <code>Memento</code> 给 <code>RoleStateCaretaker</code> 及其他对象使用。这样 <code>GameRole</code> 类看到的是 <code>RoleStateMemento</code> 所有的接口，而<code>RoleStateCaretaker</code> 及其他对象看到的仅仅是标识接口 <code>Memento</code> 所暴露出来的接口，从而维护了封装型。类图如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230127221315.png" style="zoom:70%;" /></p><h4 id="3-1-2-1-Demo"><a href="#3-1-2-1-Demo" class="headerlink" title="3.1.2.1. Demo"></a>3.1.2.1. Demo</h4><p>黑马：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;memento&#x2F;white_box&#x2F;RoleStateCaretaker.java]]</p><h1 id="4-适用场景"><a href="#4-适用场景" class="headerlink" title="4. 适用场景"></a>4. 适用场景</h1><ul><li>需要保存与恢复数据的场景，如玩游戏时的中间结果的存档功能。</li><li>需要提供一个可回滚操作的场景，如 Word、记事本、Photoshop，idea等软件在编辑时按 Ctrl+Z 组合键，还有数据库中事务操作。</li></ul><h1 id="5-优缺点"><a href="#5-优缺点" class="headerlink" title="5. 优缺点"></a>5. 优缺点</h1><p><strong>1，优点：</strong></p><ul><li>提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。</li><li>实现了内部状态的封装。除了创建它的发起人之外，其他对象都不能够访问这些状态信息。</li><li>简化了发起人类。发起人不需要管理和保存其内部状态的各个备份，所有状态信息都保存在备忘录中，并由管理者进行管理，这符合单一职责原则。</li></ul><p><strong>2，缺点：</strong></p><ul><li>资源消耗大。如果要保存的内部状态信息过多或者特别频繁，将会占用比较大的内存资源。</li></ul><h1 id="6-JDK源码分析"><a href="#6-JDK源码分析" class="headerlink" title="6. JDK源码分析"></a>6. JDK源码分析</h1><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-24、解释器模式</title>
      <link href="/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-24%E3%80%81%E8%A7%A3%E9%87%8A%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/22/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-24%E3%80%81%E8%A7%A3%E9%87%8A%E5%99%A8%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义⭐️🔴"><a href="#1-模式定义⭐️🔴" class="headerlink" title="1. 模式定义⭐️🔴"></a>1. 模式定义⭐️🔴</h1><p>解释器模式（Interpreter Pattern）：是指给定一个语言（表达式），定义它的文法的一种表示，并定义一个解释器，这个解释器使用该标识来解释语言中的句子。</p><p><strong>第一步</strong>：我们需要将待解决的问题，<span style="background-color:#00ff00">提取出规则，抽象为一种“语言”</span>。即<strong>文法（语法）规则</strong>。比如加减法运算，规则为：由数值和+-符号组成的合法序列，“1+3-2” 就是这种语言的句子。<br><strong>第二步</strong>：解析出像“1+3-2” 这样语句的含义，即<span style="background-color:#00ff00">通过一定的表现形式来表达语句的含义，比如抽象语法树（AbstractSyntaxTree，AST）</span>，或简称语法树（Syntax tree）就是一种抽象表示，是用树形来表示符合文法规则的句子。❕<span style="display:none">%%<br>0717-🏡⭐️◼️解释器模式的概念是什么🔜📝 将我们待解决的问题，比如已经变成了一段表达式，我们再抽象成文法表示，然后给出一个解释器，通过一定的变现形式，比如抽象语法树，来表示出该类表达式所表示的含义◼️⭐️-point-202301300717%%</span></p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><ul><li><code>Context</code>环境角色：含有解释器之外的全局信息</li><li><code>AbstractExpression</code>抽象表达式：声明一个抽象的解释操作，该方法为抽象语法树中所有节点共享</li><li><code>TerminalExpression</code>终结符表达式：实现与文法中终结符相关的解释操作</li><li><code>NonTerminalExpression</code>非终结符表达式：实现与文法中非终结符相关的解释操作</li></ul><h2 id="2-2-UML⭐️🔴"><a href="#2-2-UML⭐️🔴" class="headerlink" title="2.2. UML⭐️🔴"></a>2.2. UML⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128134131.png" alt="image.png"></p><h2 id="2-3-实现逻辑"><a href="#2-3-实现逻辑" class="headerlink" title="2.3. 实现逻辑"></a>2.3. 实现逻辑</h2><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-设计实现加减法"><a href="#3-1-设计实现加减法" class="headerlink" title="3.1. 设计实现加减法"></a>3.1. 设计实现加减法</h2><h3 id="3-1-1-UML"><a href="#3-1-1-UML" class="headerlink" title="3.1.1. UML"></a>3.1.1. UML</h3><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128131704.png" style="zoom:80%;" /><h3 id="3-1-2-实现逻辑⭐️🔴"><a href="#3-1-2-实现逻辑⭐️🔴" class="headerlink" title="3.1.2. 实现逻辑⭐️🔴"></a>3.1.2. 实现逻辑⭐️🔴</h3><p><span style="background-color:#ff0000">继承+聚合</span></p><ol><li>Context聚合并集合管理Variable变量类</li><li>终结符表达式类(Variable)、非终结符表达式类(Plus)、非终结符表达式类(Minus)<span style="background-color:#ff00ff">继承</span>抽象表达式类(AbstractExpression)</li><li>因为是非终结，所以<span style="background-color:#ff0000">非终结符表达式类</span>(Plus)、非终结符表达式类(Minus)<span style="background-color:#ff0000">还聚合</span>抽象表达式类(AbstractExpression)为其左右符号变量</li></ol><h3 id="3-1-3-Demo"><a href="#3-1-3-Demo" class="headerlink" title="3.1.3. Demo"></a>3.1.3. Demo</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;interpreter&#x2F;AbstractExpression.java]]</p><h1 id="4-适用场景"><a href="#4-适用场景" class="headerlink" title="4. 适用场景"></a>4. 适用场景</h1><ul><li>当语言的文法较为简单，<span style="background-color:#ff00ff">且执行效率不是关键问题时</span>，因为存在递归解释现象。</li><li>当问题重复出现，且可以用一种简单的语言来进行表达时。</li><li>当一个语言需要解释执行，并且语言中的句子可以表示为一个抽象语法树的时候。</li></ul><h1 id="5-优缺点"><a href="#5-优缺点" class="headerlink" title="5. 优缺点"></a>5. 优缺点</h1><p><strong>1. 优点：</strong></p><ul><li><p>易于改变和扩展文法。<br>由于在解释器模式中使用类来表示语言的文法规则，因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类，因此可以方便地实现一个简单的语言。</p></li><li><p>实现文法较为容易。<br>在抽象语法树中每一个表达式节点类的实现方式都是相似的，这些类的代码编写都不会特别复杂。</p></li><li><p>增加新的解释表达式较为方便。<br>如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类，原有表达式类代码无须修改，<span style="background-color:#00ff00">符合 “开闭原则”</span>。</p></li></ul><p><strong>2. 缺点：</strong></p><ul><li><p>对于复杂文法难以维护。<br>在解释器模式中，每一条规则至少需要定义一个类，因此如果一个语言包含太多文法规则，类的个数将会急剧增加，导致系统难以管理和维护。</p></li><li><p>执行效率较低。<br>由于在解释器模式中使用了大量的循环和递归调用，因此在解释较为复杂的句子时其速度很慢，而且代码的调试过程也比较麻烦。</p></li></ul><h1 id="6-JDK源码分析"><a href="#6-JDK源码分析" class="headerlink" title="6. JDK源码分析"></a>6. JDK源码分析</h1><h3 id="6-1-Spring框架中SpelExpressionParser"><a href="#6-1-Spring框架中SpelExpressionParser" class="headerlink" title="6.1. Spring框架中SpelExpressionParser"></a>6.1. Spring框架中SpelExpressionParser</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128155322.png" alt="image.png"></p><p><strong>角色及职责</strong></p><ul><li><code>Expression</code>表达式接口</li><li>下面有不同表达式实现类，比如<code>SpelExpression</code>、<code>LiteralExpression</code>和<code>CompositeStringExpression</code></li><li>使用时，根据创建解释器对象的不同，返回不同的<code>Expression</code>对象</li></ul><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><ul><li>1）当有一个语言需要解释执行，可将该语言中的句子表示为一个抽象语法树，就可以考虑使用解释器模式，让程序具有良好的扩展性</li><li>2）应用场景：编译器、运算表达式计算、正则表达式、机器人等</li><li>3）使用解释器可能带来的问题：解释器模式会引起类膨胀、解释器模式采用递归调用方法，将会导致调试非常复杂、效率可能降低</li></ul><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 行为型模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-16、命令模式</title>
      <link href="/2023/01/17/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-16%E3%80%81%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/17/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-16%E3%80%81%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230118-1911"      data-title='009- 内功心法专题 - 设计模式 -16、命令模式 '      data-class='orange'      data-type='range'      data-end='2023-01-18'><br></span></p><hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><p>命令模式（Command Pattern）：&#x3D;&#x3D;将一个请求封装为一个对象，使发出请求的责任和执行请求的责任分割开&#x3D;&#x3D;。这样两者之间通过命令对象进行沟通，这样方便将命令对象进行存储、传递、调用、增加与管理。❕<span style="display:none">%%<br>2218-🏡⭐️◼️命令模式的定义？将一个请求封装为对象，调用者和执行者通过命令对象进行沟通，使得发出请求的责任和执行请求的责任分离◼️⭐️-point-202301222218%%</span></p><p>在软件设计中，我们经常需要向某些对象发送请求，但是并不知道请求的接收者是谁，也不知道被请的操作是哪个，我们<span style="background-color:#00ff00">只需在程序运行时指定具体的请求接收者即可</span>。此时可以使用命令模式来进行设计</p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-⭐️🔴UML-图示"><a href="#2-1-⭐️🔴UML-图示" class="headerlink" title="2.1. ⭐️🔴UML 图示"></a>2.1. ⭐️🔴UML 图示</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118193159.png" alt="image.png"></p><h2 id="2-2-⭐️🔴角色分析"><a href="#2-2-⭐️🔴角色分析" class="headerlink" title="2.2. ⭐️🔴角色分析"></a>2.2. ⭐️🔴角色分析</h2><ul><li><strong>抽象命令类（Command）角色</strong>： 定义命令的接口，声明执行的方法，可以是接口或抽象类。</li><li><strong>具体命令（Concrete Command）角色</strong>：具体的命令，实现命令接口；<span style="background-color:#ff00ff">通常会持有接收者，并调用接收者的功能来完成命令要执行的操作</span>。</li><li><strong>实现者&#x2F;接收者（Receiver）角色</strong>： 接收者，真正执行命令的对象。任何类都可能成为一个接收者，只要它能够实现命令要求实现的相应功能。</li><li><strong>调用者&#x2F;请求者（Invoker）角色</strong>： 要求命令对象执行请求，<span style="background-color:#ff00ff">通常会持有命令对象，可以持有很多的命令对象</span>。这个是客户端真正触发命令并要求命令执行相应操作的地方，也就是说相当于<span style="background-color:#00ff00">使用命令对象的入口</span>。</li></ul><h1 id="3-⭐️🔴实现逻辑"><a href="#3-⭐️🔴实现逻辑" class="headerlink" title="3. ⭐️🔴实现逻辑"></a>3. ⭐️🔴实现逻辑</h1><p><span style="background-color:#ff00ff">具体命令角色聚合命令接受者：聚合 + 构造导入</span><br><span style="background-color:#ff00ff">调用者 (请求者) 聚合命令接口：聚合 +setter 导入命令并放在集合中 (餐馆堂食) 或者 构造方法中 new 命令放到集合中 (智能生活)</span><br>❕<span style="display:none">%%<br>2222-🏡⭐️◼️餐馆堂食和智能生活 2 个案例中命令模式的角色分配及实现逻辑？餐馆堂食：Waiter 是 Invoker，持有 Command，通过 setter 方法 set 到命令集合中。具体的命令 OrderCommand 是具体的命令，持有 SeniorChef(命令接收者)，通过构造函数导入。智能家居：RemoteController 是调用者，通过在构造方法中使用 new 关键字创建命令实例放入到命令集合中，并持有这个集合。TVOffCommand 等这种具体的命令，跟餐馆堂食案例一样，也是通过构造导入的方式持有命令接受者，比如对应的 TVReciver。◼️⭐️-point-202301222222%%</span></p><h1 id="4-案例分析"><a href="#4-案例分析" class="headerlink" title="4. 案例分析"></a>4. 案例分析</h1><h2 id="4-1-餐馆堂食⭐️🔴"><a href="#4-1-餐馆堂食⭐️🔴" class="headerlink" title="4.1. 餐馆堂食⭐️🔴"></a>4.1. 餐馆堂食⭐️🔴</h2><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118193325.png" style="zoom:60%;" /><p>将上面的案例用代码实现，那我们就需要分析命令模式的角色在该案例中由谁来充当。<br><strong>服务员</strong>： 就是调用者角色，由她来发起命令。<br><strong>资深大厨</strong>： 就是接收者角色，真正命令执行的对象。<br><strong>订单</strong>： 命令中包含订单。<br>类图如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118193410.png" style="zoom:75%;" /></p><h3 id="4-1-1-示例代码"><a href="#4-1-1-示例代码" class="headerlink" title="4.1.1. 示例代码"></a>4.1.1. 示例代码</h3><p>[[Waitor.java]]</p><h3 id="4-1-2-实现逻辑"><a href="#4-1-2-实现逻辑" class="headerlink" title="4.1.2. 实现逻辑"></a>4.1.2. 实现逻辑</h3><p><span style="background-color:#ff00ff">具体命令角色聚合命令接受者：聚合 + 构造导入</span><br><span style="background-color:#ff00ff">调用者 (请求者) 聚合命令接口：聚合 +setter 导入命令并放在集合中</span></p><h2 id="4-2-智能家居⭐️🔴"><a href="#4-2-智能家居⭐️🔴" class="headerlink" title="4.2. 智能家居⭐️🔴"></a>4.2. 智能家居⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118200017.png" alt="image.png"></p><h3 id="4-2-1-undoCommand"><a href="#4-2-1-undoCommand" class="headerlink" title="4.2.1. undoCommand"></a>4.2.1. undoCommand</h3><p>示例代码：[[RemoteController.java]]</p><h3 id="4-2-2-实现逻辑"><a href="#4-2-2-实现逻辑" class="headerlink" title="4.2.2. 实现逻辑"></a>4.2.2. 实现逻辑</h3><p><span style="background-color:#ff00ff">具体命令角色聚合命令接受者：聚合 + 构造导入</span><br><span style="background-color:#ff00ff">调用者 (请求者) 聚合命令接口：聚合 + 构造方法中 new 命令放到集合中 (智能家居)</span></p><h1 id="5-⭐️🔴JDK-源码分析"><a href="#5-⭐️🔴JDK-源码分析" class="headerlink" title="5. ⭐️🔴JDK 源码分析"></a>5. ⭐️🔴JDK 源码分析</h1><h2 id="5-1-Runable"><a href="#5-1-Runable" class="headerlink" title="5.1. Runable"></a>5.1. Runable</h2><p>Runable 是一个典型命令模式，<span style="background-color:#ff00ff">Runnable 担当抽象命令的角色，Thread 充当的是调用者，持有 Runnable。</span>start 方法就是其执行方法。<span style="background-color:#ff00ff">我们自定义的 TurnOffThread 类属于具体命令角色，持有命令接收者 Reciver。</span></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-comment">//命令接口(抽象命令角色)</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-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-comment">//调用者</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Thread</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Runnable</span> &#123;<br>    <span class="hljs-keyword">private</span> Runnable target;<br>    <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">start</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (threadStatus != <span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalThreadStateException</span>();<br><br>        group.add(<span class="hljs-built_in">this</span>);<br><br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">started</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            start0();<br>            started = <span class="hljs-literal">true</span>;<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-keyword">if</span> (!started) &#123;<br>                    group.threadStartFailed(<span class="hljs-built_in">this</span>);<br>                &#125;<br>            &#125; <span class="hljs-keyword">catch</span> (Throwable ignore) &#123;<br>            &#125;<br>        &#125;<br>    &#125;<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">start0</span><span class="hljs-params">()</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>会调用一个 native 方法 start0 ()，调用系统方法，开启一个线程。而接收者是对程序员开放的，可以自己定义接收者。</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-comment">/**</span><br><span class="hljs-comment"> * jdk Runnable 命令模式</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TurnOffThread</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Runnable</span>&#123;<br>     <span class="hljs-keyword">private</span> Receiver receiver;<br>    <br>     <span class="hljs-keyword">public</span> <span class="hljs-title function_">TurnOffThread</span><span class="hljs-params">(Receiver receiver)</span> &#123;<br>     <span class="hljs-built_in">this</span>.receiver = receiver;<br>     &#125;<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>     receiver.turnOFF();<br>     &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="5-2-JdbcTemplate-✅"><a href="#5-2-JdbcTemplate-✅" class="headerlink" title="5.2. JdbcTemplate ✅"></a>5.2. JdbcTemplate ✅</h2><p>以 <code>JdbcTemplate</code> 类中 <code>query()</code> 方法中为例，我们可以发现其中定义了一个内部类 <code>QueryStatementCallback</code>，而且 <code>QueryStatementCallback</code> 类实现了 <code>StatementCallback</code> 接口的 <code>doInStatement</code> 方法。这就是命令模式在 Spring 框架 <code>JdbcTemplate</code> 源码中的应用，其中，</p><ul><li><code>StatementCallback</code> 充当了 <code>Command</code> 命令，其下有多个实现</li><li><code>QueryStatementCallback</code> 充当了 <code>ConcreteCommand</code> 具体的命令角色</li><li><code>Statement</code> 充当了 <code>Receiver</code> 接收者角色</li><li><code>JdbcTemplate</code> 本身作为调用者</li></ul><h1 id="6-优缺点⭐️🔴"><a href="#6-优缺点⭐️🔴" class="headerlink" title="6. 优缺点⭐️🔴"></a>6. 优缺点⭐️🔴</h1><p><strong>1. 优点：</strong></p><ul><li>降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。<span style="background-color:#00ff00">“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的，命令对象起到了纽带桥梁的作用</span></li><li>增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类，它<span style="background-color:#ff00ff">满足“开闭原则”</span>，对扩展比较灵活。</li><li>可以实现宏命令。命令模式可以与组合模式结合，将多个命令装配成一个组合命令，即宏命令。<span style="background-color:#ff00ff">设计一个命令队列</span>。只要把命令对象放到列队，就可以多线程的执行命令</li><li>方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合，实现命令的撤销与恢复。</li></ul><p><strong>2. 缺点：</strong></p><ul><li><span style="background-color:#ff00ff">使用命令模式可能会导致某些系统有过多的具体命令类。</span></li><li>系统结构更加复杂。</li></ul><h1 id="7-适用场景⭐️🔴"><a href="#7-适用场景⭐️🔴" class="headerlink" title="7. 适用场景⭐️🔴"></a>7. 适用场景⭐️🔴</h1><ul><li>系统需要<span style="background-color:#00ff00">将请求调用者和请求接收者解耦</span>，使得调用者和接收者不直接交互。</li><li>系统需要<span style="background-color:#00ff00">在不同的时间指定请求、将请求排队和执行请求</span>。</li><li>系统需要支持命令的撤销 (Undo) 操作和恢复 (Redo) 操作。</li></ul><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><ul><li>空命令也是一种设计模式，它为我们省去了判空的操作。在上面的实例中，如果没有用空命令，我们每按下一个按键都要判空，这给我们编码带来一定的麻烦</li><li>命令模式经典的应用场景：界面的一个按钮对应一条命令、模拟 CMD（DOS 命令）订单的撤销&#x2F;恢复、触发 - 反馈机制</li></ul><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-12、组合模式</title>
      <link href="/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-12%E3%80%81%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-12%E3%80%81%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><p>又名部分整体模式，是用于<span style="background-color:#00ff00">把一组相似的对象当作一个单一的对象</span>。组合模式&#x3D;&#x3D;依据树形结构&#x3D;&#x3D;来组合对象，用来表示部分以及整体层次。这种类型的设计模式属于结构型模式，它创建了对象组的树形结构。❕<span style="display:none">%%<br>0804-🏡⭐️◼️组合模式与树形结构如何关联记忆？组合模式又叫部分整体模式，是将一组相似的对象看成是一个对象处理，通过树形结构来表示整体与部分的层次关系的。属于结构型设计模式◼️⭐️-point-202301230804%%</span></p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><p>组合模式主要包含三种角色：</p><ul><li><strong>抽象根节点（Component）</strong>：定义系统各层次对象的共有方法和属性，可以预先定义一些默认行为和属性。</li><li><strong>树枝节点（Composite）</strong>：定义树枝节点的行为，存储子节点，组合树枝节点和叶子节点形成一个树形结构。</li><li><strong>叶子节点（Leaf）</strong>：叶子节点对象，其下再无分支，是系统层次遍历的最小单位。</li></ul><h2 id="2-2-⭐️🔴UML图示"><a href="#2-2-⭐️🔴UML图示" class="headerlink" title="2.2. ⭐️🔴UML图示"></a>2.2. ⭐️🔴UML图示</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117133928.png" alt="image.png"><br>❕<span style="display:none">%%<br>0817-🏡⭐️◼️2个UML对象之间2种关系的模式：装饰者模式、组合模式，都是聚合+继承◼️⭐️-point-202301230817%%</span></p><h2 id="2-3-⭐️🔴实现逻辑"><a href="#2-3-⭐️🔴实现逻辑" class="headerlink" title="2.3. ⭐️🔴实现逻辑"></a>2.3. ⭐️🔴实现逻辑</h2><p><span style="background-color:#ff00ff">继承(实现)+聚合</span></p><p><span style="background-color:#ff0000">继承</span>：树枝节点和叶子节点都继承抽象根节点，抽象根节点中给出默认实现，树枝节点重写节点操作方法，叶子节点重写叶子节点应该具有的方法。<br><span style="background-color:#ff0000">聚合</span>：树枝节点【聚合】抽象根节点为一个List集合，抽象根节点作为集合的泛型，用于添加叶子节点类型，方便递归遍历打印节点❕<span style="display:none">%%<br>0805-🏡⭐️◼️组合模式要点是什么？树枝节点聚合抽象根节点为List集合属性，抽象根节点类型作为集合的泛型，用于add叶子节点，方便递归遍历打印下属节点◼️⭐️-point-202301230805%%</span></p><h1 id="3-分类"><a href="#3-分类" class="headerlink" title="3. 分类"></a>3. 分类</h1><p>在使用组合模式时，根据抽象构件类的定义形式，我们可将组合模式分为透明组合模式和安全组合模式两种形式。</p><ul><li><p>透明组合模式<br>透明组合模式中，抽象根节点角色中声明了所有用于管理成员对象的方法，比如在示例中 <code>MenuComponent</code> 声明了 <code>add</code>、<code>remove</code> 、<code>getChild</code> 方法，这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。</p><p>透明组合模式的缺点是不够安全，因为叶子对象和容器对象在本质上是有区别的，叶子对象不可能有下一个层次的对象，即不可能包含成员对象，因此为其提供 add()、remove() 等方法是没有意义的，这在编译阶段不会出错，但在运行阶段如果调用这些方法可能会出错（如果没有提供相应的错误处理代码）</p></li><li><p>安全组合模式<br>在安全组合模式中，在抽象构件角色中没有声明任何用于管理成员对象的方法，而是在树枝节点 <code>Menu</code> 类中声明并实现这些方法。安全组合模式的缺点是不够透明，因为叶子构件和容器构件具有不同的方法，且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义，因此客户端不能完全针对抽象编程，必须有区别地对待叶子构件和容器构件。</p></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117150404.png" alt="组合模式-安全性.png"></p><h1 id="4-案例分析"><a href="#4-案例分析" class="headerlink" title="4. 案例分析"></a>4. 案例分析</h1><h2 id="4-1-软件菜单"><a href="#4-1-软件菜单" class="headerlink" title="4.1. 软件菜单"></a>4.1. 软件菜单</h2><p>如下图，我们在访问别的一些管理系统时，经常可以看到类似的菜单。一个菜单可以包含菜单项（菜单项是指不再包含其他内容的菜单条目），也可以包含带有其他菜单项的菜单，因此使用组合模式描述菜单就很恰当，我们的需求是针对一个菜单，打印出其包含的所有菜单以及菜单项的名称。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117133717.png" alt="image.png"></p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117145438.png" style="zoom:80%;" />这里的MenuComponent定义为抽象类，因为有一些共有的属性和行为要在该类中实现，Menu和MenuItem类就可以只覆盖自己感兴趣的方法，而不用搭理不需要或者不感兴趣的方法，举例来说，<span style="background-color:#00ff00">Menu类可以包含子菜单，因此需要覆盖add()、remove()、getChild()方法，但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常，你也可以根据自己的需要改写默认实现。</span><h2 id="4-2-部门层级"><a href="#4-2-部门层级" class="headerlink" title="4.2. 部门层级"></a>4.2. 部门层级</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117133834.png" alt="image.png"></p><h1 id="5-⭐️🔴优点"><a href="#5-⭐️🔴优点" class="headerlink" title="5. ⭐️🔴优点"></a>5. ⭐️🔴优点</h1><ul><li>组合模式可以清楚地定义分层次的复杂对象，表示对象的全部或部分层次，<span style="background-color:#00ff00">它让客户端忽略了层次的差异，方便对整个层次结构进行控制。</span></li><li>客户端可以一致地使用一个组合结构或其中单个对象，<span style="background-color:#00ff00">不必关心处理的是单个对象还是整个组合结构，简化了客户端代码。</span></li><li>在组合模式中增加新的树枝节点和叶子节点都很方便，无须对现有类库进行任何修改，<span style="background-color:#00ff00">符合“开闭原则”。</span></li><li>组合模式为树形结构的面向对象实现提供了一种灵活的解决方案，通过叶子节点和树枝节点的<span style="background-color:#ff00ff">递归组合，可以形成复杂的树形结构，但对树形结构的控制却非常简单</span></li></ul><h1 id="6-适用场景"><a href="#6-适用场景" class="headerlink" title="6. 适用场景"></a>6. 适用场景</h1><p><span style="background-color:#ff00ff">组合模式正是应树形结构而生</span>，所以组合模式的使用场景就是出现树形结构的地方。比如：文件目录显示，多级目录呈现等树形结构数据的操作。</p><h1 id="7-JDK源码分析⭐️🔴"><a href="#7-JDK源码分析⭐️🔴" class="headerlink" title="7. JDK源码分析⭐️🔴"></a>7. JDK源码分析⭐️🔴</h1><h2 id="7-1-HashMap中的组合模式"><a href="#7-1-HashMap中的组合模式" class="headerlink" title="7.1. HashMap中的组合模式"></a>7.1. HashMap中的组合模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117150615.png" alt="image.png"></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-comment">// Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Map</span>&lt;K,V&gt; &#123;<br>    <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Entry</span>&lt;K,V&gt; &#123;&#125;<br>&#125;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractMap</span>&lt;K,V&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Map</span>&lt;K,V&gt; &#123;&#125;<br><span class="hljs-comment">// Composite</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HashMap</span>&lt;K,V&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractMap</span>&lt;K,V&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Map</span>&lt;K,V&gt;, Cloneable, Serializable &#123;<br>    <span class="hljs-comment">// Leaf</span><br>    <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;&#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>说明</strong></p><ul><li>1）Map 就是一个抽象的构建，类似<code>Component</code></li><li>2）HashMap 是一个中间的构建，类似<code>Composite</code>，实现 &#x2F; 继承了相关方法 put、putAll</li><li>3）Node 是 HashMap 的静态内部类，类似<code>Leaf</code>叶子节点，这里就没有 put<br>❕<span style="display:none">%%<br>0834-🏡⭐️◼️因为Node也实现了Map接口，所以相当于HashMap聚合了抽象根节点即Map，跟典型的UML画法没有本质区别◼️⭐️-point-202301230834%%</span></li></ul><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><ul><li>1）简化客户端操作：客户端只需要面对一致的对象，而不用考虑整体部分或者节点叶子的问题</li><li>2）具有较强扩展性：当我们要更改组合对象时，我们只需要调整内部的层次关系，客户端不用做出任何改动</li><li>3）方便创建复杂的层次结构：客户端不用理会组合里面的组成细节，容易添加节点或者叶子，从而创建出复杂的树形结构</li><li>4）<span style="background-color:#ff00ff">需要遍历组织机构，或者处理的对象具有树形结构时，非常适合使用组合模式</span></li><li>5）要求较高的抽象性，<span style="background-color:#ff00ff">如果节点和叶子有很多差异性的话，比如很多方法和属性都不一样，不适合使用组合模式</span></li></ul><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-13、享元模式</title>
      <link href="/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-13%E3%80%81%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-13%E3%80%81%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230117-1807"      data-title='009- 内功心法专题 - 设计模式 -13、享元模式 '      data-class='orange'      data-type='range'      data-end='2023-01-17'><br></span></p><hr><h1 id="1-模式定义⭐️🔴"><a href="#1-模式定义⭐️🔴" class="headerlink" title="1. 模式定义⭐️🔴"></a>1. 模式定义⭐️🔴</h1><ul><li>1）享元模式（Flyweight Pattern）也叫 <strong>蝇量模式</strong>：运用<span style="background-color:#ff00ff">共享技术</span>有效地支持大量细粒度的对象</li><li>2）常用于系统底层开发，&#x3D;&#x3D;解决系统的性能问题&#x3D;&#x3D;。像数据库连接池，里面都是创建好的连接对象，在这些连接对象中有我们需要的则直接拿来用，避免重新创建，如果没有我们需要的，则创建一个</li><li>3）享元模式能够&#x3D;&#x3D;解决重复对象的内存浪费的问题&#x3D;&#x3D;。当系统中有大量相似对象，需要缓冲池时，不需总是创建新对象，可以从缓冲池里拿。这样可以降低系统内存，同时提高效率</li><li>4）享元模式经典的应用场景就是池技术了，<span style="background-color:#00ff00">String 常量池、数据库连接池、缓冲池等等都是享元模式的应用</span>，享元模式是池技术的重要实现方式</li></ul><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-两种状态"><a href="#2-1-两种状态" class="headerlink" title="2.1. 两种状态"></a>2.1. 两种状态</h2><p>享元（Flyweight ）模式中存在以下两种状态：</p><ol><li><strong>内部状态</strong>，存储在享元对象内部且不会随着环境的改变而改变的可共享部分。</li><li><strong>外部状态</strong>，指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态，并将外部状态外部化。</li></ol><p>比如围棋、五子棋、跳棋，它们都有大量的棋子对象，围棋和五子棋只有黑白两色，跳棋颜色多一点。<span style="background-color:#00ff00">所以棋子颜色就是棋子的内部状态</span>；而各个棋子之间的差别就是位置的不同。当我们落子后，<span style="background-color:#ff00ff">落子颜色是定的，但位置是变化的</span>，所以<span style="background-color:#00ff00">棋子坐标就是棋子的外部状态</span></p><p>举个例子：围模理论上有 361 个空位可以放棋子，每盘棋都有可能有两三百个棋子对象产生。因为内存空间有限，一台服务器很难支持更多的玩家玩围模游戏。如果用享元模式来处理棋子，那么棋子对象就可以减少到只有两个实例，这样就很好的解决了对象的开销问题</p><h2 id="2-2-角色"><a href="#2-2-角色" class="headerlink" title="2.2. 角色"></a>2.2. 角色</h2><p>享元模式的主要有以下角色：</p><ul><li><strong>抽象享元角色（Flyweight）</strong>：通常是一个接口或抽象类，在抽象享元类中&#x3D;&#x3D;声明了具体享元类公共的方法&#x3D;&#x3D;，这些方法可以向外界提供享元对象的内部数据（内部状态），同时也可以通过这些方法来设置外部数据（外部状态）。</li><li><strong>具体享元（Concrete Flyweight）角色</strong> ：它实现了抽象享元类，称为享元对象；在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类，为每一个具体享元类提供唯一的享元对象。</li><li><strong>非享元（Unsharable Flyweight) 角色</strong> ：并不是所有的抽象享元类的子类都需要被共享，不能被共享的子类可设计为非共享具体享元类；当需要一个非共享具体享元类的对象时可以直接通过实例化创建。</li><li><strong>享元工厂（Flyweight Factory）角色</strong> ：负责创建和管理享元角色。当客户对象请求一个享元对象时，享元工厂检査系统中是否存在符合要求的享元对象，如果存在则提供给客户；如果不存在的话，则创建一个新的享元对象。</li></ul><h2 id="2-3-UML-图示⭐️🔴"><a href="#2-3-UML-图示⭐️🔴" class="headerlink" title="2.3. UML 图示⭐️🔴"></a>2.3. UML 图示⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117182222.png" alt="image.png"></p><h2 id="2-4-实现逻辑⭐️🔴"><a href="#2-4-实现逻辑⭐️🔴" class="headerlink" title="2.4. 实现逻辑⭐️🔴"></a>2.4. 实现逻辑⭐️🔴</h2><p>享元工厂<span style="background-color:#ff0000">聚合</span>抽象享元，作为 <code>HashMap&lt;String,AbstractBox&gt;</code> 这个池子的 Value 属性，将具体享元对象<span style="background-color:#ff00ff">放入这个享元池中</span>。方便获取并能节省空间，提高性能。<br>❕<span style="display:none">%%<br>0750-🏡⭐️◼️享元模式的实现逻辑：享元工厂聚合抽象享元，作为其成员属性 HashMap(String,AbstractBox) 的 value 项，并将具体享元对象放入其中。方便获取，节省内存，提高性能◼️⭐️-point-202301230750%%</span></p><h1 id="3-案例分析⭐️🔴"><a href="#3-案例分析⭐️🔴" class="headerlink" title="3. 案例分析⭐️🔴"></a>3. 案例分析⭐️🔴</h1><p>❕<span style="display:none">%%<br>0747-🏡⭐️◼️享元模式的例子？1. 俄罗斯方块：颜色是内部状态数据，形状是外部状态数据。外部状态数据是通过抽象享元类的抽象方法，由具体享元类重写时传入。而内部状态是由 client 在调用时传入的，即相同颜色可以有不同形状的方块。2. 小网站用户共享：内部状态数据就是各个网站可以共享的数据，比如网站布局、皮肤，各个用户不需要个性化就可以共享，外部状态数据就是用户以及个性化内容。抽象享元类定义了传入外部状态的抽象方法由子类实现并传入◼️⭐️-point-202301230747%%</span></p><h2 id="3-1-俄罗斯方块"><a href="#3-1-俄罗斯方块" class="headerlink" title="3.1. 俄罗斯方块"></a>3.1. 俄罗斯方块</h2><p>下面的图片是众所周知的俄罗斯方块中的一个个方块，如果在俄罗斯方块这个游戏中，每个不同的方块都是一个实例对象，这些对象就要占用很多的内存空间，下面利用享元模式进行实现。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117183117.jpeg" style="zoom:60%;" /></p><p><strong>享元模式 UML</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117183150.png" style="zoom:80%;" /><br><strong>AbstractBox</strong>：抽象享元角色，俄罗斯方块有不同的形状，我们可以对这些形状向上抽取出 AbstractBox，用来定义共性的属性和行为。</p><p><strong>IBox、LBox、OBox</strong>：具体享元角色</p><p><strong>BoxFactory</strong>：享元工厂角色，用来管理享元对象（也就是 AbstractBox 子类对象），该工厂类对象只需要一个，所以可以使用单例模式。并给工厂类提供一个获取形状的方法。</p><p><span style="background-color:#00ff00">外部状态：各种图形的 shape，数据不共享</span><br><span style="background-color:#00ff00">内部状态：client 调用时指定的颜色是不会再变化，是不同 shape 共享的数据</span></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">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractBox</span> &#123;  <br>  <br>    <span class="hljs-comment">//获取图形的方法  </span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> String <span class="hljs-title function_">getShape</span><span class="hljs-params">()</span>;  <br>  <br>    <span class="hljs-comment">//显示图形及颜色  </span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">display</span><span class="hljs-params">(String color)</span> &#123;  <br>        System.out.println(<span class="hljs-string">&quot;方块形状：&quot;</span> + getShape() + <span class="hljs-string">&quot;, 颜色：&quot;</span> + color);  <br>    &#125;  <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><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></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_">BoxFactory</span> &#123;  <br>  <br>    <span class="hljs-keyword">private</span> HashMap&lt;String,AbstractBox&gt; map;  <br>  <br>    <span class="hljs-comment">//在构造方法中进行初始化操作  </span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">BoxFactory</span><span class="hljs-params">()</span> &#123;  <br>        map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;String, AbstractBox&gt;();  <br>        map.put(<span class="hljs-string">&quot;I&quot;</span>,<span class="hljs-keyword">new</span> <span class="hljs-title class_">IBox</span>());  <br>        map.put(<span class="hljs-string">&quot;L&quot;</span>,<span class="hljs-keyword">new</span> <span class="hljs-title class_">LBox</span>());  <br>        map.put(<span class="hljs-string">&quot;O&quot;</span>,<span class="hljs-keyword">new</span> <span class="hljs-title class_">OBox</span>());  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//提供一个方法获取该工厂类对象  </span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> BoxFactory <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-keyword">return</span> factory;  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">BoxFactory</span> <span class="hljs-variable">factory</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BoxFactory</span>();  <br>  <br>    <span class="hljs-comment">//根据名称获取图形对象  </span><br>    <span class="hljs-keyword">public</span> AbstractBox <span class="hljs-title function_">getShape</span><span class="hljs-params">(String name)</span> &#123;  <br>        <span class="hljs-keyword">return</span> map.get(name);  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><p>示例代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;flyweight&#x2F;BoxFactory.java]]</p><h2 id="3-2-小网站多用户"><a href="#3-2-小网站多用户" class="headerlink" title="3.2. 小网站多用户"></a>3.2. 小网站多用户</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230119131318.png" alt="image.png"></p><p>内部状态：网站可共享的部分，不需要个性化的数据<br>外部状态：User</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">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebSite</span> &#123;  <br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">use</span><span class="hljs-params">(User user)</span>;<span class="hljs-comment">//抽象方法  </span><br>&#125;<br></code></pre></td></tr></table></figure><p>示例代码：[[WebSiteFactory.java]]</p><h1 id="4-优缺点⭐️🔴"><a href="#4-优缺点⭐️🔴" class="headerlink" title="4. 优缺点⭐️🔴"></a>4. 优缺点⭐️🔴</h1><p><strong>优点</strong></p><ol><li><span style="background-color:#00ff00">极大减少内存中相似或相同对象数量，节约系统资源，提供系统性能</span></li><li>享元模式中的外部状态相对独立，且不影响内部状态</li></ol><p><strong>缺点</strong></p><ol><li>为了使对象可以共享，需要将享元对象的部分状态外部化，<span style="background-color:#00ff00">分离内部状态和外部状态，并且需要有一个工厂类加以控制，使程序逻辑复杂</span></li><li>用享元模式<span style="background-color:#ff00ff">需要维护一个存储享元对象的享元池，而这需要耗费一定的系统资源</span>，因此应当在需要<span style="background-color:#ff0000">多次重复使用</span>享元对象时才值得使用享元模式。</li></ol><h2 id="4-1-外部化"><a href="#4-1-外部化" class="headerlink" title="4.1. 外部化"></a>4.1. 外部化</h2><p>比如俄罗斯方块案例中，颜色是在 client 调用时传入的，破坏了享元类的封装性<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230120081227.png" alt="image.png"></p><h1 id="5-适用场景⭐️🔴"><a href="#5-适用场景⭐️🔴" class="headerlink" title="5. 适用场景⭐️🔴"></a>5. 适用场景⭐️🔴</h1><ol><li>一个系统有大量相同或者相似的对象，造成内存的大量耗费。</li><li>对象的大部分状态都可以外部化，可以将这些外部状态传入对象中。</li></ol><h1 id="6-JDK-源码分析"><a href="#6-JDK-源码分析" class="headerlink" title="6. JDK 源码分析"></a>6. JDK 源码分析</h1><h2 id="6-1-Integer"><a href="#6-1-Integer" class="headerlink" title="6.1. Integer"></a>6.1. Integer</h2><p>Integer 类使用了享元模式。我们先看下面的例子：</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_">Demo</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">Integer</span> <span class="hljs-variable">i1</span> <span class="hljs-operator">=</span> <span class="hljs-number">127</span>;  <br>        <span class="hljs-type">Integer</span> <span class="hljs-variable">i2</span> <span class="hljs-operator">=</span> <span class="hljs-number">127</span>;  <br>  <br>        System.out.println(<span class="hljs-string">&quot;i1和i2对象是否是同一个对象？&quot;</span> + (i1 == i2));  <br>  <br>        <span class="hljs-type">Integer</span> <span class="hljs-variable">i3</span> <span class="hljs-operator">=</span> <span class="hljs-number">128</span>;  <br>        <span class="hljs-type">Integer</span> <span class="hljs-variable">i4</span> <span class="hljs-operator">=</span> <span class="hljs-number">128</span>;  <br>  <br>        System.out.println(<span class="hljs-string">&quot;i3和i4对象是否是同一个对象？&quot;</span> + (i3 == i4));  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117191228.png" alt="image.png"></p><p>上面代码可以看到，直接给 Integer 类型的变量赋值基本数据类型数据的操作底层使用的是 <code>valueOf()</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></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_">Demo</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">Integer</span> <span class="hljs-variable">i1</span> <span class="hljs-operator">=</span> Integer.valueOf((<span class="hljs-type">int</span>)<span class="hljs-number">127</span>);<br>        Integer i2 Integer.valueOf((<span class="hljs-type">int</span>)<span class="hljs-number">127</span>);<br>        System.out.println((String)<span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>().append((String)<span class="hljs-string">&quot;i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f&quot;</span>).append((<span class="hljs-type">boolean</span>)(i1 == i2)).toString());<br>        <span class="hljs-type">Integer</span> <span class="hljs-variable">i3</span> <span class="hljs-operator">=</span> Integer.valueOf((<span class="hljs-type">int</span>)<span class="hljs-number">128</span>);<br>        <span class="hljs-type">Integer</span> <span class="hljs-variable">i4</span> <span class="hljs-operator">=</span> Integer.valueOf((<span class="hljs-type">int</span>)<span class="hljs-number">128</span>);<br>        System.out.println((String)<span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>().append((String)<span class="hljs-string">&quot;i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f&quot;</span>).append((<span class="hljs-type">boolean</span>)(i3 == i4)).toString());<br>    &#125;<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><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></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-keyword">class</span> <span class="hljs-title class_">Integer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Number</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Comparable</span>&lt;Integer&gt; &#123;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Integer <span class="hljs-title function_">valueOf</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span> &#123;<br>        <span class="hljs-keyword">if</span> (i &gt;= IntegerCache.low &amp;&amp; i &lt;= IntegerCache.high)<br>            <span class="hljs-keyword">return</span> IntegerCache.cache[i + (-IntegerCache.low)];<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Integer</span>(i);<br>    &#125;<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">IntegerCache</span> &#123;<br>        <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-variable">low</span> <span class="hljs-operator">=</span> -<span class="hljs-number">128</span>;<br>        <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> high;<br>        <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Integer cache[];<br><br>        <span class="hljs-keyword">static</span> &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> <span class="hljs-number">127</span>;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">integerCacheHighPropValue</span> <span class="hljs-operator">=</span><br>                sun.misc.VM.getSavedProperty(<span class="hljs-string">&quot;java.lang.Integer.IntegerCache.high&quot;</span>);<br>            <span class="hljs-keyword">if</span> (integerCacheHighPropValue != <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> parseInt(integerCacheHighPropValue);<br>                    i = Math.max(i, <span class="hljs-number">127</span>);<br>                    <span class="hljs-comment">// Maximum array size is Integer.MAX_VALUE</span><br>                    h = Math.min(i, Integer.MAX_VALUE - (-low) -<span class="hljs-number">1</span>);<br>                &#125; <span class="hljs-keyword">catch</span>( NumberFormatException nfe) &#123;<br>                &#125;<br>            &#125;<br>            high = h;<br>            cache = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Integer</span>[(high - low) + <span class="hljs-number">1</span>];<br>            <span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> low;<br>            <span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> <span class="hljs-variable">k</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; k &lt; cache.length; k++)<br>                cache[k] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Integer</span>(j++);<br>            <span class="hljs-comment">// range [-128, 127] must be interned (JLS7 5.1.7)</span><br>            <span class="hljs-keyword">assert</span> IntegerCache.high &gt;= <span class="hljs-number">127</span>;<br>        &#125;<br><br>        <span class="hljs-keyword">private</span> <span class="hljs-title function_">IntegerCache</span><span class="hljs-params">()</span> &#123;&#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>可以看到 <code>Integer</code> 默认先创建并缓存 <code>-128 ~ 127</code> 之间数的 <code>Integer</code> 对象，当调用 <code>valueOf</code> 时如果参数在 <code>-128 ~ 127</code> 之间则计算下标并从缓存中返回，<span style="background-color:#ff00ff">否则创建一个新的 <code>Integer</code> 对象。</span><br>❕<span style="display:none">%%<br>0753-🏡⭐️◼️Integer 用到了享元模式，判断逻辑在 valueOf 方法中，如果是 -128 到 127 之间的数字会返回享元池中的对象，否则创建一个新对象◼️⭐️-point-202301230753%%</span></p><h1 id="7-享元模式与其他模式技术区别⭐️🔴"><a href="#7-享元模式与其他模式技术区别⭐️🔴" class="headerlink" title="7. 享元模式与其他模式技术区别⭐️🔴"></a>7. 享元模式与其他模式技术区别⭐️🔴</h1><h2 id="7-1-VS-单例模式"><a href="#7-1-VS-单例模式" class="headerlink" title="7.1. VS 单例模式"></a>7.1. VS 单例模式</h2><ol><li>在单例模式中，一个类只能创建一个对象，而在享元模式中，一个类可以创建多个对象，每个对象被多处代码引用共享。实际上，享元模式有点类似于之前讲到的单例的变体：多例。</li><li>应用享元模式是为了对象复用，节省内存，而应用单例多例模式是为了限制对象的个数。</li></ol><h2 id="7-2-VS-池化技术"><a href="#7-2-VS-池化技术" class="headerlink" title="7.2. VS 池化技术"></a>7.2. VS 池化技术</h2><ol><li>池化技术中的“复用”可以理解为“<span style="background-color:#00ff00">重复的独占使用</span>”，主要目的是节省时间（比如从数据库池中取一个连接，不需要重新创建）。在任意时刻，每一个对象、连接、线程，并不会被多处使用，而是被一个使用者独占，当使用完成之后，放回到池中，再由其他使用者重复利用。</li><li>享元模式中的“复用”可以理解为“<span style="background-color:#ff00ff">共享使用</span>”，在整个生命周期中，都是被所有使用者共享的，主要目的是节省空间。</li></ol><h2 id="7-3-VS-缓存"><a href="#7-3-VS-缓存" class="headerlink" title="7.3. VS 缓存"></a>7.3. VS 缓存</h2><ol><li>应用单例模式是为了保证对象全局唯一。应用享元模式是为了<span style="background-color:#ff00ff">实现对象复用，节省内存</span>。缓存是为了<span style="background-color:#ffff00">提高访问效率，而非复用</span>。</li></ol><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><ul><li>1）在享元模式这样理解，“享”就表示共享，“元”表示对象</li><li>2）系统中有大量对象，这些对象消耗大量内存，并且对象的状态大部分可以外部化时，我们就可以考虑选用享元模式</li><li>3）用唯一标识码判断，如果在内存中有，则返回这个唯一标识码所标识的对象，用 HashMap&#x2F;HashTable 存储</li><li>4）享元模式大大减少了对象的创建，降低了程序内存的占用，提高效率</li><li>5）享元模式提高了系统的复杂度，需要分离出内部状态和外部状态。而外部状态具有固化特性，不应该随着内部状态的改变而改变，这是我们使用享元模式需要注意的地方</li><li>6）使用享元模式时，注意划分内部状态和外部状态，并且需要有一个工厂类加以控制</li><li>7）享元模式经典的应用场景是需要缓冲池的场景，比如 String 常量池、数据库连接池</li></ul><h2 id="8-1-线程安全问题"><a href="#8-1-线程安全问题" class="headerlink" title="8.1. 线程安全问题"></a>8.1. 线程安全问题</h2><h3 id="8-1-1-不可变性"><a href="#8-1-1-不可变性" class="headerlink" title="8.1.1. 不可变性"></a>8.1.1. 不可变性</h3><p>在我们的代码中，经常要传递容器类的对象，比如 Map，Set， List 等。 在这样的传递中，通常很少考虑不可变性。 作为应用的开发者， 这样写问题不大。 但是作为框架的开发者，提供 library 给外部用户， 如果不考虑这些问题通常就会导致一些问题。比如返回一个 HashMap, 如果这个对象在遍历的时候，有新的对象插入就会有并发的问题。 那么这个 HashMap 到底希望拿到这个对象的用户修改，还是不希望他们修改。<br>如果不希望修改，那么就应该做成 immutable 的， 首先不会出现上文提到的并发调用的冲突问题，其实 immutalbe 的对象是不用考虑并发的问题的，它是天然线程安全的。<br>如果希望修改，通常就考虑返回线程安全的容器，比如 ConcurrentHashMap 之类。</p><p>链接： <a href="https://www.jianshu.com/p/b9cb755b0ad9">https://www.jianshu.com/p/b9cb755b0ad9</a></p><p><a href="https://bbs.huaweicloud.com/blogs/325161">https://bbs.huaweicloud.com/blogs/325161</a></p><p>#todo</p><span style="display:none">- [ ] 🚩 - 享元模式的线程安全问题 - 🏡 2023-02-08 13:33</span><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-14、模板方法模式</title>
      <link href="/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-14%E3%80%81%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-14%E3%80%81%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230117-1953"      data-title='009- 内功心法专题 - 设计模式 -14、模板方法模式 '      data-class='orange'      data-type='range'      data-end='2023-01-17'><br></span></p><hr><h1 id="1-行为型模式"><a href="#1-行为型模式" class="headerlink" title="1. 行为型模式"></a>1. 行为型模式</h1><p>行为型模式<span style="background-color:#ff00ff">用于描述程序在运行时复杂的流程控制，即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务，它涉及算法与对象间职责的分配。</span></p><p>行为型模式分为<span style="background-color:#00ff00">类行为模式</span>和<span style="background-color:#00ff00">对象行为模式</span>，前者采用继承机制来在类间分派行为，后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低，满足“合成复用原则”，所以对象行为模式比类行为模式具有更大的灵活性。</p><p>行为型模式分为：</p><ul><li>模板方法模式 (TemplateMethod)</li><li>策略模式 (Strategy)</li><li>命令模式 (Command)</li><li>职责链模式 (ChainOfResponsibility)</li><li>状态模式 (State)</li><li>观察者模式 (Observer)</li><li>中介者模式 (Mediator)</li><li>迭代器模式 (Iterator)</li><li>访问者模式 (Visitor)</li><li>备忘录模式 (Memento)</li><li>解释器模式 (Interpreter)<br>❕<span style="display:none">%%<br>2129-🏡⭐️◼️行为型设计模式有哪些◼️⭐️-point-202301222129%%</span><br>以上 11 种行为型模式<span style="background-color:#ff00ff">，除了模板方法模式和解释器模式是类行为型模式，其他的全部属于对象行为型模式。</span><br>❕<span style="display:none">%%<br>2130-🏡⭐️◼️23 种设计模式都有什么？1. 创建者模式：单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式；2. 结构型模式：代理模式、适配器模式、装饰者模式、桥接模式、外观模式、组合模式、享元模式；3. 行为型模式：模板方法模式、策略模式、命令模式、责任链模式、状态模式、观察者模式、中介模式、迭代器模式、访问者模式、备忘录模式、解释器模式◼️⭐️-point-202301222130%%</span></li></ul><h1 id="2-模板模式概述"><a href="#2-模板模式概述" class="headerlink" title="2. 模板模式概述"></a>2. 模板模式概述</h1><p>在面向对象程序设计过程中，程序员常常会遇到这种情况：&#x3D;&#x3D;设计一个系统时知道了算法所需的关键步骤，而且确定了这些步骤的执行顺序&#x3D;&#x3D;，但<span style="background-color:#00ff00">某些步骤的具体实现还未知</span>，或者说某些步骤的实现与具体的环境相关。</p><p>例如，去银行办理业务一般要经过以下 4 个流程：取号、排队、办理具体业务、对银行工作人员进行评分等，&#x3D;&#x3D;其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的&#x3D;&#x3D;，可以在父类中实现，<span style="background-color:#ff00ff">但是办理具体业务却因人而异，它可能是存款、取款或者转账等，可以延迟到子类中实现。</span></p><h2 id="2-1-定义"><a href="#2-1-定义" class="headerlink" title="2.1. 定义"></a>2.1. 定义</h2><p>定义一个操作中的<span style="background-color:#00ff00">算法骨架 (相同的步骤及步骤顺序都是一样的)</span>，而将算法的一些步骤延迟到子类中，使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。</p><h1 id="3-模式结构⭐️🔴"><a href="#3-模式结构⭐️🔴" class="headerlink" title="3. 模式结构⭐️🔴"></a>3. 模式结构⭐️🔴</h1><p>模板方法（Template Method）模式包含以下主要角色：</p><h2 id="3-1-抽象类（Abstract-Class）"><a href="#3-1-抽象类（Abstract-Class）" class="headerlink" title="3.1. 抽象类（Abstract Class）"></a>3.1. 抽象类（Abstract Class）</h2><p>负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成<br>    1.   模板方法：<span style="background-color:#00ff00">定义了算法的骨架，按某种顺序调用其包含的基本方法</span>。<span style="background-color:#ff0000">相同的地方</span><br>    2.   基本方法：是实现算法各个步骤的方法，是模板方法的组成部分。基本方法又可以分为三种：<br>        - <strong>抽象方法 (Abstract Method)</strong> ：<span style="background-color:#ff0000">变化的地方</span>  <span style="background-color:#ff00ff">一个抽象方法由抽象类声明、由其具体子类实现。</span>  ❕<span style="display:none">%%<br>2205-🏡⭐️◼️模板方法中不变与变化的地方分别怎么体现和处理？不变的地方，放到抽象类的模板方法中。变化的地方，放到抽象类的抽象方法中，让子类去实现◼️⭐️-point-202301222205%%</span><br>        - <strong>具体方法 (Concrete Method)</strong> ：一个具体方法由一个抽象类或具体类声明并实现，其子类可以进行覆盖也可以直接继承。<br>        - <strong>钩子方法 (Hook Method)</strong> ：在抽象类中已经实现，包括用于判断的逻辑方法和需要子类重写的空方法两种。<br>            一般钩子方法是用于判断的逻辑方法，这类方法名一般为 isXxx，返回值类型为 boolean 类型。</p><h2 id="3-2-具体子类（Concrete-Class）"><a href="#3-2-具体子类（Concrete-Class）" class="headerlink" title="3.2. 具体子类（Concrete Class）"></a>3.2. 具体子类（Concrete Class）</h2><p><span style="background-color:#ff00ff">实现抽象类中所定义的抽象方法和钩子方法</span>，它们是一个顶级逻辑的组成步骤。</p><h1 id="4-UML-图示⭐️🔴"><a href="#4-UML-图示⭐️🔴" class="headerlink" title="4. UML 图示⭐️🔴"></a>4. UML 图示⭐️🔴</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117204323.png" alt="image.png"></p><p><strong>对原理类图的说明——即模板方法模式的角色和职责</strong></p><ul><li><code>AbstractClass</code> 抽象类中实现了模板方法，定义了算法的骨架，具体子类需要去实现其抽象方法或重写其中方法</li><li><code>ConcreteClass</code> 实现了抽象方法，已完成算法中特定子类的步骤</li></ul><h1 id="5-实现逻辑⭐️🔴"><a href="#5-实现逻辑⭐️🔴" class="headerlink" title="5. 实现逻辑⭐️🔴"></a>5. 实现逻辑⭐️🔴</h1><p><span style="background-color:#00ff00">在抽象类中</span><br><span style="background-color:#00ff00">1. 定义 final 类型的模板方法，将不变的内容封装起来</span><br><span style="background-color:#00ff00">2. 定义普通方法，是模板方法中封装的不变的部分</span><br><span style="background-color:#00ff00">3. 定义抽象方法，是变化的部分，让子类实现</span><br><span style="background-color:#00ff00">4. 定义钩子函数，也是变化的部分，让子类根据情况实现</span><br><span style="background-color:#00ff00">在实现类中，实现需要实现的方法</span></p><p><span style="background-color:#ff0000">继承</span><span style="background-color:#00ff00">抽象类 + 封装不变逻辑 + 下发变化逻辑</span><br>❕<span style="display:none">%%<br>2206-🏡⭐️◼️模板方法的核心实现逻辑是什么？🔜📝 继承抽象类，封装不变逻辑，下发变化逻辑，还可以定义钩子函数，让子类根据情况实现◼️⭐️-point-202301222206%%</span></p><h1 id="6-案例分析⭐️🔴"><a href="#6-案例分析⭐️🔴" class="headerlink" title="6. 案例分析⭐️🔴"></a>6. 案例分析⭐️🔴</h1><h2 id="6-1-炒菜"><a href="#6-1-炒菜" class="headerlink" title="6.1. 炒菜"></a>6.1. 炒菜</h2><p>炒菜的步骤是固定的，分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118083601.png" style="zoom:80%;" /><br>❕<span style="display:none">%%<br>2206-🏡⭐️◼️模板方法的例子是什么？炒菜、做豆浆◼️⭐️-point-202301222206%%</span></p><h2 id="6-2-作豆浆"><a href="#6-2-作豆浆" class="headerlink" title="6.2. 作豆浆"></a>6.2. 作豆浆</h2><p>编写制作豆浆的程序，说明如下：</p><ul><li>1）制作豆浆的流程选材 —-&gt; 添加配料 —-&gt; 浸泡 —-&gt; 放到豆浆机打碎</li><li>2）通过添加不同的配料，可以制作出不同口味的豆浆</li><li>3）选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的</li><li>4）请使用模板方法模式完成</li></ul><p>说明：因为模板方法模式比较简单，很容易就想到这个方案，因此就直接使用，不再使用传统的方案来引出模板方法模式</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117204623.png" alt="image.png"></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">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SoyaMilk</span> &#123;  <br>  <br>    <span class="hljs-comment">//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.  </span><br>    <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">make</span><span class="hljs-params">()</span> &#123;  <br>        select();  <br>        addCondiments();  <br>        soak();  <br>        beat();  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//选材料  </span><br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">select</span><span class="hljs-params">()</span> &#123;  <br>        System.out.println(<span class="hljs-string">&quot;第一步：选择好的新鲜黄豆  &quot;</span>);  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//添加不同的配料， 抽象方法, 子类具体实现  </span><br>    <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addCondiments</span><span class="hljs-params">()</span>;  <br>  <br>    <span class="hljs-comment">//浸泡  </span><br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">soak</span><span class="hljs-params">()</span> &#123;  <br>        System.out.println(<span class="hljs-string">&quot;第三步， 黄豆和配料开始浸泡， 需要3小时 &quot;</span>);  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">beat</span><span class="hljs-params">()</span> &#123;  <br>        System.out.println(<span class="hljs-string">&quot;第四步：黄豆和配料放到豆浆机去打碎  &quot;</span>);  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h3 id="6-2-1-钩子方法"><a href="#6-2-1-钩子方法" class="headerlink" title="6.2.1. 钩子方法"></a>6.2.1. 钩子方法</h3><ul><li>1）在模板方法模式的父类中，我们可以定义一个方法，它默认不做任何事，子类可以视情况要不要覆盖它，该方法称为“钩子”</li><li>2）还是用上面做豆浆的例子来讲解，比如，我们还希望制作纯豆浆，不添加任何的配料，请使用钩子方法对前面的模板方法进行改造</li></ul><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">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SoyaMilk</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">make</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-comment">// ...</span><br>        <span class="hljs-keyword">if</span> (customAddIngredients()) &#123;<br>            addIngredients();<br>        &#125;<br>        <span class="hljs-comment">// ...</span><br>    &#125;<br>    <span class="hljs-comment">// ...</span><br>&#125;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 纯豆浆</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">PureSoyaMilk</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">SoyaMilk</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">PureSoyaMilk</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;============纯豆浆============&quot;</span>);<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addIngredients</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-comment">// 空实现即可</span><br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Boolean <span class="hljs-title function_">customAddIngredients</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="7-⭐️🔴优缺点"><a href="#7-⭐️🔴优缺点" class="headerlink" title="7. ⭐️🔴优缺点"></a>7. ⭐️🔴优缺点</h1><h2 id="7-1-优点"><a href="#7-1-优点" class="headerlink" title="7.1. 优点"></a>7.1. 优点</h2><ul><li>提高代码复用性<br>  将相同部分的代码放在抽象的父类中，而将不同的代码放入不同的子类中。</li><li>实现了反向控制<br>  通过一个父类调用其子类的操作，通过对子类的具体实现扩展不同的行为，实现了反向控制 ，并符合“开闭原则”。例如 InputStream 中的 <code>int c = read()</code> 调用的就是子类实现的方法</li></ul><h2 id="7-2-缺点"><a href="#7-2-缺点" class="headerlink" title="7.2. 缺点"></a>7.2. 缺点</h2><ul><li><span style="background-color:#ffff00">对每个不同的实现都需要定义一个子类，这会导致类的个数增加，系统更加庞大，设计也更加抽象。</span></li><li>父类中的抽象方法由子类实现，子类执行的结果会影响父类的结果，这导致一种反向的控制结构，它提高了代码阅读的难度。<br>❕<span style="display:none">%%<br>2210-🏡⭐️◼️模板方法存在什么问题？🔜📝 每一种不同的算法都需要一个子类去实现，容易使系统变得臃肿。钩子函数使得子类的实现控制父类结果，这种反向控制让代码可读性变差◼️⭐️-point-202301222210%%</span></li></ul><h1 id="8-⭐️🔴适用场景"><a href="#8-⭐️🔴适用场景" class="headerlink" title="8. ⭐️🔴适用场景"></a>8. ⭐️🔴适用场景</h1><ul><li><span style="background-color:#00ff00">算法的整体步骤很固定，但其中个别部分易变时</span>，这时候可以使用模板方法模式，将容易变的部分抽象出来，供子类实现。</li><li>需要通过子类来决定父类算法中某个步骤是否执行，实现子类对父类的反向控制。使用钩子函数</li></ul><h1 id="9-⭐️🔴JDK-源码分析"><a href="#9-⭐️🔴JDK-源码分析" class="headerlink" title="9. ⭐️🔴JDK 源码分析"></a>9. ⭐️🔴JDK 源码分析</h1><p>❕<span style="display:none">%%<br>2211-🏡⭐️◼️JDK 源码中哪里用到了模板方法模式？🔜📝 InputStream 中的 read 方法有 3 个参数，最终调用抽象方法 read，子类实现不同逻辑不同，这里用到的就是模板方法模式。Spring 的 refresh 方法中，用了好多抽象方法让子类实现，还有钩子函数等，是一个典型的模板模式的应用◼️⭐️-point-202301222211%%</span></p><h2 id="9-1-InputStream"><a href="#9-1-InputStream" class="headerlink" title="9.1. InputStream"></a>9.1. InputStream</h2><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">InputStream</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Closeable</span> &#123;<br>    <span class="hljs-comment">//抽象方法，要求子类必须重写</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-type">int</span> <span class="hljs-title function_">read</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">read</span><span class="hljs-params">(<span class="hljs-type">byte</span> b[])</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>        <span class="hljs-keyword">return</span> read(b, <span class="hljs-number">0</span>, b.length);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">read</span><span class="hljs-params">(<span class="hljs-type">byte</span> b[], <span class="hljs-type">int</span> off, <span class="hljs-type">int</span> len)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>        <span class="hljs-keyword">if</span> (b == <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NullPointerException</span>();<br>        &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (off &lt; <span class="hljs-number">0</span> || len &lt; <span class="hljs-number">0</span> || len &gt; b.length - off) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IndexOutOfBoundsException</span>();<br>        &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (len == <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>        &#125;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> read(); <span class="hljs-comment">//调用了无参的read方法，该方法是每次读取一个字节数据</span><br>        <span class="hljs-keyword">if</span> (c == -<span class="hljs-number">1</span>) &#123;<br>            <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>;<br>        &#125;<br>        b[off] = (<span class="hljs-type">byte</span>)c;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">for</span> (; i &lt; len ; i++) &#123;<br>                c = read();<br>                <span class="hljs-keyword">if</span> (c == -<span class="hljs-number">1</span>) &#123;<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>                b[off + i] = (<span class="hljs-type">byte</span>)c;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (IOException ee) &#123;<br>        &#125;<br>        <span class="hljs-keyword">return</span> i;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>从上面代码可以看到，无参的 <code>read()</code> 方法是抽象方法，要求子类必须实现。而 <code>read(byte b[])</code> 方法调用了 <code>read(byte b[], int off, int len)</code> 方法，所以在此处重点看的方法是带三个参数的方法。在该方法中第 18 行、27 行，可以看到调用了无参的抽象的 <code>read()</code> 方法。<br><strong>总结如下：</strong> 在 InputStream 父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节，并将其存储到数组的第一个索引位置，读取 len 个字节数据。具体如何读取一个字节数据呢？由子类实现。</p><h2 id="9-2-AbstractApplicationContext"><a href="#9-2-AbstractApplicationContext" class="headerlink" title="9.2. AbstractApplicationContext"></a>9.2. AbstractApplicationContext</h2><p><code>AbstractApplicationContext.java</code> 中有一个 <code>refresh()</code> 方法就是模板方法，其中定义了抽象方法和钩子方法</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117205908.png" alt="image.png"></p><h1 id="10-实战经验"><a href="#10-实战经验" class="headerlink" title="10. 实战经验"></a>10. 实战经验</h1><ul><li>1）<strong>基本思想</strong>：<span style="background-color:#00ff00">算法只存在于一个地方，也就是在父类中，容易修改。</span>需要修改算法时，只要修改父类的模板方法或者已经实现的某些步骤，子类就会继承这些修改</li><li>2）实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用</li><li>3）既统一了算法，也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变，同时由子类提供部分步骤的实现</li><li>4）一般模板方法都加上 <code>final</code> 关键字，防止子类重写模板方法</li></ul><h1 id="11-参考与感谢"><a href="#11-参考与感谢" class="headerlink" title="11. 参考与感谢"></a>11. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-15、策略模式</title>
      <link href="/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-15%E3%80%81%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/16/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-15%E3%80%81%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230117-2153"      data-title='009- 内功心法专题 - 设计模式 -15、策略模式 '      data-class='orange'      data-type='range'      data-end='2023-01-17'><br></span></p><hr><h1 id="1-是什么⭐️🔴"><a href="#1-是什么⭐️🔴" class="headerlink" title="1. 是什么⭐️🔴"></a>1. 是什么⭐️🔴</h1><ul><li>1）策略模式（Strategy Pattern）中，定义算法族，分别封装起来，让他们之间&#x3D;&#x3D;可以互相替换&#x3D;&#x3D;。此模式让算法的变化&#x3D;&#x3D;独立于使用算法的客户&#x3D;&#x3D;，并委派给不同的对象对这些算法进行管理。</li><li>2）这算法体现了几个设计原则<ul><li>第一、<span style="background-color:#00ff00">把变化的代码从不变的代码中分离出来</span></li><li>第二、针对接口编程而不是具体类（定义了策略接口）</li><li>第三、多用组合&#x2F;聚合，少用继承（客户通过组合方式使用策略</li></ul></li></ul><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><p>策略模式的主要角色如下：</p><ul><li><strong>抽象策略（Strategy）类</strong>：这是一个抽象角色，通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。</li><li><strong>具体策略（Concrete Strategy）类</strong>：实现了抽象策略定义的接口，提供具体的算法实现或行为。</li><li><strong>环境（Context）类</strong>：持有一个策略类的引用，最终给客户端调用。</li></ul><h1 id="3-UML-图示"><a href="#3-UML-图示" class="headerlink" title="3. UML 图示"></a>3. UML 图示</h1><h2 id="3-1-原理图"><a href="#3-1-原理图" class="headerlink" title="3.1. 原理图"></a>3.1. 原理图</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117215759.png" alt="image.png"></p><p>说明：从上图可以看到，客户 <code>Context</code> 有成员变量 <code>Strategy</code> 或者其他的策略接口。至于需要使用到哪个策略，可以在构造器中指定</p><ol><li>环境类 (Context)<span style="background-color:#ff0000">聚合</span>不同的抽象策略类 (Strategy)</li><li>Context<span style="background-color:#ff0000">构造函数导入</span>抽象策略类 (Strategy)</li><li>客户端<span style="background-color:#ff0000">调用时传入 Context 构造函数具体策略类</span>(Concrete Strategy)</li></ol><h1 id="4-案例分析⭐️🔴"><a href="#4-案例分析⭐️🔴" class="headerlink" title="4. 案例分析⭐️🔴"></a>4. 案例分析⭐️🔴</h1><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230410-1022%%</span>❕ ^zx08me</p><h2 id="4-1-商城促销"><a href="#4-1-商城促销" class="headerlink" title="4.1. 商城促销"></a>4.1. 商城促销</h2><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117221650.png" style="zoom:80%;" />1. 环境类 (SalesMan)<span style="background-color:#ff0000">聚合</span>抽象策略类 (Strategy)2. 环境类 (SalesMan)<span style="background-color:#ff0000">构造函数导入</span>抽象策略类 (Strategy)3. 客户端调用时传入 Context 角色类 SalesMan 的构造函数具体策略类 (Concrete Strategy)4. 切换策略时，使用 SalesMan 的 setter 方法，set 不同的策略<p>示例代码：[[SalesMan.java]]<br>❕<span style="display:none">%%<br>01222232-🏡⭐️◼️策略模式的简单用法是什么逻辑？1. Context 类聚合 + 构造导入 Strategy 接口；2. Client 调用时，传入不同的 Strategy 实现类给 Context 的构造函数◼️⭐️-point-202301222232%%</span></p><h2 id="4-2-主打特性"><a href="#4-2-主打特性" class="headerlink" title="4.2. 主打特性"></a>4.2. 主打特性</h2><p><span style="background-color:#00ff00">比如某个型号手机主打拍照策略，另一个型号主打游戏策略等</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117221225.png" alt="image.png"></p><ol><li>环境类 (Duck)<span style="background-color:#ff0000">聚合</span>不同的抽象策略类 (XXXBehavior)，并通过<span style="background-color:#ff0000">setter 方法</span>引入</li><li>不同的环境类的子类 (XXXDuck) 继承 Duck 后，就能直接使用 Duck 聚合的 FlyBehavior 等策略接口</li><li>不同的环境类 (XXXDuck) 的子类通过调用各自的策略接口设置不同的 Behavior</li></ol><p>示例代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;strategy&#x2F;improve&#x2F;Duck.java]]</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Duck</span> &#123;  <br>    <span class="hljs-comment">//属性, 策略接口  </span><br>    FlyBehavior flyBehavior;  <br>    <span class="hljs-comment">//其它属性&lt;-&gt;策略接口  </span><br>    QuackBehavior quackBehavior;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Duck</span><span class="hljs-params">()</span> &#123;  <br>  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">display</span><span class="hljs-params">()</span>;<span class="hljs-comment">//显示鸭子信息  </span><br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">quack</span><span class="hljs-params">()</span> &#123;  <br>        System.out.println(<span class="hljs-string">&quot;鸭子嘎嘎叫~~&quot;</span>);  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">swim</span><span class="hljs-params">()</span> &#123;  <br>        System.out.println(<span class="hljs-string">&quot;鸭子会游泳~~&quot;</span>);  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">fly</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-comment">//改进  </span><br>        <span class="hljs-keyword">if</span> (flyBehavior != <span class="hljs-literal">null</span>) &#123;  <br>            flyBehavior.fly();  <br>        &#125;  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setFlyBehavior</span><span class="hljs-params">(FlyBehavior flyBehavior)</span> &#123;  <br>        <span class="hljs-built_in">this</span>.flyBehavior = flyBehavior;  <br>    &#125;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setQuackBehavior</span><span class="hljs-params">(QuackBehavior quackBehavior)</span> &#123;  <br>        <span class="hljs-built_in">this</span>.quackBehavior = quackBehavior;  <br>    &#125;  <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></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_">WildDuck</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Duck</span> &#123;  <br>   <span class="hljs-comment">//构造器，传入FlyBehavor 的对象  </span><br>   <span class="hljs-keyword">public</span>  <span class="hljs-title function_">WildDuck</span><span class="hljs-params">()</span> &#123;  <br>      flyBehavior = <span class="hljs-keyword">new</span> <span class="hljs-title class_">GoodFlyBehavior</span>();  <br>   &#125;  <br>     <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_">display</span><span class="hljs-params">()</span> &#123;  <br>      System.out.println(<span class="hljs-string">&quot; 这是野鸭 &quot;</span>);  <br>   &#125;  <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></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_">FlyBehavior</span> &#123;  <br>   <span class="hljs-keyword">void</span> <span class="hljs-title function_">fly</span><span class="hljs-params">()</span>; <span class="hljs-comment">// 子类具体实现  </span><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></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_">NoFlyBehavior</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">FlyBehavior</span>&#123;  <br>  <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_">fly</span><span class="hljs-params">()</span> &#123;  <br>      <span class="hljs-comment">// TODO Auto-generated method stub  </span><br>      System.out.println(<span class="hljs-string">&quot; 不会飞翔  &quot;</span>);  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="5-优缺点⭐️🔴"><a href="#5-优缺点⭐️🔴" class="headerlink" title="5. 优缺点⭐️🔴"></a>5. 优缺点⭐️🔴</h1><h2 id="5-1-优点"><a href="#5-1-优点" class="headerlink" title="5.1. 优点"></a>5.1. 优点</h2><ul><li>策略类之间可以<span style="background-color:#00ff00">自由切换</span><br>  由于策略类都实现同一个接口，所以使它们之间可以<span style="background-color:#00ff00">自由切换</span>。</li><li><span style="background-color:#00ff00">易于扩展</span><br>增加一个新的策略只需要添加一个具体的策略类即可，基本不需要改变原有的代码，<span style="background-color:#00ff00">符合“开闭原则“，避免了使用多重转移语句（<code>if...else if...else</code>）</span></li><li><span style="background-color:#ff00ff">通过 set 方法动态替换</span><br> 提供了可以替换继承关系的办法：策略模式将算法封装在独立的 <code>Strategy</code> 类中，使得我们可以独立于其 <code>Context</code> 改变它，使它易于切换、易于理解、易于扩展<br> 比如下面代码中的 pekingDuck set 了其他 flyBehavior<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">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">// TODO Auto-generated method stub  </span><br>   <span class="hljs-type">WildDuck</span> <span class="hljs-variable">wildDuck</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">WildDuck</span>();  <br>   wildDuck.fly();<span class="hljs-comment">//  </span><br>   <span class="hljs-type">ToyDuck</span> <span class="hljs-variable">toyDuck</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ToyDuck</span>();  <br>   toyDuck.fly();  <br>     <br>   <span class="hljs-type">PekingDuck</span> <span class="hljs-variable">pekingDuck</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PekingDuck</span>();  <br>   pekingDuck.fly();  <br>     <br>   <span class="hljs-comment">//动态改变某个对象的行为, 北京鸭 不能飞  </span><br>   pekingDuck.setFlyBehavior(<span class="hljs-keyword">new</span> <span class="hljs-title class_">NoFlyBehavior</span>());  <br>   System.out.println(<span class="hljs-string">&quot;北京鸭的实际飞翔能力&quot;</span>);  <br>   pekingDuck.fly();  <br>&#125;<br></code></pre></td></tr></table></figure></li></ul><h2 id="5-2-缺点"><a href="#5-2-缺点" class="headerlink" title="5.2. 缺点"></a>5.2. 缺点</h2><ul><li>策略模式将造成<span style="background-color:#ff0000">产生很多策略类</span>，可以通过使用享元模式在一定程度上减少对象的数量。</li></ul><h1 id="6-适用场景⭐️🔴"><a href="#6-适用场景⭐️🔴" class="headerlink" title="6. 适用场景⭐️🔴"></a>6. 适用场景⭐️🔴</h1><ol><li>一个系统需要<span style="background-color:#00ff00">动态地在几种算法中选择一种</span>时，可将每个算法封装到策略类中。</li><li>一个类定义了<span style="background-color:#ff00ff">多种行为，并且这些行为在这个类的操作中以多个条件语句的形式出现</span>，可将每个条件分支移入它们各自的策略类中以代替这些条件语句。</li><li>系统中<span style="background-color:#00ff00">各算法彼此完全独立，且要求对客户隐藏具体算法的实现细节</span>时。<br>❕<span style="display:none">%%<br>0805-🏡⭐️◼️策略模式的适用场景？1. 需要在不同算法动态选择时；2. 多种行为在大量 ifelse 之间判断时；3. 多种算法独立，需要对 client 透明时◼️⭐️-point-202301250805%%</span></li></ol><h1 id="7-JDK-源码分析⭐️🔴"><a href="#7-JDK-源码分析⭐️🔴" class="headerlink" title="7. JDK 源码分析⭐️🔴"></a>7. JDK 源码分析⭐️🔴</h1><h2 id="7-1-Arrays-的-Comparator"><a href="#7-1-Arrays-的-Comparator" class="headerlink" title="7.1. Arrays 的 Comparator"></a>7.1. Arrays 的 Comparator</h2><p>❕<span style="display:none">%%<br>01222232-🏡⭐️◼️Arrays 的 sort 方法是用了什么设计模式？模式中的角色分别是什么？Arrays 是 Context 角色，控制具体策略的使用；Comparator 接口是 Strategy 接口；传入的 Comparator 的匿名内部类就是具体的策略◼️⭐️-point-202301222232%%</span><br><code>Comparator</code> 中的策略模式。在 Arrays 类中有一个 <code>sort()</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></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_">Arrays</span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;T&gt; <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(T[] a, Comparator&lt;? <span class="hljs-built_in">super</span> T&gt; c)</span> &#123;<br>        <span class="hljs-keyword">if</span> (c == <span class="hljs-literal">null</span>) &#123;<br>            sort(a);<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-keyword">if</span> (LegacyMergeSort.userRequested)<br>                legacyMergeSort(a, c);<br>            <span class="hljs-keyword">else</span><br>                TimSort.sort(a, <span class="hljs-number">0</span>, a.length, c, <span class="hljs-literal">null</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><span style="background-color:#ff00ff">Arrays 就是一个环境角色类</span>，这个 sort 方法可以传一个新策略让 Arrays 根据这个策略来进行排序。就比如下面的测试类。</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_">demo</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><br>        Integer[] data = &#123;<span class="hljs-number">12</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>&#125;;<br>        <span class="hljs-comment">// 实现降序排序</span><br>        Arrays.sort(data, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Comparator</span>&lt;Integer&gt;() &#123;<br>            <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">compare</span><span class="hljs-params">(Integer o1, Integer o2)</span> &#123;<br>                <span class="hljs-keyword">return</span> o2 - o1;<br>            &#125;<br>        &#125;);<br>        System.out.println(Arrays.toString(data)); <span class="hljs-comment">//[12, 5, 4, 3, 2, 2, 1]</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这里我们在调用 Arrays 的 sort 方法时，<span style="background-color:#ff0000">第二个参数传递的是 Comparator 接口的子实现类对象。所以 Comparator 充当的是抽象策略角色，而具体的子实现类充当的是具体策略角色</span>。也就是我们自定义的匿名内部类 <code>public int compare(Integer o1, Integer o2) &#123;xxx&#125;</code>。环境角色类（Arrays）应该持有抽象策略的引用来调用。那么，Arrays 类的 sort 方法到底有没有使用 Comparator 子实现类中的 <code>compare()</code> 方法吗？让我们继续查看 TimSort 类的 <code>sort()</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">TimSort</span>&lt;T&gt; &#123;<br>    <span class="hljs-keyword">static</span> &lt;T&gt; <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(T[] a, <span class="hljs-type">int</span> lo, <span class="hljs-type">int</span> hi, Comparator&lt;? <span class="hljs-built_in">super</span> T&gt; c,</span><br><span class="hljs-params">                         T[] work, <span class="hljs-type">int</span> workBase, <span class="hljs-type">int</span> workLen)</span> &#123;<br>        <span class="hljs-keyword">assert</span> c != <span class="hljs-literal">null</span> &amp;&amp; a != <span class="hljs-literal">null</span> &amp;&amp; lo &gt;= <span class="hljs-number">0</span> &amp;&amp; lo &lt;= hi &amp;&amp; hi &lt;= a.length;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">nRemaining</span>  <span class="hljs-operator">=</span> hi - lo;<br>        <span class="hljs-keyword">if</span> (nRemaining &lt; <span class="hljs-number">2</span>)<br>            <span class="hljs-keyword">return</span>;  <span class="hljs-comment">// Arrays of size 0 and 1 are always sorted</span><br><br>        <span class="hljs-comment">// If array is small, do a &quot;mini-TimSort&quot; with no merges</span><br>        <span class="hljs-keyword">if</span> (nRemaining &lt; MIN_MERGE) &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">initRunLen</span> <span class="hljs-operator">=</span> countRunAndMakeAscending(a, lo, hi, c);<br>            binarySort(a, lo, hi, lo + initRunLen, c);<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        ...<br>    &#125;   <br>        <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> &lt;T&gt; <span class="hljs-type">int</span> <span class="hljs-title function_">countRunAndMakeAscending</span><span class="hljs-params">(T[] a, <span class="hljs-type">int</span> lo, <span class="hljs-type">int</span> hi,Comparator&lt;? <span class="hljs-built_in">super</span> T&gt; c)</span> &#123;<br>        <span class="hljs-keyword">assert</span> lo &lt; hi;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">runHi</span> <span class="hljs-operator">=</span> lo + <span class="hljs-number">1</span>;<br>        <span class="hljs-keyword">if</span> (runHi == hi)<br>            <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br><br>        <span class="hljs-comment">// Find end of run, and reverse range if descending</span><br>        <span class="hljs-keyword">if</span> (c.compare(a[runHi++], a[lo]) &lt; <span class="hljs-number">0</span>) &#123; <span class="hljs-comment">// Descending</span><br>            <span class="hljs-keyword">while</span> (runHi &lt; hi &amp;&amp; c.compare(a[runHi], a[runHi - <span class="hljs-number">1</span>]) &lt; <span class="hljs-number">0</span>)<br>                runHi++;<br>            reverseRange(a, lo, runHi);<br>        &#125; <span class="hljs-keyword">else</span> &#123;                              <span class="hljs-comment">// Ascending</span><br>            <span class="hljs-keyword">while</span> (runHi &lt; hi &amp;&amp; c.compare(a[runHi], a[runHi - <span class="hljs-number">1</span>]) &gt;= <span class="hljs-number">0</span>)<br>                runHi++;<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> runHi - lo;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>上面的代码中最终会跑到 <code>countRunAndMakeAscending()</code> 这个方法中。我们可以看见，只用了 compare 方法，所以在调用 Arrays.sort 方法只传具体 compare 重写方法的类对象就行，这也是 Comparator 接口中必须要子类实现的一个方法。</p><h1 id="8-策略模式与桥接模式的区别⭐️🔴"><a href="#8-策略模式与桥接模式的区别⭐️🔴" class="headerlink" title="8. 策略模式与桥接模式的区别⭐️🔴"></a>8. 策略模式与桥接模式的区别⭐️🔴</h1><p>❕<span style="display:none">%%<br>01222233-🏡⭐️◼️桥接模式和策略模式的区别？桥接模式和策略模式各自的 UML 及例子是什么？◼️⭐️-point-202301222233%%</span><br>[[策略模式与桥接模式区别-pudn.com]]</p><h2 id="8-1-相同点"><a href="#8-1-相同点" class="headerlink" title="8.1. 相同点"></a>8.1. 相同点</h2><p><strong>策略模式</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118141939.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230118142353.png" alt="image.png"></p><p><strong>桥接模式</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230119095951.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115225856.png" alt="image.png"></p><p><span style="background-color:#ff00ff">核心逻辑都是使用了聚合 + 构造导入</span></p><h2 id="8-2-不同点"><a href="#8-2-不同点" class="headerlink" title="8.2. 不同点"></a>8.2. 不同点</h2><p>❕<span style="display:none">%%<br>01230717-🏡⭐️◼️策略模式和桥接模式的区别？变化不同：桥接模式涉及到多个维度，抽象化类和实现化类都涉及到变化，而策略模式中 Context 不涉及变化，主要是在不同的抽象化 Strategy 中进行选择。目的不同：桥接模式是将不同维度的抽象，通过聚合和构造导入的方式，使得松散的扩展抽象化类和具体实现化类变的更加紧密，目的是高内聚。而策略模式是将算法逻辑封装起来与调用者进行隔离，目的是解耦达到低耦合的目的。范畴不同：桥接模式通过不同维度的聚合、组合加构造导入的方式，以达到更多层次构建系统的目的。而策略模式主要在策略一个层次上进行选择，格局比桥接模式低一点。桥接模式在范围上是包含策略模式的。◼️⭐️-point-202301230717%%</span></p><h3 id="8-2-1-变化不同"><a href="#8-2-1-变化不同" class="headerlink" title="8.2.1. 变化不同"></a>8.2.1. 变化不同</h3><p> 在桥接模式中不仅 Implementor 具有变化（ConcreateImplementior），而且 Abstraction 也可以发生变化（RefinedAbstraction），而且<span style="background-color:#ff00ff">两者的变化是完全独立的</span>，RefinedAbstraction 与 ConcreateImplementior 之间松散耦合，它们仅仅通过 Abstraction 与 Implementor 之间的关系联系起来。<span style="background-color:#ff00ff">而在策略模式中，并不考虑 Context 的变化，只有算法的可替代性</span>。因此桥接模式一般比策略模式更加复杂。</p><h3 id="8-2-2-目的不同"><a href="#8-2-2-目的不同" class="headerlink" title="8.2.2. 目的不同"></a>8.2.2. 目的不同</h3><ol><li>在桥接模式中不仅定义 Implementor 的接口而且定义 Abstraction 的接口，Abstraction 的接口不仅仅是为了与 Implementor 通信而存在的，这也反映了结构型模式的特点：通过继承、聚合的方式组合类和对象<span style="background-color:#00ff00">以形成更大的结构</span>。<span style="background-color:#00ff00">属于结构型模式，目的可以理解为高内聚</span>。</li><li>在策略模式中，Startegy 和 Context 的接口都是两者之间的协作接口，并不涉及到其它的功能接口，所以它是行为模式的一种。行为模式的主要特点就是处理的是对象之间的通信方式，往往是<span style="background-color:#00ff00">通过引入中介者对象将通信双方解耦，在这里实际上就是将 Context 与实际的算法提供者解耦。属于行为型模式，目的可以理解为低耦合</span>。<br>❕<span style="display:none">%%<br>0808-🏡⭐️◼️设计模式的目的：结构上高内聚，行为上低耦合◼️⭐️-point-202301250808%%</span></li></ol><h3 id="8-2-3-范畴不同"><a href="#8-2-3-范畴不同" class="headerlink" title="8.2.3. 范畴不同"></a>8.2.3. 范畴不同</h3><ol><li>相对与策略模式，桥接模式要表达的内容要更多，结构也更加复杂。桥接模式表达的主要意义其实是接口隔离的原则，即把本质上并不内聚的两种体系区别开来，使得它们可以松散的组合，而策略在解耦上还仅仅是某一个算法的层次，没有到体系这一层次。</li><li>从结构图中可以看到，策略的结构是包容在桥接结构中的，桥接中必然存在着策略模式，Abstraction 与 Implementor 之间就可以认为是策略模式，但是桥接模式一般 Implementor 将提供一系列的成体系的操作，而且 Implementor 是具有状态和数据的静态结构。而且桥接模式 Abstraction 也可以独立变化。</li></ol><h3 id="8-2-4-逻辑不同"><a href="#8-2-4-逻辑不同" class="headerlink" title="8.2.4. 逻辑不同"></a>8.2.4. 逻辑不同</h3><p><span style="background-color:#ff0000">聚合其他接口的类的子类，通过构造导入的方式，可以调用接口实现类的方法</span></p><h1 id="9-策略模式与状态模式的区别"><a href="#9-策略模式与状态模式的区别" class="headerlink" title="9. 策略模式与状态模式的区别"></a>9. 策略模式与状态模式的区别</h1><ol><li>策略模式有多个抽象策略类，而状态模式一般只有一个抽象状态类</li><li>策略模式不同策略之间不存在流转等其他关系，而状态模式不同状态之间需要流转</li></ol><h1 id="10-实战经验⭐️🔴"><a href="#10-实战经验⭐️🔴" class="headerlink" title="10. 实战经验⭐️🔴"></a>10. 实战经验⭐️🔴</h1><ul><li>1）策略模式的关键是：分析项目中变化部分与不变部分</li><li>2）策略模式的核心思想是：多用组合&#x2F;聚合，少用继承；用行为类组合，而不是行为的继承，更有弹性<br>❕<span style="display:none">%%<br>01230729-🏡⭐️◼️涉及动态替换的模式：策略模式、装饰者模式、责任链模式。涉及变化与不变的模式：策略模式（算法不同）、模板方法模式（流程和细节都有不同）、建造者模式（流程相同，细节不同）、享元模式（内部状态相同，外部状态不同）◼️⭐️-point-202301230729%%</span></li></ul><h1 id="11-参考与感谢"><a href="#11-参考与感谢" class="headerlink" title="11. 参考与感谢"></a>11. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 行为型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-11、外观模式</title>
      <link href="/2023/01/15/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-11%E3%80%81%E5%A4%96%E8%A7%82%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/15/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-11%E3%80%81%E5%A4%96%E8%A7%82%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230116-1106"      data-title='009-内功心法专题-设计模式-11、外观模式'      data-class='orange'      data-type='range'      data-end='2023-01-16'><br></span></p><hr><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><p>外观模式（Facade），也叫<span style="background-color:#00ff00">门面模式</span>，也叫过程模式。是“迪米特法则”的典型应用。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117083825.png" alt="image.png"></p><p>外观模式为子系统中的一组接口提供一个一致的界面，此模式<span style="background-color:#00ff00">定义了一个高层接口，用以屏蔽内部子系统的细节，使得调用端只需跟这个接口发生调用，而无需关心这个子系统的内部细节，这个接口使得这一子系统更加容易使用</span>❕<span style="display:none">%%<br>0835-🏡⭐️◼️外观模式的作用原理和意义是什么？通过定一个高层接口，对外提供子系统功能，屏蔽子系统实现细节，使得外部调用更加轻松容易，而不至于调用端调用混乱◼️⭐️-point-202301230835%%</span></p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><p>外观（Facade）模式包含以下主要角色：</p><ul><li><strong>外观（Facade）角色</strong>：为多个子系统对外提供一个共同的接口。</li><li><strong>子系统（Sub System）角色</strong>：实现系统的部分功能，客户可以通过外观角色访问它。</li></ul><h2 id="2-2-UML图示"><a href="#2-2-UML图示" class="headerlink" title="2.2. UML图示"></a>2.2. UML图示</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117083644.png" alt="image.png"></p><h2 id="2-3-实现逻辑⭐️🔴"><a href="#2-3-实现逻辑⭐️🔴" class="headerlink" title="2.3. 实现逻辑⭐️🔴"></a>2.3. 实现逻辑⭐️🔴</h2><p><span style="background-color:#ff0000">聚合</span>：外观类聚合子系统类</p><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-影院管理项目"><a href="#3-1-影院管理项目" class="headerlink" title="3.1. 影院管理项目"></a>3.1. 影院管理项目</h2><p>DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机，要求完成使用家庭影院的功能，其过程为：</p><ul><li>直接用遥控器：统筹各设备开关</li><li>开爆米花机</li><li>放下屏幕</li><li>开投影仪</li><li>开音响</li><li>开DVD，选dvd</li><li>去拿爆米花</li><li>调暗灯光</li><li>播放</li><li>观影结束后，关闭各种设备</li></ul><h3 id="3-1-1-传统方案"><a href="#3-1-1-传统方案" class="headerlink" title="3.1.1. 传统方案"></a>3.1.1. 传统方案</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117083248.png" alt="image.png"></p><p><strong>传统方式解决影院管理问题分析</strong></p><ul><li>1）在 ClientTest 的 main 方法中，创建各个子系统的对象，并直接去调用子系统（对象）相关方法，<span style="background-color:#ffff00">会造成调用过程混乱，没有清晰的过程</span></li><li>2）<span style="background-color:#ffff00">不利于在 ClientTest 中去维护对子系统的操作</span></li><li>3）解决思路：<span style="background-color:#00ff00">定义一个高层接口，给子系统中的一组接口提供一个一致的界面（比如在高层接口提供四个方法ready，play，pause，end），用来访问子系统中的一群接口</span></li><li>4）也就是说就是通过定义一个一致的接口（界面类），用以屏蔽内部子系统的细节，使得调用端只需跟这个接口发生调用，而无需关心这个子系统的内部细节</li></ul><h3 id="3-1-2-外观模式"><a href="#3-1-2-外观模式" class="headerlink" title="3.1.2. 外观模式"></a>3.1.2. 外观模式</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230119202453.png" alt="image.png"></p><h2 id="3-2-智能家电控制"><a href="#3-2-智能家电控制" class="headerlink" title="3.2. 智能家电控制"></a>3.2. 智能家电控制</h2><p>通过智能音箱，语音直接控制这些智能家电的开启和关闭<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230119202334.png" style="zoom:80%;" /></p><h1 id="4-优缺点⭐️🔴"><a href="#4-优缺点⭐️🔴" class="headerlink" title="4. 优缺点⭐️🔴"></a>4. 优缺点⭐️🔴</h1><p><strong>优点：</strong></p><ul><li><span style="background-color:#00ff00">降低了子系统与客户端之间的耦合度，使得子系统的变化不会影响调用它的客户类。</span></li><li><span style="background-color:#00ff00">对客户屏蔽了子系统组件，减少了客户处理的对象数目，并使得子系统使用起来更加容易。</span></li></ul><p><strong>缺点：</strong></p><ul><li><span style="background-color:#ffff00">不符合开闭原则，修改很麻烦：如果子系统有变更，客户端不需要修改，但是Facade类需要做相应的修改</span>。<br>❕<span style="display:none">%%<br>0835-🏡⭐️◼️外观模式的优点：隔离子系统的变更风险，不会影响到客户端；使得客户端调用更加简单。缺点：不符合开闭原则，子系统修改后，客户端不需要改变，但facade类需要做相应变更◼️⭐️-point-202301230835%%</span></li></ul><h1 id="5-适用场景⭐️🔴"><a href="#5-适用场景⭐️🔴" class="headerlink" title="5. 适用场景⭐️🔴"></a>5. 适用场景⭐️🔴</h1><ol><li><strong>对于系统内部而言</strong>：对分层结构系统构建时，<span style="background-color:#00ff00">使用外观模式定义子系统中每层的入口点</span>可以简化子系统之间的依赖关系。</li><li><strong>对于系统边缘而言</strong>：当一个复杂系统的子系统很多时，外观模式可以为系统设计一个简单的接口供外界访问</li><li><strong>对于客户端而言</strong>：当客户端与多个子系统之间存在很大的联系时，引入外观模式可将它们分离，从而提高子系统的独立性和可移植性。</li></ol><h1 id="6-JDK源码分析"><a href="#6-JDK源码分析" class="headerlink" title="6. JDK源码分析"></a>6. JDK源码分析</h1><h2 id="6-1-Tomcat-RequestFacade"><a href="#6-1-Tomcat-RequestFacade" class="headerlink" title="6.1. Tomcat RequestFacade"></a>6.1. Tomcat RequestFacade</h2><p>使用tomcat作为web容器时，接收浏览器发送过来的请求，tomcat会将请求信息封装成ServletRequest对象，如下图①处对象。但是大家想想ServletRequest是一个接口，它还有一个子接口HttpServletRequest，而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象，到底是哪个类的对象呢？可以通过输出request对象，我们就会发现是一个名为RequestFacade的类的对象。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117085502.png" alt="image.png"><br>RequestFacade类就使用了外观模式。先看结构图：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117085531.png" alt="image.png"></p><p><strong>为什么在此处使用外观模式呢？</strong></p><p>定义 RequestFacade 类，实现 ServletRequest ，同时定义私有成员变量 Request ，并且方法的实现调用 Request 的实现。然后，将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法，这样即使在 servlet 中被下转为 RequestFacade ，也不能访问私有成员变量对象中的方法。既用了 Request ，又能防止其中方法被不合理的访问。</p><h2 id="6-2-MyBatis-框架源码分析"><a href="#6-2-MyBatis-框架源码分析" class="headerlink" title="6.2. MyBatis 框架源码分析"></a>6.2. MyBatis 框架源码分析</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117090256.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117090412.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117090412.png"></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><ul><li>1）外观模式对外屏蔽了子系统的细节，因此外观模式降低了客户端对子系统使用的复杂性</li><li>2）外观模式对客户端与子系统的耦合关系，让子系统内部的模块更易维护和扩展</li><li>3）通过合理的使用外观模式，可以帮我们更好的划分访问的层次</li><li>4）当系统需要进行分层设计时，可以考虑使用 Facade 模式</li><li>5）在维护一个遗留的大型系统时，可能这个系统已经变得非常难以维护和扩展，此时可以考虑为新系统开发一个 Facade 类，来提供遗留系统的比较清晰简单的接口，让新系统与 Facade 类交互，提高复用性</li><li>6）不能过多的或者不合理的使用外观模式，使用外观模式好，还是直接调用模块好。要以让系统有层次，利于维护为目的</li></ul><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-10、桥接模式</title>
      <link href="/2023/01/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-10%E3%80%81%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-10%E3%80%81%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230115-2115"      data-title='009- 内功心法专题 - 设计模式 -10、桥接模式 '      data-class='orange'      data-type='range'      data-end='2023-01-15'><br></span></p><hr><h1 id="1-模式定义⭐️🔴"><a href="#1-模式定义⭐️🔴" class="headerlink" title="1. 模式定义⭐️🔴"></a>1. 模式定义⭐️🔴</h1><ol><li><span style="background-color:#00ff00">将抽象与实现分离，使它们可以独立变化。它是用组合关系代替继承关系来实现，来降低了抽象和实现这两个可变维度的耦合度</span>。</li><li>基于类的最小设计原则，通过使用封装、聚合及继承等行为让不同的类承担不同的职责❕<span style="display:none">%%<br>1224-🏡⭐️◼️桥接模式的主要特点？把抽象（Abstraction）与行为实现（Implementation）分离开来，从而可以保持各部分的独立性以及应对他们的功能扩展◼️⭐️-point-202301261224%%</span></li></ol><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-模式角色"><a href="#2-1-模式角色" class="headerlink" title="2.1. 模式角色"></a>2.1. 模式角色</h2><p>桥接（Bridge）模式包含以下主要角色：</p><ul><li><strong>抽象化（Abstraction）角色</strong> ：定义&#x3D;&#x3D;抽象类&#x3D;&#x3D;，<span style="background-color:#ff00ff">并包含一个对实现化对象的引用</span></li><li><strong>扩展抽象化（Refined Abstraction）角色</strong> ：是抽象化角色的子类，实现父类中的业务方法，<span style="background-color:#00ff00">并通过父类与实现化类的组合关系调用实现化类中的业务方法</span>。</li><li><strong>实现化（Implementor）角色</strong> ：定义实现化角色的&#x3D;&#x3D;接口&#x3D;&#x3D;，供扩展抽象化角色调用。</li><li><strong>具体实现化（Concrete Implementor）角色</strong> ：给出实现化角色接口的具体实现。</li></ul><h2 id="2-2-UML⭐️🔴"><a href="#2-2-UML⭐️🔴" class="headerlink" title="2.2. UML⭐️🔴"></a>2.2. UML⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115224629.png" alt="image.png"></p><p><strong>原理类图说明</strong></p><ul><li><strong>Client</strong>：桥接模式的调用者</li><li><strong>Abstraction</strong>：Abstraction 充当桥接类，维护了 Implementor，即 ConcreteImplementorA &#x2F; ConcreteImplementorB</li><li><strong>RefinedAbstraction</strong>：Abstraction 抽象类的子类</li><li><strong>Implementor</strong>：行为实现类的接口</li><li><strong>ConcreteImplementorA &#x2F; ConcreteImplementorB</strong>：行为的具体实现类</li><li>这里的抽象类和接口是聚合的关系，也是调用者和被调用者的关系</li></ul><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><h2 id="3-1-手机品牌"><a href="#3-1-手机品牌" class="headerlink" title="3.1. 手机品牌"></a>3.1. 手机品牌</h2><p>现在对不同手机类型的不同品牌实现操作编程 (比如: 开机、关机、上网，打电话等)，如图:<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115224728.png" alt="image.png"></p><h3 id="3-1-1-传统方案⭐️🔴"><a href="#3-1-1-传统方案⭐️🔴" class="headerlink" title="3.1.1. 传统方案⭐️🔴"></a>3.1.1. 传统方案⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115224147.png" alt="image.png"><br>传统方案解决手机操作问题分析</p><ol><li>扩展性问题 (类爆炸)，如果我们再增加手机的样式 (旋转式)，就需要增加各个品牌手机的类，同样如果我们增加一个手机品牌，也要在各个手机样式类下增加。</li><li>违反了单一职责原则，当我们增加手机样式时，要同时增加所有品牌的手机，这样增加了代码维护成本.</li><li>解决方案 - 使用桥接模式</li></ol><h3 id="3-1-2-桥接模式"><a href="#3-1-2-桥接模式" class="headerlink" title="3.1.2. 桥接模式"></a>3.1.2. 桥接模式</h3><h4 id="3-1-2-1-UML-图示⭐️🔴"><a href="#3-1-2-1-UML-图示⭐️🔴" class="headerlink" title="3.1.2.1. UML 图示⭐️🔴"></a>3.1.2.1. UML 图示⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230130075916.png" alt="image.png"></p><p>[[FoldedPhone.java]]</p><h4 id="3-1-2-2-⭐️🔴实现逻辑"><a href="#3-1-2-2-⭐️🔴实现逻辑" class="headerlink" title="3.1.2.2. ⭐️🔴实现逻辑"></a>3.1.2.2. ⭐️🔴实现逻辑</h4><p><span style="background-color:#ff00ff">聚合 + 构造导入</span> ❕<span style="display:none">%%<br>0838-🏡⭐️◼️抽象化类 Phone 的子类是扩展抽象化类角色，比如 FoldedPhone，在 new 的时候，通过构造函数传入抽象化类 Brand 的实现类，比如 Vivo。这样扩展抽象化类通过 Phone 与 Brand 之间聚合 + 构造导入的关系 连接起来，其中 Phone 作为桥梁的作用。◼️⭐️-point-202301230838%%</span></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">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Phone</span> &#123;  <br>   <span class="hljs-comment">//抽象化类Phone【聚合】实现化类Brand  </span><br>   <span class="hljs-keyword">private</span> Brand brand;  <br>   <span class="hljs-comment">//【构造导入】实现化类Brand  </span><br>   <span class="hljs-keyword">public</span> <span class="hljs-title function_">Phone</span><span class="hljs-params">(Brand brand)</span> &#123;  <br>      <span class="hljs-built_in">super</span>();  <br>      <span class="hljs-built_in">this</span>.brand = brand;  <br>   &#125;  <br>     <br>   <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">open</span><span class="hljs-params">()</span> &#123;  <br>      <span class="hljs-built_in">this</span>.brand.open();  <br>   &#125;  <br>   <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">close</span><span class="hljs-params">()</span> &#123;  <br>      brand.close();  <br>   &#125;  <br>   <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">call</span><span class="hljs-params">()</span> &#123;  <br>      brand.call();  <br>   &#125;  <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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//获取折叠式手机 (样式 + 品牌 )  </span><br>  <br><span class="hljs-type">Phone</span> <span class="hljs-variable">phone1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FoldedPhone</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">XiaoMi</span>());  <br>  <br>phone1.open();  <br>phone1.call();  <br>phone1.close();<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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Phone</span> <span class="hljs-variable">phone2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FoldedPhone</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Vivo</span>());  <br>  <br>phone2.open();  <br>phone2.call();  <br>phone2.close();<br></code></pre></td></tr></table></figure><p>抽象类 Phone<span style="background-color:#ff00ff">聚合了接口 Brand，并通过构造函数传入</span>Brand。<br>抽象类 Phone 的子类比如 FoldedPhone 的构造函数传入 Brand 的实现类比如 Vivo。<br>FoldedPhone 和 Vivo 就通过抽象化类 Phone 完成了桥接。❕<span style="display:none">%%<br>0839-🏡⭐️◼️桥接的精华所在：抽象化类聚合实现化接口，并在构造函数中传入该接口。那么抽象化类的子类就可以通过构造函数来导入接口的实现类。那么就通过抽象化类完成了其子类与实现化接口的实现类的桥接。通过抽象化类的子类调用接口的实现类中的方法。◼️⭐️-point-202301230837%%</span></p><h2 id="3-2-视频播放器"><a href="#3-2-视频播放器" class="headerlink" title="3.2. 视频播放器"></a>3.2. 视频播放器</h2><p>#todo</p><span style="display:none">- [ ] 🚩 - 黑马桥接模式案例分析 - 🏡 2023-01-25 09:52</span><h1 id="4-优缺点⭐️🔴"><a href="#4-优缺点⭐️🔴" class="headerlink" title="4. 优缺点⭐️🔴"></a>4. 优缺点⭐️🔴</h1><h2 id="4-1-优点"><a href="#4-1-优点" class="headerlink" title="4.1. 优点"></a>4.1. 优点</h2><ol><li><span style="background-color:#00ff00">实现了抽象和实现部分的分离</span>，从而极大的提供了系统的灵活性，让抽象部分和实现部分独立开来。这有助于系统进行分层设计，从而产生更好的结构化系统</li><li>对于系统的高层部分，只需要知道抽象部分和实现部分的接口就可以了，其它的部分由具体业务来完成</li><li><span style="background-color:#00ff00">桥接模式替代多层继承方案，可以减少子类的个数，降低系统的管理和维护成本</span></li></ol><h2 id="4-2-缺点"><a href="#4-2-缺点" class="headerlink" title="4.2. 缺点"></a>4.2. 缺点</h2><ol><li>桥接模式的引入<span style="background-color:#ffff00">增加了系统的理解和设计难度</span>，由于聚合关联关系建立在抽象层，要求开发者针对抽象进行设计和编程</li><li>桥接模式要求<span style="background-color:#ffff00">正确识别出系统中两个独立变化的维度</span>，因此其使用范围有一定的后限性，即需要有这样的应用场景 ❕<span style="display:none">%%<br>0800-🏡⭐️◼️桥接模式的缺点？🔜📝 1.增加了设计难度；2.需要正确的识别出独立◼️⭐️-point-202301300800%%</span></li></ol><h1 id="5-应用场景⭐️🔴"><a href="#5-应用场景⭐️🔴" class="headerlink" title="5. 应用场景⭐️🔴"></a>5. 应用场景⭐️🔴</h1><ul><li>当一个类<span style="background-color:#00ff00">存在两个或多个<font color=#ff0000>独立变化的</font>维度</span>，且这些维度都需要进行扩展时。</li><li>当一个系统<span style="background-color:#00ff00">不希望使用继承或因为多层次继承导致系统类的个数急剧增加</span>时。</li><li>当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系，通过桥接模式可以使它们在抽象层建立一个关联关系。</li></ul><h2 id="5-1-常见的应用场景"><a href="#5-1-常见的应用场景" class="headerlink" title="5.1. 常见的应用场景"></a>5.1. 常见的应用场景</h2><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230410-1024%%</span>❕ ^h555hp</p><ol><li>JDBC 驱动程序</li><li>银行转账系统</li></ol><ul><li>转账分类：网上转账、柜台转账、AMT 转账       <span style="background-color:#00ff00">（抽象化类）</span></li><li>转账用户类型：普通用户、银卡用户、金卡用户 <span style="background-color:#00ff00">（实现化类）</span></li></ul><ol start="3"><li>消息管理</li></ol><ul><li>消息类型：即时消息、延时消息  <span style="background-color:#00ff00">（抽象化类）</span></li><li>消息分类：手机短信、邮件消息、QQ 消息…<span style="background-color:#00ff00">（实现化类）</span></li></ul><h1 id="6-JDK-源码分析"><a href="#6-JDK-源码分析" class="headerlink" title="6. JDK 源码分析"></a>6. JDK 源码分析</h1><p>JDBC 的 Driver 接口：如果从桥接模式来看，Driver 就是一个接口，下面可以有 MySQL 的 Driver、Oracle 的 Driver，这些就可以当做实现接口类<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117212500.png" alt="image.png"></p><ul><li>MySQL 有自己的 Connectionlmpl 类，同样 Oracle 也有对应的实现类</li><li>Driver 和 Connection 之间是<span style="background-color:#ff00ff">通过 DriverManager 类进行桥连接的</span></li></ul><h1 id="7-与装饰者模式区别⭐️🔴"><a href="#7-与装饰者模式区别⭐️🔴" class="headerlink" title="7. 与装饰者模式区别⭐️🔴"></a>7. 与装饰者模式区别⭐️🔴</h1><p>❕<span style="display:none">%%<br>0839-🏡⭐️◼️桥接模式与装饰者模式的区别是什么？◼️⭐️-point-202301230839%%</span></p><h2 id="7-1-实现逻辑"><a href="#7-1-实现逻辑" class="headerlink" title="7.1. 实现逻辑"></a>7.1. 实现逻辑</h2><p>装饰者模式：继承 + 组合 + 构造导入<br>桥接模式：聚合 + 构造导入</p><h2 id="7-2-应用场景"><a href="#7-2-应用场景" class="headerlink" title="7.2. 应用场景"></a>7.2. 应用场景</h2><p>装饰者模式：适合动态插拔对象能力的情况<br>桥接模式：适合多个维度都需要扩展，避免类爆炸的情况</p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-9、装饰者模式</title>
      <link href="/2023/01/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-9%E3%80%81%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-9%E3%80%81%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-模式定义⭐️🔴"><a href="#1-模式定义⭐️🔴" class="headerlink" title="1. 模式定义⭐️🔴"></a>1. 模式定义⭐️🔴</h1><p>装饰者模式：<span style="background-color:#ff00ff">动态地将新功能附加到对象上</span>。在对象功能扩展方面，它比继承更有弹性，装饰者模式体现了开闭原则（OCP）❕<span style="display:none">%%<br>1032-🏡⭐️◼️涉及到动态概念的设计模式？装饰者模式、策略模式、责任链模式（通过 setter 方法可以动态的切换不同的策略）◼️⭐️-point-202301231032%%</span><br><strong>又名包装(Wrapper)模式</strong></p><h1 id="2-模式结构⭐️🔴"><a href="#2-模式结构⭐️🔴" class="headerlink" title="2. 模式结构⭐️🔴"></a>2. 模式结构⭐️🔴</h1><ul><li><strong>抽象构件（Component）角色</strong> ：定义一个抽象接口以规范准备接收附加责任的对象</li><li><strong>具体构件（Concrete Component）角色</strong> ：实现抽象构件，通过装饰角色为其添加一些职责。</li><li><strong>抽象装饰（Decorator）角色</strong> ： <span style="background-color:#ff0000">继承或实现抽象构件，并包含具体构件的实例</span>，可以通过其子类扩展具体构件的功能。</li><li><strong>具体装饰（ConcreteDecorator）角色</strong> ：实现抽象装饰的相关方法，并给具体构件对象添加附加的责任。<br>❕<span style="display:none">%%<br>1034-🏡⭐️◼️涉及到 2 个结构有 2 种 UML 关系的设计模式：装饰者模式（抽象装饰类【继承 + 聚合】抽象构件类）、组合模式（树枝节点【继承 + 组合】抽象根节点）、解释器模式（非终止表达式【继承 + 聚合】抽象表达式，而且非终止表达式为多个）◼️⭐️-point-202301231034%%</span></li></ul><h2 id="2-1-⭐️🔴-UML-图示"><a href="#2-1-⭐️🔴-UML-图示" class="headerlink" title="2.1. ⭐️🔴 UML 图示"></a>2.1. ⭐️🔴 UML 图示</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230119204836.png" alt="image.png"><br>❕<span style="display:none">%%<br>1027-🏡⭐️◼️具体装饰类（比如 Milk）是如何计算总价的 ?🔜MSTM📝 抽象装饰类，以组合方式持有被装饰的抽象构件，当具体装饰类包装了具体构件后，就将具体构件传递给抽象装饰类，就可以获取到具体构件的属性 (比如例子中的 price)，同时抽象装饰类还可以获取到具体装饰类的属性 (price)，就可以完成 cost 业务◼️⭐️-point-202301231027%%</span></p><h2 id="2-2-⭐️🔴-实现逻辑"><a href="#2-2-⭐️🔴-实现逻辑" class="headerlink" title="2.2. ⭐️🔴 实现逻辑"></a>2.2. ⭐️🔴 实现逻辑</h2><p><span style="background-color:#ff00ff">继承 + 组合 + 构造导入</span> ❕<span style="display:none">%%<br>1030-🏡⭐️◼️装饰者模式的核心逻辑：继承 + 组合 + 构造导入 抽象构件，构造导入：比如具体装饰类 Milk 构造导入具体构建类 LongBlack◼️⭐️-point-202301231030%%</span><br>继承的作用是使用抽象构件 Drink 的 cost 以及 setDesc 方法</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-comment">//抽象装饰类【继承】抽象构件类</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Decorator</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Drink</span> &#123;  <br>   <span class="hljs-comment">//抽象装饰类【组合】抽象构件类</span><br>   <span class="hljs-keyword">private</span> Drink obj;  <br>   <span class="hljs-comment">//【构造导入】抽象构件类</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-title function_">Decorator</span><span class="hljs-params">(Drink obj)</span> &#123; <span class="hljs-comment">//组合  </span><br>      <span class="hljs-built_in">this</span>.obj = obj;  <br>   &#125;  <br>     <br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> <span class="hljs-type">float</span> <span class="hljs-title function_">cost</span><span class="hljs-params">()</span> &#123;  <br>      <span class="hljs-comment">// getPrice 自己价格  </span><br>      <span class="hljs-keyword">return</span> getPrice() + obj.cost();  <br>   &#125;  <br>     <br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getDes</span><span class="hljs-params">()</span> &#123;  <br>      <span class="hljs-comment">// obj.getDes() 输出被装饰者的信息  </span><br>      <span class="hljs-keyword">return</span> des + <span class="hljs-string">&quot; &quot;</span> + getPrice() + <span class="hljs-string">&quot; &amp;&amp; &quot;</span> + obj.getDes();  <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><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_">CoffeeBar</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>      <span class="hljs-comment">// TODO Auto-generated method stub  </span><br>      <span class="hljs-comment">// 装饰者模式下的订单：2份巧克力+一份牛奶的LongBlack  </span><br>  <br>      <span class="hljs-comment">// 1. 点一份 LongBlack      </span><br>      <span class="hljs-type">Drink</span> <span class="hljs-variable">order</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">LongBlack</span>();  <br>      System.out.println(<span class="hljs-string">&quot;费用1=&quot;</span> + order.cost());  <br>      System.out.println(<span class="hljs-string">&quot;描述=&quot;</span> + order.getDes());  <br>  <br>      <span class="hljs-comment">// 2. order 加入一份牛奶  </span><br>      order = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Milk</span>(order);  <br>  <br>      System.out.println(<span class="hljs-string">&quot;order 加入一份牛奶 费用 =&quot;</span> + order.cost());  <br>      System.out.println(<span class="hljs-string">&quot;order 加入一份牛奶 描述 = &quot;</span> + order.getDes());<br>      ...<br>      ...<br>   &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="3-案例分析"><a href="#3-案例分析" class="headerlink" title="3. 案例分析"></a>3. 案例分析</h1><ol><li>咖啡种类&#x2F;单品咖啡：Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)</li><li>调料：Milk、Soy(豆浆)、Chocolate</li><li>要求在扩展新的咖啡种类时，具有良好的扩展性、改动方便、维护方便</li><li>使用 OO 的来计算不同种类咖啡的费用: 客户可以点单品咖啡，也可以单品咖啡 + 调料组合。</li></ol><h2 id="3-1-组合继承方案"><a href="#3-1-组合继承方案" class="headerlink" title="3.1. 组合继承方案"></a>3.1. 组合继承方案</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115162536.png" alt="image.png"></p><ul><li>这样设计，会有很多类。当我们增加一个单品咖啡，或者一个新的调料，类的数量就会倍增，出现类爆炸</li></ul><h2 id="3-2-装饰内置方案"><a href="#3-2-装饰内置方案" class="headerlink" title="3.2. 装饰内置方案"></a>3.2. 装饰内置方案</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115162635.png" alt="image.png"></p><ul><li>1）方案 2 可以控制类的数量，不至于造成很多的类</li><li>2）在增加或者删除调料种类时，<span style="background-color:#ffff00">代码的维护量很大</span></li><li>3）考虑到用户可以添加多份调料时，可以将 hasMilk 返回一个对应 int</li><li>4）考虑使用装饰者模式</li></ul><h2 id="3-3-装饰者模式方案"><a href="#3-3-装饰者模式方案" class="headerlink" title="3.3. 装饰者模式方案"></a>3.3. 装饰者模式方案</h2><p>示例代码：[[Decorator.java]]</p><p>装饰者模式下的订单：2 份巧克力 + 一份牛奶的 LongBlack</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115165538.png" alt="image.png"></p><p><strong>说明</strong></p><ul><li>1）Milk 包含了 LongBlack</li><li>2）一份 Chocolate 包含了 Milk + LongBlack</li><li>3）一份 Chocolate 包含了 Chocolate + Milk + LongBlack</li><li>4）这样不管是什么形式的单品咖啡 + 调料组合，通过递归方式可以方便的组合和维护</li></ul><h1 id="4-⭐️🔴优缺点"><a href="#4-⭐️🔴优缺点" class="headerlink" title="4. ⭐️🔴优缺点"></a>4. ⭐️🔴优缺点</h1><p><strong>主要优点</strong></p><ol><li>对于扩展一个对象的功能，装饰模式比继承更加灵活性，<span style="background-color:#ff00ff">不会导致类的个数急剧增加</span>。</li><li>可以通过一种<span style="background-color:#ff00ff">动态的方式来扩展一个对象的功能</span>，通过配置文件可以在运行时选择不同的具体装饰类，从而实现不同的行为。</li><li>可以对一个对象<span style="background-color:#ff00ff">进行多次装饰</span>，通过使用不同的具体装饰类以及这些装饰类的排列组合，可以创造出很多不同行为的组合，得到功能更为强大的对象。</li><li>具体构件类与具体装饰类<span style="background-color:#00ff00">可以独立变化</span>，用户可以根据需要增加新的具体构件类和具体装饰类，<span style="background-color:#ff00ff">原有类库代码无须改变，符合 “开闭原则”</span>。</li></ol><p><strong>主要缺点</strong></p><ol><li>使用装饰模式进行系统设计时将<span style="background-color:#ff0000">产生很多小对象</span>，每个对象仅负责一部分装饰任务，<span style="background-color:#ff0000">大量小对象的产生势必会占用更多的系统资源，在一定程序上影响程序的性能</span>。</li><li>装饰模式提供了<span style="background-color:#ff0000">一种比继承更加灵活机动的解决方案，但同时也意味着比继承更加易于出错，排错也很困难</span>，对于多次装饰的对象，调试时寻找错误可能需要逐级排查，较为繁琐。</li></ol><h1 id="5-应用场景⭐️🔴"><a href="#5-应用场景⭐️🔴" class="headerlink" title="5. 应用场景⭐️🔴"></a>5. 应用场景⭐️🔴</h1><ul><li><p>当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。</p><p>  <span style="background-color:#00ff00">不能采用继承的情况</span>主要有两类：</p><ul><li>第一类是系统中<span style="background-color:#ffff00">存在大量独立的扩展</span>，为支持每一种组合将产生大量的子类，<span style="background-color:#ffff00">使得子类数目呈爆炸性增长</span>；</li><li>第二类是因为<span style="background-color:#ffff00">类定义不能继承（如 final 类）</span></li></ul></li><li><p>在不影响其他对象的情况下，当对象的功能<span style="background-color:#ff00ff">要求可以动态地添加，也可以再动态地撤销时</span>。</p></li></ul><h1 id="6-⭐️🔴框架源码分析"><a href="#6-⭐️🔴框架源码分析" class="headerlink" title="6. ⭐️🔴框架源码分析"></a>6. ⭐️🔴框架源码分析</h1><h2 id="6-1-BufferedWriter"><a href="#6-1-BufferedWriter" class="headerlink" title="6.1. BufferedWriter"></a>6.1. BufferedWriter</h2><p>IO 流中的包装类使用到了装饰者模式。<span style="background-color:#00ff00">BufferedInputStream，BufferedOutputStream，BufferedReader，BufferedWriter</span>。</p><p>我们以 BufferedWriter 举例来说明，先看看如何使用 BufferedWriter</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_">Demo</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> Exception&#123;<br>        <span class="hljs-comment">//创建BufferedWriter对象</span><br>        <span class="hljs-comment">//创建FileWriter对象</span><br>        <span class="hljs-type">FileWriter</span> <span class="hljs-variable">fw</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileWriter</span>(<span class="hljs-string">&quot;C:\\Users\\Think\\Desktop\\a.txt&quot;</span>);<br>        <span class="hljs-type">BufferedWriter</span> <span class="hljs-variable">bw</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BufferedWriter</span>(fw);<br><br>        <span class="hljs-comment">//写数据</span><br>        bw.write(<span class="hljs-string">&quot;hello Buffered&quot;</span>);<br><br>        bw.close();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>结构</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230117213155.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230120064908.png" alt="image.png"></p><p><strong>小结</strong></p><p><span style="background-color:#00ff00">BufferedWriter 使用装饰者模式对 Writer 子实现类进行了增强，添加了缓冲区，提高了写数据的效率。</span></p><h2 id="6-2-FilterlnputStream"><a href="#6-2-FilterlnputStream" class="headerlink" title="6.2. FilterlnputStream"></a>6.2. FilterlnputStream</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115185633.png" alt="image.png"><br>由上图可知在 Java 中应用程序通过输入流（InputStream）的 Read 方法从源地址处读取字节，然后通过输出流（OutputStream）的 Write 方法将流写入到目的地址。</p><p>流的来源主要有三种：本地的文件（File）、控制台、通过 socket 实现的网络通信</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115185909.png" alt="image.png"></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-comment">// 是一个抽象类，即Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">InputStream</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Closeable</span> &#123;&#125; <br><span class="hljs-comment">// 是一个装饰类，即Decorator</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">FilterInputStream</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">InputStream</span> &#123; <br>    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">volatile</span> InputStream in;<br>    <span class="hljs-keyword">protected</span> <span class="hljs-title function_">FilterInputStream</span><span class="hljs-params">(InputStream in)</span> &#123;<br>        <span class="hljs-built_in">this</span>.in = in;<br>    &#125;<br>&#125;<br><span class="hljs-comment">// FilterInputStream子类，也继承了被装饰的对象 in</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DataInputStream</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">FilterInputStream</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">DataInput</span> &#123; <br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">DataInputStream</span><span class="hljs-params">(InputStream in)</span> &#123;<br>        <span class="hljs-built_in">super</span>(in);<br>    &#125;<br></code></pre></td></tr></table></figure><h3 id="6-2-1-分析⭐️🔴"><a href="#6-2-1-分析⭐️🔴" class="headerlink" title="6.2.1. 分析⭐️🔴"></a>6.2.1. 分析⭐️🔴</h3><ul><li>1）InputStream 是抽象类，类似我们前面讲的 Drink</li><li>2）FileInputStream 是 InputStream 子类，类似我们前面的 DeCaf、LongBlack</li><li>3）FilterInputStream 是 InputStream 子类，类似我们前面的 Decorator，修饰者</li><li>4）DataInputStream 是 FilterInputStream 子类，类似前面的 Milk，Soy 等，具体的修饰者</li><li>5）FilterInputStream 类有 <code>protected volatile InputStream in;</code>，即含被装饰者</li><li>6）分析得出在 JDK 的 IO 体系，就是使用装饰者模式</li></ul><h2 id="6-3-Mybatis-中的缓存设计"><a href="#6-3-Mybatis-中的缓存设计" class="headerlink" title="6.3. Mybatis 中的缓存设计"></a>6.3. Mybatis 中的缓存设计</h2><h3 id="6-3-1-CachingExecutor"><a href="#6-3-1-CachingExecutor" class="headerlink" title="6.3.1. CachingExecutor"></a>6.3.1. CachingExecutor</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230419154141.png" alt="image-20210827153314890"><br>CachingExecutor：缓存执行器，装饰器模式，在开启缓存的时候。会在上面三种执行器的外面包上 CachingExecutor；</p><h3 id="6-3-2-TransactionalCache"><a href="#6-3-2-TransactionalCache" class="headerlink" title="6.3.2. TransactionalCache"></a>6.3.2. TransactionalCache</h3><p>TransactionalCache 是一种缓存装饰器，可以为 Cache 实例增加事务功能。下面分析一下该类的逻辑。</p><h1 id="7-代理和装饰者的区别"><a href="#7-代理和装饰者的区别" class="headerlink" title="7. 代理和装饰者的区别"></a>7. 代理和装饰者的区别</h1><p>静态代理和装饰者模式的区别：</p><p><strong>相同点：</strong></p><ol><li>都要实现与目标类相同的业务接口</li><li>在两个类中都要声明目标对象</li><li>都可以在不修改目标类的前提下增强目标方法</li></ol><p><strong>不同点：</strong></p><ol><li>目的不同<br>   装饰者是为了增强目标对象<br>   静态代理是为了保护和隐藏目标对象</li><li>获取目标对象构建的地方不同<br>   装饰者是由外界传递进来，可以通过构造方法传递<br>   静态代理是在代理类内部创建，以此来隐藏目标对象</li></ol><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><p>示例代码：[[Decorator.java]]</p><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a><p><a href="https://mp.weixin.qq.com/s?__biz=MzI1NDU0MTE1NA==&mid=2247483726&idx=1&sn=df583e5b297ddaff5e1ab822df762274&chksm=e9c2ed43deb56455d2a099f3c3e8622031027d6da00202e6a7d731eac691e4d87d673ca103b5&scene=0#rd">设计模式 | 装饰者模式及典型应用</a></p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-8、适配器模式</title>
      <link href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="20230114-2010"      data-title='009- 内功心法专题 - 设计模式 -8、适配器模式 '      data-class='orange'      data-type='range'      data-end='2023-01-14'><br></span></p><h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><p>将一个类的接口转换成客户希望的另外一个接口，使得原本由于接口不兼容而不能一起工作的那些类能一起工作。</p><p>适配器模式分为&#x3D;&#x3D;类适配器模式&#x3D;&#x3D;和&#x3D;&#x3D;对象适配器模式&#x3D;&#x3D;，前者类之间的耦合度比后者高，且要求程序员了解现有组件库中的相关组件的内部结构，所以应用相对较少些。</p><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><p>适配器模式（Adapter）包含以下主要角色：</p><ul><li>目标（Target）接口：当前系统业务<span style="background-color:#00ff00">所期待的接口</span>，它可以是抽象类或接口。</li><li>适配者（Adaptee）类：它是被访问和适配的<span style="background-color:#00ff00">现有组件库</span>中的组件接口。</li><li>适配器（Adapter）类：它是一个转换器，通过继承或引用适配者的对象，把适配者接口转换成目标接口，让客户按目标接口的格式访问适配者。</li></ul><h1 id="3-工作原理"><a href="#3-工作原理" class="headerlink" title="3. 工作原理"></a>3. 工作原理</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114220152.png" alt="image.png"></p><p>适配者类就是系统中现有的接口，需要被适配拥有其他能力</p><h1 id="4-分类"><a href="#4-分类" class="headerlink" title="4. 分类"></a>4. 分类</h1><h2 id="4-1-类适配器"><a href="#4-1-类适配器" class="headerlink" title="4.1. 类适配器"></a>4.1. 类适配器</h2><p>示例代码：[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;adapter&#x2F;classadapter&#x2F;VoltageAdapter.java]]</p><h3 id="4-1-1-实现逻辑"><a href="#4-1-1-实现逻辑" class="headerlink" title="4.1.1. 实现逻辑"></a>4.1.1. 实现逻辑</h3><p><span style="background-color:#ff00ff">基本介绍：Adapter 类，通过继承 src 类 (源头类)，实现 dst 类接口 (目标接口)，完成 src-&gt;dst 的适配</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114225731.png" alt="image.png"></p><h3 id="4-1-2-优缺点"><a href="#4-1-2-优缺点" class="headerlink" title="4.1.2. 优缺点"></a>4.1.2. 优缺点</h3><ol><li>缺点：</li></ol><ul><li>违背了合成复用原则</li><li>Java 是单继承机制，所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口，有一定局限性;</li><li>src 类的方法在 Adapter 中都会暴露出来，也增加了使用的成本。</li></ul><ol start="3"><li>优点：由于其继承了 src 类，所以它可以根据需求重写 src 类的方法，使得 Adapter 的灵活性增强了。</li></ol><h2 id="4-2-对象适配器"><a href="#4-2-对象适配器" class="headerlink" title="4.2. 对象适配器"></a>4.2. 对象适配器</h2><h3 id="4-2-1-实现逻辑⭐️🔴"><a href="#4-2-1-实现逻辑⭐️🔴" class="headerlink" title="4.2.1. 实现逻辑⭐️🔴"></a>4.2.1. 实现逻辑⭐️🔴</h3><ol><li>基本思路和类的适配器模式相同，只是将 Adapter 类作修改，<span style="background-color:#ff00ff">不是继承 src 类，而是持有 src 类的实例</span>，以解决兼容性的问题。 即：<span style="background-color:#00ff00">持有 src 类，实现 dst 类接口，完成 src-&gt;dst 的适配</span></li><li>根据“合成复用原则”，在系统中尽量使用关联关系（聚合）来替代继承关系。</li><li>对象适配器模式是适配器模式常用的一种</li></ol><h4 id="4-2-1-1-⭐️🔴UML-图示"><a href="#4-2-1-1-⭐️🔴UML-图示" class="headerlink" title="4.2.1.1. ⭐️🔴UML 图示"></a>4.2.1.1. ⭐️🔴UML 图示</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230116104719.png" alt="image.png"></p><h4 id="4-2-1-2-实现逻辑⭐️🔴"><a href="#4-2-1-2-实现逻辑⭐️🔴" class="headerlink" title="4.2.1.2. 实现逻辑⭐️🔴"></a>4.2.1.2. 实现逻辑⭐️🔴</h4><p><span style="background-color:#ff00ff">实现 dst+ 组合 src+ 构造导入 src</span><span style="display:none">%%🏡⭐️◼️适配器模式：聚合 src+ 实现 dst◼️⭐️-point-202301152324%%</span></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">class</span> <span class="hljs-title class_">VoltageAdapter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">IVoltage5V</span> &#123;  <br>    <span class="hljs-comment">//适配器类【组合】被适配者类</span><br>    <span class="hljs-keyword">private</span> Voltage220V voltage220V;  <br>    <span class="hljs-comment">//通过【构造导入】一个 Voltage220V 实例    </span><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">VoltageAdapter</span><span class="hljs-params">(Voltage220V voltage220v)</span> &#123;  <br>        <span class="hljs-built_in">this</span>.voltage220V = voltage220v;  <br>    &#125;  <br>  <br>    <span class="hljs-meta">@Override</span>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">output5V</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-type">int</span> <span class="hljs-variable">dst</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;  <br>        <span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> != voltage220V) &#123;  <br>            <span class="hljs-type">int</span> <span class="hljs-variable">src</span> <span class="hljs-operator">=</span> voltage220V.output220V();<span class="hljs-comment">//获取220V 电压  </span><br>            System.out.println(<span class="hljs-string">&quot;使用对象适配器，进行适配~~&quot;</span>);  <br>            dst = src / <span class="hljs-number">44</span>;  <br>            System.out.println(<span class="hljs-string">&quot;适配完成，输出的电压为=&quot;</span> + dst);  <br>        &#125;  <br>        <span class="hljs-keyword">return</span> dst;  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h3 id="4-2-2-优缺点⭐️🔴"><a href="#4-2-2-优缺点⭐️🔴" class="headerlink" title="4.2.2. 优缺点⭐️🔴"></a>4.2.2. 优缺点⭐️🔴</h3><ol><li>对象适配器和类适配器其实算是同一种思想，只不过实现方式不同。 根据合成复用原则，使用组合替代继承， 所以它解决了类适配器必须继承 src 的局限性问题，也不再要求 dst 必须是接口。</li><li>使用成本更低，更灵活。</li></ol><h2 id="4-3-接口适配器"><a href="#4-3-接口适配器" class="headerlink" title="4.3. 接口适配器"></a>4.3. 接口适配器</h2><ol><li>一些书籍称为：适配器模式 (Default Adapter Pattern) 或缺省适配器模式。</li><li>核心思路：当不需要全部实现接口提供的方法时，<span style="background-color:#00ff00">可先设计一个抽象类实现接口，并为该接口中<font color=#ff9900>每个方法</font>提供一个默认实现（空方法）</span>，那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求</li><li>适用于一个接口不想使用其所有的方法的情况。</li></ol><p><span style="background-color:#ff00ff">类似于接口隔离的设计原则</span></p><h1 id="5-应用场景⭐️🔴"><a href="#5-应用场景⭐️🔴" class="headerlink" title="5. 应用场景⭐️🔴"></a>5. 应用场景⭐️🔴</h1><ul><li><span style="background-color:#00ff00">以前开发的系统</span>存在满足新系统功能需求的类，但其接口同新系统的接口不一致。</li><li><span style="background-color:#00ff00">使用第三方提供的组件</span>，但组件接口定义和自己要求的接口定义不同。</li></ul><h1 id="6-JDK-源码分析⭐️🔴"><a href="#6-JDK-源码分析⭐️🔴" class="headerlink" title="6. JDK 源码分析⭐️🔴"></a>6. JDK 源码分析⭐️🔴</h1><h2 id="6-1-InputStreamReader"><a href="#6-1-InputStreamReader" class="headerlink" title="6.1. InputStreamReader"></a>6.1. InputStreamReader</h2><p><span style="display:none">%%🏡⭐️◼️InputStreamDecoder 是适配器类，持有 src：InputStream 并继承了 dst：Reader，实现字节流到字符流的转换◼️⭐️-point-202301150726%%</span><br>Reader（字符流）、InputStream（字节流）的适配使用的是 InputStreamReader。</p><p>InputStreamReader 继承自 java.io 包中的 Reader，对他中的抽象的未实现的方法给出实现。如：</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_">read</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>    <span class="hljs-keyword">return</span> sd.read();<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">read</span><span class="hljs-params">(<span class="hljs-type">char</span> cbuf[], <span class="hljs-type">int</span> offset, <span class="hljs-type">int</span> length)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>    <span class="hljs-keyword">return</span> sd.read(cbuf, offset, length);<br>&#125;<br></code></pre></td></tr></table></figure><p>如上代码中的 sd（StreamDecoder 类对象），在 Sun 的 JDK 实现中，实际的方法实现是对 sun.nio.cs.StreamDecoder 类的同名方法的调用封装。类结构图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114230616.png" alt="image.png"></p><p>从上图可以看出：</p><ul><li>InputStreamReader 是对同样实现了 Reader 的 StreamDecoder 的封装。</li><li>StreamDecoder 不是 Java SE API 中的内容，是 Sun  JDK 给出的自身实现。但我们知道他们对构造方法中的字节流类（InputStream）进行封装，并通过该类进行了字节流和字符流之间的解码转换。</li></ul><p><font color="red">结论：</font></p><pre><code>适配者类(StreamDecoder)持有src(适配者类InputStream)，实现或者继承dst(目标类Reader)</code></pre><p>​<span style="background-color:#ff00ff">从表层来看，InputStreamReader 做了 InputStream 字节流类到 Reader 字符流之间的转换。而从如上 Sun JDK 中的实现类关系结构中可以看出，是 StreamDecoder 的设计实现在实际上采用了适配器模式。</span></p><h2 id="6-2-SpringMVC-的-HandlerAdapter⭐️🔴"><a href="#6-2-SpringMVC-的-HandlerAdapter⭐️🔴" class="headerlink" title="6.2. SpringMVC 的 HandlerAdapter⭐️🔴"></a>6.2. SpringMVC 的 HandlerAdapter⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115091216.png" alt="image.png"></p><h3 id="6-2-1-源码分析"><a href="#6-2-1-源码分析" class="headerlink" title="6.2.1. 源码分析"></a>6.2.1. 源码分析</h3><p>源码：</p><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;SpringMVC&#x2F;srclib&#x2F;spring-webmvc-4.0.0.RELEASE.jar!&#x2F;org&#x2F;springframework&#x2F;web&#x2F;servlet&#x2F;HandlerAdapter.class</p><ol><li>SpringMvc 中的 HandlerAdapter 使用了适配器模式</li><li>使用 HandlerAdapter 的原因分析: 可以看到处理器的类型不同，有多种实现方式，那么调用方式就不是确定的，<span style="background-color:#ff00ff">如果需要直接调用 Controller 方法，需要调用的时候就得不断是使用 ifelse 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller，就得修改原来的代码，这样违背了 OCP 原则</span>。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114231608.png" alt="image.png"></p><h4 id="6-2-1-1-HandlerAdapter-实现"><a href="#6-2-1-1-HandlerAdapter-实现" class="headerlink" title="6.2.1.1. HandlerAdapter 实现"></a>6.2.1.1. HandlerAdapter 实现</h4><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">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">HandlerAdapter</span> &#123;<br><br>    <span class="hljs-comment">// 判断当前的这个HandlerAdapter  是否 支持给与的handler</span><br>    <span class="hljs-comment">// 因为一般来说：每个适配器只能作用于一种处理器</span><br>    <span class="hljs-type">boolean</span> <span class="hljs-title function_">supports</span><span class="hljs-params">(Object handler)</span>;<br>    <br>    <span class="hljs-comment">// 核心方法：利用 Handler 处理请求，然后返回一个ModelAndView </span><br>    <span class="hljs-comment">// DispatcherServlet最终就是调用此方法，来返回一个ModelAndView的</span><br>    <span class="hljs-meta">@Nullable</span><br>    ModelAndView <span class="hljs-title function_">handle</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="hljs-keyword">throws</span> Exception;<br>    <span class="hljs-comment">// 同HttpServlet 的 getLastModified方法</span><br>    <span class="hljs-comment">// Can simply return -1 if there&#x27;s no support in the handler class.</span><br>    <span class="hljs-type">long</span> <span class="hljs-title function_">getLastModified</span><span class="hljs-params">(HttpServletRequest request, Object handler)</span>;<br><br>&#125;<br></code></pre></td></tr></table></figure><h5 id="6-2-1-1-1-supports⭐️🔴"><a href="#6-2-1-1-1-supports⭐️🔴" class="headerlink" title="6.2.1.1.1. supports⭐️🔴"></a>6.2.1.1.1. supports⭐️🔴</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230325102355.png" alt="image.png"></p><p><span style="background-color:#ff0000">不同的 HandlerAdapter 分别预先定义了不同的 supports 方法，通过 instanceof 分别与不同的类型的 controller 对应</span>  <span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230325-1028%%</span>❕ ^qh1uma</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126072159.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115075631.png" alt="image.png"></p><p>RequestMappingHandlerAdapter 继承自 AbstractHandlerMethodAdapter<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230325102314.png" alt="image.png"></p><p><code>HandlerAdapter.supports()</code> 方法的主要作用在于判断当前的 HandlerAdapter 是否能够支持当前的 handler 的适配。这里的 handler 是由 <code>HandlerExecutionChain HandlerMapping.getHandler(HttpServletRequest)</code> 方法获取到的。从这里可以看出：<br><span style="background-color:#ff00ff">每一种 Controller 或者其他请求方式都有一个 HandlerAdapter 与之对应，通过在遍历中调用 supports 方法中 instanceof 的方式进行判断匹配</span></p><h5 id="6-2-1-1-2-handle"><a href="#6-2-1-1-2-handle" class="headerlink" title="6.2.1.1.2. handle"></a>6.2.1.1.2. handle</h5><p><span style="background-color:#ff00ff">handle 方法入参中有 <code>mappedHandler.getHandler()</code>，因此可以看出 SpringMVC 的适配器模式是对象适配器模式</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115080039.png" alt="image.png"></p><p><span style="background-color:#ff00ff">在进入到 handle 方法中时，传入是 Object，但因为对应的 HandlerAdapter 子类已经是确定的，可以直接强转</span>，然后调用 handleRequest 方法<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115091055.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115091120.png" alt="image.png"></p><p>执行真正开发者开发的处理方法的地方。<code>Spring MVC</code> 自动帮我们完成数据绑定、视图渲染等等一切周边工作</p><h5 id="6-2-1-1-3-getLastModified"><a href="#6-2-1-1-3-getLastModified" class="headerlink" title="6.2.1.1.3. getLastModified"></a>6.2.1.1.3. getLastModified</h5><p>获取当前请求的最后更改时间，主要用于供给浏览器判断当前请求是否修改过，从而判断是否可以直接使用之前缓存的结果</p><h4 id="6-2-1-2-getHandler⭐️🔴"><a href="#6-2-1-2-getHandler⭐️🔴" class="headerlink" title="6.2.1.2. getHandler⭐️🔴"></a>6.2.1.2. getHandler⭐️🔴</h4><p><code>HandlerExecutionChain mappedHandler = this.getHandler(processedRequest)</code><br><code>HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler())</code></p><ul><li><code>HandlerMapping</code> 的作用主要是根据 request 请求匹配&#x2F;映射上能够处理当前 request 的 handler</li><li><code>HandlerAdapter</code> 的作用在于将 request 中的各个属性，如 <code>request param</code> 适配为 handler 能够处理的形式 - 参数绑定、数据校验、内容协商…几乎所有的 web 层问题都在在这里完成的。</li></ul><h4 id="6-2-1-3-getHandlerAdapter-入参-hadler-是-Object-匹配过程⭐️🔴"><a href="#6-2-1-3-getHandlerAdapter-入参-hadler-是-Object-匹配过程⭐️🔴" class="headerlink" title="6.2.1.3. getHandlerAdapter- 入参 hadler 是 Object- 匹配过程⭐️🔴"></a>6.2.1.3. getHandlerAdapter- 入参 hadler 是 Object- 匹配过程⭐️🔴</h4><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230324-1444%%</span>❕ ^xcsdza</p><p><span style="background-color:#ff00ff">遍历 handlerAdapters 集合，调用 supports 方法</span>，<span style="background-color:#00ff00">根据 handler 的类型 instanceof 获取到对应的 HandlerAdapter 子类</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115075721.png" alt="image.png"></p><p>对应的子类有如下类型<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115075802.png" alt="image.png"></p><h4 id="6-2-1-4-doDispatch-分发流程"><a href="#6-2-1-4-doDispatch-分发流程" class="headerlink" title="6.2.1.4. doDispatch 分发流程"></a>6.2.1.4. doDispatch 分发流程</h4><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">DispatcherServlet：<br>    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doDispatch</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        ...<br>        <span class="hljs-comment">//1、根据URL（当然不一定非得是URL）匹配到一个处理器</span><br>        mappedHandler = getHandler(processedRequest);<br>        <span class="hljs-keyword">if</span> (mappedHandler == <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-comment">// 若匹配不到Handler处理器，就404了</span><br>            noHandlerFound(processedRequest, response);<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br><br>        <span class="hljs-comment">//2、从Chain里拿出Handler（注意是Object类型哦~ ）然后找到属于它的适配器</span><br>        <span class="hljs-type">HandlerAdapter</span> <span class="hljs-variable">ha</span> <span class="hljs-operator">=</span> getHandlerAdapter(mappedHandler.getHandler());<br>        ...<br>        <span class="hljs-comment">//3、执行作用在此Handler上的所有拦截器的Pre方法</span><br>        <span class="hljs-keyword">if</span> (!mappedHandler.applyPreHandle(processedRequest, response)) &#123;<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        <span class="hljs-comment">//4、真正执行handle方法（也就是你自己书写的逻辑方法），得到一个ModelAndView</span><br>        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());<br><br>        <span class="hljs-comment">//5、视图渲染</span><br>        applyDefaultViewName(processedRequest, mv);<br>        <span class="hljs-comment">//6、执行拦截器的post方法（可见它是视图渲染完成了才会执行的哦~）</span><br>        mappedHandler.applyPostHandle(processedRequest, response, mv);<br>        ...<br>        <span class="hljs-comment">//7、执行拦截器的afterCompletion方法（不管抛出与否）</span><br>    &#125;<br></code></pre></td></tr></table></figure><p>从执行步骤中可以看到：<code>HandlerAdapter</code> 对于执行流程的通用性起到了非常重要的作用，<span style="background-color:#ff00ff">它能把任何一个处理器（Object）</span>都适配成一个 <code>HandlerAdapter</code>，从而可以做统一的流程处理，<strong>这也是为何</strong><code>DispatcherServlet</code><strong>它能作为其它 web 处理框架的分发器的原因（因为它没有耦合具体的处理器，我们完全可以自己去实现）</strong></p><h3 id="6-2-2-扩展性分析⭐️🔴⭐️🔴"><a href="#6-2-2-扩展性分析⭐️🔴⭐️🔴" class="headerlink" title="6.2.2. 扩展性分析⭐️🔴⭐️🔴"></a>6.2.2. 扩展性分析⭐️🔴⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115091216.png" alt="image.png"></p><h4 id="6-2-2-1-适配器-HandlerAdapter⭐️🔴"><a href="#6-2-2-1-适配器-HandlerAdapter⭐️🔴" class="headerlink" title="6.2.2.1. 适配器 HandlerAdapter⭐️🔴"></a>6.2.2.1. 适配器 HandlerAdapter⭐️🔴</h4><p><strong>为什么要用适配器 HandlerAdapter</strong></p><p><code>Spring MVC</code> 的 <code>Handler</code> 有四种不同的表现形式，包括 <strong>Controller 接口，HttpRequestHandler，Servlet、@RequestMapping</strong>，那么调用方式就不是确定的，如果需要直接调用 Controller 方法，需要调用的时候就得不断使用 if else 来进行判断是哪一种子类然后执行。<br>如果后面要扩展 Controller，就得修改原来的代码，这样违背了开闭原则。❕<span style="display:none">%%<br>1710-🏡⭐️◼️SpringMVC 为什么要用适配器模式 ?🔜MSTM📝 ◼️⭐️-point-202302071710%%</span></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">if</span>(mappedHandler.getHandler() <span class="hljs-keyword">instanceof</span> MultiActionController)&#123;  <br>   ((MultiActionController)mappedHandler.getHandler()).xxx  <br>&#125;<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(mappedHandler.getHandler() <span class="hljs-keyword">instanceof</span> XXX)&#123;  <br>    ...  <br>&#125;<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(...)&#123;  <br>   ...  <br>&#125;<br></code></pre></td></tr></table></figure><p>而 HandlerAdapter 设计模式下，<span style="background-color:#ff0000">getHandlerAdapter- 入参是 Object 就能模糊掉具体的实现</span>，后面通过<span style="background-color:#ff00ff">supports 方法中的 instanceof</span>进行判断，就替换了如果不用设计模式下的一坨坨 ifelse。</p><p><span style="background-color:#00ff00;font-weight:bold">从根本上说，这些 ifelse 并没有消失，而是转移了。转移到我们需要事先定义好 Handler 和 HandlerAdapter 的对应关系，即一个 Handler 要配对一个 HandlerAdapter，然后才能在 supports 方法中的 instanceof 进行判断</span><span style="display:none">%%🏡⭐️◼️对 HandlerAdapter 最终理解：ifelse 最后去哪了？◼️⭐️-point-202301151221%%</span></p><p>体现了<span style="background-color:#00ff00"> 约定优于配置，配置优于编码 (硬代码)</span>的思想</p><h4 id="6-2-2-2-src：Adaptee-被适配者"><a href="#6-2-2-2-src：Adaptee-被适配者" class="headerlink" title="6.2.2.2. src：Adaptee(被适配者)"></a>6.2.2.2. src：Adaptee(被适配者)</h4><p><code>Handler</code>（<strong>Controller 接口，HttpRequestHandler，Servlet、@RequestMapping</strong>）有四种不同的表现形式及调用方式</p><h4 id="6-2-2-3-dst：Target-目标类"><a href="#6-2-2-3-dst：Target-目标类" class="headerlink" title="6.2.2.3. dst：Target(目标类)"></a>6.2.2.3. dst：Target(目标类)</h4><p><span style="background-color:#00ff00">Controller 具体实现类</span></p><h4 id="6-2-2-4-总结"><a href="#6-2-2-4-总结" class="headerlink" title="6.2.2.4. 总结"></a>6.2.2.4. 总结</h4><ol><li>首先是适配器接口 DispatchServlet 中<span style="background-color:#00ff00">有一个集合维护所有的 HandlerAdapter</span>，如果配置文件中没有对适配器进行配置，那么 DispatchServlet 会在创建时对该变量进行初始化，注册所有默认的 HandlerAdapter。</li><li>当一个请求过来时，DispatchServlet 会<span style="background-color:#00ff00">根据传过来的 handler 类型</span>从该集合中寻找对应的 HandlerAdapter 子类进行处理，并且调用它的 handler() 方法</li><li>对应的 HandlerAdapter 中的 handler() 方法又会<span style="background-color:#00ff00">执行对应 Controller 的 handleRequest() 方法</span></li></ol><p><span style="background-color:#ff0000">适配器与 handler 有对应关系</span>，而各个适配器又都是适配器接口的实现类，因此，<span style="background-color:#00ff00">它们都遵循相同的适配器标准，所以用户可以按照相同的方式，通过不同的 handler 去处理请求</span>。当然了，Spring 框架中也为我们定义了一些默认的 Handler 对应的适配器。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230115132629.png" alt="image.png"></p><p>通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理，免去了写大量的 ifelse 语句对 Controller 进行判断，也更利于扩展新的 Controller 类型</p><p>AbstractHandlerMethodAdapter<br>从命名中其实就可以看出端倪，它主要是支持到了 <code>org.springframework.web.method.HandlerMethod</code> 这种处理器，显然这种处理器也是我们 <strong>最为常用的</strong>。它的子类 <code>RequestMappingHandlerAdapter</code> 把 <code>HandlerMethod</code> 的实现精确到了 <code>@RequestMapping</code> 注解方案。这个 <code>HandlerAdapter</code> 可谓是 <code>Spring MVC</code> 的精华之所在</p><h3 id="6-2-3-大致流程"><a href="#6-2-3-大致流程" class="headerlink" title="6.2.3. 大致流程"></a>6.2.3. 大致流程</h3><ol><li><p>首先是适配器接口 DispatchServlet 中有一个集合维护所有的 HandlerAdapter，如果配置文件中没有对适配器进行配置，那么 DispatchServlet 会在创建时对该变量进行初始化，注册所有默认的 HandlerAdapter。</p></li><li><p>当一个请求过来时，DispatchServlet 会根据传过来的 handler 类型从该集合中寻找对应的 HandlerAdapter 子类进行处理，并且调用它的 handler() 方法</p></li><li><p>对应的 HandlerAdapter 中的 handler() 方法又会执行对应 Controller 的 handleRequest() 方法</p></li></ol><h3 id="6-2-4-示例代码"><a href="#6-2-4-示例代码" class="headerlink" title="6.2.4. 示例代码"></a>6.2.4. 示例代码</h3><p>&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;架构师之路&#x2F;尚硅谷 - 设计模式&#x2F;代码&#x2F;Spring&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;test&#x2F;Adapter.java</p><h2 id="6-3-AOP-中-Advice-适配案例"><a href="#6-3-AOP-中-Advice-适配案例" class="headerlink" title="6.3. AOP 中 Advice 适配案例"></a>6.3. AOP 中 Advice 适配案例</h2><p>#todo</p><span style="display:none">- [ ] 🚩 - SpringAOP 中 Advice 适配案例：https://blog.csdn.net/yuan882696yan/article/details/105602359 - 🏡 2023-02-09 21:59</span>https://blog.csdn.net/yuan882696yan/article/details/105602359<p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230210231559.png" alt="image.png"></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><p>&#x3D;&#x3D;适配器模式，韩顺平老师案例要好一些&#x3D;&#x3D;<br>示例代码手写 mvc：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;SpringMVC</p><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a><p>[[Spring MVC适配器模式实践之HandlerAdapter源码分析【享学Spring MVC】 - 腾讯云开发者社区-腾讯云]]</p><p>[[(210条消息) 从SpringMVC来看适配器模式__PPB的博客-CSDN博客_springmvc适配器模式]]</p><p>[[随遇而安的适配器模式  Spring 中的适配器_Java_大头星_InfoQ写作社区]]</p><p>[[设计模式  适配器模式及典型应用 - 掘金]]<br>![[设计模式  适配器模式及典型应用 - 掘金#^57xek8]]<br>![[设计模式  适配器模式及典型应用 - 掘金#^m50nku]]</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-6、建造者模式</title>
      <link href="/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-6%E3%80%81%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-6%E3%80%81%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<h1 id="1-模式定义"><a href="#1-模式定义" class="headerlink" title="1. 模式定义"></a>1. 模式定义</h1><ul><li>建造者模式（Builder Pattern）又叫<span style="background-color:#ffff00">生成器模式</span>，是一种对象构建模式。它可以<span style="background-color:#ff0000">将复杂对象的建造过程抽象出来</span>：部件构造(Builder)和装配流程(Director)</li><li>建造者模式<span style="background-color:#ff00ff">将部件的构造(由Builder来负责)和装配(组装流程，由Director负责)分开</span>，是一步一步创建一个复杂的对象，它<span style="background-color:#00ff00">允许用户只通过指定复杂对象的类型和内容就可以构建它们，用户不需要知道内部的具体构建细节</span></li><li>由于实现了构建和装配的解耦。不同的构建器，相同的装配，也可以做出不同的对象；相同的构建器，不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦，实现了更好的复用。 ❕<span style="display:none">%%<br>1120-🏡⭐️◼️建造者模式的核心思想是什么？🔜📝❔将复杂对象的创建过程抽象成部件构造Builder和组装流程Director，并将其分离开来，通过不同构建的不同Builder和不同Builder来一步步建造复杂对象◼️⭐️-point-202301271120%%</span></li></ul><h1 id="2-模式结构"><a href="#2-模式结构" class="headerlink" title="2. 模式结构"></a>2. 模式结构</h1><h2 id="2-1-UML⭐️🔴"><a href="#2-1-UML⭐️🔴" class="headerlink" title="2.1. UML⭐️🔴"></a>2.1. UML⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125193434.png" alt="image.png"></p><h2 id="2-2-实现逻辑⭐️🔴"><a href="#2-2-实现逻辑⭐️🔴" class="headerlink" title="2.2. 实现逻辑⭐️🔴"></a>2.2. 实现逻辑⭐️🔴</h2><p><span style="background-color:#00ff00">将传统方案中功能混杂的抽象类AbstractHouse进行拆解</span></p><ol><li>构造&#x3D;&#x3D;抽象建造者类（Builder）&#x3D;&#x3D;：<span style="background-color:#00ff00">组合产品类</span>，封装生产产品每个步骤为抽象方法，将产品（House）与具体构建方法解耦</li><li>构造&#x3D;&#x3D;指挥者类（Director）&#x3D;&#x3D;：<span style="background-color:#00ff00">聚合抽象建造者类</span>，封装产品的装配流程，将构建方法与装配流程解耦，负责产品的生产流程。同时隔离客户与产品的生产过程</li><li>构造&#x3D;&#x3D;具体建造者（ConcreteBuilder）&#x3D;&#x3D;：<span style="background-color:#00ff00">实现抽象建造者类或者接口</span>，完成创建步骤或者制作产品组件<br>❕<span style="display:none">%%<br>0844-🏡⭐️◼️建造者模式的核心逻辑是什么？🔜📝❔抽象建造者组合产品，指挥者聚合抽象建造者并构造导入，具体建造者继承或者实现抽象建造者，client在调用指挥者时传入不同的具体建造者作为构造入参◼️⭐️-point-202301230844%%</span></li></ol><h1 id="3-案例分析⭐️🔴"><a href="#3-案例分析⭐️🔴" class="headerlink" title="3. 案例分析⭐️🔴"></a>3. 案例分析⭐️🔴</h1><h2 id="3-1-建房子"><a href="#3-1-建房子" class="headerlink" title="3.1. 建房子"></a>3.1. 建房子</h2><ol><li>需要建房子：这一过程为打桩、砌墙、封顶，这个过程不同房子都是相同的。</li><li>房子有各种各样的，比如普通房，高楼，别墅，各种房子的过程虽然一样，但是创建过程中的3个步骤要求是不相同的。</li></ol><h3 id="3-1-1-传统方式"><a href="#3-1-1-传统方式" class="headerlink" title="3.1.1. 传统方式"></a>3.1.1. 传统方式</h3><h4 id="3-1-1-1-UML"><a href="#3-1-1-1-UML" class="headerlink" title="3.1.1.1. UML"></a>3.1.1.1. UML</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230113095840.png" alt="image.png"></p><h4 id="3-1-1-2-案例分析"><a href="#3-1-1-2-案例分析" class="headerlink" title="3.1.1.2. 案例分析"></a>3.1.1.2. 案例分析</h4><ol><li>将构建方法（每一个步骤，即每个组件的构建方法或者制造方法）抽象封装为抽象类</li><li>具体产品也继承了该抽象类，导致具体产品与它的构建方法耦合</li><li>将装配流程（所有步骤顺序，即装配流程）也放到了该抽象类中，导致构建方法与装配流程耦合<br>解决方案就是使用建造者模式将2）、3）中提到的耦合解开</li></ol><h3 id="3-1-2-UML"><a href="#3-1-2-UML" class="headerlink" title="3.1.2. UML"></a>3.1.2. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125193953.png" alt="image.png"></p><h3 id="3-1-3-示例代码"><a href="#3-1-3-示例代码" class="headerlink" title="3.1.3. 示例代码"></a>3.1.3. 示例代码</h3><p>建房子案例：[[HouseBuilder.java]]</p><h2 id="3-2-生产自行车-✅"><a href="#3-2-生产自行车-✅" class="headerlink" title="3.2. 生产自行车 ✅"></a>3.2. 生产自行车 ✅</h2><ol><li>生产自行车是一个复杂的过程，它包含了车架，车座等组件的生产。这个过程所有自行车是相同的。</li><li>自行车分为摩拜和ofo等，不同品牌自行车的车架有碳纤维，铝合金等材质的，车座有橡胶，真皮等材质。</li></ol><h3 id="3-2-1-UML"><a href="#3-2-1-UML" class="headerlink" title="3.2.1. UML"></a>3.2.1. UML</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230125193825.png" alt="image.png"></p><h3 id="3-2-2-示例代码"><a href="#3-2-2-示例代码" class="headerlink" title="3.2.2. 示例代码"></a>3.2.2. 示例代码</h3><p>自行车案例：[[Builder.java]]</p><h2 id="3-3-场景总结⭐️🔴"><a href="#3-3-场景总结⭐️🔴" class="headerlink" title="3.3. 场景总结⭐️🔴"></a>3.3. 场景总结⭐️🔴</h2><p><span style="background-color:#00ff00">创建流程相同</span><br><span style="background-color:#00ff00">创建流程中的细节不同</span> ❕<span style="display:none">%%<br>0844-🏡⭐️◼️建造者模式适用场景？建造步骤相同，每个建造步骤细节不同。而模板方法模式是有几个步骤细节也是相同的◼️⭐️-point-202301230844%%</span></p><h1 id="4-优缺点⭐️🔴"><a href="#4-优缺点⭐️🔴" class="headerlink" title="4. 优缺点⭐️🔴"></a>4. 优缺点⭐️🔴</h1><p><strong>优点：</strong></p><ol><li>建造者模式的封装性很好。使用建造者模式可以<span style="background-color:#00ff00">有效的封装变化</span>，在使用建造者模式的场景中，一般产品类和建造者类是比较稳定的，因此，将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。</li><li>在建造者模式中，<span style="background-color:#00ff00">客户端不必知道产品内部组成的细节，将产品本身与产品的创建过程解耦</span>，使得相同的创建过程可以创建不同的产品对象。</li><li><span style="background-color:#00ff00">可以更加精细地控制产品的创建过程</span> 。将复杂产品的创建步骤分解在不同的方法中，使得创建过程更加清晰，也更方便使用程序来控制创建过程。</li><li><span style="background-color:#00ff00">建造者模式很容易进行扩展</span>。如果有新的需求，通过实现一个新的建造者类就可以完成，基本上不用修改之前已经测试通过的代码，因此也就不会对原有功能引入风险。符合开闭原则。</li></ol><p><strong>缺点：</strong></p><ol><li>造者模式所创建的<span style="background-color:#ffff00">产品一般具有较多的共同点，其组成部分相似</span>，如果产品之间的差异性很大，则不适合使用建造者模式，因此其使用范围受到一定的限制。比如ofo和摩拜可以构成建造者模式，而ofo和iPhone就无法构成建造者模式。</li><li>如果需要组装的构件太多的话，<span style="background-color:#ffff00">会生成很多的类</span> ❕<span style="display:none">%%<br>0729-🏡⭐️◼️建造者模式的缺点🔜📝 1. 一组产品必须是相似的，比如ofo和摩拜就可以使用建造者模式，而ofo和iPhone就无法使用建造者模式 2.如果建造对象过于复杂，则会产生很多类，易维护性差◼️⭐️-point-202301300729%%</span></li></ol><h1 id="5-适用场景⭐️🔴"><a href="#5-适用场景⭐️🔴" class="headerlink" title="5. 适用场景⭐️🔴"></a>5. 适用场景⭐️🔴</h1><p>建造者（Builder）模式创建的是复杂对象，其产品的各个部分经常面临着剧烈的变化，但将它们组合在一起的算法却相对稳定，所以它通常在以下场合使用。❕<span style="display:none">%%<br>0731-🏡⭐️◼️建造者模式的适用场景是什么？🔜📝 对象各部分构件变化很大，但不同对象构件的组装流程极为相似◼️⭐️-point-202301300731%%</span></p><ul><li>创建的<span style="background-color:#00ff00">对象较复杂</span>，由多个部件构成，<span style="background-color:#00ff00">各部件面临着复杂的变化，但构件间的建造顺序是稳定的。</span></li><li>创建复杂对象的<span style="background-color:#00ff00">算法独立于该对象的组成部分以及它们的装配方式</span>，即产品的构建过程和最终的表示是独立的。</li></ul><h1 id="6-JDK-源码分析⭐️🔴"><a href="#6-JDK-源码分析⭐️🔴" class="headerlink" title="6. JDK 源码分析⭐️🔴"></a>6. JDK 源码分析⭐️🔴</h1><h2 id="6-1-StringBuilder类图"><a href="#6-1-StringBuilder类图" class="headerlink" title="6.1. StringBuilder类图"></a>6.1. StringBuilder类图</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230113171233.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230113171134.png" alt="image.png"></p><h2 id="6-2-角色分析"><a href="#6-2-角色分析" class="headerlink" title="6.2. 角色分析"></a>6.2. 角色分析</h2><ul><li><code>Appendable</code>接口定义了多个<code>append</code>方法（抽象方法），即<code>Appendable</code>为抽象建造者，定义了抽象方法</li><li><code>AbstractStringBuilder</code>实现了<code>Appendable</code>接口方法，这里的<code>AbstractStringBuilder</code>已经是建造者，只是不能实例化</li><li><code>StringBuilder</code><span style="background-color:#00ff00">既充当了指挥者角色，又充当了具体的建造者</span>，建造方法的实现是由<code>AbstractStringBuilder</code>完成，<code>StringBuilder</code>继承了<code>AbstractStringBuilder</code></li></ul><h1 id="7-注意事项和细节"><a href="#7-注意事项和细节" class="headerlink" title="7. 注意事项和细节"></a>7. 注意事项和细节</h1><p>● 1）&#x3D;&#x3D;客户端（使用程序）不必知道产品内部组成的细节&#x3D;&#x3D;，<span style="background-color:#00ff00">将产品本身与产品的创建过程解耦</span>，使得相同的创建过程可以创建不同的产品对象<br>● 2）&#x3D;&#x3D;每一个具体建造者都相对独立，而与其他的具体建造者无关&#x3D;&#x3D;，因此可以很方便地替换具体建造者或增加新的具体建造者，用户使用不同的具体建造者即可得到不同的产品对象<br>● 3）可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中，使得创建过程更加清晰，也更方便使用程序来控制创建过程<br>● 4）增加新的具体建造者无须修改原有类库的代码，指挥者类针对抽象建造者类编程，系统扩展方便，符合“开闭原则”<br>● 5）<span style="background-color:#ffff00">建造者模式所创建的产品一般具有较多的共同点，其组成部分相似，如果产品之间的差异性很大，则不适合使用建造者模式</span>，因此其使用范围受到一定的限制<br>● 6）<span style="background-color:#ffff00">如果产品的内部变化复杂，可能会导致需要定义很多具体建造者类来实现这种变化，导致系统变得很庞大</span>。因此在这种情况下，要考虑是否选择建造者模式</p><h1 id="8-工厂方法模式-VS-建造者模式"><a href="#8-工厂方法模式-VS-建造者模式" class="headerlink" title="8. 工厂方法模式 VS 建造者模式"></a>8. 工厂方法模式 VS 建造者模式</h1><p>工厂方法模式注重的是整体对象的创建方式；而建造者模式注重的是部件构建的过程，意在通过一步一步地精确构造创建出一个复杂的对象。</p><p>我们举个简单例子来说明两者的差异，如要制造一个超人，如果使用工厂方法模式，直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人；而如果使用建造者模式，则需要组装手、头、脚、躯干等部分，然后再把内裤外穿，于是一个超人就诞生了。</p><h1 id="9-抽象工厂模式-VS-建造者模式"><a href="#9-抽象工厂模式-VS-建造者模式" class="headerlink" title="9. 抽象工厂模式 VS 建造者模式"></a>9. 抽象工厂模式 VS 建造者模式</h1><p>抽象工厂模式实现对产品家族的创建，一个产品家族是这样的一系列产品：具有不同分类维度的产品组合，采用抽象工厂模式不需要关心构建过程，只关心什么产品由什么工厂生产即可。</p><p>而建造者模式则是要求按照指定的蓝图建造产品，它的主要目的是通过组装零配件而产生一个新产品</p><h1 id="10-实战经验"><a href="#10-实战经验" class="headerlink" title="10. 实战经验"></a>10. 实战经验</h1><h1 id="11-参考与感谢"><a href="#11-参考与感谢" class="headerlink" title="11. 参考与感谢"></a>11. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 创建者模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-7、代理模式</title>
      <link href="/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7%E3%80%81%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7%E3%80%81%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-结构型模式⭐️🔴"><a href="#1-结构型模式⭐️🔴" class="headerlink" title="1. 结构型模式⭐️🔴"></a>1. 结构型模式⭐️🔴</h1><p>结构型模式<span style="background-color:#ff00ff">用于描述如何将类或对象按某种布局组成更大的结构</span>。它分为类结构型模式和对象结构型模式，前者采用继承机制来组织接口和类，后者釆用组合或聚合来组合对象。</p><p>由于组合关系或聚合关系比继承关系耦合度低，满足“合成复用原则”，所以对象结构型模式比类结构型模式具有更大的灵活性。</p><p>结构型模式分为以下 7 种：</p><ul><li>代理模式(Proxy)</li><li>适配器模式(Adapter)</li><li>装饰者模式(Decorator)</li><li>桥接模式(Bridge)</li><li>外观模式(Facade)</li><li>组合模式(Composite)</li><li>享元模式(Flyweight)<br>❕<span style="display:none">%%<br>2102-🏡⭐️◼️结构型设计模式：代理、适配器、装饰、桥接、外观、组合、享元◼️⭐️-point-202301232220%%</span></li></ul><h1 id="2-代理模式"><a href="#2-代理模式" class="headerlink" title="2. 代理模式"></a>2. 代理模式</h1><h2 id="2-1-场景概述"><a href="#2-1-场景概述" class="headerlink" title="2.1. 场景概述"></a>2.1. 场景概述</h2><p>由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时，访问对象不适合或者不能直接引用目标对象，代理对象作为访问对象和目标对象之间的中介。</p><p>原因包括：<span style="background-color:#00ff00">需要安全保护的对象；需要功能增强的对象</span><br>ps：功能增强也可以使用装饰者模式，可以动态的插拔能力</p><p>Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成，而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。</p><h2 id="2-2-结构"><a href="#2-2-结构" class="headerlink" title="2.2. 结构"></a>2.2. 结构</h2><p>代理（Proxy）模式分为三种角色：</p><ul><li><strong>抽象主题（Subject）类</strong>： 通过接口或抽象类声明真实主题和代理对象实现的业务方法。</li><li><strong>真实主题（Real Subject）类</strong>： 实现了抽象主题中的具体业务，是代理对象所代表的真实对象，是最终要引用的对象。</li><li><strong>代理（Proxy）类</strong> ： 提供了与真实主题相同的接口，其内部含有对真实主题的引用，它可以访问、控制或扩展真实主题的功能。</li></ul><h1 id="3-静态代理"><a href="#3-静态代理" class="headerlink" title="3. 静态代理"></a>3. 静态代理</h1><h2 id="3-1-实现方法"><a href="#3-1-实现方法" class="headerlink" title="3.1. 实现方法"></a>3.1. 实现方法</h2><p>【例】火车站卖票</p><p>如果要买火车票的话，需要去火车站买票，坐车到火车站，排队等一系列的操作，显然比较麻烦。而火车站在多个地方都有代售点，我们去代售点买票就方便很多了。这个例子其实就是典型的&#x3D;&#x3D;代理模式，火车&#x3D;&#x3D;站是目标对象，代售点是代理对象。类图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114074250.png"></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></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">interface</span> <span class="hljs-title class_">SellTickets</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">sell</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-comment">//火车站  火车站具有卖票功能，所以需要实现SellTickets接口</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TrainStation</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">SellTickets</span> &#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sell</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;火车站卖票&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-comment">//代售点</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ProxyPoint</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">SellTickets</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">TrainStation</span> <span class="hljs-variable">station</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TrainStation</span>();<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sell</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;代理点收取一些服务费用&quot;</span>);<br>        station.sell();<br>    &#125;<br>&#125;<br><br><span class="hljs-comment">//测试类</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Client</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">ProxyPoint</span> <span class="hljs-variable">pp</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ProxyPoint</span>();<br>        pp.sell();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>从上面代码中可以看出测试类直接访问的是ProxyPoint类对象，也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强（代理点收取一些服务费用）。</p><h2 id="3-2-静态代理优缺点⭐️🔴"><a href="#3-2-静态代理优缺点⭐️🔴" class="headerlink" title="3.2. 静态代理优缺点⭐️🔴"></a>3.2. 静态代理优缺点⭐️🔴</h2><p>优点：在不修改目标对象的功能前提下，能通过代理对象对目标功能扩展<br>缺点：</p><ol><li>必须基于接口</li><li><span style="background-color:#ffff00">一旦接口增加方法，目标对象与代理对象都要维护</span>；</li><li><span style="background-color:#ff00ff">基于单一职责原则和接口隔离原则，一个代理类最好固定代理某一类接口。如果需要代理其他接口，就必须重新编写一个代理类</span> ❕<span style="display:none">%%<br>1209-🏡⭐️◼️静态代理的缺点？如果接口新增方法，则代理类和被代理类都需要修改；基于单一职责原则和接口隔离原则，一个代理类只能代理一个接口，如果代理其他接口，则需要新增一个代理类◼️⭐️-point-202301260902%%</span></li></ol><h1 id="4-JDK动态代理"><a href="#4-JDK动态代理" class="headerlink" title="4. JDK动态代理"></a>4. JDK动态代理</h1><h2 id="4-1-代理过程"><a href="#4-1-代理过程" class="headerlink" title="4.1. 代理过程"></a>4.1. 代理过程</h2><p>[[02_尚硅谷大数据技术之Spring(老师原版).docx]]</p><p>JAVA大数据集合：<br><a href="https://www.bilibili.com/video/av58225341?p=261">https://www.bilibili.com/video/av58225341?p=261</a></p><p><span style="background-color:#ff00ff">由代理对象决定是否、何时 将方法调用转到原始对象上</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210112001.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114084706.jpg" alt="image-20200404161821542"></p><h2 id="4-2-使用方法"><a href="#4-2-使用方法" class="headerlink" title="4.2. 使用方法"></a>4.2. 使用方法</h2><p>资料来源：<a href="/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-2%E3%80%81%E5%A4%9A%E6%80%81%E4%B8%8E%E4%BB%A3%E7%90%86/" title="基本原理-2、多态与代理">基本原理-2、多态与代理</a><br>示例代码：spring02: [[ArithmeticCalculatorProxy.java]]</p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;proxy&#x2F;jdk_proxy&#x2F;ProxyFactory.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210114456.png"></p><h3 id="4-2-1-生成代理对象⭐️🔴"><a href="#4-2-1-生成代理对象⭐️🔴" class="headerlink" title="4.2.1. 生成代理对象⭐️🔴"></a>4.2.1. 生成代理对象⭐️🔴</h3><p><strong>有2种方式：</strong></p><h4 id="4-2-1-1-先生成代理类的Class对象"><a href="#4-2-1-1-先生成代理类的Class对象" class="headerlink" title="4.2.1.1. 先生成代理类的Class对象"></a>4.2.1.1. 先生成代理类的Class对象</h4><p><code>Proxy.getProxyClass(loader, interfaces)</code>获取到Class后再获取Constructor，然后通过<code>constructor.newInstance</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Class</span> <span class="hljs-variable">proxyClass</span> <span class="hljs-operator">=</span> Proxy.getProxyClass(loader, interfaces);  <br>  <br><span class="hljs-comment">//Class  创建对象?   newInstance()  </span><br>  <br><span class="hljs-type">Constructor</span> <span class="hljs-variable">con</span> <span class="hljs-operator">=</span>   <br>      proxyClass.getDeclaredConstructor(InvocationHandler.class);<br>proxy = con.newInstance(<span class="hljs-keyword">new</span> <span class="hljs-title class_">InvocationHandler</span>() &#123;  <br>     <br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>      <span class="hljs-comment">//将方法的调用转回到目标对象上.   </span><br>        <br>      <span class="hljs-comment">//获取方法的名字  </span><br>      <span class="hljs-type">String</span> <span class="hljs-variable">methodName</span> <span class="hljs-operator">=</span> method.getName();  <br>      <span class="hljs-comment">//记录日志  </span><br>      System.out.println(<span class="hljs-string">&quot;LoggingProxy2==&gt; The method &quot;</span> + methodName+<span class="hljs-string">&quot; begin with &quot;</span>+ Arrays.asList(args));  <br>      <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> method.invoke(target, args);  <span class="hljs-comment">// 目标对象执行目标方法. 相当于执行ArithmeticCalculatorImpl中的+ - * /  </span><br>            <span class="hljs-comment">//记录日志  </span><br>      System.out.println(<span class="hljs-string">&quot;LoggingProxy2==&gt; The method &quot;</span> + methodName  +<span class="hljs-string">&quot; ends with :&quot;</span> +result   );  <br>      <span class="hljs-keyword">return</span> result ;  <br>   &#125;  <br>&#125;);<br></code></pre></td></tr></table></figure><h4 id="4-2-1-2-直接生成代理对象"><a href="#4-2-1-2-直接生成代理对象" class="headerlink" title="4.2.1.2. 直接生成代理对象"></a>4.2.1.2. 直接生成代理对象</h4><p><span style="background-color:#ff00ff">维护一个目标对象 , Object，在Client端设置到ProxyFactory的构造参数中</span></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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//维护一个目标对象 , Object，在Client端设置到ProxyFactory的构造参数中</span><br><span class="hljs-keyword">private</span> Object target;<br><span class="hljs-comment">/*  </span><br><span class="hljs-comment"> * loader:  ClassLoader对象。 类加载器对象.  帮我们加载动态生成的代理类。  </span><br><span class="hljs-comment"> * interfaces: 接口们.  提供目标对象的所有的接口.  目的是让代理对象保证与目标对象都有接口中相同的方法.  可以代理目标类实现的所有接口的所有方法</span><br><span class="hljs-comment"> * h:  InvocationHandler类型的对象.  </span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">ClassLoader</span> <span class="hljs-variable">loader</span> <span class="hljs-operator">=</span> target.getClass().getClassLoader();  <br>Class[] interfaces = target.getClass().getInterfaces();<br><br><span class="hljs-type">Object</span> <span class="hljs-variable">proxy</span> <span class="hljs-operator">=</span> Proxy.newProxyInstance(loader, interfaces, <span class="hljs-keyword">new</span> <span class="hljs-title class_">InvocationHandler</span>() &#123;  <br>   <span class="hljs-comment">/**  </span><br><span class="hljs-comment">    * invoke:  代理对象调用代理方法， 会回来调用invoke方法。    *    * proxy: 代理对象 ， 在invoke方法中一般不会使用.   </span><br><span class="hljs-comment">    *   </span><br><span class="hljs-comment">    * method: 正在被调用的方法对象.   </span><br><span class="hljs-comment">    *   </span><br><span class="hljs-comment">    * args:   正在被调用的方法的参数.   </span><br><span class="hljs-comment">    */</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>        <br>      <span class="hljs-comment">//将方法的调用转回到目标对象上.   </span><br>        <br>      <span class="hljs-comment">//获取方法的名字  </span><br>      <span class="hljs-type">String</span> <span class="hljs-variable">methodName</span> <span class="hljs-operator">=</span> method.getName();  <br>      <span class="hljs-comment">//记录日志  </span><br>      System.out.println(<span class="hljs-string">&quot;LoggingProxy==&gt; The method &quot;</span> + methodName+<span class="hljs-string">&quot; begin with &quot;</span>+ Arrays.asList(args));  <br>      <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> method.invoke(target, args);  <span class="hljs-comment">// 目标对象执行目标方法. 相当于执行ArithmeticCalculatorImpl中的+ - * /  </span><br>            <span class="hljs-comment">//记录日志  </span><br>      System.out.println(<span class="hljs-string">&quot;LoggingProxy==&gt; The method &quot;</span> + methodName  +<span class="hljs-string">&quot; ends with :&quot;</span> +result   );  <br>      <span class="hljs-keyword">return</span> result ;  <br>   &#125;  <br>&#125;);<br></code></pre></td></tr></table></figure><h3 id="4-2-2-编写代理逻辑⭐️🔴"><a href="#4-2-2-编写代理逻辑⭐️🔴" class="headerlink" title="4.2.2. 编写代理逻辑⭐️🔴"></a>4.2.2. 编写代理逻辑⭐️🔴</h3><p><span style="background-color:#00ff00">new InvocationHandler()匿名内部类中的invoke方法</span></p><h2 id="4-3-代理类查看方法"><a href="#4-3-代理类查看方法" class="headerlink" title="4.3. 代理类查看方法"></a>4.3. 代理类查看方法</h2><h3 id="4-3-1-保存代理类"><a href="#4-3-1-保存代理类" class="headerlink" title="4.3.1. 保存代理类"></a>4.3.1. 保存代理类</h3><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-comment">//将动态生成的代理类保存下来  </span><br> <span class="hljs-type">Properties</span> <span class="hljs-variable">properties</span> <span class="hljs-operator">=</span> System.getProperties();  <br> properties.put(<span class="hljs-string">&quot;sun.misc.ProxyGenerator.saveGeneratedFiles&quot;</span>, <span class="hljs-string">&quot;true&quot;</span>);<br></code></pre></td></tr></table></figure><p>缺点：还需要使用反编译工具进行反编译</p><h3 id="4-3-2-使用工具类"><a href="#4-3-2-使用工具类" class="headerlink" title="4.3.2. 使用工具类"></a>4.3.2. 使用工具类</h3><p>通过阿里巴巴开源的 Java 诊断工具（Arthas【阿尔萨斯】）</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126120250.png" alt="image.png"></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">java -jar arthas-boot.jar<br>jad com.sun.proxy.$Proxy0<br></code></pre></td></tr></table></figure><p>可以直接copy出来保存❕<span style="display:none">%%<br>2221-🏡⭐️◼️阿尔萨斯 jad 可以保存内存中类的信息◼️⭐️-point-202301232221%%</span></p><h2 id="4-4-代理类作用原理⭐️🔴⭐️🔴"><a href="#4-4-代理类作用原理⭐️🔴⭐️🔴" class="headerlink" title="4.4. 代理类作用原理⭐️🔴⭐️🔴"></a>4.4. 代理类作用原理⭐️🔴⭐️🔴</h2><p>^8s8bbj</p><p><a href="https://www.bilibili.com/video/BV1Dx411f7ib?p=265&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Dx411f7ib?p=265&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>代理类<code>$Proxy00</code>样例：[[$Proxy00.java]]</p><p>基于接口的动态代理中，因为代理类与被代理类实现了同一个接口，所以<span style="background-color:#00ff00">构成了多态的形式</span><br>那么在动态的运行过程中，<span style="background-color:#ff00ff">调用代理类的方法是如何转移到被代理类上去的呢？</span></p><ol><li>通过InvocationHandler<span style="background-color:#00ff00">进行传递</span></li><li>通过代理类$Proxy00中的静态代码块中的信息进行<span style="background-color:#00ff00">进行关联</span></li><li>通过多态机制找到最终的被代理类 ❕<span style="display:none">%%<br>2221-🏡⭐️◼️动态代理的作用原理：newProxyInstance的第三个构造参数是new InvocationHandler的匿名内部类，传进去之后最终传给了Proxy的invocationhandler属性，在代理类中通过super.h调用，就说我们定义的匿名内部类。而super.h.invoke的入参中，目标类的方法，是通过class.forname接口然后getMethond 得到◼️⭐️-point-202301232221%%</span></li></ol><h3 id="4-4-1-InvocationHandler-super-h"><a href="#4-4-1-InvocationHandler-super-h" class="headerlink" title="4.4.1. InvocationHandler(super.h)"></a>4.4.1. InvocationHandler(super.h)</h3><p>❕<span style="display:none">%%<br>2222-🏡⭐️◼️super.h就是我们自己定义的匿名内部类new InvocationHandler()◼️⭐️-point-202301232222%%</span><br><span style="background-color:#00ff00">Proxy.newProxyInstance构造参数中的匿名内部类<code>new InvocationHandler()</code>传递给了代理类的有参构造方法<code>$Proxy00(InvocationHandler arg0)</code></span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114161308.png" alt="image.png"></p><p><span style="background-color:#00ff00">然后又传给了Proxy的有参构造方法<code>super(arg0)</code></span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114162038.png" alt="image.png"></p><p><strong>最终传给了Proxy的私有成员变量<code>protected InvocationHandler h</code></strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114162650.png" alt="image.png"></p><p><strong>所以动态代理类中<code>super.h.invoke</code>最终就是调用的我们自己定义的匿名内部类<code>new InvocationHandler()</code>中的方法</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210133511.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210133702.png"><br>❕<span style="display:none">%%<br>0748-🏡⭐️◼️动态代理的调用逻辑是什么？🔜📝 我们生成代理对象的newProxyInstance的方法中入参new InvocationHandler匿名内部类中编写了我们的增强逻辑，而这个匿名内部类最终传递给了Proxy的成员属性；在JDK帮我们生成的代理类中，也会生成跟目标对象一样的方法，其中的内容是super.h.invoke(this,m3,m3入参)，这个super.h就是我们的匿名内部类，.invoke就是匿名内部类中的方法。而这个m3在proxy中是class.forName(接口).getMethod(xxx)，这里用到了反射，然后在匿名内部类的invoke方法中method.invoke第二次用到了反射，匿名内部类中的invoke只是个名字，它不是反射◼️⭐️-point-202301300748%%</span></p><h3 id="4-4-2-代理类中方法判断"><a href="#4-4-2-代理类中方法判断" class="headerlink" title="4.4.2. 代理类中方法判断"></a>4.4.2. 代理类中方法判断</h3><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230409-1754%%</span>❕❕ ^qrabdh</p><p>静态代码块中指定了所调用方法真正属于的类型为 <code>com.atguigu.spring.aop.proxy.ArithmeticCalculator</code>，这一步<span style="background-color:#ff00ff">完成了关联</span> ❕<span style="display:none">%%<br>1726-🏡⭐️◼️调用动态代理的方法如何关联到目标对象上的 ?🔜MSTM📝 JDK 动态代理类与目标类实现了相同的接口们，这个是我们在 newProxyInstance 时传入的 interfaces 参数确定的。然后动态代理对象中有一个静态代码块，通过 Class.forName 接口.getMethod 的方式获取目标对象方法的反射出来的引用，然后动态代理自己定义了同名的方法，在方法里使用 super.h.invoke 的方式进行调用。这个 super.h 就是我们 newProxyInstance 时传入的带有我们自己特定增强的 new InvocationHandler 匿名内部类，invoke 就是匿名内部类里面 invoke 方法。而真正的反射动作是 method.invoke 方法◼️⭐️-point-20230219-1726%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210134234.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210134127.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210134321.png"></p><h3 id="4-4-3-method-invoke"><a href="#4-4-3-method-invoke" class="headerlink" title="4.4.3. method.invoke"></a>4.4.3. method.invoke</h3><p><a href="http://rui0.cn/archives/1112">http://rui0.cn/archives/1112</a><br><a href="https://binshao.site/2019/04/03/Reflection/#%E5%8F%8D%E5%B0%84%E8%B0%83%E7%94%A8%E7%9A%84%E6%B5%81%E7%A8%8B">https://binshao.site/2019/04/03/Reflection/#%E5%8F%8D%E5%B0%84%E8%B0%83%E7%94%A8%E7%9A%84%E6%B5%81%E7%A8%8B</a></p><h2 id="4-5-代理类生成原理"><a href="#4-5-代理类生成原理" class="headerlink" title="4.5. 代理类生成原理"></a>4.5. 代理类生成原理</h2><p>#todo</p><span style="display:none">- [ ] 🚩 - 代理类生成原理 待研究 - 🏡 2023-01-26 10:56</span>addProxyMethodhttps://www.cnblogs.com/Xianhuii/p/16918191.htmlhttps://blog.csdn.net/qq_43259865/article/details/113944901https://www.cnblogs.com/Xianhuii/p/16918191.html![](https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210223633.png)<p><a href="https://gentryhuang.com/posts/d38b32e5/index.html">https://gentryhuang.com/posts/d38b32e5/index.html</a><br><a href="https://www.jianshu.com/p/471c80a7e831">https://www.jianshu.com/p/471c80a7e831</a></p><h2 id="4-6-为什么要用接口⭐️🔴"><a href="#4-6-为什么要用接口⭐️🔴" class="headerlink" title="4.6. 为什么要用接口⭐️🔴"></a>4.6. 为什么要用接口⭐️🔴</h2><p>通过父类<code>Proxy</code>的构造方法，保存了创建代理对象过程中传进来的<code>InvocationHandler</code>的实例，使用<code>protected</code>修饰保证了它可以在子类中被访问和使用。但是同时，<span style="background-color:#ff00ff">因为java是单继承的，因此代理类<code>$Proxy00</code>在继承了<code>Proxy</code>后，只能通过实现目标接口的方式来实现方法的扩展，达到我们增强目标方法逻辑的目的。</span><br><a href="https://www.cnblogs.com/trunks2008/p/15930642.html">https://www.cnblogs.com/trunks2008/p/15930642.html</a></p><h1 id="5-CGlib动态代理"><a href="#5-CGlib动态代理" class="headerlink" title="5. CGlib动态代理"></a>5. CGlib动态代理</h1><p><a href="https://juejin.cn/post/7026846580426833956#heading-15">https://juejin.cn/post/7026846580426833956#heading-15</a><br><a href="https://puppylpg.github.io/2020/08/02/java-reflection-dynamic-proxy/#%E8%83%BD%E5%8A%9B%E9%97%AE%E9%A2%98">https://puppylpg.github.io/2020/08/02/java-reflection-dynamic-proxy/#%E8%83%BD%E5%8A%9B%E9%97%AE%E9%A2%98</a></p><h2 id="5-1-基本介绍"><a href="#5-1-基本介绍" class="headerlink" title="5.1. 基本介绍"></a>5.1. 基本介绍</h2><p>● 1）静态代理和 JDK 代理模式都要求目标对象是实现一个接口，但是有时候目标对象只是一个单独的对象，<span style="background-color:#00ff00">并没有实现任何的接口，这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理</span><br>● 2）Cglib 代理也叫作子类代理，它是在内存中构建一个子类对象从而实现对目标对象功能扩展，有些书也将 Cglib 代理归属到动态代理。<br>● 3）Cglib 是一个强大的高性能的代码生成包，它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用，例如 Spring AOP，实现方法拦截<br>● 4）在 AOP 编程中如何选择代理模式：<br>  ○ 目标对象需要实现接口，用 JDK 代理<br>  ○ 目标对象不需要实现接口，用 Cglib 代理<br>● 5）Cglib 包的底层是通过<span style="background-color:#00ff00">使用字节码处理框架 ASM </span>来转换字节码并生成新的类<br>❕<span style="display:none">%%<br>1059-🏡⭐️◼️cglib底层使用的是ASM字节码处理框架，来转换字节码并生成新的类◼️⭐️-point-202301261059%%</span></p><h2 id="5-2-使用方法"><a href="#5-2-使用方法" class="headerlink" title="5.2. 使用方法"></a>5.2. 使用方法</h2><h3 id="5-2-1-引入jar包"><a href="#5-2-1-引入jar包" class="headerlink" title="5.2.1. 引入jar包"></a>5.2.1. 引入jar包</h3><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></pre></td><td class="code"><pre><code class="hljs xml"><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>cglib<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>cglib<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>2.2.2<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></code></pre></td></tr></table></figure><h3 id="5-2-2-示例代码"><a href="#5-2-2-示例代码" class="headerlink" title="5.2.2. 示例代码"></a>5.2.2. 示例代码</h3><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></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_">ProxyFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MethodInterceptor</span> &#123;  <br>  <br>   <span class="hljs-comment">//维护一个目标对象  </span><br>   <span class="hljs-keyword">private</span> Object target;  <br>     <br>   <span class="hljs-comment">//构造器，传入一个被代理的对象  </span><br>   <span class="hljs-keyword">public</span> <span class="hljs-title function_">ProxyFactory</span><span class="hljs-params">(Object target)</span> &#123;  <br>      <span class="hljs-built_in">this</span>.target = target;  <br>   &#125;  <br>  <br>   <span class="hljs-comment">//返回一个代理对象:  是 target 对象的代理对象  </span><br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">getProxyInstance</span><span class="hljs-params">()</span> &#123;  <br>      <span class="hljs-comment">//1. 创建一个工具类  </span><br>      <span class="hljs-type">Enhancer</span> <span class="hljs-variable">enhancer</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Enhancer</span>();  <br>      <span class="hljs-comment">//2. 设置父类  </span><br>      enhancer.setSuperclass(target.getClass());  <br>      <span class="hljs-comment">//3. 设置回调函数  </span><br>      enhancer.setCallback(<span class="hljs-built_in">this</span>);  <br>      <span class="hljs-comment">//4. 创建子类对象，即代理对象  </span><br>      <span class="hljs-keyword">return</span> enhancer.create();  <br>        <br>   &#125;  <br>   <span class="hljs-comment">//重写  intercept 方法，会调用目标对象的方法  </span><br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">intercept</span><span class="hljs-params">(Object arg0, Method method, Object[] args, MethodProxy arg3)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>      <span class="hljs-comment">// TODO Auto-generated method stub  </span><br>      System.out.println(<span class="hljs-string">&quot;Cglib代理模式 ~~ 开始&quot;</span>);  <br>      <span class="hljs-type">Object</span> <span class="hljs-variable">returnVal</span> <span class="hljs-operator">=</span> method.invoke(target, args);  <br>      System.out.println(<span class="hljs-string">&quot;Cglib代理模式 ~~ 提交&quot;</span>);  <br>      <span class="hljs-keyword">return</span> returnVal;  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="6-三种代理的对比⭐️🔴"><a href="#6-三种代理的对比⭐️🔴" class="headerlink" title="6. 三种代理的对比⭐️🔴"></a>6. 三种代理的对比⭐️🔴</h1><ul><li><p>jdk代理和CGLIB代理</p><p>使用CGLib实现动态代理，CGLib底层采用ASM字节码生成框架，使用字节码技术生成代理类，<span style="background-color:#ffff00">在JDK1.6之前比使用Java反射效率要高</span>。唯一需要注意的是，CGLib不能对声明为final的类或者方法进行代理，因为CGLib原理是动态生成被代理类的子类。</p><p>在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后，在调用次数较少的情况下，JDK代理效率高于CGLib代理效率，只有当进行大量调用的时候，JDK1.6和JDK1.7比CGLib代理效率低一点，<span style="background-color:#ff00ff">但是到JDK1.8的时候，JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理，如果没有接口使用CGLIB代理。</span></p></li><li><p>动态代理和静态代理</p><ol><li>动态代理与静态代理相比较，最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理（InvocationHandler.invoke）。这样，在接口方法数量比较多的时候，我们可以进行灵活处理，而不需要像静态代理那样每一个方法进行中转。</li><li>如果接口增加一个方法，静态代理模式<span style="background-color:#ff0000">除了所有实现类需要实现这个方法外，所有代理类也需要实现此方法。</span>增加了代码维护的复杂度。而动态代理不会出现该问题</li><li>动态代理可以代理目标类的所有接口中的方法，而静态代理，最好一个代理负责一个接口，否则很可能违背单一职责原则和接口隔离原则 ❕<span style="display:none">%%</li></ol></li></ul><p>1155-🏡⭐️◼️Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {xxx})中的第二个参数是目标类所实现的所有接口，意义是一个代理类可以代理目标类所有接口中的所有方法。代理类与目标类实现了相同的接口，起点是在这里配置的◼️⭐️-point-202301261155%%</span></p><p>如果目标类实现多个接口，那么代理类也会实现多个接口</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126120521.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230126120445.png" alt="image.png"></p><h1 id="7-优缺点⭐️🔴"><a href="#7-优缺点⭐️🔴" class="headerlink" title="7. 优缺点⭐️🔴"></a>7. 优缺点⭐️🔴</h1><p><strong>优点：</strong></p><ul><li>代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用；</li><li>代理对象可以扩展目标对象的功能；</li><li>代理模式能将客户端与目标对象分离，在一定程度上降低了系统的耦合度；</li></ul><p><strong>缺点：</strong></p><ul><li>增加了系统的复杂度；</li></ul><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><h2 id="9-1-黑马程序员"><a href="#9-1-黑马程序员" class="headerlink" title="9.1. 黑马程序员"></a>9.1. 黑马程序员</h2><p><a href="https://www.bilibili.com/video/BV1Np4y1z7BU?p=35">https://www.bilibili.com/video/BV1Np4y1z7BU?p=35</a></p><p>课件已下载：&#x2F;Volumes&#x2F;Seagate Bas&#x2F;001-ArchitectureRoad&#x2F;资料-java设计模式（图解+框架源码分析+实战）<br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns</p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;009-内功心法专题&#x2F;设计模式&#x2F;黑马&#x2F;设计模式]]</p><h2 id="9-2-尚硅谷"><a href="#9-2-尚硅谷" class="headerlink" title="9.2. 尚硅谷"></a>9.2. 尚硅谷</h2><p><a href="https://www.bilibili.com/video/BV1Dx411f7ib/?p=266&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Dx411f7ib/?p=266&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring02</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 结构型模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-5、原型模式</title>
      <link href="/2023/01/11/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-5%E3%80%81%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/11/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-5%E3%80%81%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>用一个已经创建的实例作为原型，<span style="background-color:#00ff00">通过复制该原型对象</span>来创建一个和原型对象相同的新对象。</p><h1 id="2-结构"><a href="#2-结构" class="headerlink" title="2. 结构"></a>2. 结构</h1><p>原型模式包含如下角色：</p><ul><li>抽象原型类：规定了具体原型对象必须实现的的 clone() 方法。</li><li>具体原型类：实现抽象原型类的 clone() 方法，它是可被复制的对象。</li><li>访问类：使用具体原型类中的 clone() 方法来复制新的对象。</li></ul><p>接口类图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230112213807.png" alt="image-20230112212928339"></p><h1 id="3-实现"><a href="#3-实现" class="headerlink" title="3. 实现"></a>3. 实现</h1><p>原型模式的克隆分为浅克隆和深克隆。</p><blockquote><p>浅克隆：创建一个新对象，新对象的属性和原来对象完全相同，对于<span style="background-color:#ff00ff">非基本类型属性，比如某个类的对象或者数组对象，仍指向原有属性所指向的对象的内存地址。</span> ❕<span style="display:none">%%<br>0853-🏡⭐️◼️浅克隆中非基本数据类型的对象包括什么？普通对象和数组对象◼️⭐️-point-202301270853%%</span></p><p>深克隆：创建一个新对象，属性中引用的其他对象也会被克隆，不再指向原有对象地址。</p></blockquote><p>Java 中的 Object 类中提供了 <code>clone()</code> 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类，而实现了 Cloneable 接口的子实现类就是具体的原型类。代码如下：</p><p><strong>Realizetype（具体的原型类）：</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></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_">Realizetype</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Cloneable</span> &#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Realizetype</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;具体的原型对象创建完成！&quot;</span>);<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Realizetype <span class="hljs-title function_">clone</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> CloneNotSupportedException &#123;<br>        System.out.println(<span class="hljs-string">&quot;具体原型复制成功！&quot;</span>);<br>        <span class="hljs-keyword">return</span> (Realizetype) <span class="hljs-built_in">super</span>.clone();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>PrototypeTest（测试访问类）：</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></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_">PrototypeTest</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> CloneNotSupportedException &#123;<br>        <span class="hljs-type">Realizetype</span> <span class="hljs-variable">r1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Realizetype</span>();<br>        <span class="hljs-type">Realizetype</span> <span class="hljs-variable">r2</span> <span class="hljs-operator">=</span> r1.clone();<br><br>        System.out.println(<span class="hljs-string">&quot;对象r1和r2是同一个对象？&quot;</span> + (r1 == r2));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="4-注意事项"><a href="#4-注意事项" class="headerlink" title="4. 注意事项"></a>4. 注意事项</h1><ol><li>创建新的对象比较复杂时，可以利用原型模式简化对象的创建过程，同时也能够提高效率</li><li>不用重新初始化对象，而是动态地获得对象运行时的状态</li><li>如果原始对象发生变化 (增加或者减少属性)，克隆出来的克隆对象也会发生相应的变化，无需修改代码。如果是使用默认克隆方法，即浅克隆，修改原始对象的非基本数据类型属性会影响其他克隆对象的对应属性，因为浅克隆最终指向的是同一个对象。</li><li>在实现深克隆的时候可能需要比较复杂的代码</li><li>缺点：要实现原型模式<span style="background-color:#ff00ff">需要为每一个类配备一个克隆方法，这对全新的类来说不是很难，但对已有</span><span style="background-color:#ff00ff">的类进行改造时，需要修改其源代码，违背了 ocp 原则</span>。❕<span style="display:none">%%<br>1037-🏡⭐️◼️使用 cloneable 接口存在违反 OCP 原则的问题◼️⭐️-point-202301231037%%</span></li></ol><h1 id="5-clone-与-new-的区别⭐️🔴"><a href="#5-clone-与-new-的区别⭐️🔴" class="headerlink" title="5. clone 与 new 的区别⭐️🔴"></a>5. clone 与 new 的区别⭐️🔴</h1><ol><li>new 操作符本意是在堆中开辟内存空间。程序执行到 new 时，会根据 new 后面的类型来计算分配的内存空间大小，分配完空间以后，调用构造函数填充对象的各个域，然后返回该对象的地址。具体流程可参考 <a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a></li><li>在调用 clone 方法时，程序会分配一块与原对象相同大小的内存空间，然后用原对象的各个域填充到新对象相应的各个域中，然后返回新对象的地址。</li><li>由于 native 方法通常比非 native 方法更加高效，所以 clone 方法效率更高</li></ol><h1 id="6-浅拷贝-浅克隆"><a href="#6-浅拷贝-浅克隆" class="headerlink" title="6. 浅拷贝 (浅克隆)"></a>6. 浅拷贝 (浅克隆)</h1><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230604-2216%%</span>❕ ^42eo02</p><ol><li>对于基本数据类型的成员变量，浅拷贝会直接进行值传递，也就是将该属性值复制一份给新的对象</li><li>对于引用数据类型的成员变量，比如说成员变量<span style="background-color:#ff00ff">是某个数组</span>、某个类的对象等，那么浅拷贝会进行引用传递，也就是只是将该成员变量的引用值（内存地址）复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下，<span style="background-color:#ffff00">在一个对象中修改该成员变量会影响到另一个对象的该成员变量值</span></li><li>只有可变对象的引用才会出现上述问题，<span style="background-color:#00ff00">不可变类（IMMUTABLE CLASS）的引用，如 String、基本类型的包装类、BigInteger 和 BigDecimal 等</span>，每次对不可变对象的修改都将产生一个新的不可变对象，因此无论修改 clone 出的对象的可变对象，还是修改原对象中的可变对象，都会导致可变对象的引用值发生变化，而不是可变对象本身发生变化。</li></ol><h1 id="7-深拷贝-深克隆"><a href="#7-深拷贝-深克隆" class="headerlink" title="7. 深拷贝 (深克隆)"></a>7. 深拷贝 (深克隆)</h1><ol><li>复制对象的所有基本数据类型的成员变量值</li><li>为所有引用数据类型的成员变量申请存储空间，并复制每个引用数据类型成员变量所引用的对象，直到该对象可达的所有对象。也就是说，对象进行深拷贝要对整个对象进行拷贝</li></ol><h2 id="7-1-实现方式"><a href="#7-1-实现方式" class="headerlink" title="7.1. 实现方式"></a>7.1. 实现方式</h2><ol><li>重写 clone 方法<br>   如果类比较多或者层次比较深，那么修改起来非常困难，不推荐，推荐使用序列化方式实现深克隆。而且违反 OCP 原则。</li><li>序列化<br>   通过对象序列化实现深拷贝</li></ol><h1 id="8-clone-的替代方案⭐️🔴"><a href="#8-clone-的替代方案⭐️🔴" class="headerlink" title="8. clone 的替代方案⭐️🔴"></a>8. clone 的替代方案⭐️🔴</h1><p><span style="display:none">%%<br>▶82.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-2242%%</span>❕ ^ngb0a7</p><p>使用 clone() 方法来拷贝一个对象即复杂又有风险，它会抛出异常，并且还需要类型转换。Effective Java 书上讲到，最好不要去使用 clone()，可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。</p><p><span style="display:none">%%<br>▶79.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-1705%%</span>❕ ^zjymzy</p><ol><li>拷贝构造器： public Yum(Yum yum);</li><li>拷贝工厂: public static Yum newInstance(Yum yum);</li><li>使用序列化</li></ol><h2 id="8-1-拷贝工厂-拷贝构造器"><a href="#8-1-拷贝工厂-拷贝构造器" class="headerlink" title="8.1. 拷贝工厂 (拷贝构造器)"></a>8.1. 拷贝工厂 (拷贝构造器)</h2><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-comment">// 复制构造函数  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">Student</span><span class="hljs-params">(Student student)</span>  <br>&#123;  <br>   <span class="hljs-built_in">this</span>.name = student.name;  <br>   <span class="hljs-built_in">this</span>.age = student.age;  <br>  <br>   <span class="hljs-comment">// 浅拷贝  </span><br>   <span class="hljs-comment">// this.subjects = student.subjects;  </span><br>  <br>   <span class="hljs-comment">// 深拷贝——创建一个新的 `HashSet` 实例  </span><br>   <span class="hljs-built_in">this</span>.subjects = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashSet</span>&lt;&gt;(student.subjects);  <br>&#125;  <br>  <br><span class="hljs-comment">// 复制工厂  </span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Student <span class="hljs-title function_">newInstance</span><span class="hljs-params">(Student student)</span> &#123;  <br>   <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Student</span>(student);  <br>&#125;<br></code></pre></td></tr></table></figure><p>DesignPattern: [[OptimizationDeepCopy.java]]</p><h1 id="9-实战经验"><a href="#9-实战经验" class="headerlink" title="9. 实战经验"></a>9. 实战经验</h1><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a><p><a href="https://www.techiedelight.com/zh/copy-constructor-factory-method-java/">https://www.techiedelight.com/zh/copy-constructor-factory-method-java/</a></p>]]></content>
      
      
      <categories>
          
          <category> timeline </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 创建者模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-4、工厂模式</title>
      <link href="/2023/01/10/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-4%E3%80%81%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/10/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-4%E3%80%81%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-场景概述"><a href="#1-场景概述" class="headerlink" title="1. 场景概述"></a>1. 场景概述</h1><p>需求：设计一个咖啡店点餐系统。</p><p>设计一个咖啡类（Coffee），并定义其两个子类（美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】）；再设计一个咖啡店类（CoffeeStore），咖啡店具有点咖啡的功能。</p><h2 id="1-1-传统设计"><a href="#1-1-传统设计" class="headerlink" title="1.1. 传统设计"></a>1.1. 传统设计</h2><p>具体类的设计如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111200700.png"></p><h2 id="1-2-存在问题"><a href="#1-2-存在问题" class="headerlink" title="1.2. 存在问题"></a>1.2. 存在问题</h2><p>在java中，万物皆对象，这些对象都需要创建，如果创建的时候直接new该对象，就会对该对象耦合严重，<span style="background-color:#ffff00">假如要更换对象，所有new对象的地方都需要修改一遍，或者要新增一种咖啡，那么就要加if else</span>。这显然违背了软件设计的开闭原则。</p><h2 id="1-3-解决方案"><a href="#1-3-解决方案" class="headerlink" title="1.3. 解决方案"></a>1.3. 解决方案</h2><p>如果我们使用工厂来生产对象，我们就只和工厂打交道就可以了，彻底和对象解耦，如果要更换对象，直接在工厂里更换该对象即可，达到了与对象解耦的目的；所以说，工厂模式最大的优点就是：<strong>解耦</strong>。</p><p>在本教程中会介绍三种工厂的使用</p><ul><li>简单工厂模式（不属于GOF的23种经典设计模式）</li><li>工厂方法模式</li><li>抽象工厂模式</li></ul><h1 id="2-简单工厂模式"><a href="#2-简单工厂模式" class="headerlink" title="2. 简单工厂模式"></a>2. 简单工厂模式</h1><p>简单工厂不是一种设计模式，反而比较像是一种编程习惯。</p><h2 id="2-1-结构"><a href="#2-1-结构" class="headerlink" title="2.1. 结构"></a>2.1. 结构</h2><p>简单工厂包含如下角色：</p><ul><li>抽象产品 ：定义了产品的规范，描述了产品的主要特性和功能。</li><li>具体产品 ：实现或者继承抽象产品的子类</li><li>具体工厂 ：提供了创建产品的方法，调用者通过该方法来获取产品。</li></ul><h2 id="2-2-实现"><a href="#2-2-实现" class="headerlink" title="2.2. 实现"></a>2.2. 实现</h2><p>现在使用简单工厂对上面案例进行改进，类图如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111200857.png" style="zoom:70%;" /></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></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_">SimpleCoffeeFactory</span> &#123;<br><br>    <span class="hljs-keyword">public</span> Coffee <span class="hljs-title function_">createCoffee</span><span class="hljs-params">(String type)</span> &#123;<br>        <span class="hljs-type">Coffee</span> <span class="hljs-variable">coffee</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>        <span class="hljs-keyword">if</span>(<span class="hljs-string">&quot;americano&quot;</span>.equals(type)) &#123;<br>            coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">AmericanoCoffee</span>();<br>        &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-string">&quot;latte&quot;</span>.equals(type)) &#123;<br>            coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LatteCoffee</span>();<br>        &#125;<br>        <span class="hljs-keyword">return</span> coffee;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>工厂（factory）处理创建对象的细节，一旦有了SimpleCoffeeFactory，CoffeeStore类中的orderCoffee()就变成此对象的客户，后期如果需要Coffee对象直接从工厂中获取即可。这样也就<span style="background-color:#00ff00">解除了CoffeeStore类和Coffee实现类的耦合</span>，<span style="background-color:#ffff00">同时又产生了新的耦合，CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合，以及工厂对象和商品对象的耦合</span>。本质就是把if else从CoffeeStore中转移到了SimpleCoffeeFactory中。❕<span style="display:none">%%<br>1149-🏡⭐️◼️涉及到if else优化的设计模式：工厂方法模式、抽象工厂模式、适配器模式、策略模式、状态模式、责任链模式◼️⭐️-point-202301231149%%</span></p><p>后期如果再加新品种的咖啡，我们势必要需求修改SimpleCoffeeFactory的代码，违反了开闭原则。工厂类的客户端可能有很多，比如创建美团外卖等，这样只需要修改工厂类的代码，省去其他的修改操作。</p><h2 id="2-3-优缺点⭐️🔴"><a href="#2-3-优缺点⭐️🔴" class="headerlink" title="2.3. 优缺点⭐️🔴"></a>2.3. 优缺点⭐️🔴</h2><p><strong>优点：</strong></p><p><span style="background-color:#ff0000">封装了创建对象的过程</span>，可以通过参数直接获取对象。把对象的创建和业务逻辑层分开，这样以后就避免了修改客户端代码，如果要实现新产品直接修改工厂类，而不需要在原代码中修改，这样就降低了客户代码修改的可能性，更加容易扩展。</p><p><strong>缺点：</strong></p><p>增加新产品时还是需要修改工厂类的代码，<span style="background-color:#ffff00">有很多ifelse判断，违背了“开闭原则”</span></p><h2 id="2-4-扩展"><a href="#2-4-扩展" class="headerlink" title="2.4. 扩展"></a>2.4. 扩展</h2><p><strong>静态工厂</strong></p><p>在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的，这个就是静态工厂模式，它也不是23种设计模式中的。代码如下：</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">class</span> <span class="hljs-title class_">SimpleCoffeeFactory</span> &#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Coffee <span class="hljs-title function_">createCoffee</span><span class="hljs-params">(String type)</span> &#123;<br>        <span class="hljs-type">Coffee</span> <span class="hljs-variable">coffee</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>        <span class="hljs-keyword">if</span>(<span class="hljs-string">&quot;americano&quot;</span>.equals(type)) &#123;<br>            coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">AmericanoCoffee</span>();<br>        &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-string">&quot;latte&quot;</span>.equals(type)) &#123;<br>            coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LatteCoffee</span>();<br>        &#125;<br>        <span class="hljs-keyword">return</span> coffe;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="3-工厂方法模式"><a href="#3-工厂方法模式" class="headerlink" title="3. 工厂方法模式"></a>3. 工厂方法模式</h1><h2 id="3-1-工厂方法模式"><a href="#3-1-工厂方法模式" class="headerlink" title="3.1. 工厂方法模式"></a>3.1. 工厂方法模式</h2><p>针对上例中的缺点，使用工厂方法模式就可以完美的解决，完全遵循开闭原则。</p><h2 id="3-2-模式概念"><a href="#3-2-模式概念" class="headerlink" title="3.2. 模式概念"></a>3.2. 模式概念</h2><p>定义一个用于创建对象的接口，让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。</p><h2 id="3-3-模式结构⭐️🔴"><a href="#3-3-模式结构⭐️🔴" class="headerlink" title="3.3. 模式结构⭐️🔴"></a>3.3. 模式结构⭐️🔴</h2><h3 id="3-3-1-模式角色"><a href="#3-3-1-模式角色" class="headerlink" title="3.3.1. 模式角色"></a>3.3.1. 模式角色</h3><p>工厂方法模式的主要角色：</p><ul><li><strong>抽象工厂（Abstract Factory）</strong>：提供了创建产品的接口，调用者通过它访问具体工厂的工厂方法来创建产品。</li><li><strong>具体工厂（ConcreteFactory）</strong>：主要是实现抽象工厂中的抽象方法，完成具体产品的创建。</li><li><strong>抽象产品（Product）</strong>：定义了产品的规范，描述了产品的主要特性和功能。</li><li><strong>具体产品（ConcreteProduct）</strong>：实现了抽象产品角色所定义的接口，由具体工厂来创建，它同具体工厂之间一一对应。</li></ul><h3 id="3-3-2-UML⭐️🔴"><a href="#3-3-2-UML⭐️🔴" class="headerlink" title="3.3.2. UML⭐️🔴"></a>3.3.2. UML⭐️🔴</h3><p>使用工厂方法模式对上例进行改进，类图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230128101244.png" alt="image.png"></p><h3 id="3-3-3-实现逻辑⭐️🔴"><a href="#3-3-3-实现逻辑⭐️🔴" class="headerlink" title="3.3.3. 实现逻辑⭐️🔴"></a>3.3.3. 实现逻辑⭐️🔴</h3><ol><li>CoffeeStore<span style="background-color:#ff00ff">【聚合+构造导入】</span>CoffeeFactory，Client调用是指定具体工厂类传入CoffeeStore的构造方法中</li><li>具体工厂类依赖(使用)具体产品类，将工厂和产品的关系通过人为编码的方式提前配置好。类似于适配器模式中的HandlerAdapter，把if else转移到了具体工厂类和具体产品类的人为配对上。❕<span style="display:none">%%<br>2053-🏡⭐️◼️工厂方法模式核心逻辑？🔜📝❔CoffeeStore聚合+setter导入CoffeeFactory，Client调用CoffeeStore时传入具体工厂类到CoffeeStore的构造方法中，这样就指定了具体的CoffeeFactory子类了，而在CoffeeFactory子类中绑定了对应的Coffee◼️⭐️-point-202301252053%%</span><br>❕<span style="display:none">%%<br>1207-🏡⭐️◼️将3种具体类提前关联起来的设计方式：工厂方法模式、适配器模式、迭代器模式，比如LatteCoffeeFactory对应LateeCoffee，HttpHandlerAdapter对应HttpController◼️⭐️-point-202301231207%%</span><br>抽象工厂：</li></ol><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_">CoffeeFactory</span> &#123;<br>    Coffee <span class="hljs-title function_">createCoffee</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></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_">LatteCoffeeFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">CoffeeFactory</span> &#123;<br><br>    <span class="hljs-keyword">public</span> Coffee <span class="hljs-title function_">createCoffee</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_">LatteCoffee</span>();<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AmericanCoffeeFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">CoffeeFactory</span> &#123;<br><br>    <span class="hljs-keyword">public</span> Coffee <span class="hljs-title function_">createCoffee</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_">AmericanCoffee</span>();<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></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_">CoffeeStore</span> &#123;<br><br>    <span class="hljs-keyword">private</span> CoffeeFactory factory;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CoffeeStore</span><span class="hljs-params">(CoffeeFactory factory)</span> &#123;<br>        <span class="hljs-built_in">this</span>.factory = factory;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Coffee <span class="hljs-title function_">orderCoffee</span><span class="hljs-params">(String type)</span> &#123;<br>        <span class="hljs-type">Coffee</span> <span class="hljs-variable">coffee</span> <span class="hljs-operator">=</span> factory.createCoffee();<br>        coffee.addMilk();<br>        coffee.addsugar();<br>        <span class="hljs-keyword">return</span> coffee;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>Client:</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">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Client</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">CoffeeStore</span> <span class="hljs-variable">store</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CoffeeStore</span>();  <br>        <span class="hljs-comment">//创建对象  </span><br>        <span class="hljs-comment">//CoffeeFactory factory = new AmericanCoffeeFactory();  </span><br>        <span class="hljs-type">CoffeeFactory</span> <span class="hljs-variable">factory</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">LatteCoffeeFactory</span>();  <br>        store.setFactory(factory);  <br>  <br>        <span class="hljs-comment">//点咖啡  </span><br>        <span class="hljs-type">Coffee</span> <span class="hljs-variable">coffee</span> <span class="hljs-operator">=</span> store.orderCoffee();  <br>  <br>        System.out.println(coffee.getName());  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><p>从以上的编写的代码可以看到，<span style="background-color:#00ff00">要增加产品类时也要相应地增加工厂类</span>，<span style="background-color:#ffff00">不需要修改工厂类的代码了，这样就解决了简单工厂模式的缺点</span>。</p><p>工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性，工厂方法模式保持了简单工厂模式的优点，而且克服了它的缺点。</p><h2 id="3-4-优缺点⭐️🔴"><a href="#3-4-优缺点⭐️🔴" class="headerlink" title="3.4. 优缺点⭐️🔴"></a>3.4. 优缺点⭐️🔴</h2><p><strong>优点：</strong></p><ul><li>用户只需要知道具体工厂的名称就可得到所要的产品，无须知道产品的具体创建过程；</li><li>在系统增加新的产品时<span style="background-color:#00ff00">只需要添加具体产品类和对应的具体工厂类，无须对原工厂进行任何修改，满足开闭原则；</span></li></ul><p><strong>缺点：</strong></p><ul><li><span style="background-color:#ff00ff">每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类，这增加了系统的复杂度。</span></li></ul><h1 id="4-抽象工厂模式"><a href="#4-抽象工厂模式" class="headerlink" title="4. 抽象工厂模式"></a>4. 抽象工厂模式</h1><p>前面介绍的工厂方法模式中考虑的是一类产品的生产，如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。</p><p>这些工厂只生产同种类产品，同种类产品称为同等级产品，也就是说：工厂方法模式<span style="background-color:#ffff00">只考虑生产同等级的</span>产品，但是在现实生活中许多工厂是综合型的工厂，能生产多等级（种类） 的产品，如电器厂既生产电视机又生产洗衣机或空调，大学既有软件专业又有生物专业等。</p><p>本节要介绍的抽象工厂模式将考虑多等级产品的生产，将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族，下图所示<span style="background-color:#00ff00">横轴是产品等级，也就是同一类产品</span>；<span style="background-color:#00ff00">纵轴是产品族，也就是同一品牌的</span>产品，同一品牌的产品产自同一个工厂。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111204822.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111204829.png"></p><h2 id="4-1-概念"><a href="#4-1-概念" class="headerlink" title="4.1. 概念"></a>4.1. 概念</h2><p>是一种为访问类提供一个创建一组相关或相互依赖对象的接口，且访问类无须指定所要产品的具体类就能得到同族的不同等级的<span style="background-color:#00ff00">(即同品牌的一套产品，比如苹果四件套)</span>产品的模式结构。</p><p>抽象工厂模式是工厂方法模式的升级版本，工厂方法模式只生产一个等级的产品，而抽象工厂模式可生产多个等级的产品。</p><h2 id="4-2-结构"><a href="#4-2-结构" class="headerlink" title="4.2. 结构"></a>4.2. 结构</h2><p>抽象工厂模式的主要角色如下：</p><ul><li>抽象工厂（Abstract Factory）：提供了创建产品的接口，它包含多个创建产品的方法，可以创建多个不同等级的产品。</li><li>具体工厂（Concrete Factory）：主要是实现抽象工厂中的多个抽象方法，完成具体产品的创建。</li><li>抽象产品（Product）：定义了产品的规范，描述了产品的主要特性和功能，抽象工厂模式有多个抽象产品。</li><li>具体产品（ConcreteProduct）：实现了抽象产品角色所定义的接口，由具体工厂来创建，它 同具体工厂之间是多对一的关系。</li></ul><h2 id="4-3-实现"><a href="#4-3-实现" class="headerlink" title="4.3. 实现"></a>4.3. 实现</h2><p>现咖啡店业务发生改变，不仅要生产咖啡还要生产甜点，如提拉米苏、抹茶慕斯等，要是按照工厂方法模式，需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类，很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级，都是咖啡；提拉米苏、抹茶慕斯也是一个产品等级；拿铁咖啡和提拉米苏是同一产品族（也就是都属于意大利风味），美式咖啡和抹茶慕斯是同一产品族（也就是都属于美式风味）。所以这个案例可以使用抽象工厂模式实现。类图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111204840.png"></p><p>代码如下：</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></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_">DessertFactory</span> &#123;<br><br>    Coffee <span class="hljs-title function_">createCoffee</span><span class="hljs-params">()</span>;<br><br>    Dessert <span class="hljs-title function_">createDessert</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><span class="line">21</span><br><span class="line">22</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">class</span> <span class="hljs-title class_">AmericanDessertFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">DessertFactory</span> &#123;<br><br>    <span class="hljs-keyword">public</span> Coffee <span class="hljs-title function_">createCoffee</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_">AmericanCoffee</span>();<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Dessert <span class="hljs-title function_">createDessert</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_">MatchaMousse</span>();<br>    &#125;<br>&#125;<br><span class="hljs-comment">//意大利风味甜点工厂</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ItalyDessertFactory</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">DessertFactory</span> &#123;<br><br>    <span class="hljs-keyword">public</span> Coffee <span class="hljs-title function_">createCoffee</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_">LatteCoffee</span>();<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Dessert <span class="hljs-title function_">createDessert</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_">Tiramisu</span>();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>如果要加同一个产品族的话，只需要再加一个对应的工厂类即可，不需要修改其他的类。</p><h2 id="4-4-优缺点"><a href="#4-4-优缺点" class="headerlink" title="4.4. 优缺点"></a>4.4. 优缺点</h2><p><strong>优点：</strong></p><p>当一个产品族中的多个对象被设计成一起工作时，它能保证客户端始终只使用同一个产品族中的对象。</p><p><strong>缺点：</strong></p><p>当产品族中需要增加一个新的产品时，所有的工厂类都需要进行修改。</p><h2 id="4-5-使用场景"><a href="#4-5-使用场景" class="headerlink" title="4.5. 使用场景"></a>4.5. 使用场景</h2><ul><li>当需要创建的对象是一系列相互关联或相互依赖的产品族时，如电器工厂中的电视机、洗衣机、空调等。</li><li>系统中有多个产品族，但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。</li><li>系统中提供了产品的类库，且所有产品的接口相同，客户端不依赖产品实例的创建细节和内部结构。</li></ul><p>如：输入法换皮肤，一整套一起换。生成不同操作系统的程序。</p><h1 id="5-模式扩展⭐️🔴"><a href="#5-模式扩展⭐️🔴" class="headerlink" title="5. 模式扩展⭐️🔴"></a>5. 模式扩展⭐️🔴</h1><p><strong>简单工厂+配置文件解除耦合</strong><br>❕<span style="display:none">%%<br>1216-🏡⭐️◼️配置化的简单工厂的核心逻辑是什么？将具体工厂类与具体产品类的对应关系，从人为指定好配置成对的类，放到了配置文件中，具体工厂类也只需要1个即可，入参变成具体产品类的名称，在具体工厂类中通过反射生成具体的产品类并返回给调用者◼️⭐️-point-202301231216%%</span><br>可以通过工厂模式+配置文件的方式<span style="background-color:#00ff00">解除工厂对象和产品对象的耦合</span>。在工厂类中加载配置文件中的全类名，并创建对象进行存储，客户端如果需要对象，直接进行获取即可。</p><p>第一步：定义配置文件</p><p>为了演示方便，我们使用properties文件作为配置文件，名称为bean.properties</p><figure class="highlight properties"><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 properties"><span class="hljs-attr">american</span>=<span class="hljs-string">com.itheima.pattern.factory.config_factory.AmericanCoffee</span><br><span class="hljs-attr">latte</span>=<span class="hljs-string">com.itheima.pattern.factory.config_factory.LatteCoffee</span><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><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_">CoffeeFactory</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Map&lt;String,Coffee&gt; map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>();<br><br>    <span class="hljs-keyword">static</span> &#123;<br>        <span class="hljs-type">Properties</span> <span class="hljs-variable">p</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Properties</span>();<br>        <span class="hljs-type">InputStream</span> <span class="hljs-variable">is</span> <span class="hljs-operator">=</span> CoffeeFactory.class.getClassLoader().getResourceAsStream(<span class="hljs-string">&quot;bean.properties&quot;</span>);<br>        <span class="hljs-keyword">try</span> &#123;<br>            p.load(is);<br>            <span class="hljs-comment">//遍历Properties集合对象</span><br>            Set&lt;Object&gt; keys = p.keySet();<br>            <span class="hljs-keyword">for</span> (Object key : keys) &#123;<br>                <span class="hljs-comment">//根据键获取值（全类名）</span><br>                <span class="hljs-type">String</span> <span class="hljs-variable">className</span> <span class="hljs-operator">=</span> p.getProperty((String) key);<br>                <span class="hljs-comment">//获取字节码对象</span><br>                <span class="hljs-type">Class</span> <span class="hljs-variable">clazz</span> <span class="hljs-operator">=</span> Class.forName(className);<br>                <span class="hljs-type">Coffee</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> (Coffee) clazz.newInstance();<br>                map.put((String)key,obj);<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Coffee <span class="hljs-title function_">createCoffee</span><span class="hljs-params">(String name)</span> &#123;<br><br>        <span class="hljs-keyword">return</span> map.get(name);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>静态成员变量用来存储创建的对象（键存储的是名称，值存储的是对应的对象），而读取配置文件以及创建对象写在静态代码块中，目的就是只需要执行一次。</p><h1 id="6-JDK源码解析⭐️🔴"><a href="#6-JDK源码解析⭐️🔴" class="headerlink" title="6. JDK源码解析⭐️🔴"></a>6. JDK源码解析⭐️🔴</h1><h2 id="6-1-Collection-iterator方法"><a href="#6-1-Collection-iterator方法" class="headerlink" title="6.1. Collection.iterator方法"></a>6.1. Collection.iterator方法</h2><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_">Demo</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>        List&lt;String&gt; list = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>        list.add(<span class="hljs-string">&quot;令狐冲&quot;</span>);<br>        list.add(<span class="hljs-string">&quot;风清扬&quot;</span>);<br>        list.add(<span class="hljs-string">&quot;任我行&quot;</span>);<br><br>        <span class="hljs-comment">//获取迭代器对象</span><br>        Iterator&lt;String&gt; it = list.iterator();<br>        <span class="hljs-comment">//使用迭代器遍历</span><br>        <span class="hljs-keyword">while</span>(it.hasNext()) &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">ele</span> <span class="hljs-operator">=</span> it.next();<br>            System.out.println(ele);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>对上面的代码大家应该很熟，使用迭代器遍历集合，获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。我们看通过类图看看结构：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111215714.png" alt="image.png"></p><p>Collection接口是抽象工厂类，ArrayList是具体的工厂类；Iterator接口是抽象商品类，ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。</p><h2 id="6-2-其他"><a href="#6-2-其他" class="headerlink" title="6.2. 其他"></a>6.2. 其他</h2><blockquote><p>另：</p><p>​1：DateForamt 类中的 getInstance()方法使用的是工厂模式；<br>​2：Calendar 类中的 getInstance()方法使用的是简单工厂模式；</p></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111220707.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111221256.png" alt="image.png"></p><h1 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h1><ol><li>工厂模式的意义<br><span style="background-color:#00ff00">将实例化对象的代码提取出来，放到一个类中统一管理和维护</span>，达到和主项目的<br>依赖关系的解耦。从而提高项目的扩展和维护性。</li><li>三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)</li><li>设计模式的依赖抽象原则</li></ol><ul><li>创建对象实例时，<span style="background-color:#ff0000">不要直接 new 类</span>, 而是把这个new 类的动作放在一个工厂的方法中，并返回。有的书上说，<span style="background-color:#00ff00"><font color=#ff0000>变量不要直接持有具体类的引用</font></span>。</li><li>不要让类继承具体类，而是继承抽象类或者是实现interface(接口)</li><li>不要覆盖基类中已经实现的方法。</li></ul><h1 id="8-工厂模式进阶"><a href="#8-工厂模式进阶" class="headerlink" title="8. 工厂模式进阶"></a>8. 工厂模式进阶</h1><h2 id="8-1-工厂方法和抽象工厂的区别"><a href="#8-1-工厂方法和抽象工厂的区别" class="headerlink" title="8.1. 工厂方法和抽象工厂的区别"></a>8.1. 工厂方法和抽象工厂的区别</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230123113144.png" alt="image.png"></p><h1 id="9-实战经验"><a href="#9-实战经验" class="headerlink" title="9. 实战经验"></a>9. 实战经验</h1><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 创建者模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>001-基础知识专题-关键字和接口-4、static</title>
      <link href="/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-4%E3%80%81static/"/>
      <url>/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-4%E3%80%81static/</url>
      
        <content type="html"><![CDATA[<h1 id="1-使用方法"><a href="#1-使用方法" class="headerlink" title="1. 使用方法"></a>1. 使用方法</h1><p>static 是 java 中非常重要的一个关键字，而且它的用法也很丰富，主要有四种用法：</p><ol><li>用来修饰成员变量，将其变为类的成员，从而实现所有对象对于该成员的共享；</li><li>用来修饰成员方法，将其变为类方法，可以直接使用“类名.方法名”的方式调用，常用于工具类；</li><li>静态块用法，将多个类成员放在一起初始化，使得程序更加规整，其中理解对象的初始化过程非常关键；</li><li>静态导包用法，将类的方法直接导入到当前类中，从而直接使用“方法名”即可调用类方法，更加方便。</li></ol><h1 id="2-特殊含义"><a href="#2-特殊含义" class="headerlink" title="2. 特殊含义"></a>2. 特殊含义</h1><h2 id="2-1-静态变量"><a href="#2-1-静态变量" class="headerlink" title="2.1. 静态变量"></a>2.1. 静态变量</h2><p>按照是否静态的对类成员变量进行分类可分两种：一种是被 static 修饰的变量，叫静态变量或类变量；另一种是没有被 static 修饰的变量，叫实例变量。</p><p>两者的区别是：</p><p>对于静态变量在内存中只有一个拷贝（节省内存），JVM 只为静态分配一次内存，在加载类的过程中完成静态变量的内存分配，可用类名直接访问（方便），当然也可以通过对象来访问（但是这是不推荐的）。</p><p>对于实例变量，没创建一个实例，就会为实例变量分配一次内存，实例变量可以在内存中有多个拷贝，互不影响（灵活）。</p><p><span style="background-color:#ff00ff">static 成员变量的初始化顺序按照定义的顺序进行初始化</span>。static 不可以修饰局部变量；</p><p>所以一般在需要实现以下两个功能时使用静态变量：<br>在对象之间共享值时<br>方便访问变量时</p><h2 id="2-2-静态方法"><a href="#2-2-静态方法" class="headerlink" title="2.2. 静态方法"></a>2.2. 静态方法</h2><p>静态方法的好处就是不用生成类的实例就能直接调用，可以这样理解使用 static 修饰的成员不再归对象所以，而是属于类 可以理解为是共有的，也就说只要通过类名就可以访问，不需要耗费资源反复创建对象，因为在程序第一次加载的时候就已经在内存中了，直到程序结束该内存才会释放。如果不是 static 修饰的成员在使用完之后该内存就会被回收，所以说 static 要慎用,根据实际情况而定</p><p>如果这个方法是作为一个工具来使用，就声明为 static，不用 new 一个对象出来就可以使用了，比如连接到数据库，我声明一个 getConnection() 的方法，就定义为静态的，因为连接到数据库不是某一个对象所特有的。它只作为一个连接到数据库的工具。至于提高效率的也未 必，要看具体的方法的用处，去定义这个方法是不是静态的。</p><h2 id="2-3-静态代码块"><a href="#2-3-静态代码块" class="headerlink" title="2.3. 静态代码块"></a>2.3. 静态代码块</h2><p>static 代码块也叫静态代码块，是在类中独立于类成员的 static 语句块，可以有多个，位置可以随便放，它不在任何的方法体内，JVM 加载类时会执行这些静态的代码块，如果 static 代码块有多个，JVM 将按照它们在类中出现的先后顺序依次执行它们，每个代码块只会被执行一次，所以说 static 块可以用来优化程序性能。</p><h2 id="2-4-静态代码块和静态方法的区别"><a href="#2-4-静态代码块和静态方法的区别" class="headerlink" title="2.4. 静态代码块和静态方法的区别"></a>2.4. 静态代码块和静态方法的区别</h2><h3 id="2-4-1-执行"><a href="#2-4-1-执行" class="headerlink" title="2.4.1. 执行"></a>2.4.1. 执行</h3><p>静态代码块是自动执行的;<br>静态方法是被调用的时候才执行的.</p><h3 id="2-4-2-场景"><a href="#2-4-2-场景" class="headerlink" title="2.4.2. 场景"></a>2.4.2. 场景</h3><p>静态方法：如果我们在程序编写的时候需要一个不实例化对象就可以调用的方法，我们就可以使用静态方法，具体实现是在方法前面加上 static</p><blockquote><p>在静态方法里只能直接调用同类中其他的静态成员（包括变量和方法），而不能直接访问类中的非静态成员。这是因为，对于非静态的方法和变量，需要先创建类的实例对象后才可使用，而静态方法在使用前不用创建任何对象。（备注：静态变量是属于整个类的变量而不是属于某个对象的）</p></blockquote><blockquote><p>静态方法<span style="background-color:#ff00ff">不能以任何方式引用 this 和 super 关键字</span>，因为静态方法在使用前不用创建任何实例对象，当静态方法调用时，this 所引用的对象根本没有产生。</p></blockquote><p>静态程序块：当一个类需要在被载入时就执行一段程序或者加载资源，这样可以使用静态程序块。</p><h2 id="2-5-✅✅类加载相关"><a href="#2-5-✅✅类加载相关" class="headerlink" title="2.5. ✅✅类加载相关"></a>2.5. ✅✅类加载相关</h2><h3 id="2-5-1-静态变量的内存分配和零值赋值"><a href="#2-5-1-静态变量的内存分配和零值赋值" class="headerlink" title="2.5.1. 静态变量的内存分配和零值赋值"></a>2.5.1. 静态变量的内存分配和零值赋值</h3><p>在类加载过程中链接阶段的第二个小阶段，准备阶段，分配内存并赋零值</p><h4 id="2-5-1-1-被-final-修饰的基本数据类型"><a href="#2-5-1-1-被-final-修饰的基本数据类型" class="headerlink" title="2.5.1.1. 被 final 修饰的基本数据类型"></a>2.5.1.1. 被 final 修饰的基本数据类型</h4><p>赋零值是在编译阶段，在准备阶段进行的是显示初始化，就是说此种情况已经在准备阶段初始化完成了，不会在类加载的初始化阶段再进行初始化。也就是说此类变量的使用，不会触发类的初始化动作</p><h4 id="2-5-1-2-被-final-修饰但涉及计算"><a href="#2-5-1-2-被-final-修饰但涉及计算" class="headerlink" title="2.5.1.2. 被 final 修饰但涉及计算"></a>2.5.1.2. 被 final 修饰但涉及计算</h4><p>因为涉及到计算，所以必须在 <code>&lt;clinit&gt;</code> 中进行计算赋值，所以此种类型的变量的使用会触发类的初始化动作</p><h3 id="2-5-2-静态变量初始化"><a href="#2-5-2-静态变量初始化" class="headerlink" title="2.5.2. 静态变量初始化"></a>2.5.2. 静态变量初始化</h3><p>在初始化阶段执行 <code>&lt;clinit&gt;</code> 方法时进行</p><h3 id="2-5-3-静态代码块执行"><a href="#2-5-3-静态代码块执行" class="headerlink" title="2.5.3. 静态代码块执行"></a>2.5.3. 静态代码块执行</h3><p>在初始化阶段执行 <code>&lt;clinit&gt;</code> 方法时进行</p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><p><a href="https://www.cnblogs.com/dotgua/p/6354151.html?utm_source=itdadao&amp;utm_medium=referral">https://www.cnblogs.com/dotgua/p/6354151.html?utm_source=itdadao&amp;utm_medium=referral</a><br>[[[java]static 关键字的四种用法 - dotgua - 博客园]]</p>]]></content>
      
      
      <categories>
          
          <category> 001-基础知识专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 关键字和接口 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>001-基础知识专题-关键字和接口-5、this</title>
      <link href="/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-5%E3%80%81this/"/>
      <url>/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-5%E3%80%81this/</url>
      
        <content type="html"><![CDATA[<h1 id="实战经验"><a href="#实战经验" class="headerlink" title="实战经验"></a>实战经验</h1><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> 001-基础知识专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 关键字和接口 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>001-基础知识专题-关键字和接口-6、enum</title>
      <link href="/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-6%E3%80%81enum/"/>
      <url>/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-6%E3%80%81enum/</url>
      
        <content type="html"><![CDATA[<h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p>Java 枚举是一个特殊的类，一般表示一组常量，比如一年的 4 个季节，一个年的 12 个月份，一个星期的 7 天，方向有东南西北等。</p><p>Java 枚举类使用 enum 关键字来定义，各个常量使用逗号 <code> , </code> 来分割。</p><h1 id="2-保证线程安全"><a href="#2-保证线程安全" class="headerlink" title="2. 保证线程安全"></a>2. 保证线程安全</h1><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">enum</span> <span class="hljs-title class_">t</span> &#123; SPRING,SUMMER,AUTUMN,WINTER; &#125;<br></code></pre></td></tr></table></figure><h2 id="2-1-enum-关键字的作用"><a href="#2-1-enum-关键字的作用" class="headerlink" title="2.1. enum 关键字的作用"></a>2.1. enum 关键字的作用</h2><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></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-keyword">class</span> <span class="hljs-title class_">T</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Enum</span><br>&#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">T</span><span class="hljs-params">(String s, <span class="hljs-type">int</span> i)</span><br>    &#123;<br>        <span class="hljs-built_in">super</span>(s, i);<br>    &#125;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> T[] values()<br>    &#123;<br>        T at[];<br>        <span class="hljs-type">int</span> i;<br>        T at1[];<br>        System.arraycopy(at = ENUM$VALUES, <span class="hljs-number">0</span>, at1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>[i = at.length], <span class="hljs-number">0</span>, i);<br>        <span class="hljs-keyword">return</span> at1;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> T <span class="hljs-title function_">valueOf</span><span class="hljs-params">(String s)</span><br>    &#123;<br>        <span class="hljs-keyword">return</span> (T)Enum.valueOf(demo/T, s);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T SPRING;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T SUMMER;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T AUTUMN;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T WINTER;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T ENUM$VALUES[];<br>    <span class="hljs-keyword">static</span><br>    &#123;<br>        SPRING = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;SPRING&quot;</span>, <span class="hljs-number">0</span>);<br>        SUMMER = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;SUMMER&quot;</span>, <span class="hljs-number">1</span>);<br>        AUTUMN = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;AUTUMN&quot;</span>, <span class="hljs-number">2</span>);<br>        WINTER = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;WINTER&quot;</span>, <span class="hljs-number">3</span>);<br>        ENUM$VALUES = (<span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>[] &#123;<br>            SPRING, SUMMER, AUTUMN, WINTER<br>        &#125;);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>通过反编译后代码我们可以看到，<code>public final class T extends Enum</code>，说明，该类是继承了 <code>Enum</code> 类的，同时 <code>final</code> 关键字告诉我们，这个类也是不能被继承的。当我们使用 <code>enum</code> 来定义一个枚举类型的时候，<span style="background-color:#ff00ff">编译器会自动帮我们创建一个 <code>final</code> 类型的类继承 Enum 类</span> ，所以枚举类型不能被继承。这个类中有私有构造函数、公共的 <code>values() </code> 和 <code>valueof(String s)</code> 方法、<code>常量类型的枚举内容</code> 以及 <code>给常量赋值的静态代码块</code>❕<span style="display:none">%%<br>1625-🏡⭐️◼️我们用 enum 关键字创建一个 enum 时 JVM 做了什么？会帮我们创建一个 final 修饰的类并继承 Enum 类◼️⭐️-point-202301261625%%</span></p><h2 id="2-2-为什么线程安全⭐️🔴"><a href="#2-2-为什么线程安全⭐️🔴" class="headerlink" title="2.2. 为什么线程安全⭐️🔴"></a>2.2. 为什么线程安全⭐️🔴</h2><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></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">final</span> T SPRING;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T SUMMER;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T AUTUMN;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T WINTER;<br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> T ENUM$VALUES[];<br><br><span class="hljs-keyword">static</span>&#123;<br>SPRING = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;SPRING&quot;</span>, <span class="hljs-number">0</span>);<br>SUMMER = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;SUMMER&quot;</span>, <span class="hljs-number">1</span>);<br>AUTUMN = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;AUTUMN&quot;</span>, <span class="hljs-number">2</span>);<br>WINTER = <span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>(<span class="hljs-string">&quot;WINTER&quot;</span>, <span class="hljs-number">3</span>);<br>ENUM$VALUES = (<span class="hljs-keyword">new</span> <span class="hljs-title class_">T</span>[] &#123;<br>SPRING, SUMMER, AUTUMN, WINTER<br>&#125;);<br>&#125;<br></code></pre></td></tr></table></figure><p>我们可以看到都是 <code>static</code> 类型的，因为 <code>static</code> 类型的属性会在类被加载之后被初始化，当一个 <code>Java</code> 类第一次被主动使用 (<a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/" title="性能调优-基础-6、JVM-类装载子系统">性能调优-基础-6、JVM-类装载子系统</a>) 到的时候静态资源被初始化、<code>Java</code><span style="background-color:#ff0000">类的加载和初始化过程都是线程安全的</span>。所以，创建一个 <code>enum</code> 类型是线程安全的。</p><blockquote><p>初始化时，对于 <code>&lt;clinit&gt; ()</code> 方法的调用，虚拟机会自己确保其在多线程环境中的安全性。因为 <code>&lt;clinit&gt; ()</code> 方法是<span style="background-color:#ff0000">带锁线程安全</span>，所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞，并且这种阻塞很难被发现。</p></blockquote><p>然后 new 关键字会创建对象，在创建对象过程中，JVM 会通过<span style="background-color:#ff0000">TLAB 以及 CAS</span>的方式保证<span style="background-color:#ff0000">创建对象时的线程安全</span>，综合来说，enum 是线程安全的。</p><h1 id="3-最佳单例实现方式⭐️🔴"><a href="#3-最佳单例实现方式⭐️🔴" class="headerlink" title="3. 最佳单例实现方式⭐️🔴"></a>3. 最佳单例实现方式⭐️🔴</h1><p><code>Effective Java，</code> 作者，<code>Josh Bloch，</code> 提倡使用枚举的方式，既然大神说这种方式好，那我们就要知道它为什么好？❕<span style="display:none">%%<br>0846-🏡⭐️◼️为什么枚举是最佳单例实现？写法简单；自己处理反序列化，不会破坏单例；禁止被反射创建◼️⭐️-point-202301230846%%</span></p><h2 id="3-1-枚举写法简单"><a href="#3-1-枚举写法简单" class="headerlink" title="3.1. 枚举写法简单"></a>3.1. 枚举写法简单</h2><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">enum</span> <span class="hljs-title class_">EasySingleton</span>&#123;<br>    INSTANCE;<br>&#125;<br></code></pre></td></tr></table></figure><p>可以直接通过 <code>EasySingleton.INSTANCE</code> 来访问。</p><h2 id="3-2-枚举自己处理序列化"><a href="#3-2-枚举自己处理序列化" class="headerlink" title="3.2. 枚举自己处理序列化"></a>3.2. 枚举自己处理序列化</h2><p><span style="background-color:#00ff00">因为枚举自己处理序列化，而且又无法反射获取到单实例。所以唯一一种不会被破坏的单例实现模式。</span>其他方式都会被<span style="background-color:#ff00ff">反射或者反序列化</span>破坏掉单例模式。❕<span style="display:none">%%<br>0847-🏡⭐️◼️单例被破坏的情况和原因：反射会破坏单例，最后调用 newInstance 生成一个新对象。反序列化同样的原理◼️⭐️-point-202301230847%%</span></p><p>我们知道，以前的所有的单例模式都有一个比较大的问题，就是一旦实现了 <code>Serializable</code> 接口之后，就不再是单例的了，因为，每次调用 <code>readObject()</code> 方法返回的都是一个新创建出来的对象，有一种解决办法就是使用 <code>readResolve()</code> <span style="display:none">%%⭐️解决序列化破坏单例⭐️-point-202301102128%%</span>方法来避免此事发生。 [[内功心法专题-设计模式-3、单例模式#5 1 2 1 为什么readResolve可以解决⭐️🔴]]</p><p>但是，为了保证枚举类型像 <code>Java</code> 规范中所说的那样，每一个枚举类型极其定义的枚举变量在 <code>JVM中</code> 都是唯一的，在枚举类型的序列化和反序列化上，<code>Java</code> 做了特殊的规定。原文如下：</p><blockquote><p>Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.</p></blockquote><p>大概意思就是说，在序列化的时候 <code>Java</code> <span style="background-color:#00ff00">仅仅是将枚举对象的 <code>name</code> 属性</span>输出到结果中，反序列化的时候则是通过 <code>java.lang.Enum</code> 的 <code>valueOf</code> 方法来根据名字查找枚举对象。同时，编译器是不允许任何对这种序列化机制的进行定制，因此禁用了 <code>writeObject、readObject、readObjectNoData、writeReplace</code> 和 <code>readResolve</code> 等方法。我们看一下这个 <code>valueOf</code> 方法：<span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230401-2123%%</span>❕ ^eef92o</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230112072603.png" alt="image.png"></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">static</span> &lt;T <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Enum</span>&lt;T&gt;&gt; T <span class="hljs-title function_">valueOf</span><span class="hljs-params">(Class&lt;T&gt; enumType,  </span><br><span class="hljs-params">                                            String name)</span> &#123;  <br>    <span class="hljs-type">T</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> enumType.enumConstantDirectory().get(name);  <br>    <span class="hljs-keyword">if</span> (result != <span class="hljs-literal">null</span>)  <br>        <span class="hljs-keyword">return</span> result;  <br>    <span class="hljs-keyword">if</span> (name == <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>(<span class="hljs-string">&quot;Name is null&quot;</span>);  <br>    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>(  <br>        <span class="hljs-string">&quot;No enum constant &quot;</span> + enumType.getCanonicalName() + <span class="hljs-string">&quot;.&quot;</span> + name);  <br>&#125;<br></code></pre></td></tr></table></figure><p>从代码中可以看到，代码会尝试从调用 <code>enumType</code> 这个 <code>Class</code> 对象的 <code>enumConstantDirectory()</code> 方法返回的 <code>map</code> 中获取名字为 <code>name</code> 的枚举对象，如果不存在就会抛出异常。再进一步跟到 <code>enumConstantDirectory()</code> 方法中调用了 <code>getEnumConstantsShared()</code>，就会发现<span style="background-color:#ff0000">到最后会以反射的方式调用 <code>enumType</code> 这个类型的 <code>values()</code> 静态方法</span>，也就是上面我们看到的编译器为我们创建的那个 <code>values()</code> 方法，然后用返回结果填充 <code>enumType</code> 这个 <code>Class</code> 对象中的 <code>enumConstantDirectory</code> 属性。❕<span style="display:none">%%<br>1633-🏡⭐️◼️虽然枚举类禁止被反射获取，但枚举类自己处理反序列化时却用到了反射，如此对比也没什么意义，只是想到了这一点。枚举自己处理反序列化时，最终底层通过反射调用了枚举类中的 values 方法获取到了枚举类放到 enumConstantDirectory 中◼️⭐️-point-202301261633%%</span><br>❕<span style="display:none">%%<br>🏡⭐️◼️enum 反序列化不破坏单例的原理◼️⭐️-point-202301202254%%</span></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">Map&lt;String, T&gt; <span class="hljs-title function_">enumConstantDirectory</span><span class="hljs-params">()</span> &#123;  <br>    <span class="hljs-keyword">if</span> (enumConstantDirectory == <span class="hljs-literal">null</span>) &#123;  <br>        T[] universe = getEnumConstantsShared();  <br>        <span class="hljs-keyword">if</span> (universe == <span class="hljs-literal">null</span>)  <br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>(  <br>                getName() + <span class="hljs-string">&quot; is not an enum type&quot;</span>);  <br>        Map&lt;String, T&gt; m = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;(<span class="hljs-number">2</span> * universe.length);  <br>        <span class="hljs-keyword">for</span> (T constant : universe)  <br>            m.put(((Enum&lt;?&gt;)constant).name(), constant);  <br>        enumConstantDirectory = m;  <br>    &#125;  <br>    <span class="hljs-keyword">return</span> enumConstantDirectory;  <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><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></pre></td><td class="code"><pre><code class="hljs java">T[] getEnumConstantsShared() &#123;  <br>    <span class="hljs-keyword">if</span> (enumConstants == <span class="hljs-literal">null</span>) &#123;  <br>        <span class="hljs-keyword">if</span> (!isEnum()) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;  <br>        <span class="hljs-keyword">try</span> &#123;  <br>            <span class="hljs-keyword">final</span> <span class="hljs-type">Method</span> <span class="hljs-variable">values</span> <span class="hljs-operator">=</span> getMethod(<span class="hljs-string">&quot;values&quot;</span>);  <br>            java.security.AccessController.doPrivileged(  <br>                <span class="hljs-keyword">new</span> <span class="hljs-title class_">java</span>.security.PrivilegedAction&lt;Void&gt;() &#123;  <br>                    <span class="hljs-keyword">public</span> Void <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;  <br>                            values.setAccessible(<span class="hljs-literal">true</span>);  <br>                            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;  <br>                        &#125;  <br>                    &#125;);  <br>            <span class="hljs-meta">@SuppressWarnings(&quot;unchecked&quot;)</span>  <br>            T[] temporaryConstants = (T[])values.invoke(<span class="hljs-literal">null</span>);  <br>            enumConstants = temporaryConstants;  <br>        &#125;  <br>        <span class="hljs-comment">// These can happen when users concoct enum-like classes  </span><br>        <span class="hljs-comment">// that don&#x27;t comply with the enum spec.        catch (InvocationTargetException | NoSuchMethodException |  </span><br>               IllegalAccessException ex) &#123; <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; &#125;  <br>    &#125;  <br>    <span class="hljs-keyword">return</span> enumConstants;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-3-枚举禁止被反射创建"><a href="#3-3-枚举禁止被反射创建" class="headerlink" title="3.3. 枚举禁止被反射创建"></a>3.3. 枚举禁止被反射创建</h2><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-comment">/**</span><br><span class="hljs-comment"> * 枚举测试类</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">Etp</span> &#123;<br>    INSTANCE;<br> <br>    <span class="hljs-keyword">public</span> Etp <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-keyword">return</span> INSTANCE;<br>    &#125;<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><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_">Test</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">Etp</span> <span class="hljs-variable">instance</span> <span class="hljs-operator">=</span> Etp.INSTANCE;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">Class</span> <span class="hljs-variable">simple</span> <span class="hljs-operator">=</span> Class.forName(<span class="hljs-string">&quot;com.yuanping.sjms_demo.singleton.Etp&quot;</span>);<br>            <span class="hljs-type">Constructor</span> <span class="hljs-variable">declaredConstructor</span> <span class="hljs-operator">=</span> simple.getDeclaredConstructor(String.class, <span class="hljs-type">int</span>.class);<br>            declaredConstructor.setAccessible(<span class="hljs-literal">true</span>);<br>            <span class="hljs-type">Etp</span> <span class="hljs-variable">o</span> <span class="hljs-operator">=</span> (Etp)declaredConstructor.newInstance();<br>            System.out.println(instance == o);<br>        &#125; <span class="hljs-keyword">catch</span> (ClassNotFoundException e) &#123;<br> <br>        &#125; <span class="hljs-keyword">catch</span> (NoSuchMethodException e) &#123;<br>            e.printStackTrace();<br>        &#125; <span class="hljs-keyword">catch</span> (InvocationTargetException e) &#123;<br>            e.printStackTrace();<br>        &#125; <span class="hljs-keyword">catch</span> (InstantiationException e) &#123;<br>            e.printStackTrace();<br>        &#125; <span class="hljs-keyword">catch</span> (IllegalAccessException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br> <br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230110180454.png"></p><p>否则报错：<code>Cannot reflectively create enum objects</code> ❕<span style="display:none">%%<br>🏡⭐️◼️反射创建枚举对象会报错，newInstance 方法中做了判断处理◼️⭐️-point-202301202254%%</span></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h2 id="4-1-开发使用"><a href="#4-1-开发使用" class="headerlink" title="4.1. 开发使用"></a>4.1. 开发使用</h2><p>枚举类中的抽象方法实现，需要枚举类中的每个对象都对其进行实现。</p><h2 id="4-2-服务升级-反序列化失败"><a href="#4-2-服务升级-反序列化失败" class="headerlink" title="4.2. 服务升级 - 反序列化失败"></a>4.2. 服务升级 - 反序列化失败</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230401-2133%%</span>❕ ^tai2qg</p><h3 id="4-2-1-序列化内容与代码中不一致"><a href="#4-2-1-序列化内容与代码中不一致" class="headerlink" title="4.2.1. 序列化内容与代码中不一致"></a>4.2.1. 序列化内容与代码中不一致</h3><p>在系统或者类库升级时，对其中定义的枚举类型多加注意，为了保持代码上的兼容性，如果我们定义的枚举类型有可能会被序列化保存 (放到文件中、保存到数据库中，进入分布式内存缓存中)，那么我们是<span style="background-color:#ff0000">不能够删除原来枚举类型中定义的任何枚举对象的，否则程序在运行过程中，JVM 拿着之前保存的名称进行反序列化时，就会抱怨找不到与某个名字对应的枚举对象了</span>。</p><h3 id="4-2-2-服务端与客户端不一致"><a href="#4-2-2-服务端与客户端不一致" class="headerlink" title="4.2.2. 服务端与客户端不一致"></a>4.2.2. 服务端与客户端不一致</h3><p>另外，在远程方法调用过程中，如果我们发布的客户端接口返回值中使用了枚举类型，那么服务端在升级过程中就需要特别注意。如果在接口的返回结果的枚举类型中添加了新的枚举值，那就会导致仍然在使用老的客户端的那些应用出现调用失败的情况。<br>#todo</p><span style="display:none">- [ ] 🚩 - 实战经验列表筛选规则定义 - 🏡 2023-02-09 09:30</span><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><p><a href="https://www.cnblogs.com/z00377750/p/9177097.html">https://www.cnblogs.com/z00377750/p/9177097.html</a><br>[[深度分析Java的枚举类型—-枚举的线程安全性及序列化问题 - 风动静泉 - 博客园]]</p>]]></content>
      
      
      <categories>
          
          <category> 关键字和接口 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> enum </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-1、UML</title>
      <link href="/2023/01/07/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-1%E3%80%81UML/"/>
      <url>/2023/01/07/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-1%E3%80%81UML/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p>统一建模语言（Unified Modeling Language，UML）是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。</p><p>UML 从目标系统的不同角度出发，定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。</p><h1 id="2-类图概述"><a href="#2-类图概述" class="headerlink" title="2. 类图概述"></a>2. 类图概述</h1><p>类图(Class diagram)是显示了模型的静态结构，特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。</p><h1 id="3-类图的作用"><a href="#3-类图的作用" class="headerlink" title="3. 类图的作用"></a>3. 类图的作用</h1><ul><li>在软件工程中，类图是一种静态的结构图，描述了系统的类的集合，类的属性和类之间的关系，可以简化了人们对系统的理解；</li><li>类图是系统分析和设计阶段的重要产物，是系统编码和测试的重要模型。</li></ul><h1 id="4-类的表示方式"><a href="#4-类的表示方式" class="headerlink" title="4. 类的表示方式"></a>4. 类的表示方式</h1><p>在UML类图中，类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示，比如下图表示一个Employee类，它包含name,age和address这3个属性，以及work()方法。</p><p>属性&#x2F;方法名称前加的加号和减号表示了这个属性&#x2F;方法的可见性，UML类图中表示可见性的符号有三种：</p><ul><li>+：表示public</li><li>-：表示private</li><li><h1 id="：表示protected"><a href="#：表示protected" class="headerlink" title="：表示protected"></a>：表示protected</h1></li></ul><p>属性的完整表示方式是： <strong>可见性 名称 ：类型 [ &#x3D; 缺省值]</strong></p><p>方法的完整表示方式是： <strong>可见性 名称(参数列表) [ ： 返回类型]</strong></p><blockquote><p>注意：</p><p>1，中括号中的内容表示是可选的</p><p>2，也有将类型放在变量名前面，返回值类型放在方法名前面</p></blockquote><p><strong>举个栗子：</strong></p><p>上图Demo类定义了三个方法：</p><ul><li>method()方法：修饰符为public，没有参数，没有返回值。</li><li>method1()方法：修饰符为private，没有参数，返回值类型为String。</li><li>method2()方法：修饰符为protected，接收两个参数，第一个参数类型为int，第二个参数类型为String，返回值类型是int。</li></ul><h1 id="5-类与类之间关系"><a href="#5-类与类之间关系" class="headerlink" title="5. 类与类之间关系"></a>5. 类与类之间关系</h1><p><a href="https://www.open-open.com/lib/view/open1328059700311.html">https://www.open-open.com/lib/view/open1328059700311.html</a></p><h2 id="5-1-泛化（Generalization）"><a href="#5-1-泛化（Generalization）" class="headerlink" title="5.1. 泛化（Generalization）"></a>5.1. 泛化（Generalization）</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107211949.jpg" alt="image-20200320111648269"></p><h2 id="5-2-实现（Realization）"><a href="#5-2-实现（Realization）" class="headerlink" title="5.2. 实现（Realization）"></a>5.2. 实现（Realization）</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107212007.jpg" alt="image-20200320111719491"></p><h2 id="5-3-关联（Association"><a href="#5-3-关联（Association" class="headerlink" title="5.3. 关联（Association)"></a>5.3. 关联（Association)</h2><p>代码体现：<span style="background-color:#00ff00">成员变量</span></p><p>是一种拥有关系，它使一个类知道另一个类的属性和方法；双向的关联可以有两个箭头或者没有箭头，单向的关联有一个箭头。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107212017.jpg" alt="image-20200320111105079"></p><h2 id="5-4-聚合（Aggregation）"><a href="#5-4-聚合（Aggregation）" class="headerlink" title="5.4. 聚合（Aggregation）"></a>5.4. 聚合（Aggregation）</h2><p>代码体现：<span style="background-color:#00ff00">成员变量</span></p><p>与关联不同的是，聚合和组合<span style="background-color:#00ff00">有整体和部分关系的含义</span>。与关联相同的是，也有拥有的关系。</p><p>与组合不同的是，整体拥有部分，<span style="background-color:#00ff00">部分可以离开整体而独立存在且有意义</span>，比如汽车和轮胎的关系</p><p>聚合关系是关联关系的一种，是强的关联关系；关联和聚合在语法上无法区分，必须考察具体的逻辑关系。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107212028.jpg" alt="image-20200320110441358"></p><h2 id="5-5-组合（Composition）"><a href="#5-5-组合（Composition）" class="headerlink" title="5.5. 组合（Composition）"></a>5.5. 组合（Composition）</h2><p>代码体现：<span style="background-color:#00ff00">成员变量</span></p><p>是<span style="background-color:#00ff00">has-a</span>的关系，整体包含部分，部分不能离开整体单独存在而有意义，<span style="background-color:#00ff00">比如公司和部门的关系</span></p><p>组合关系是关联关系的一种，是比聚合关系还要强的关系，它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107212101.jpg" alt="https://tva1.sinaimg.cn/large/00831rSTly1gd07t4z33vj312q0m8q4o.jpg"></p><h2 id="5-6-依赖（Dependency）"><a href="#5-6-依赖（Dependency）" class="headerlink" title="5.6. 依赖（Dependency）"></a>5.6. 依赖（Dependency）</h2><p>代码体现：<span style="background-color:#00ff00">局部变量、方法参数、静态方法的调用</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107212115.jpg" alt="image-20200320111814018"><br>❕<span style="display:none">%%<br>1626-🏡⭐️◼️迪米特法则要求：依赖关系中的局部变量不要引入其他类，否则会导致引入耦合关系。解决办法就是将非直接朋友封装到直接朋友中，比如方法参数引用的类中◼️⭐️-point-202301231626%%</span></p><h2 id="5-7-总结"><a href="#5-7-总结" class="headerlink" title="5.7. 总结"></a>5.7. 总结</h2><p>各种关系的强弱顺序：</p><p><strong>泛化</strong> <strong>&#x3D;</strong> <strong>实现</strong> <strong>&gt;</strong> <strong>组合</strong> <strong>&gt;</strong> <strong>聚合</strong> <strong>&gt;</strong> <strong>关联</strong> <strong>&gt;</strong> <strong>依赖</strong></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/00831rSTly1gd0867je2ej30m20hx74m.jpg" alt="https://tva1.sinaimg.cn/large/00831rSTly1gd0867je2ej30m20hx74m.jpg"></p><h1 id="6-IDEA-plantUML-使用"><a href="#6-IDEA-plantUML-使用" class="headerlink" title="6. IDEA plantUML 使用"></a>6. IDEA plantUML 使用</h1><h1 id="7-其他工具"><a href="#7-其他工具" class="headerlink" title="7. 其他工具"></a>7. 其他工具</h1><p>visual paradigm</p><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> UML </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-2、设计模式及设计原则</title>
      <link href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"/>
      <url>/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/</url>
      
        <content type="html"><![CDATA[<h1 id="1-设计模式"><a href="#1-设计模式" class="headerlink" title="1. 设计模式"></a>1. 设计模式</h1><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p>软件工程中，设计模式（design pattern）是对软件设计中<span style="background-color:#00ff00">普遍存在（反复出现）</span><br>的各种问题，所提出的解决方案。</p><ol><li>设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验，模式不是代码，而是某类问题的<span style="background-color:#00ff00">通用解决方案，设计模式（Design pattern）代表了最佳的实践</span>。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。</li><li>设计模式的本质<span style="background-color:#00ff00">提高软件的维护性，通用性和扩展性，并降低软件的复杂度</span></li></ol><h2 id="1-2-为什么"><a href="#1-2-为什么" class="headerlink" title="1.2. 为什么"></a>1.2. 为什么</h2><p>编写软件过程中，程序员面临着来自 <span style="background-color:#ffff00">耦合性，内聚性以及可维护性，可扩展性，重</span><span style="background-color:#ffff00">用性，灵活性 </span>等多方面的挑战，设计模式是为了让程序 (软件)，具有更好</p><ol><li>代码重用性 (即：相同功能的代码，不用多次编写)</li><li>可读性 (即：编程规范性, 便于其他程序员的阅读和理解)</li><li>可扩展性 (即：当需要增加新的功能时，非常的方便，称为可维护)</li><li>可靠性 (即：当我们增加新的功能后，对原来的功能没有影响)</li><li>使程序呈现高内聚，低耦合的特性<br>❕<span style="display:none">%%<br>1620-🏡⭐️◼️设计模式的目的？高内聚 (结构上)、低耦合 (行为上)；(3 易 2 可)：易复用、易扩展、易维护；高可读、高可靠◼️⭐️-point-202301231620%%</span></li></ol><h2 id="1-3-分类"><a href="#1-3-分类" class="headerlink" title="1.3. 分类"></a>1.3. 分类</h2><p>设计模式分为三种类型，共 23 种</p><ol><li>创建型模式：</li></ol><ul><li>单例模式</li><li>工厂方法模式</li><li>抽象工厂模式</li><li>原型模式</li><li>建造者模式</li></ul><ol start="2"><li>结构型模式：</li></ol><ul><li>代理模式</li><li>适配器模式</li><li>装饰者模式</li><li>桥接模式</li><li>外观模式</li><li>组合模式</li><li>享元模式</li></ul><ol start="3"><li>行为型模式：</li></ol><ul><li>模板方法模式</li><li>策略模式</li><li>命令模式</li><li>职责链模式</li><li>状态模式</li><li>观察者模式</li><li>中介者模式</li><li>迭代器模式</li><li>访问者模式</li><li>备忘录模式</li><li>解释器模式<br>❕<span style="display:none">%%<br>1427-🏡⭐️◼️23 种设计模式有什么？◼️⭐️-point-202301231427%%</span></li></ul><h1 id="2-七大设计原则"><a href="#2-七大设计原则" class="headerlink" title="2. 七大设计原则"></a>2. 七大设计原则</h1><p>设计模式原则，其实就是程序员在编程时，应当遵守的原则，也是各种设计模<br>式的基础 (即：设计模式为什么这样设计的依据)</p><p>设计模式常用的七大原则有:</p><ol><li>开闭原则</li><li>里氏替换原则</li><li>合成复用原则</li><li>依赖倒转 (倒置) 原则</li><li>接口隔离原则</li><li>迪米特法则</li><li>单一职责原则<br>❕<span style="display:none">%%<br>1432-🏡⭐️◼️七大设计原则：开闭原则、里氏代换原则、组合复用原则、依赖倒转原则、接口隔离、迪米特法则、单一职责原则◼️⭐️-point-202301231432%%</span></li></ol><h2 id="2-1-开闭原则-Open-Closed-Principle"><a href="#2-1-开闭原则-Open-Closed-Principle" class="headerlink" title="2.1. 开闭原则 (Open Closed Principle)"></a>2.1. 开闭原则 (Open Closed Principle)</h2><h3 id="2-1-1-是什么"><a href="#2-1-1-是什么" class="headerlink" title="2.1.1. 是什么"></a>2.1.1. 是什么</h3><ol><li>开闭原则（Open Closed Principle）是编程中最基础、最重要的设计原则</li><li>一个软件实体如类，模块和函数应该对扩展开放 (<span style="background-color:#ffff00">对提供方</span>)，对修改关闭 (<span style="background-color:#ffff00">对使用</span><span style="background-color:#ffff00">方</span>)。用抽象构建框架，用实现扩展细节。</li><li>当软件需要变化时，<span style="background-color:#ff00ff">尽量通过扩展软件实体的行为来实现变化，而不是通过修改已</span><span style="background-color:#ff00ff">有的代码来实现变化。</span></li><li>编程中遵循其它原则，以及使用设计模式的目的就是遵循开闭原则。</li></ol><h3 id="2-1-2-为什么"><a href="#2-1-2-为什么" class="headerlink" title="2.1.2. 为什么"></a>2.1.2. 为什么</h3><p><strong>对扩展开放，对修改关闭</strong>。在程序需要进行拓展的时候，不修改原有的代码，而实现一个热插拔的效果。简言之，是为了使程序的扩展性好，易于维护和升级。</p><p>想要达到这样的效果，我们<span style="background-color:#00ff00">需要使用接口和抽象类</span>。</p><p>因为抽象灵活性好，适应性广，只要抽象的合理，可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展，当软件需要发生变化时，只需要根据需求重新派生一个实现类来扩展就可以了。</p><h3 id="2-1-3-案例"><a href="#2-1-3-案例" class="headerlink" title="2.1.3. 案例"></a>2.1.3. 案例</h3><p>下面以 <code>搜狗输入法</code> 的皮肤为例介绍开闭原则的应用。</p><p>【例】<code>搜狗输入法</code> 的皮肤设计。</p><p>分析：<code>搜狗输入法</code> 的皮肤是输入法背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的输入法的皮肤，也可以从网上下载新的皮肤。这些皮肤有共同的特点，可以为其定义一个抽象类（AbstractSkin），而每个具体的皮肤（DefaultSpecificSkin 和 HeimaSpecificSkin）是其子类。用户窗体可以根据需要选择或者增加新的主题，而不需要修改原代码，所以它是满足开闭原则的。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205225928.png"></p><h3 id="2-1-4-满足开闭原则的设计模式"><a href="#2-1-4-满足开闭原则的设计模式" class="headerlink" title="2.1.4. 满足开闭原则的设计模式"></a>2.1.4. 满足开闭原则的设计模式</h3><p>[[内功心法专题-设计模式-0、设计模式总结#^8yg365]]</p><h2 id="2-2-里氏代换原则-Liskov-Substitution-Principle"><a href="#2-2-里氏代换原则-Liskov-Substitution-Principle" class="headerlink" title="2.2. 里氏代换原则 (Liskov Substitution Principle)"></a>2.2. 里氏代换原则 (Liskov Substitution Principle)</h2><h3 id="2-2-1-是什么"><a href="#2-2-1-是什么" class="headerlink" title="2.2.1. 是什么"></a>2.2.1. 是什么</h3><p>里氏代换原则是面向对象设计的基本原则之一。<span style="background-color:#00ff00">通常类的复用分为继承复用和合成复用两种。里氏代换原则就属于继承复用的复用类型</span></p><p>里氏代换原则：任何基类可以出现的地方，子类一定可以出现。通俗理解：<span style="background-color:#00ff00">子类可以扩展父类的功能，但不能改变父类原有的功能</span>。换句话说，子类继承父类时，除添加新的方法完成新增功能外，<span style="background-color:#ff0000">尽量不要重写</span>父类的方法。</p><p>如果通过重写父类的方法来完成新的功能，这样写起来虽然简单，但是整个继承体系的可复用性会比较差，特别是运用多态比较频繁时，程序运行出错的概率会非常大。</p><p><span style="background-color:#ff00ff">子类可以在任意方法中，替代父类作为方法的入参</span></p><h3 id="2-2-2-为什么"><a href="#2-2-2-为什么" class="headerlink" title="2.2.2. 为什么"></a>2.2.2. 为什么</h3><ol><li>继承包含这样一层含义：父类中凡是已经实现好的方法，实际上是在设定规范和契<br>约，<u>虽然它不强制要求所有的子类必须遵循这些契约，但是如果子类对这些已经实</u><br><u>现的方法任意修改，<span style="background-color:#ffff00">就会对整个继承体系造成破坏</span>。</u></li><li><span style="background-color:#ff00ff">继承在给程序设计带来便利的同时，也带来了弊端。</span>比如使用继承会给程序带来侵<br>入性，程序的可移植性降低，增加对象间的耦合性，<span style="background-color:#ff0000">如果一个类被其他的类所继承，</span><br><span style="background-color:#ff0000">则当这个类需要修改时，必须考虑到所有的子类，并且父类修改后，所有涉及到子</span><br><span style="background-color:#ff0000">类的功能都有可能产生故障</span></li><li>问题提出：在编程中，如何正确的使用继承? &#x3D;&gt; <span style="background-color:#00ff00">里氏替换原则</span></li></ol><h3 id="2-2-3-如何遵循里氏替换原则⭐️🔴"><a href="#2-2-3-如何遵循里氏替换原则⭐️🔴" class="headerlink" title="2.2.3. 如何遵循里氏替换原则⭐️🔴"></a>2.2.3. 如何遵循里氏替换原则⭐️🔴</h3><ol><li>子类可以实现父类的抽象方法，但<span style="background-color:#ff0000">不能覆盖父类的非抽象方法</span>。</li><li>子类中可以增加自己特有的方法。</li><li>当子类的方法重载父类的方法时，方法的前置条件（即方法的形参）要比父类方法的输入参数更宽松。</li><li>当子类的方法实现父类的抽象方法时，方法的后置条件（即方法的返回值）要比父类更严格。</li><li>里氏替换原则告诉我们，继承实际上让两个类耦合性增强了，在适当的情况下，可</li><li>以通过<span style="background-color:#00ff00">聚合，组合，依赖</span> 来解决问题。</li><li>其他方案：抽象出更高层的抽象类或者接口。</li></ol><p>❕<span style="display:none">%%<br>1440-🏡⭐️◼️里氏代换原则是正确使用继承的法则：如果无法避免使用继承，那么尽量不要覆盖父类的方法。最好使用组合、聚合 + 构造导入、setter 导入的方式◼️⭐️-point-202301231440%%</span></p><h2 id="2-3-合成复用原则-Composite-Reuse-Principle"><a href="#2-3-合成复用原则-Composite-Reuse-Principle" class="headerlink" title="2.3. 合成复用原则 (Composite Reuse Principle)"></a>2.3. 合成复用原则 (Composite Reuse Principle)</h2><p>合成复用原则是指：尽量先使用<span style="background-color:#ff00ff">组合或者聚合等关联关系</span>来实现，<span style="background-color:#ffff00">其次才考虑使用继承关系</span>来实现。</p><p>通常类的复用分为继承复用和合成复用两种。</p><p>继承复用虽然有简单和易实现的优点，但它也存在以下缺点：</p><ol><li>继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类，父类对子类是透明的，所以这种复用又称为<span style="background-color:#ffff00">“白箱”复用</span>。</li><li><span style="background-color:#ff00ff">子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化</span>，这不利于类的扩展与维护。</li><li>它限制了复用的灵活性。从父类继承而来的实现是静态的，在编译时已经定义，所以在运行时不可能发生变化。</li></ol><p>采用组合或聚合复用时，可以将已有对象纳入新对象中，使之成为新对象的一部分，新对象可以调用已有对象的功能，它有以下优点：</p><ol><li>它维持了类的封装性。因为成分对象的内部细节是新对象看不见的，所以这种复用又称为<span style="background-color:#00ff00">“黑箱”复用</span>。</li><li>对象间的耦合度低。可以在类的成员位置声明抽象。</li><li>复用的灵活性高。这种复用可以在运行时动态进行，新对象可以动态地引用与成分对象类型相同的对象。❕<span style="display:none">%%<br>1443-🏡⭐️◼️白箱复用：继承复用，破坏了类的封装特性，将父类细节暴露给子类，带来变更风险<br>黑箱复用：组合复用，被使用类的内部细节对使用类不可见，保护了封装性，不会让变更扩散◼️⭐️-point-202301231443%%</span></li></ol><p>下面看一个例子来理解合成复用原则</p><p>【例】汽车分类管理程序</p><p>汽车按“动力源”划分可分为汽油汽车、电动汽车等；按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类，其组合就很多。类图如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107211859.png" alt="image-20191229173554296"></p><p>从上面类图我们可以看到使用继承复用产生了很多子类，如果现在又有新的动力源或者新的颜色的话，就需要再定义新的类。我们试着将继承复用改为聚合复用看一下。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107211906.png" alt="image-20191229173554296"></p><h2 id="2-4-依赖倒转原则-Dependence-Inversion-Principle"><a href="#2-4-依赖倒转原则-Dependence-Inversion-Principle" class="headerlink" title="2.4. 依赖倒转原则 (Dependence Inversion Principle)"></a>2.4. 依赖倒转原则 (Dependence Inversion Principle)</h2><h3 id="2-4-1-是什么"><a href="#2-4-1-是什么" class="headerlink" title="2.4.1. 是什么"></a>2.4.1. 是什么</h3><p>依赖倒转原则 (Dependence Inversion Principle) 是指：<br><span style="background-color:#ff00ff">1) 高层模块不应该依赖低层模块，二者都应该依赖其抽象</span><br><span style="background-color:#ff00ff">2) 抽象不应该依赖细节，细节应该依赖抽象</span><br>3) 依赖倒转 (倒置) 的中心思想是<span style="background-color:#00ff00">面向接口编程</span><br>4) 依赖倒转原则是基于这样的设计理念：相对于细节的多变性，抽象的东西要稳定的<br>多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中，<span style="background-color:#00ff00">抽象</span><br><span style="background-color:#00ff00">指的是接口或抽象类，细节就是具体的实现类</span><br>5) 使用接口或抽象类的目的是制定好规范，而不涉及任何具体的操作，把展现细节的<br>任务交给他们的实现类去完成<br>6) 依赖倒转原则可以看做是开闭原则的具体实践</p><h3 id="2-4-2-如何做⭐️🔴"><a href="#2-4-2-如何做⭐️🔴" class="headerlink" title="2.4.2. 如何做⭐️🔴"></a>2.4.2. 如何做⭐️🔴</h3><p>依赖关系传递的三种方式</p><ol><li>接口传递 ❌</li><li>构造方法传递</li><li>setter 方式传递<br>❕<span style="display:none">%%<br>1445-🏡⭐️◼️依赖传递的 3 种方式：接口传递、构造方法传递、setter 方法传递，其中接口注入由于在灵活性和易用性比较差，现在从 Spring4 开始已被废弃。◼️⭐️-point-202301231445%%</span></li></ol><p>依赖注入是时下最流行的 IOC 实现方式，依赖注入分为接口注入（Interface Injection），Setter 方法注入（Setter Injection）和构造器注入（Constructor Injection）三种方式。<span style="background-color:#ff0000">其中接口注入由于在灵活性和易用性比较差，现在从 Spring4 开始已被废弃。</span><br>构造器依赖注入：构造器依赖注入通过容器触发一个类的构造器来实现的，该类有一系列参数，每个参数代表一个对其他类的依赖。<br>Setter 方法注入：Setter 方法注入是容器通过调用无参构造器或无参 static 工厂 方法实例化 bean 之后，调用该 bean 的 setter 方法，即实现了基于 setter 的依赖注入。</p><h3 id="2-4-3-注意细节"><a href="#2-4-3-注意细节" class="headerlink" title="2.4.3. 注意细节"></a>2.4.3. 注意细节</h3><ol><li>低层模块尽量都要有抽象类或接口，或者两者都有，程序稳定性更好.</li><li><span style="background-color:#ff00ff">变量的声明类型尽量是抽象类或接口</span>, 这样我们的变量引用和实际对象间，就存在<br>一个缓冲层，利于程序扩展和优化</li><li><span style="background-color:#ff00ff">继承时遵循里氏替换原则</span></li></ol><h3 id="2-4-4-案例"><a href="#2-4-4-案例" class="headerlink" title="2.4.4. 案例"></a>2.4.4. 案例</h3><p>下面看一个例子来理解依赖倒转原则<br>【例】组装电脑</p><p>现要组装一台电脑，需要配件 cpu，硬盘，内存条。只有这些配置都有了，计算机才能正常的运行。选择 cpu 有很多选择，如 Intel，AMD 等，硬盘可以选择希捷，西数等，内存条可以选择金士顿，海盗船等。</p><p><strong>类图如下：</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107222653.png"></p><p>上面代码可以看到已经组装了一台电脑，但是似乎组装的电脑的 cpu 只能是 Intel 的，内存条只能是金士顿的，硬盘只能是希捷的，这对用户肯定是不友好的，用户有了机箱肯定是想按照自己的喜好，选择自己喜欢的配件。</p><p>根据依赖倒转原则进行改进：</p><p>代码我们只需要修改 Computer 类，让 Computer 类依赖抽象（各个配件的接口），而不是依赖于各个组件具体的实现类。</p><p><strong>类图如下：</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107222701.png" alt="image-20191229173554296"><br>面向对象的开发很好的解决了这个问题，一般情况下<span style="background-color:#00ff00">抽象的变化概率很小</span>，让用户程序依赖于抽象，实现的细节也依赖于抽象。即使实现细节不断变动，<span style="background-color:#00ff00">只要抽象不变，客户程序就不需要变化</span>。这大大降低了客户程序与实现细节的耦合度。</p><h2 id="2-5-接口隔离原则-Interface-Segregation-Principle"><a href="#2-5-接口隔离原则-Interface-Segregation-Principle" class="headerlink" title="2.5. 接口隔离原则 (Interface Segregation Principle)"></a>2.5. 接口隔离原则 (Interface Segregation Principle)</h2><p>客户端不应该被迫依赖于它不使用的方法；<span style="background-color:#ff00ff">一个类对另一个类的依赖应该建立在最小的接口上</span></p><p>下面看一个例子来理解接口隔离原则</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107232938.png"></p><p>类 A 通过接口 Interface1 依赖类 B，类 C 通过接口 Interface1 依赖类 D，如果接口 Interface1 对于类 A 和类 C 来说不是最小接口，那么类 B 和类 D 必须去实现他们不需要的方法。</p><p>按隔离原则应当这样处理：<br>将接口 Interface1 拆分为独立的几个接口，类 A 和类 C 分别与他们需要的接口建立依赖<br>关系。也就是采用接口隔离原则</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107232903.png"><br>❕<span style="display:none">%%<br>1446-🏡⭐️◼️这个图与模板方法模式有点类似◼️⭐️-point-202301231446%%</span></p><h2 id="2-6-迪米特法则-Demeter-Principle"><a href="#2-6-迪米特法则-Demeter-Principle" class="headerlink" title="2.6. 迪米特法则 (Demeter Principle)"></a>2.6. 迪米特法则 (Demeter Principle)</h2><h3 id="2-6-1-是什么"><a href="#2-6-1-是什么" class="headerlink" title="2.6.1. 是什么"></a>2.6.1. 是什么</h3><ol><li>一个对象应该对其他对象保持最少的了解</li><li>类与类关系越密切，耦合度越大</li><li>迪米特法则 (Demeter Principle) 又叫<span style="background-color:#00ff00">最少知道原则</span>，<span style="background-color:#ff00ff">即一个类对自己依赖的类知道的</span><br><span style="background-color:#ff00ff">越少越好</span>。也就是说，对于被依赖的类不管多么复杂，都尽量将逻辑封装在类的内<br>部。对外除了提供的 public 方法，不对外泄露任何信息 ❕<span style="display:none">%%<br>0846-🏡⭐️◼️迪米特法则的含义是什么？🔜📝最少知道原则，即一个类对自己所依赖的类知道的越少越好。被依赖的类不管有多复杂，都不要暴露细节给其他类，而应该封装好只对外提供 public 方法，如此才能减少耦合，提高设计的扩展性和复用性。◼️⭐️-point-202301280846%%</span></li><li>迪米特法则还有个更简单的定义：<span style="background-color:#00ff00">只与直接的朋友通信</span></li><li>直接的朋友：每个对象都会与其他对象有耦合关系，只要两个对象之间有耦合关系，<br>我们就说这两个对象之间是朋友关系。耦合的方式很多，依赖，关联，组合，聚合<br>等。其中，我们称<span style="background-color:#ff00ff">出现在<font color=#ffff00>成员变量，方法参数，方法返回值中的类</font>为直接的朋友</span>，<span style="background-color:#ff0000">而</span><br><span style="background-color:#ff0000">出现在局部变量中的类不是直接的朋友。也就是说，陌生的类最好不要以局部变量</span><br><span style="background-color:#ff0000">的形式出现在类的内部</span>。<span style="background-color:#00ff00">如果出现则转移到方法入参中，交给自己的直接朋友来隔离变更</span> ❕<span style="display:none">%%<br>1456-🏡⭐️◼️直接朋友是什么意思？指依赖关系中的成员变量、方法参数、方法返回值所引用的对象 (记忆方法：类的内容从下往下数)，而非直接朋友指局部变量。如果一个方法中通过成员变量、方法参数或者方法返回值这种直接朋友带入了不认识类作为方法中的局部变量，那么就要将这些变量拿到直接朋友中封装起来◼️⭐️-point-202301231456%%</span></li></ol><h3 id="2-6-2-案例"><a href="#2-6-2-案例" class="headerlink" title="2.6.2. 案例"></a>2.6.2. 案例</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107230401.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107230819.png"></p><p>代码示例：<br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;principle&#x2F;demeter&#x2F;Demeter1.java]]</p><h3 id="2-6-3-注意"><a href="#2-6-3-注意" class="headerlink" title="2.6.3. 注意"></a>2.6.3. 注意</h3><ul><li>1）迪米特法则的核心是降低类之间的耦合</li><li>2）但是注意：由于每个类都减少了不必要的依赖，因此迪米特法则只是要求降低类间（对象间）耦合关系，并不是要求完全没有依赖关系</li></ul><h2 id="2-7-单一职责原则-Single-Responsibility-Principle"><a href="#2-7-单一职责原则-Single-Responsibility-Principle" class="headerlink" title="2.7. 单一职责原则 (Single Responsibility Principle)"></a>2.7. 单一职责原则 (Single Responsibility Principle)</h2><h3 id="2-7-1-是什么"><a href="#2-7-1-是什么" class="headerlink" title="2.7.1. 是什么"></a>2.7.1. 是什么</h3><p>对类来说的，即<span style="background-color:#ff00ff">一个类应该只负责一项职责</span>。如类 A 负责两个不同职责：职责 1，职责 2。<br>当职责 1 需求变更而改变 A 时，可能造成职责 2 执行错误，所以需要将类 A 的粒度分解为 A1，A2</p><p>坚持类级别的单一职责原则，尽量少用方法级别的，否则后面很可能会面临拆分的麻烦</p><h3 id="2-7-2-为什么"><a href="#2-7-2-为什么" class="headerlink" title="2.7.2. 为什么"></a>2.7.2. 为什么</h3><ol><li>降低类的复杂度，一个类只负责一项职责。</li><li>提高类的可读性，可维护性</li><li>降低变更引起的风险</li><li>通常情况下，我们应当遵守单一职责原则，<span style="background-color:#00ff00">只有逻辑足够简单，才可以在代码级违</span><br><span style="background-color:#00ff00">反单一职责原则；也只有在类中方法数量足够少的情况下，才可以下方到方法级别保持单一职责原则</span></li></ol><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><h2 id="3-1-尚硅谷韩顺平"><a href="#3-1-尚硅谷韩顺平" class="headerlink" title="3.1. 尚硅谷韩顺平"></a>3.1. 尚硅谷韩顺平</h2><p><a href="https://www.bilibili.com/video/BV1G4411c7N4?p=20&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1G4411c7N4?p=20&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>课件已下载：&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;架构师之路&#x2F;尚硅谷 - 设计模式<br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;DesignPattern</p><h2 id="3-2-黑马程序员"><a href="#3-2-黑马程序员" class="headerlink" title="3.2. 黑马程序员"></a>3.2. 黑马程序员</h2><p><a href="https://www.bilibili.com/video/BV1Np4y1z7BU?p=35">https://www.bilibili.com/video/BV1Np4y1z7BU?p=35</a></p><p>课件已下载：&#x2F;Volumes&#x2F;Seagate Bas&#x2F;001-ArchitectureRoad&#x2F;资料 -java 设计模式（图解 + 框架源码分析 + 实战）<br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns</p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;009-内功心法专题&#x2F;设计模式&#x2F;黑马&#x2F;设计模式]]</p><h2 id="3-3-网络笔记"><a href="#3-3-网络笔记" class="headerlink" title="3.3. 网络笔记"></a>3.3. 网络笔记</h2><p>笔记： <a href="https://www.yuque.com/u21195183/fnz31h">https://www.yuque.com/u21195183/fnz31h</a><br>代码： <a href="https://github.com/vectorxxxx/NOTE_DesignPatterns">https://github.com/vectorxxxx/NOTE_DesignPatterns</a><br>或 <a href="https://gitee.com/vectorx/NOTE_DesignPatterns">https://gitee.com/vectorx/NOTE_DesignPatterns</a></p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 设计原则 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内功心法专题-设计模式-3、单例模式</title>
      <link href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-3%E3%80%81%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
      <url>/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-3%E3%80%81%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</url>
      
        <content type="html"><![CDATA[<h1 id="1-创建者模式"><a href="#1-创建者模式" class="headerlink" title="1. 创建者模式"></a>1. 创建者模式</h1><p>创建型模式的主要关注点是“怎样创建对象？”，它的主要特点是“将对象的创建与使用分离”。<br>这样可以降低系统的耦合度，使用者不需要关注对象的创建细节。<br>创建型模式分为：</p><ul><li>单例模式 (Singleton)</li><li>工厂方法模式 (FactoryMethod)</li><li>抽象工程模式 (AbstractFactory)</li><li>原型模式 (Prototype)</li><li>建造者模式 (Builder)<br>❕<span style="display:none">%%<br>1804-🏡⭐️◼️5 中创建者设计模式：单例模式、工厂方法模式、抽象工厂模式、原型模式、建造者模式◼️⭐️-point-202301221804%%</span></li></ul><h1 id="2-是什么"><a href="#2-是什么" class="headerlink" title="2. 是什么"></a>2. 是什么</h1><p>所谓类的单例设计模式，就是采取一定的方法保证在整个的软件系统中，<span style="background-color:#00ff00">对某个类只能存在一个<font color=#ff0000>对象实例</font></span>，并且该类只提供一个取得其对象实例的方法 (静态方法)。<br>比如 Hibernate 的 SessionFactory，它充当数据存储源的代理，并负责创建 Session 对象。SessionFactory 并不是轻量级的，一般情况下，一个项目通常只需要一个 SessionFactory 就够，这是就会使用到单例模式。</p><h1 id="3-实现方式⭐️🔴"><a href="#3-实现方式⭐️🔴" class="headerlink" title="3. 实现方式⭐️🔴"></a>3. 实现方式⭐️🔴</h1><h2 id="3-1-八种单例模式"><a href="#3-1-八种单例模式" class="headerlink" title="3.1. 八种单例模式"></a>3.1. 八种单例模式</h2><ol><li>饿汉式 (静态常量)</li><li>饿汉式（静态代码块）</li><li>懒汉式 (线程不安全)</li><li>懒汉式 (线程安全，同步方法)</li><li>懒汉式 (线程安全，同步代码块)</li><li>双重检查⭐️🔴</li><li>静态内部类⭐️🔴</li><li>枚举⭐️🔴<br>❕<span style="display:none">%%<br>1807-🏡⭐️◼️单例模式的 8 种实现方式 ?🔜MSTM📝 2 个懒汉 3 个饿汉双检静内加枚举◼️⭐️-point-202301221807%%</span></li></ol><h2 id="3-2-饿汉式（静态常量）"><a href="#3-2-饿汉式（静态常量）" class="headerlink" title="3.2. 饿汉式（静态常量）"></a>3.2. 饿汉式（静态常量）</h2><h3 id="3-2-1-实现方法"><a href="#3-2-1-实现方法" class="headerlink" title="3.2.1. 实现方法"></a>3.2.1. 实现方法</h3><ol><li>构造器私有化 (防止 new)</li><li>类的内部创建对象</li><li>向外暴露一个静态的公共方法。getInstance</li></ol><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_">Singleton</span> &#123;<br>    <span class="hljs-comment">// 1、构造器私有化</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-comment">// 2、类的内部创建对象</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Singleton</span> <span class="hljs-variable">instance</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br><br>    <span class="hljs-comment">// 3、向外暴露一个静态的公共方法</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> instance;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="3-2-1-1-为什么-getInstance-要是-static-的"><a href="#3-2-1-1-为什么-getInstance-要是-static-的" class="headerlink" title="3.2.1.1. 为什么 getInstance 要是 static 的"></a>3.2.1.1. 为什么 getInstance 要是 static 的</h4><p>因为我们要求外部无法 new Singleton 实例对象，所以外部无法使用实例对象调用非静态方法，设置为 static 时，外部才能调用 getInstance 方法来获取我们创建的单实例</p><h4 id="3-2-1-2-为什么-instance-要是-static-的"><a href="#3-2-1-2-为什么-instance-要是-static-的" class="headerlink" title="3.2.1.2. 为什么 instance 要是 static 的"></a>3.2.1.2. 为什么 instance 要是 static 的</h4><p>因为 getInstance 是静态的，所以方法中要访问的变量 instance 必须也是静态的</p><h3 id="3-2-2-优缺点"><a href="#3-2-2-优缺点" class="headerlink" title="3.2.2. 优缺点"></a>3.2.2. 优缺点</h3><ol><li>优点：这种写法比较简单，就是在<span style="background-color:#ff0000">类装载的时候就完成实例化。避免了线程同步问题</span></li><li>缺点：在类装载的时候就完成实例化，没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例，则会造成内存的浪费</li><li>这种方式<span style="background-color:#ff00ff">基于 classloder 机制避免了多线程的同步问题</span>。不过，instance 在类装载时就实例化，在单例模式中大多数都是调用 getlnstance 方法，但是导致类装载的原因有很多种，因此不能确定有其他的方式（或者其他的静态方法）导致类装载，这时候初始化 instance 就没有达到 Lazy loading 的效果</li><li>结论：这种单例模式可用，可能<span style="background-color:#ffff00">造成内存浪费</span></li></ol><p>温故知新：<a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/" title="性能调优-基础-6、JVM-类装载子系统">性能调优-基础-6、JVM-类装载子系统</a></p><h3 id="3-2-3-基于-classloder-机制避免了多线程的同步问题"><a href="#3-2-3-基于-classloder-机制避免了多线程的同步问题" class="headerlink" title="3.2.3. 基于 classloder 机制避免了多线程的同步问题"></a>3.2.3. 基于 classloder 机制避免了多线程的同步问题</h3><p>❕<span style="display:none">%%<br>01221808-🏡⭐️◼️为什么 classloader 机制可以避免线程安全问题◼️⭐️-point-202301221808%%</span></p><h4 id="3-2-3-1-类加载"><a href="#3-2-3-1-类加载" class="headerlink" title="3.2.3.1. 类加载"></a>3.2.3.1. 类加载</h4><p>ClassLoader 的 loadClass 方法使用了 synchronized 加锁</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109170146.png"></p><h4 id="3-2-3-2-创建对象"><a href="#3-2-3-2-创建对象" class="headerlink" title="3.2.3.2. 创建对象"></a>3.2.3.2. 创建对象</h4><p>虚拟机使用了<span style="background-color:#00ff00">TLAB 和 CAS</span>方式保证并发安全问题</p><h2 id="3-3-饿汉式（静态代码块）"><a href="#3-3-饿汉式（静态代码块）" class="headerlink" title="3.3. 饿汉式（静态代码块）"></a>3.3. 饿汉式（静态代码块）</h2><h3 id="3-3-1-实现方法"><a href="#3-3-1-实现方法" class="headerlink" title="3.3.1. 实现方法"></a>3.3.1. 实现方法</h3><ol><li>构造器私有化</li><li>类的内部声明对象</li><li>在静态代码块中创建对象</li><li>向外暴露一个静态的公共方法</li></ol><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_">Singleton</span> &#123;<br>    <span class="hljs-comment">// 1、构造器私有化</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-comment">// 2、类的内部声明对象</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton instance;<br><br>    <span class="hljs-comment">// 3、在静态代码块中创建对象</span><br>    <span class="hljs-keyword">static</span> &#123;<br>        instance = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br>    &#125;<br><br>    <span class="hljs-comment">// 4、向外暴露一个静态的公共方法</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> instance;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-3-2-优缺点"><a href="#3-3-2-优缺点" class="headerlink" title="3.3.2. 优缺点"></a>3.3.2. 优缺点</h3><ol><li>这种方式和上面的方式其实类似，只不过将类实例化的过程放在了静态代码块中，也是在类装载的时候，就执行静态代码块中的代码，初始化类的实例。优缺点和上面是一样的</li><li>结论：这种单例模式可用，但是可能造成内存浪费</li></ol><h2 id="3-4-懒汉式（线程不安全）"><a href="#3-4-懒汉式（线程不安全）" class="headerlink" title="3.4. 懒汉式（线程不安全）"></a>3.4. 懒汉式（线程不安全）</h2><h3 id="3-4-1-实现方法"><a href="#3-4-1-实现方法" class="headerlink" title="3.4.1. 实现方法"></a>3.4.1. 实现方法</h3><ol><li>构造器私有化</li><li>类的内部创建对象</li><li>向外暴露一个静态的公共方法，当使用到该方法时，才去创建 instance</li></ol><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-comment">// 1、构造器私有化</span><br><span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;<br>&#125;<br><br><span class="hljs-comment">// 2、类的内部声明对象</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton instance;<br><br><span class="hljs-comment">// 3、向外暴露一个静态的公共方法，当使用到该方法时，才去创建 instance</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>) &#123;<br>        instance = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br>    &#125;<br>    <span class="hljs-keyword">return</span> instance;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-4-2-优缺点"><a href="#3-4-2-优缺点" class="headerlink" title="3.4.2. 优缺点"></a>3.4.2. 优缺点</h3><p>● 1）起到了 Lazy Loading 的效果，但是只能在单线程下使用<br>● 2）如果在多线程下，一个线程进入了判断语句块，还未来得及往下执行，另一个线程也通过了这个判断语句，这时便会产生多个实例<br>● 3）结论：在实际开发中，不要使用这种方式</p><h2 id="3-5-懒汉式（线程安全，同步方法）"><a href="#3-5-懒汉式（线程安全，同步方法）" class="headerlink" title="3.5. 懒汉式（线程安全，同步方法）"></a>3.5. 懒汉式（线程安全，同步方法）</h2><h3 id="3-5-1-实现方法"><a href="#3-5-1-实现方法" class="headerlink" title="3.5.1. 实现方法"></a>3.5.1. 实现方法</h3><p>● 1）构造器私有化<br>● 2）类的内部创建对象<br>● 3）向外暴露一个静态的公共方法，加入同步处理的代码，解决线程安全问题</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_">Singleton</span> &#123;<br>    <span class="hljs-comment">// 1、构造器私有化</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-comment">// 2、类的内部声明对象</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton instance;<br><br>    <span class="hljs-comment">// 3、向外暴露一个静态的公共方法，加入同步处理的代码，解决线程安全问题</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>) &#123;<br>            instance = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br>        &#125;<br>        <span class="hljs-keyword">return</span> instance;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-5-2-优缺点"><a href="#3-5-2-优缺点" class="headerlink" title="3.5.2. 优缺点"></a>3.5.2. 优缺点</h3><p>● 1）解决了线程不安全问题<br>● 2）效率太低了，每个线程在想获得类的实例时候，执行 getlnstance() 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了，后面的想获得该类实例，直接 return 就行了。方法进行同步效率太低<br>● 3）结论：在实际开发中，不推荐使用这种方式</p><h2 id="3-6-✅双重检查⭐️🔴"><a href="#3-6-✅双重检查⭐️🔴" class="headerlink" title="3.6. ✅双重检查⭐️🔴"></a>3.6. ✅双重检查⭐️🔴</h2><h3 id="3-6-1-实现方法"><a href="#3-6-1-实现方法" class="headerlink" title="3.6.1. 实现方法"></a>3.6.1. 实现方法</h3><p>● 1）构造器私有化<br>● 2）类的内部创建对象，同时用<span style="background-color:#ff00ff">volatile 关键字修饰</span>修饰<br>● 3）向外暴露一个静态的公共方法，加入同步处理的代码块，并进行双重判断，解决线程安全问题</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_">Singleton</span> &#123;<br>    <span class="hljs-comment">// 1、构造器私有化</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-comment">// 2、类的内部声明对象，同时用`volatile`关键字修饰修饰</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">volatile</span> Singleton instance;<br><br>    <span class="hljs-comment">// 3、向外暴露一个静态的公共方法，加入同步处理的代码块，并进行双重判断，解决线程安全问题</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">synchronized</span> (Singleton.class) &#123;<br>                <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>) &#123;<br>                    instance = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br>                &#125;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> instance;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-6-2-优缺点"><a href="#3-6-2-优缺点" class="headerlink" title="3.6.2. 优缺点"></a>3.6.2. 优缺点</h3><p>● 1）Double-Check 概念是多线程开发中常使用到的，我们进行了两次检查，这样就可以保证线程安全了<br>● 2）这样实例化代码只用执行一次，后面再次访问时直接 return 实例化对象，也避免的反复进行方法同步<br>● 3）线程安全；延迟加载；效率较高<br>● 4）结论：在实际开发中，推荐使用这种单例设计模式<br><span style="background-color:#00ff00">添加 <code>volatile</code> 关键字之后的双重检查锁模式是一种比较好的单例实现模式，能够保证在多线程的情况下线程安全也不会有性能问题。</span></p><h3 id="3-6-3-JDK-源码⭐️🔴"><a href="#3-6-3-JDK-源码⭐️🔴" class="headerlink" title="3.6.3. JDK 源码⭐️🔴"></a>3.6.3. JDK 源码⭐️🔴</h3><h4 id="3-6-3-1-LifecycleMetadata"><a href="#3-6-3-1-LifecycleMetadata" class="headerlink" title="3.6.3.1. LifecycleMetadata"></a>3.6.3.1. LifecycleMetadata</h4><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;spring-framework&#x2F;spring-beans&#x2F;src&#x2F;main&#x2F;java&#x2F;org&#x2F;springframework&#x2F;beans&#x2F;factory&#x2F;annotation&#x2F;InitDestroyAnnotationBeanPostProcessor.java]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202071715.png" alt="image.png"></p><h4 id="3-6-3-2-getSingleton"><a href="#3-6-3-2-getSingleton" class="headerlink" title="3.6.3.2. getSingleton"></a>3.6.3.2. getSingleton</h4><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;spring-framework&#x2F;spring-beans&#x2F;src&#x2F;main&#x2F;java&#x2F;org&#x2F;springframework&#x2F;beans&#x2F;factory&#x2F;support&#x2F;DefaultSingletonBeanRegistry.java]]<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202071842.png" alt="image.png"></p><h2 id="3-7-✅静态内部类⭐️🔴"><a href="#3-7-✅静态内部类⭐️🔴" class="headerlink" title="3.7. ✅静态内部类⭐️🔴"></a>3.7. ✅静态内部类⭐️🔴</h2><h3 id="3-7-1-实现方法"><a href="#3-7-1-实现方法" class="headerlink" title="3.7.1. 实现方法"></a>3.7.1. 实现方法</h3><p>● 1）构造器私有化<br>● 2）定义一个静态内部类，内部定义当前类的静态属性<br>● 3）向外暴露一个静态的公共方法</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">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> &#123;<br>    <span class="hljs-comment">// 1、构造器私有化</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-comment">// 2、定义一个静态内部类，内部定义当前类的静态属性</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SingletonInstance</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">Singleton</span> <span class="hljs-variable">instance</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br>    &#125;<br><br>    <span class="hljs-comment">// 3、向外暴露一个静态的公共方法</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> SingletonInstance.instance;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-7-2-优缺点"><a href="#3-7-2-优缺点" class="headerlink" title="3.7.2. 优缺点"></a>3.7.2. 优缺点</h3><p>● 1）这种方式采用了类装载的机制，来保证初始化实例时只有一个线程<br>● 2）<span style="background-color:#00ff00">静态内部类方式在 Singleton 类被装载时并不会立即进行类加载，只有内部类的属性&#x2F;方法被调用时才会被加载，并初始化其静态属性，然后后面又跟了 new 关键字，所以还会实例化一个对象出来。</span><br>● 3）类的静态属性<span style="background-color:#ff00ff">只会在第一次加载类的时候初始化</span>，JVM 帮助我们保证了线程的安全性，在类进行初始化时，别的线程是无法进入的<br>● 4）优点：避免了线程不安全，利用静态内部类特点实现延迟加载，效率高<br>● 5）结论：推荐使用<br><span style="background-color:#00ff00">静态属性由于被 <code>static</code> 修饰，保证只被实例化一次，并且严格保证实例化顺序。</span><br>❕<span style="display:none">%%<br>01222001-🏡⭐️◼️静态内部类在类加载之后并不会立即实例化，在调用静态属性或者静态放方法时才会真正实例化对象◼️⭐️-point-202301222001%%</span></p><p>静态内部类单例模式中实例由内部类创建，由于 JVM 在加载外部类的过程中，是不会加载静态内部类的，只有内部类的属性&#x2F;方法被调用时才会被加载，并初始化其静态属性。静态属性由于被 <code>static</code> 修饰，保证只被实例化一次，并且严格保证实例化顺序。</p><h3 id="3-7-3-证明静态内部类加载和初始化⭐️🔴"><a href="#3-7-3-证明静态内部类加载和初始化⭐️🔴" class="headerlink" title="3.7.3. 证明静态内部类加载和初始化⭐️🔴"></a>3.7.3. 证明静态内部类加载和初始化⭐️🔴</h3><p>❕<span style="display:none">%%<br>0742-🏡⭐️◼️静态内部类加载时机 ?🔜MSTM📝 只有静态内部类的属性或方法被使用时才会触发其类加载和初始化◼️⭐️-point-202302090742%%</span><br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;singleton&#x2F;demo5&#x2F;Singleton.java]]</p><ol><li>为了验证，故意把构造函数改为 public，然后把 Client 中使用的地方注释掉</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209073300.png" alt="image.png"></p><ol start="2"><li>设置 JVM 参数为 <code>-XX:+TraceClassLoading</code></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209073116.png" alt="image.png"></p><p>运行之后搜索 <code>SingletonHolder</code> 并未看到加载信息<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209073331.png" alt="image.png"></p><ol start="3"><li>再把使用的地方解开注释，再次运行，发现加载了静态内部类<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230209073426.png" alt="image.png"></li></ol><p>至此可以说明，<span style="background-color:#ff0000">静态内部类在父类加载时不会导致静态内部类的加载，除非静态内部类的属性或方法被使用时，才会触发静态内部类的加载，同时会触发静态内部类的初始化。</span></p><h2 id="3-8-枚举⭐️🔴"><a href="#3-8-枚举⭐️🔴" class="headerlink" title="3.8. 枚举⭐️🔴"></a>3.8. 枚举⭐️🔴</h2><h3 id="3-8-1-实现方法"><a href="#3-8-1-实现方法" class="headerlink" title="3.8.1. 实现方法"></a>3.8.1. 实现方法</h3><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-keyword">enum</span> <span class="hljs-title class_">Singleton</span> &#123;<br>    INSTANCE;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sayHello</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;Hello World&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-8-2-优缺点"><a href="#3-8-2-优缺点" class="headerlink" title="3.8.2. 优缺点"></a>3.8.2. 优缺点</h3><p>● 1）这借助 JDK1.5 中添加的枚举来实现单例模式。<span style="background-color:#00ff00">不仅能避免多线程同步问题，而且还能防止反序列化重新创建新的对象</span><br>● 2）这种方式是 Effective Java 作者 Josh Bloch 提倡的方式<br>● 3）结论：推荐使用</p><p>枚举类实现单例模式是极力推荐的单例实现模式，因为枚举类型是线程安全的，并且只会装载一次，设计者充分的利用了枚举的这个特性来实现单例模式，枚举的写法非常简单，而且枚举类型是所用单例实现中<span style="background-color:#ff00ff">唯一一种不会被破坏的单例实现模式</span>。</p><p><span style="background-color:#ff0000">枚举属于饿汉式单例模式</span></p><h3 id="3-8-3-为什么枚举是线程安全的⭐️🔴"><a href="#3-8-3-为什么枚举是线程安全的⭐️🔴" class="headerlink" title="3.8.3. 为什么枚举是线程安全的⭐️🔴"></a>3.8.3. 为什么枚举是线程安全的⭐️🔴</h3><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230401-2137%%</span>❕ ^bbu2xw</p><a href="/2023/01/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-6%E3%80%81enum/" title="关键字和接口-6、enum">关键字和接口-6、enum</a><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111162135.png" alt="image.png"></p><h1 id="5-破坏单例模式的情况⭐️🔴"><a href="#5-破坏单例模式的情况⭐️🔴" class="headerlink" title="5. 破坏单例模式的情况⭐️🔴"></a>5. 破坏单例模式的情况⭐️🔴</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230510-1941%%</span>❕ ^xk9ru2</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607164525.png" alt="image.png"></p><h2 id="5-1-多线程破坏单例"><a href="#5-1-多线程破坏单例" class="headerlink" title="5.1. 多线程破坏单例"></a>5.1. 多线程破坏单例</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607164722.png" alt="image.png"></p><p>在多线程环境下，线程的时间片是由 CPU 自由分配的，具有随机性，而单例对象作为共享资源可能会同时被多个线程同时操作，从而导致同时创建多个对象。当然，这种情况只出现在懒汉式单例中。如果是饿汉式单例，在线程启动前就被初始化了，不存在线程再创建对象的情况。<br>如果懒汉式单例出现多线程破坏的情况，我给出以下两种解决方案：<br>1、改为 DCL 双重检查锁的写法。<br>2、使用静态内部类的写法，性能更高。</p><h2 id="5-2-指令重排破坏单例"><a href="#5-2-指令重排破坏单例" class="headerlink" title="5.2. 指令重排破坏单例"></a>5.2. 指令重排破坏单例</h2><p>看似简单的一段赋值语句：instance &#x3D; new Singleton(); 其实 JVM 内部已经被转换为多条执行指令： memory &#x3D; allocate(); 分配对象的内存空间指令 ctorInstance(memory); 初始化对象 instance &#x3D; memory; 将已分配存地址赋值给对象引用 1、分配对象的内存空间指令，调用 allocate() 方法分配内存。 2、调用 ctorInstance() 方法初始化对象 3、将已分配存地址赋值给对象引用但是经过重排序后，执行顺序可能是这样的：<br>memory &#x3D; allocate(); 分配对象的内存空间指令 instance &#x3D; memory; 将已分配存地址赋值给对象引用 ctorInstance(memory); 初始化对象 1、分配对象的内存空间指令 2、设置 instance 指向刚分配的内存地址 3、初始化对象 我们可以看到指令重排之后，instance 指向分配好的内存放在了前面，而这段内存的初 始化的指令被排在了后面，在线程 T1 初始化完成这段内存之前，线程 T2 虽然进不去 同步代码块，但是在同步代码块之前的判断就会发现 instance 不为空，此时线程 T2 获得 instance 对象，如果直接使用就可能发生错误。 如果出现这种情况，我该如何解决呢？只需要在成员变量前加 volatile，保证所有线程 的可见性就可以了。 private static volatile Singleton instance &#x3D; null;</p><h2 id="5-3-克隆破坏单例"><a href="#5-3-克隆破坏单例" class="headerlink" title="5.3. 克隆破坏单例"></a>5.3. 克隆破坏单例</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607165214.png" alt="image.png"><br>在 Java 中，所有的类就继承自 Object，也就是说所有的类都实现了 clone() 方法。如果是深 clone()，每次都会重新创建新的实例。那如果我们定义的是单例对象，岂不是也可调用 clone() 方法来反复创建新的实例呢？确实，这种情况是有可能发生的。为了避免发生这样结果，我们可以在单例对象中重写 clone() 方法，将单例自身的引用作为返回值。这样，就能避免这种情况发生。</p><h2 id="5-4-反序列化破坏单例"><a href="#5-4-反序列化破坏单例" class="headerlink" title="5.4. 反序列化破坏单例"></a>5.4. 反序列化破坏单例</h2><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;singleton&#x2F;demo7&#x2F;Test.java]]</p><h3 id="5-4-1-为什么会破坏单例模式"><a href="#5-4-1-为什么会破坏单例模式" class="headerlink" title="5.4.1. 为什么会破坏单例模式"></a>5.4.1. 为什么会破坏单例模式</h3><p>[[(210条消息) 为什么序列化会破坏单例？_李昊轩的博客的博客-CSDN博客]]</p><h3 id="5-4-2-解决方案"><a href="#5-4-2-解决方案" class="headerlink" title="5.4.2. 解决方案"></a>5.4.2. 解决方案</h3><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></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_">Singleton</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-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;&#125;  <br>  <br>    <span class="hljs-comment">//定义一个静态内部类  </span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SingletonHolder</span> &#123;  <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> <span class="hljs-type">Singleton</span> <span class="hljs-variable">INSTANCE</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//提供公共的访问方式  </span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-keyword">return</span> SingletonHolder.INSTANCE;  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//当进行反序列化时，会自动调用该方法，将该方法的返回值直接返回  </span><br>    <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">readResolve</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-keyword">return</span> SingletonHolder.INSTANCE;  <br>    &#125;  <br>  <br>&#125;<br></code></pre></td></tr></table></figure><h4 id="5-4-2-1-为什么-readResolve-可以解决⭐️🔴"><a href="#5-4-2-1-为什么-readResolve-可以解决⭐️🔴" class="headerlink" title="5.4.2.1. 为什么 readResolve 可以解决⭐️🔴"></a>5.4.2.1. 为什么 readResolve 可以解决⭐️🔴</h4><p>^xpn17m<br><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230401-2134%%</span>❕ ^hv1if6</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111154348.png" alt="image.png"></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">final</span> Object <span class="hljs-title function_">readObject</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException, ClassNotFoundException&#123;<br>    ...  <br><span class="hljs-keyword">private</span> Object <span class="hljs-title function_">readOrdinaryObject</span><span class="hljs-params">(<span class="hljs-type">boolean</span> unshared)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>...<br><span class="hljs-comment">//isInstantiable 返回true，执行 desc.newInstance()，通过反射创建新的单例类，</span><br>    obj = desc.isInstantiable() ? desc.newInstance() : <span class="hljs-literal">null</span>; <br>    ...<br>    <span class="hljs-comment">// 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true</span><br>    <span class="hljs-keyword">if</span> (obj != <span class="hljs-literal">null</span> &amp;&amp; handles.lookupException(passHandle) == <span class="hljs-literal">null</span> &amp;&amp; desc.hasReadResolveMethod()) &#123;<br>    <span class="hljs-comment">// 通过反射调用 Singleton 类中的 readResolve 方法，将返回值赋值给rep变量</span><br>    <span class="hljs-comment">// 这样多次调用ObjectInputStream类中的readObject方法，继而就会调用我们定义的readResolve方法，所以返回的是同一个对象。</span><br>    <span class="hljs-type">Object</span> <span class="hljs-variable">rep</span> <span class="hljs-operator">=</span> desc.invokeReadResolve(obj);<br>     ...<br>    &#125;<br>    <span class="hljs-keyword">return</span> obj;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111160554.png" alt="image.png"><br>❕<span style="display:none">%%<br>0923-🏡⭐️◼️为什么 readResolve 可以解决反序列破坏单例的问题 ?🔜MSTM📝 因为在反序列化过程中会检测并调用此方法，我们可以重写该方法来返回同一个对象◼️⭐️-point-202302090923%%</span><br>通过反射调用 Singleton 类中的 readResolve 方法，将返回值赋值给 rep 变量<br>这样多次调用 ObjectInputStream 类中的 readObject 方法，继而就会调用我们定义的 readResolve 方法，所以返回的是同一个对象。</p><h2 id="5-5-反射破坏单例"><a href="#5-5-反射破坏单例" class="headerlink" title="5.5. 反射破坏单例"></a>5.5. 反射破坏单例</h2><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;itheima&#x2F;pattern&#x2F;singleton&#x2F;demo8&#x2F;Client.java]]</p><h3 id="5-5-1-解决方案"><a href="#5-5-1-解决方案" class="headerlink" title="5.5.1. 解决方案"></a>5.5.1. 解决方案</h3><p><span style="background-color:#00ff00">在私有的构造方法中增加判断</span></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_">Singleton</span> &#123;  <br>  <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-variable">flag</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;  <br>  <br>    <span class="hljs-comment">//私有构造方法  </span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-keyword">synchronized</span> (Singleton.class) &#123;  <br>  <br>            <span class="hljs-comment">//判断flag的值是否是true，如果是true，说明非第一次访问，直接抛一个异常，如果是false的话，说明第一次访问  </span><br>            <span class="hljs-keyword">if</span> (flag) &#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;不能创建多个对象&quot;</span>);  <br>            &#125;  <br>            <span class="hljs-comment">//将flag的值设置为true  </span><br>            flag = <span class="hljs-literal">true</span>;  <br>        &#125;  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//定义一个静态内部类  </span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SingletonHolder</span> &#123;  <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> <span class="hljs-type">Singleton</span> <span class="hljs-variable">INSTANCE</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();  <br>    &#125;  <br>  <br>    <span class="hljs-comment">//提供公共的访问方式  </span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> &#123;  <br>        <span class="hljs-keyword">return</span> SingletonHolder.INSTANCE;  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="6-案例分析"><a href="#6-案例分析" class="headerlink" title="6. 案例分析"></a>6. 案例分析</h1><h2 id="6-1-JDK-源码解析-Runtime-类"><a href="#6-1-JDK-源码解析-Runtime-类" class="headerlink" title="6.1. JDK 源码解析 -Runtime 类"></a>6.1. JDK 源码解析 -Runtime 类</h2><p>Runtime 类就是使用的单例设计模式。</p><ol><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></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_">Runtime</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Runtime</span> <span class="hljs-variable">currentRuntime</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Runtime</span>();<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * Returns the runtime object associated with the current Java application.</span><br><span class="hljs-comment">     * Most of the methods of class &lt;code&gt;Runtime&lt;/code&gt; are instance</span><br><span class="hljs-comment">     * methods and must be invoked with respect to the current runtime object.</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span>  the &lt;code&gt;Runtime&lt;/code&gt; object associated with the current</span><br><span class="hljs-comment">     *          Java application.</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Runtime <span class="hljs-title function_">getRuntime</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> currentRuntime;<br>    &#125;<br><br>    <span class="hljs-comment">/** Don&#x27;t let anyone else instantiate this class */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">Runtime</span><span class="hljs-params">()</span> &#123;&#125;<br>    ...<br>&#125;<br></code></pre></td></tr></table></figure><p>从上面源代码中可以看出 Runtime 类使用的是恶汉式（静态属性）方式来实现单例模式的。</p></li><li><p>使用 Runtime 类中的方法</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_">RuntimeDemo</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> IOException &#123;<br>        <span class="hljs-comment">//获取Runtime类对象</span><br>        <span class="hljs-type">Runtime</span> <span class="hljs-variable">runtime</span> <span class="hljs-operator">=</span> Runtime.getRuntime();<br><br>        <span class="hljs-comment">//返回 Java 虚拟机中的内存总量。</span><br>        System.out.println(runtime.totalMemory());<br>        <span class="hljs-comment">//返回 Java 虚拟机试图使用的最大内存量。</span><br>        System.out.println(runtime.maxMemory());<br><br>        <span class="hljs-comment">//创建一个新的进程执行指定的字符串命令，返回进程对象</span><br>        <span class="hljs-type">Process</span> <span class="hljs-variable">process</span> <span class="hljs-operator">=</span> runtime.exec(<span class="hljs-string">&quot;ipconfig&quot;</span>);<br>        <span class="hljs-comment">//获取命令执行后的结果，通过输入流获取</span><br>        <span class="hljs-type">InputStream</span> <span class="hljs-variable">inputStream</span> <span class="hljs-operator">=</span> process.getInputStream();<br>        <span class="hljs-type">byte</span>[] arr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">byte</span>[<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>* <span class="hljs-number">100</span>];<br>        <span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> inputStream.read(arr);<br>        System.out.println(<span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(arr,<span class="hljs-number">0</span>,b,<span class="hljs-string">&quot;gbk&quot;</span>));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li></ol><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><a href="/2023/01/06/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2%E3%80%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/" title="设计模式-2、设计模式及设计原则">设计模式-2、设计模式及设计原则</a>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 内功心法专题 </tag>
            
            <tag> 设计模式 </tag>
            
            <tag> 创建者模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-18、ThreadLocal</title>
      <link href="/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-18%E3%80%81ThreadLocal/"/>
      <url>/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-18%E3%80%81ThreadLocal/</url>
      
        <content type="html"><![CDATA[<h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106191545.png"></p><h1 id="2-能干嘛"><a href="#2-能干嘛" class="headerlink" title="2. 能干嘛"></a>2. 能干嘛</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106191633.png"></p><h1 id="3-底层原理"><a href="#3-底层原理" class="headerlink" title="3. 底层原理"></a>3. 底层原理</h1><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230413-1620%%</span>❕ ^ac6t1x</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230413162005.png" alt="image.png"></p><h2 id="3-1-关系"><a href="#3-1-关系" class="headerlink" title="3.1. 关系"></a>3.1. 关系</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106203808.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106204339.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106213251.png"></p><h2 id="3-2-Thread-和-ThreadLocal"><a href="#3-2-Thread-和-ThreadLocal" class="headerlink" title="3.2. Thread 和 ThreadLocal"></a>3.2. Thread 和 ThreadLocal</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106203703.png"></p><h2 id="3-3-ThreadLocal-和-ThreadLocalMap"><a href="#3-3-ThreadLocal-和-ThreadLocalMap" class="headerlink" title="3.3. ThreadLocal 和 ThreadLocalMap"></a>3.3. ThreadLocal 和 ThreadLocalMap</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106203728.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106204045.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106203837.png"></p><h2 id="3-4-弱引用-2-个问题⭐️🔴"><a href="#3-4-弱引用-2-个问题⭐️🔴" class="headerlink" title="3.4. 弱引用 -2 个问题⭐️🔴"></a>3.4. 弱引用 -2 个问题⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106204631.png"></p><h3 id="3-4-1-为什么要用弱引用"><a href="#3-4-1-为什么要用弱引用" class="headerlink" title="3.4.1. 为什么要用弱引用"></a>3.4.1. 为什么要用弱引用</h3><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230607-1156%%</span>❕ ^t7yb2u</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106213622.png"><br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230320-2241%%</span>❕ ^kqn4wd</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106214801.png"></p><h3 id="3-4-2-为什么要手动-remove-内存泄露-线程池重复使用⭐️🔴"><a href="#3-4-2-为什么要手动-remove-内存泄露-线程池重复使用⭐️🔴" class="headerlink" title="3.4.2. 为什么要手动 remove- 内存泄露 - 线程池重复使用⭐️🔴"></a>3.4.2. 为什么要手动 remove- 内存泄露 - 线程池重复使用⭐️🔴</h3><p>ThreadLocal 被回收为 null 之后，ThreadLocalMap 的 Entry 的 key 指向的是 null，这个结构的回收也是一个需要解决的问题。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106214954.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106214238.png"></p><h3 id="3-4-3-引用"><a href="#3-4-3-引用" class="headerlink" title="3.4.3. 引用"></a>3.4.3. 引用</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106204915.png"></p><h4 id="3-4-3-1-强引用"><a href="#3-4-3-1-强引用" class="headerlink" title="3.4.3.1. 强引用"></a>3.4.3.1. 强引用</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106205319.png"></p><h4 id="3-4-3-2-软引用"><a href="#3-4-3-2-软引用" class="headerlink" title="3.4.3.2. 软引用"></a>3.4.3.2. 软引用</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106205506.png"></p><h4 id="3-4-3-3-弱引用"><a href="#3-4-3-3-弱引用" class="headerlink" title="3.4.3.3. 弱引用"></a>3.4.3.3. 弱引用</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106205734.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106205936.png"></p><h4 id="3-4-3-4-虚引用"><a href="#3-4-3-4-虚引用" class="headerlink" title="3.4.3.4. 虚引用"></a>3.4.3.4. 虚引用</h4><p>虚引用使用案例：<a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-1%E3%80%81JVM-GC%E8%B0%83%E4%BC%98/" title="性能调优-进阶-1、JVM-GC调优">性能调优-进阶-1、JVM-GC调优</a></p><h1 id="4-代码分析"><a href="#4-代码分析" class="headerlink" title="4. 代码分析"></a>4. 代码分析</h1><h2 id="4-1-expungeStaleEntry"><a href="#4-1-expungeStaleEntry" class="headerlink" title="4.1. expungeStaleEntry"></a>4.1. expungeStaleEntry</h2><p>JDK 中提供了清理脏 Entry 的方法，需要手动调用一下</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106215341.png"></p><h1 id="5-应用总结"><a href="#5-应用总结" class="headerlink" title="5. 应用总结"></a>5. 应用总结</h1><h2 id="5-1-Spring-事务"><a href="#5-1-Spring-事务" class="headerlink" title="5.1. Spring 事务"></a>5.1. Spring 事务</h2><a href="/2023/02/11/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-2%E3%80%81Spring-%E4%BA%8B%E5%8A%A1/" title="面试专题-2、Spring-事务">面试专题-2、Spring-事务</a><h2 id="5-2-线程上下文传递"><a href="#5-2-线程上下文传递" class="headerlink" title="5.2. 线程上下文传递"></a>5.2. 线程上下文传递</h2><p>userId，traceId</p><h2 id="5-3-数据库连接管理"><a href="#5-3-数据库连接管理" class="headerlink" title="5.3. 数据库连接管理"></a>5.3. 数据库连接管理</h2><p>Mybatis 中 SqlSession 存储当前线程的数据库会话信息</p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h2 id="6-1-用户身份鉴别"><a href="#6-1-用户身份鉴别" class="headerlink" title="6.1. 用户身份鉴别"></a>6.1. 用户身份鉴别</h2><p><a href="https://www.bilibili.com/video/BV1rK4y1C7fv?p=240&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1rK4y1C7fv?p=240&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="6-2-回收♻️"><a href="#6-2-回收♻️" class="headerlink" title="6.2. 回收♻️"></a>6.2. 回收♻️</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106202717.png"></p><h3 id="6-2-1-get"><a href="#6-2-1-get" class="headerlink" title="6.2.1. get"></a>6.2.1. get</h3><p>get(“a”)：如果索引位置 key 为 null，则会添加 key:a,value:null</p><h3 id="6-2-2-set"><a href="#6-2-2-set" class="headerlink" title="6.2.2. set"></a>6.2.2. set</h3><p>启发式扫描，key 附近的为 null key 也会被释放</p><h3 id="6-2-3-remove"><a href="#6-2-3-remove" class="headerlink" title="6.2.3. remove"></a>6.2.3. remove</h3><p>但是我们使用的时候，往往将 ThreadLocal 设置为静态的，所以即使栈中的引用断掉，ThreadLocal 与所在类也是强引用的，所以 get 和 set 方法无法释放，还是需要手动 remove 才能彻底解决问题。 <span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230320-2305%%</span>❕ ^og72jf</p><h2 id="6-3-最佳实践⭐️🔴"><a href="#6-3-最佳实践⭐️🔴" class="headerlink" title="6.3. 最佳实践⭐️🔴"></a>6.3. 最佳实践⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106215639.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106215656.png"></p><h1 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106223509.png"></p><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-周阳"><a href="#8-1-周阳" class="headerlink" title="8.1. 周阳"></a>8.1. 周阳</h2><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1ar4y1x727?p=111&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ar4y1x727?p=111&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><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-number">013</span>-DemoCode/thread0308<br></code></pre></td></tr></table></figure><h2 id="8-2-黑马"><a href="#8-2-黑马" class="headerlink" title="8.2. 黑马"></a>8.2. 黑马</h2><p><a href="https://www.bilibili.com/video/BV1zr4y117JM?p=93&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zr4y117JM?p=93&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106191232.png"></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程 </tag>
            
            <tag> ThreadLocal </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-16、LockSupport</title>
      <link href="/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-16%E3%80%81LockSupport/"/>
      <url>/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-16%E3%80%81LockSupport/</url>
      
        <content type="html"><![CDATA[<h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p>是用来创建锁和其他同步类的基本线程阻塞原语<br>每个使用 LockSupport 的线程都会与一个许可关联，如果该许可可用，并且可在进程中使用，则调用 park() 将会立即返回，否则可能阻塞。如果许可尚不可用，则可以调用 unpark 使其可用<br>方法：park()、unpark()</p><h1 id="2-等待唤醒"><a href="#2-等待唤醒" class="headerlink" title="2. 等待唤醒"></a>2. 等待唤醒</h1><h2 id="2-1-synchronized"><a href="#2-1-synchronized" class="headerlink" title="2.1. synchronized"></a>2.1. synchronized</h2><h3 id="2-1-1-异常-1"><a href="#2-1-1-异常-1" class="headerlink" title="2.1.1. 异常 1"></a>2.1.1. 异常 1</h3><p><span style="background-color:#ff00ff">必须在 synchronized 代码块中调用 wait 和 notify，否则报错</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106084459.png"></p><h3 id="2-1-2-异常-2"><a href="#2-1-2-异常-2" class="headerlink" title="2.1.2. 异常 2"></a>2.1.2. 异常 2</h3><p>必须 t1 先 wait()，然后 t2 再 notify()，否则无法唤醒</p><h3 id="2-1-3-为什么要放在代-synchronized-码块中？⭐️🔴"><a href="#2-1-3-为什么要放在代-synchronized-码块中？⭐️🔴" class="headerlink" title="2.1.3. 为什么要放在代 synchronized 码块中？⭐️🔴"></a>2.1.3. 为什么要放在代 synchronized 码块中？⭐️🔴</h3><ol><li>wait 和 notify 用来实现多线程之间的协调，wait 表示让线程进入到阻塞状态， notify 表示让阻塞的线程唤醒。</li><li>wait 和 notify 必然是成对出现的，如果一个线程被 wait() 方法阻塞，那么必然需 要另外一个线程通过 notify() 方法来唤醒这个被阻塞的线程，从而实现多线程之间 的通信。</li><li>（如图）在多线程里面，要实现多个线程之间的通信，除了管道流以外，只能通过共享变量的方法来实现，也就是线程 t1 修改共享变量 s，线程 t2 获取修改后的共享变量 s，从而完成数据通信。但是多线程本身具有并行执行的特性，也就是在同一时刻，多个线程可以同时执行。在这种情况下，线程 t2 在访问共享变量 s 之前，必须要知道线程 t1 已经修改过了共享变量 s，否则就需要等待。同时，线程 t1 修改过了共享变量 S 之后，还需要通知在等待中的线程 t2。所以要在这种特性下要去实现线程之间的通信，<span style="background-color:#ff00ff">就必须要通过一个竞争条件去控制线程在什么条件下等待，什么条件下唤醒</span></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528164845.png" alt="image.png"></p><ol start="4"><li>而 Synchronized 同步关键字就可以实现这样一个互斥条件，也就是在通过共享变 量来实现多个线程通信的场景里面，参与通信的线程必须要竞争到这个共享变量的 <strong>锁资源</strong>，才有资格对共享变量做修改，修改完成后就释放锁，那么其他的线程就可 以再次来竞争同一个共享变量的锁来获取修改后的数据，从而完成线程之前的通 信。</li><li>所以这也是为什么 wait&#x2F;notify 需要放在 Synchronized 同步代码块中的原因，有了 Synchronized 同步锁，就可以实现对多个通信线程之间的互斥，实现条件等待和条件唤醒。</li><li>另外，为了避免 wait&#x2F;notify 的错误使用，jdk 强制要求把 wait&#x2F;notify 写在同步代码块里面，否则会抛出 IllegalMonitorStateException</li><li>最后，基于 wait&#x2F;notify 的特性，非常适合实现生产者消费者的模型，比如说用 wait&#x2F;notify 来实现连接池就绪前的等待与就绪后的唤醒。</li></ol><h2 id="2-2-Lock"><a href="#2-2-Lock" class="headerlink" title="2.2. Lock"></a>2.2. Lock</h2><h3 id="2-2-1-异常-1"><a href="#2-2-1-异常-1" class="headerlink" title="2.2.1. 异常 1"></a>2.2.1. 异常 1</h3><p>必须在 lock 和 unlock 中间调用 await() 和 signal()，否则报错<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106084032.png"></p><h3 id="2-2-2-异常-2"><a href="#2-2-2-异常-2" class="headerlink" title="2.2.2. 异常 2"></a>2.2.2. 异常 2</h3><p>必须 t1 先 await()，然后 t2 再 singal()，否则无法唤醒</p><h2 id="2-3-LockSupport"><a href="#2-3-LockSupport" class="headerlink" title="2.3. LockSupport"></a>2.3. LockSupport</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106085148.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106085538.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106085804.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106085918.png"></p><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><a href="/2023/01/04/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-15%E3%80%81JUC/" title="并发编程专题-基础-15、JUC">并发编程专题-基础-15、JUC</a><p><a href="https://www.yuque.com/liuyanntes/sibb9g/fpy93i">https://www.yuque.com/liuyanntes/sibb9g/fpy93i</a></p>]]></content>
      
      
      <categories>
          
          <category> Lock </category>
          
      </categories>
      
      
        <tags>
            
            <tag> LockSupport </tag>
            
            <tag> Lock </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-17、LongAdder</title>
      <link href="/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-17%E3%80%81LongAdder/"/>
      <url>/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-17%E3%80%81LongAdder/</url>
      
        <content type="html"><![CDATA[<h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106182855.png"></p><p>JDK1.8之后推出的原子操作增强类</p><h2 id="1-1-继承关系"><a href="#1-1-继承关系" class="headerlink" title="1.1. 继承关系"></a>1.1. 继承关系</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106153454.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106154222.png"></p><h1 id="2-底层原理"><a href="#2-底层原理" class="headerlink" title="2. 底层原理"></a>2. 底层原理</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106153408.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106153605.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106154309.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106154345.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106154439.png"></p><h1 id="3-源码解读"><a href="#3-源码解读" class="headerlink" title="3. 源码解读"></a>3. 源码解读</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106164504.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106164619.png"></p><h2 id="3-1-add方法"><a href="#3-1-add方法" class="headerlink" title="3.1. add方法"></a>3.1. add方法</h2><h3 id="3-1-1-base的cas"><a href="#3-1-1-base的cas" class="headerlink" title="3.1.1. base的cas"></a>3.1.1. base的cas</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106163452.png"></p><h3 id="3-1-2-cell的cas"><a href="#3-1-2-cell的cas" class="headerlink" title="3.1.2. cell的cas"></a>3.1.2. cell的cas</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106164056.png"></p><h2 id="3-2-longAccumulate方法"><a href="#3-2-longAccumulate方法" class="headerlink" title="3.2. longAccumulate方法"></a>3.2. longAccumulate方法</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106165343.png"></p><h3 id="3-2-1-getProbe"><a href="#3-2-1-getProbe" class="headerlink" title="3.2.1. getProbe()"></a>3.2.1. getProbe()</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106165718.png"></p><h3 id="3-2-2-功能划分"><a href="#3-2-2-功能划分" class="headerlink" title="3.2.2. 功能划分"></a>3.2.2. 功能划分</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106165918.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106165944.png"></p><h3 id="3-2-3-步骤拆解"><a href="#3-2-3-步骤拆解" class="headerlink" title="3.2.3. 步骤拆解"></a>3.2.3. 步骤拆解</h3><h4 id="3-2-3-1-首次进入时走CASE2"><a href="#3-2-3-1-首次进入时走CASE2" class="headerlink" title="3.2.3.1. 首次进入时走CASE2"></a>3.2.3.1. 首次进入时走CASE2</h4><p>add()方法中base在多线程下cas发生失败时，会进入longAccumulate方法。首次进入cells为null，进入CASE2</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106170834.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106170342.png"></p><h4 id="3-2-3-2-兜底方案CASE3"><a href="#3-2-3-2-兜底方案CASE3" class="headerlink" title="3.2.3.2. 兜底方案CASE3"></a>3.2.3.2. 兜底方案CASE3</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106171000.png"></p><h4 id="3-2-3-3-CASE1"><a href="#3-2-3-3-CASE1" class="headerlink" title="3.2.3.3. CASE1"></a>3.2.3.3. CASE1</h4><p>cells不为空，其中有个cell为null，进入longAccumulate方法<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106171314.png"></p><h5 id="3-2-3-3-1-坑位为空，写入"><a href="#3-2-3-3-1-坑位为空，写入" class="headerlink" title="3.2.3.3.1. 坑位为空，写入"></a>3.2.3.3.1. 坑位为空，写入</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106172956.png"></p><h5 id="3-2-3-3-2-坑位不为空，继续循环"><a href="#3-2-3-3-2-坑位不为空，继续循环" class="headerlink" title="3.2.3.3.2. 坑位不为空，继续循环"></a>3.2.3.3.2. 坑位不为空，继续循环</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106172405.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173202.png"></p><h5 id="3-2-3-3-3-坑位不为空，cas成功"><a href="#3-2-3-3-3-坑位不为空，cas成功" class="headerlink" title="3.2.3.3.3. 坑位不为空，cas成功"></a>3.2.3.3.3. 坑位不为空，cas成功</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106172637.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173256.png"></p><h5 id="3-2-3-3-4-不能扩容"><a href="#3-2-3-3-4-不能扩容" class="headerlink" title="3.2.3.3.4. 不能扩容"></a>3.2.3.3.4. 不能扩容</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173422.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173905.png"></p><h5 id="3-2-3-3-5-扩容"><a href="#3-2-3-3-5-扩容" class="headerlink" title="3.2.3.3.5. 扩容"></a>3.2.3.3.5. 扩容</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106172750.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173553.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173553.png"></p><h5 id="3-2-3-3-6-总结"><a href="#3-2-3-3-6-总结" class="headerlink" title="3.2.3.3.6. 总结"></a>3.2.3.3.6. 总结</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173759.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106173832.png"></p><h2 id="3-3-sum方法"><a href="#3-3-sum方法" class="headerlink" title="3.3. sum方法"></a>3.3. sum方法</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106174252.png"></p><h3 id="3-3-1-不精确的"><a href="#3-3-1-不精确的" class="headerlink" title="3.3.1. 不精确的"></a>3.3.1. 不精确的</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106174432.png"></p><h2 id="3-4-与AtomicLong对比"><a href="#3-4-与AtomicLong对比" class="headerlink" title="3.4. 与AtomicLong对比"></a>3.4. 与AtomicLong对比</h2><h3 id="3-4-1-AtomicLong"><a href="#3-4-1-AtomicLong" class="headerlink" title="3.4.1. AtomicLong"></a>3.4.1. AtomicLong</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106174757.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106174943.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106175001.png"></p><h3 id="3-4-2-LongAdder"><a href="#3-4-2-LongAdder" class="headerlink" title="3.4.2. LongAdder"></a>3.4.2. LongAdder</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106174710.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106175105.png"></p><h1 id="4-代码示例"><a href="#4-代码示例" class="headerlink" title="4. 代码示例"></a>4. 代码示例</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106152459.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106152433.png"></p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106151720.png"></p><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><p>[[20221111-都有了AtomicLong，为什么还要提供LongAdder？  Java识堂]]</p>]]></content>
      
      
      <categories>
          
          <category> 原子类 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原子类 </tag>
            
            <tag> LongAdder </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-JDK新特性-1、函数式编程</title>
      <link href="/2023/01/04/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/JDK%E6%96%B0%E7%89%B9%E6%80%A7-1%E3%80%81%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
      <url>/2023/01/04/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/JDK%E6%96%B0%E7%89%B9%E6%80%A7-1%E3%80%81%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/</url>
      
        <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105162135.png"></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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//Runnable无参无返回值</span><br><br><span class="hljs-meta">@Functionallnterface</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Runnable</span>&#123;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span>;<br><br>&#125;<br><br><span class="hljs-comment">//Function&lt;T,R&gt;接受一个参数，并且有返回值</span><br><br><span class="hljs-meta">@Functionallnterface</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Function</span>&lt;T,R&gt;&#123;<br><br>R <span class="hljs-title function_">apply</span><span class="hljs-params">(T t)</span>;<br><br>&#125;<br><br><span class="hljs-comment">//Supplier没有参数，有一个返回值</span><br><br><span class="hljs-meta">@Functionallnterface</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Supplier</span>&lt;T&gt;&#123;<br><br>T <span class="hljs-title function_">get</span><span class="hljs-params">()</span>;<br><br>&#125;<br><br><span class="hljs-comment">//Consumer接受一个参数，没有返回值</span><br><br><span class="hljs-meta">@Functionallnterface</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Consumer</span>&lt;T&gt;&#123;<br><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">accept</span><span class="hljs-params">(T t)</span>;<br><br>&#125;<br><br><span class="hljs-comment">//BiConsumer&lt;T,U&gt;接受两个参数（Bi，英文单词词根，代表两个的意思），没有返回值</span><br><br><span class="hljs-meta">@Functionallnterface</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">BiConsumer</span>&lt;T,U&gt;&#123;<br><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">accept</span><span class="hljs-params">(T t,U u)</span>;<br><br>&#125;<br></code></pre></td></tr></table></figure><h1 id="1-JDK20"><a href="#1-JDK20" class="headerlink" title="1. JDK20"></a>1. JDK20</h1><p><a href="https://www.java.com/releases/">https://www.java.com/releases/</a><br><a href="https://www.infoq.cn/article/0axykmqvf5lhzseqtjbp">https://www.infoq.cn/article/0axykmqvf5lhzseqtjbp</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307223306.png" alt="image.png"></p><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> JDK新特性 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JDK新特性 </tag>
            
            <tag> 函数式编程 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-14、多线程</title>
      <link href="/2023/01/04/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-14%E3%80%81ForkJoin%E3%80%81CompletableFuture/"/>
      <url>/2023/01/04/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-14%E3%80%81ForkJoin%E3%80%81CompletableFuture/</url>
      
        <content type="html"><![CDATA[<h1 id="1-ForkJoin"><a href="#1-ForkJoin" class="headerlink" title="1. ForkJoin"></a>1. ForkJoin</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105113153.png"></p><p>&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;011-Java&#x2F;JUC&#x2F;Java并发编程与高并发解决方案(完整)&#x2F;第8章 J.U.C组件拓展</p><h2 id="1-1-与线程池区别"><a href="#1-1-与线程池区别" class="headerlink" title="1.1. 与线程池区别"></a>1.1. 与线程池区别</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105113827.png"></p><ol><li>采用 “工作窃取”模式（work-stealing）</li></ol><p>当执行新的任务时它可以将其拆分分成更小的任务执行，并将小任务加 到线程队列中，然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。</p><ol start="2"><li>相对于一般的线程池实现，fork&#x2F;join框架的优势体现在对其中包含的任务<br>的处理方式上：在一般的线程池中，如果一个线程正在执行的任务由于某些<br>原因无法继续运行，那么该线程会处于等待状态。而在fork&#x2F;join框架实现中，<br>如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理<br>该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了<br>线程的等待时间，提高了性能。</li></ol><h2 id="1-2-业务场景"><a href="#1-2-业务场景" class="headerlink" title="1.2. 业务场景"></a>1.2. 业务场景</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105114045.png"></p><h1 id="2-CompletableFuture"><a href="#2-CompletableFuture" class="headerlink" title="2. CompletableFuture"></a>2. CompletableFuture</h1><h2 id="2-1-诞生原因"><a href="#2-1-诞生原因" class="headerlink" title="2.1. 诞生原因"></a>2.1. 诞生原因</h2><p>Future是Java 5添加的类，用来描述一个异步计算的结果。你可以使用<code>isDone</code>方法检查计算是否完成，或者使用<code>get</code>阻塞住调用线程，直到计算完成返回结果，你也可以使用<code>cancel</code>方法停止任务的执行。</p><p>虽然<code>Future</code>以及相关使用方法提供了异步执行任务的能力，但是对于结果的获取却是很不方便，只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背，轮询的方式又会耗费无谓的CPU资源，而且也不能及时地得到计算结果，为什么不能用观察者设计模式当计算结果完成及时通知监听者呢？</p><p>很多语言，比如Node.js，采用回调的方式实现异步编程。Java的一些框架，比如Netty，自己扩展了Java的 <code>Future</code>接口，提供了<code>addListener</code>等多个扩展方法；Google guava也提供了通用的扩展Future；Scala也提供了简单易用且功能强大的Future&#x2F;Promise异步编程模式。</p><p>作为正统的Java类库，是不是应该做点什么，加强一下自身库的功能呢？</p><p>在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture，提供了非常强大的Future的扩展功能，可以帮助我们简化异步编程的复杂性，提供了函数式编程的能力，可以通过回调的方式处理计算结果，并且提供了转换和组合CompletableFuture的方法。</p><p>CompletableFuture类实现了Future接口，所以你还是可以像以前一样通过<code>get</code>方法阻塞或者轮询的方式获得结果，但是这种方式不推荐使用。</p><p>CompletableFuture和FutureTask同属于Future接口的实现类，都可以获取线程的执行结果。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105131550.png"></p><h2 id="2-2-FutrueTask及区别"><a href="#2-2-FutrueTask及区别" class="headerlink" title="2.2. FutrueTask及区别"></a>2.2. FutrueTask及区别</h2><h3 id="2-2-1-是什么"><a href="#2-2-1-是什么" class="headerlink" title="2.2.1. 是什么"></a>2.2.1. 是什么</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105144819.png"></p><blockquote><p>Future是Java5新加的一个接口，它提供了一种异步并行计算的功能。<br>目的：异步多线程任务执行且返回有结果，三个特点：<span style="background-color:#00ff00">多线程&#x2F;有返回&#x2F;异步任务</span><br>Runnable与Callable的区别：Runnable无返回值，无抛异常，Callable有返回值，抛异常</p></blockquote><p>由Thread的构造方法可知，要想拓展Thread类，必须从Runnable入手(<span style="background-color:#ff00ff">Runnable提供多线程能力</span>)，<span style="background-color:#ff00ff">Future提供了异步并行计算的能力</span>，所以有了RunnableFuture接口，分别继承了Future与Runnable，FutureTask为其实现类，FutureTask构造提供Callable(<span style="background-color:#ff00ff">提供 带返回值能力</span>)入参，由此引出Callable+FutureTask的组合（带返回值的异步任务）</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105145731.png"></p><h3 id="2-2-2-优点"><a href="#2-2-2-优点" class="headerlink" title="2.2.2. 优点"></a>2.2.2. 优点</h3><p>(优点)future+线程池异步多线程任务配合，能显著提高程序的执行效率。</p><h3 id="2-2-3-缺点"><a href="#2-2-3-缺点" class="headerlink" title="2.2.3. 缺点"></a>2.2.3. 缺点</h3><ol><li>get方法容易阻塞(<span style="background-color:#ff00ff">如果线程没有完成调用get就会发生阻塞</span>)，一般建议放在程序后面（如图应放在忙其它任务后面），不然执行到get方法会直接导致程序阻塞，等待FutureTask任务完成才能进行其他任务。</li><li>可以通过get(long timeout, TimeUnit unit)方法设置等待时间，超时则抛异常。</li></ol><h3 id="2-2-4-isDone-轮询"><a href="#2-2-4-isDone-轮询" class="headerlink" title="2.2.4. isDone()轮询"></a>2.2.4. isDone()轮询</h3><ol><li>轮询的方式会耗费无谓的CPU资源，而且也不见得能及时地得到计算结果。</li><li>如果想要异步获取结果，通常都会以轮询的方式去获取结果，尽量不要阻塞（阻塞相当于将线程控制权交出去，会涉及到上下文切换）<br><span style="background-color:#ff0000">结论：FutureTask对于结果的获取不是很友好，只能通过阻塞或轮询的方式 得到任务的结果</span></li></ol><h3 id="2-2-5-总结"><a href="#2-2-5-总结" class="headerlink" title="2.2.5. 总结"></a>2.2.5. 总结</h3><ol><li>FutureTask+Callable的组合，对于简单业务场景完全OK；</li><li>存在的问题：无回调，通过轮询占用CPU且不优雅，多异步任务结果互相依赖处理麻烦</li></ol><h2 id="2-3-特点"><a href="#2-3-特点" class="headerlink" title="2.3. 特点"></a>2.3. 特点</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105160647.png"></p><h2 id="2-4-使用示例"><a href="#2-4-使用示例" class="headerlink" title="2.4. 使用示例"></a>2.4. 使用示例</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105161031.png"></p><p>这里注意，<span style="background-color:#ff00ff">使用CompletableFuture默认线程池出来的线程是守护线程</span>，当用户线程都结束时会自动结束，所以需要将主线程main停一下，否则whenComplete的回调不会执行。或者使用自定义线程池。</p><h2 id="2-5-使用方法"><a href="#2-5-使用方法" class="headerlink" title="2.5. 使用方法"></a>2.5. 使用方法</h2><h3 id="2-5-1-创建异步对象"><a href="#2-5-1-创建异步对象" class="headerlink" title="2.5.1. 创建异步对象"></a>2.5.1. 创建异步对象</h3><p>CompletableFuture 提供了四个静态方法来创建一个异步操作。</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> CompletableFuture&lt;Void&gt; <span class="hljs-title function_">runAsync</span><span class="hljs-params">(Runnable runnable)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> CompletableFuture&lt;Void&gt; <span class="hljs-title function_">runAsync</span><span class="hljs-params">(Runnable runnable, Executor executor)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="hljs-title function_">supplyAsync</span><span class="hljs-params">(Supplier&lt;U&gt; supplier)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="hljs-title function_">supplyAsync</span><span class="hljs-params">(Supplier&lt;U&gt; supplier, Executor executor)</span><br></code></pre></td></tr></table></figure><p><strong>没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码</strong>。如果指定线程池，则使用指定的线程池运行。以下所有的方法都类同。</p><ul><li>runAsync方法不支持返回值。</li><li>supplyAsync可以支持返回值。</li></ul><h3 id="2-5-2-计算完成时回调方法"><a href="#2-5-2-计算完成时回调方法" class="headerlink" title="2.5.2. 计算完成时回调方法"></a>2.5.2. 计算完成时回调方法</h3><p>当CompletableFuture的计算结果完成，或者抛出异常的时候，可以执行特定的Action。主要是下面的方法：</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">public</span> CompletableFuture&lt;T&gt; <span class="hljs-title function_">whenComplete</span><span class="hljs-params">(BiConsumer&lt;? <span class="hljs-built_in">super</span> T,? <span class="hljs-built_in">super</span> Throwable&gt; action)</span>;<br><span class="hljs-keyword">public</span> CompletableFuture&lt;T&gt; <span class="hljs-title function_">whenCompleteAsync</span><span class="hljs-params">(BiConsumer&lt;? <span class="hljs-built_in">super</span> T,? <span class="hljs-built_in">super</span> Throwable&gt; action)</span>;<br><span class="hljs-keyword">public</span> CompletableFuture&lt;T&gt; <span class="hljs-title function_">whenCompleteAsync</span><span class="hljs-params">(BiConsumer&lt;? <span class="hljs-built_in">super</span> T,? <span class="hljs-built_in">super</span> Throwable&gt; action, Executor executor)</span>;<br><br><span class="hljs-keyword">public</span> CompletableFuture&lt;T&gt; <span class="hljs-title function_">exceptionally</span><span class="hljs-params">(Function&lt;Throwable,? extends T&gt; fn)</span>;<br></code></pre></td></tr></table></figure><p>whenComplete可以处理正常和异常的计算结果，exceptionally处理异常情况。<code>BiConsumer&lt;? super T,? super Throwable&gt;</code>可以定义处理业务</p><p>whenComplete 和 whenCompleteAsync 的区别： whenComplete：是执行当前任务的线程执行继续执行 whenComplete 的任务。 whenCompleteAsync：是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。</p><p><strong>方法不以Async结尾，意味着Action使用相同的线程执行，而Async可能会使用其他线程执行（如果是使用相同的线程池，也可能会被同一个线程选中执行）</strong></p><h3 id="2-5-3-handle-方法"><a href="#2-5-3-handle-方法" class="headerlink" title="2.5.3. handle 方法"></a>2.5.3. handle 方法</h3><p>handle 是执行<strong>任务完成时</strong>对结果的处理。 handle 是在任务完成后再执行，还可以处理异常的任务。</p><h3 id="2-5-4-线程串行化方法"><a href="#2-5-4-线程串行化方法" class="headerlink" title="2.5.4. 线程串行化方法"></a>2.5.4. 线程串行化方法</h3><p>thenApply 方法：当一个线程依赖另一个线程时，获取上一个任务返回的结果，并返回当前任务的返回值。</p><p>thenAccept方法：消费处理结果。接收任务的处理结果，并消费处理，无返回结果。</p><p>thenRun方法：只要上面的任务执行完成，就开始执行thenRun，只是处理完任务后，执行 thenRun的后续操作</p><p>带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。</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> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="hljs-title function_">thenApply</span><span class="hljs-params">(Function&lt;? <span class="hljs-built_in">super</span> T,? extends U&gt; fn)</span><br><span class="hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="hljs-title function_">thenApplyAsync</span><span class="hljs-params">(Function&lt;? <span class="hljs-built_in">super</span> T,? extends U&gt; fn)</span><br><span class="hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="hljs-title function_">thenApplyAsync</span><span class="hljs-params">(Function&lt;? <span class="hljs-built_in">super</span> T,? extends U&gt; fn, Executor executor)</span><br><br><span class="hljs-keyword">public</span> CompletionStage&lt;Void&gt; <span class="hljs-title function_">thenAccept</span><span class="hljs-params">(Consumer&lt;? <span class="hljs-built_in">super</span> T&gt; action)</span>;<br><span class="hljs-keyword">public</span> CompletionStage&lt;Void&gt; <span class="hljs-title function_">thenAcceptAsync</span><span class="hljs-params">(Consumer&lt;? <span class="hljs-built_in">super</span> T&gt; action)</span>;<br><span class="hljs-keyword">public</span> CompletionStage&lt;Void&gt; <span class="hljs-title function_">thenAcceptAsync</span><span class="hljs-params">(Consumer&lt;? <span class="hljs-built_in">super</span> T&gt; action,Executor executor)</span>;<br><br><span class="hljs-keyword">public</span> CompletionStage&lt;Void&gt; <span class="hljs-title function_">thenRun</span><span class="hljs-params">(Runnable action)</span>;<br><span class="hljs-keyword">public</span> CompletionStage&lt;Void&gt; <span class="hljs-title function_">thenRunAsync</span><span class="hljs-params">(Runnable action)</span>;<br><span class="hljs-keyword">public</span> CompletionStage&lt;Void&gt; <span class="hljs-title function_">thenRunAsync</span><span class="hljs-params">(Runnable action,Executor executor)</span>;<br></code></pre></td></tr></table></figure><h3 id="2-5-5-两任务组合-都要完成"><a href="#2-5-5-两任务组合-都要完成" class="headerlink" title="2.5.5. 两任务组合 - 都要完成"></a>2.5.5. 两任务组合 - 都要完成</h3><p>两个任务必须都完成，触发该任务。</p><p>thenCombine：组合两个future，获取两个future的返回结果，并返回当前任务的返回值</p><p>thenAcceptBoth：组合两个future，获取两个future任务的返回结果，然后处理任务，没有返回值。</p><p>runAfterBoth：组合两个future，不需要获取future的结果，只需两个future处理完任务后，处理该任务。</p><h4 id="2-5-5-1-测试案例"><a href="#2-5-5-1-测试案例" class="headerlink" title="2.5.5.1. 测试案例"></a>2.5.5.1. 测试案例</h4><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">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.supplyAsync(() -&gt; &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;hello&quot;</span>;<br>    &#125;).thenApplyAsync(t -&gt; &#123;<br>        <span class="hljs-keyword">return</span> t + <span class="hljs-string">&quot; world!&quot;</span>;<br>    &#125;).thenCombineAsync(CompletableFuture.completedFuture(<span class="hljs-string">&quot; CompletableFuture&quot;</span>), (t, u) -&gt; &#123;<br>        <span class="hljs-keyword">return</span> t + u;<br>    &#125;).whenComplete((t, u) -&gt; &#123;<br>        System.out.println(t);<br>    &#125;);<br>&#125;<br></code></pre></td></tr></table></figure><p>输出：hello world! CompletableFuture</p><h3 id="2-5-6-两任务组合-一个完成"><a href="#2-5-6-两任务组合-一个完成" class="headerlink" title="2.5.6. 两任务组合 - 一个完成"></a>2.5.6. 两任务组合 - 一个完成</h3><p>当两个任务中，任意一个future任务完成的时候，执行任务。</p><p>applyToEither：两个任务有一个执行完成，获取它的返回值，处理任务并有新的返回值。</p><p>acceptEither：两个任务有一个执行完成，获取它的返回值，处理任务，没有新的返回值。</p><p>runAfterEither：两个任务有一个执行完成，不需要获取future的结果，处理任务，也没有返回值。</p><h3 id="2-5-7-多任务组合"><a href="#2-5-7-多任务组合" class="headerlink" title="2.5.7. 多任务组合"></a>2.5.7. 多任务组合</h3><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">static</span> CompletableFuture&lt;Void&gt; <span class="hljs-title function_">allOf</span><span class="hljs-params">(CompletableFuture&lt;?&gt;... cfs)</span>;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> CompletableFuture&lt;Object&gt; <span class="hljs-title function_">anyOf</span><span class="hljs-params">(CompletableFuture&lt;?&gt;... cfs)</span>;<br></code></pre></td></tr></table></figure><p>allOf：等待所有任务完成</p><p>anyOf：只要有一个任务完成</p><h2 id="2-6-业务场景"><a href="#2-6-业务场景" class="headerlink" title="2.6. 业务场景"></a>2.6. 业务场景</h2><h3 id="商品详情页优化"><a href="#商品详情页优化" class="headerlink" title="商品详情页优化"></a>商品详情页优化</h3><p>《商品详情页.md》</p><p>&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;011-Java&#x2F;实战项目&#x2F;谷粒商城&#x2F;谷粒商城&#x2F;8.商品详情及异步编排<br>&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;011-Java&#x2F;实战项目&#x2F;谷粒商城全套&#x2F;分布式高级篇</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-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ItemServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">ItemService</span> &#123;<br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> GmallPmsFeign pmsFeign;<br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> GmallSmsFeign smsFeign;<br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> GmallWmsFeign wmsFeign;<br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> ThreadPoolExecutor threadPoolExecutor;<br><br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> ItemVO <span class="hljs-title function_">loadData</span><span class="hljs-params">(Long skuId)</span> <span class="hljs-keyword">throws</span> ExecutionException, InterruptedException &#123;<br><br>        <span class="hljs-type">ItemVO</span> <span class="hljs-variable">itemVO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ItemVO</span>();<br><br>        <span class="hljs-comment">// 1. 获取sku的基本信息</span><br>        <span class="hljs-comment">// 后续获取sku的促销信息、spu的销售属性和spu详情信息（需要sku中的spuId）都需要skuInfoEntity</span><br>        <span class="hljs-comment">// supplyAsync有返回值</span><br>        <span class="hljs-comment">// runAsync无返回值</span><br>        <span class="hljs-comment">// 所以这里需要使用supplyAsync</span><br>        CompletableFuture&lt;SkuInfoEntity&gt; skuFuture = CompletableFuture.supplyAsync(() -&gt; &#123;<br>            Resp&lt;SkuInfoEntity&gt; skuInfoEntityResp = <span class="hljs-built_in">this</span>.pmsFeign.querySkuById(skuId);<br>            <span class="hljs-type">SkuInfoEntity</span> <span class="hljs-variable">skuInfoEntity</span> <span class="hljs-operator">=</span> skuInfoEntityResp.getData();<br>            <span class="hljs-keyword">if</span> (skuInfoEntity != <span class="hljs-literal">null</span>) &#123;<br>                BeanUtils.copyProperties(skuInfoEntity, itemVO);<br>            &#125;<br>            <span class="hljs-keyword">return</span> skuInfoEntity;<br>        &#125;, threadPoolExecutor);<br><br><br>        <span class="hljs-comment">// 2. 获取sku的图片信息</span><br>        CompletableFuture&lt;Void&gt; skuImageFuture = CompletableFuture.runAsync(() -&gt; &#123;<br>            Resp&lt;List&lt;SkuImagesEntity&gt;&gt; listResp = <span class="hljs-built_in">this</span>.pmsFeign.queryImagesBySkuId(skuId);<br>            List&lt;SkuImagesEntity&gt; images = listResp.getData();<br>            <span class="hljs-keyword">if</span> (!CollectionUtils.isEmpty(images)) &#123;<br>                List&lt;String&gt; imageUrls = images.stream().map(image -&gt; image.getImgUrl()).collect(Collectors.toList());<br>                itemVO.setPics(imageUrls);<br>            &#125;<br>        &#125;, threadPoolExecutor);<br><br><br>        <span class="hljs-comment">// 3. 获取sku的促销信息 TODO</span><br><br>        <span class="hljs-comment">// 4. 获取spu的所有销售属性</span><br>        <span class="hljs-comment">// thenAcceptAsync：有参数，无返回</span><br>        <span class="hljs-comment">// thenApplyAsync: 有参数，有返回</span><br>        <span class="hljs-comment">// 后续spu详情也需要skuInfoEntity中的spuId，所以这里使用thenApplyAsync</span><br>        CompletableFuture&lt;SkuInfoEntity&gt; spuFuture = skuFuture.thenApplyAsync(skuInfoEntity -&gt; &#123;<br>            Resp&lt;List&lt;SkuSaleAttrValueEntity&gt;&gt; skuSaleAttrValueResp = <span class="hljs-built_in">this</span>.pmsFeign.querySkuSaleAttrValueBySpuId(skuInfoEntity.getSpuId());<br>            List&lt;SkuSaleAttrValueEntity&gt; skuSaleAttrValueEntities = skuSaleAttrValueResp.getData();<br>            itemVO.setSaleAttrs(skuSaleAttrValueEntities);<br>            <span class="hljs-keyword">return</span> skuInfoEntity;<br>        &#125;, threadPoolExecutor);<br><br>        <span class="hljs-comment">// 5. 获取规格参数组及组下的规格参数 TODO</span><br><br>        <span class="hljs-comment">// 6. spu详情 TODO</span><br><br>        CompletableFuture&lt;Void&gt; future = CompletableFuture.allOf(skuFuture, skuImageFuture, spuFuture);<br>        <span class="hljs-comment">// 阻塞主进程，等待子进程全部执行完毕！</span><br>        future.get();<br><br>        <span class="hljs-keyword">return</span> itemVO;<br>    &#125;<br><br><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="电商比价"><a href="#电商比价" class="headerlink" title="电商比价"></a>电商比价</h3><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><h2 id="4-1-尚硅谷周阳"><a href="#4-1-尚硅谷周阳" class="headerlink" title="4.1. 尚硅谷周阳"></a>4.1. 尚硅谷周阳</h2><p><a href="https://www.bilibili.com/video/BV1ar4y1x727/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ar4y1x727/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.yuque.com/liuyanntes/sibb9g/fpy93i">https://www.yuque.com/liuyanntes/sibb9g/fpy93i</a></p><h2 id="4-2-网友笔记"><a href="#4-2-网友笔记" class="headerlink" title="4.2. 网友笔记"></a>4.2. 网友笔记</h2><p><a href="http://www.isyue.top/archives/%E5%B0%9A%E7%A1%85%E8%B0%B7juc%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%8E%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90#10-completablefuture%E4%B9%8B%E5%BC%95%E5%87%BAfuturetask%EF%BC%88%E4%B8%8B%EF%BC%89">http://www.isyue.top/archives/%E5%B0%9A%E7%A1%85%E8%B0%B7juc%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%8E%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90#10-completablefuture%E4%B9%8B%E5%BC%95%E5%87%BAfuturetask%EF%BC%88%E4%B8%8B%EF%BC%89</a></p>]]></content>
      
      
      <categories>
          
          <category> 多线程 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 多线程 </tag>
            
            <tag> ForkJoin </tag>
            
            <tag> CompletableFuture </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-15、JUC</title>
      <link href="/2023/01/04/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-15%E3%80%81JUC/"/>
      <url>/2023/01/04/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-15%E3%80%81JUC/</url>
      
        <content type="html"><![CDATA[<h1 id="1-实战经验"><a href="#1-实战经验" class="headerlink" title="1. 实战经验"></a>1. 实战经验</h1><h1 id="2-参考与感谢"><a href="#2-参考与感谢" class="headerlink" title="2. 参考与感谢"></a>2. 参考与感谢</h1><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230528-1642%%</span>❕ ^7cgnqd</p><h2 id="2-1-尚硅谷周阳"><a href="#2-1-尚硅谷周阳" class="headerlink" title="2.1. 尚硅谷周阳"></a>2.1. 尚硅谷周阳</h2><p><a href="https://www.bilibili.com/video/BV1ar4y1x727?p=3&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ar4y1x727?p=3&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-9%E3%80%81Java%E5%90%84%E7%A7%8D%E9%94%81/" title="并发基础-9、Java各种锁">并发基础-9、Java各种锁</a><h2 id="2-2-黑马程序员"><a href="#2-2-黑马程序员" class="headerlink" title="2.2. 黑马程序员"></a>2.2. 黑马程序员</h2><p><a href="https://www.bilibili.com/video/BV16J411h7Rd?p=271&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV16J411h7Rd?p=271&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>实例代码：013-DemoCode&#x2F;heima-concurrent<br>配套资料：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;003-并发编程专题&#x2F;黑马-并发编程</p><h2 id="2-3-网络笔记"><a href="#2-3-网络笔记" class="headerlink" title="2.3. 网络笔记"></a>2.3. 网络笔记</h2><a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-9%E3%80%81Java%E5%90%84%E7%A7%8D%E9%94%81/" title="并发基础-9、Java各种锁">并发基础-9、Java各种锁</a><p><a href="http://www.isyue.top/archives/%E5%B0%9A%E7%A1%85%E8%B0%B7juc%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%8E%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90#03-start%E7%BA%BF%E7%A8%8B%E5%BC%80%E5%90%AFc%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90">网络笔记-尚硅谷juc并发编程与源码分析</a><br>[[尚硅谷juc并发编程与源码分析  午夜星]]</p>]]></content>
      
      
      <categories>
          
          <category> 多线程 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 多线程 </tag>
            
            <tag> JUC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-13、线程池</title>
      <link href="/2023/01/03/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-13%E3%80%81%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
      <url>/2023/01/03/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-13%E3%80%81%E7%BA%BF%E7%A8%8B%E6%B1%A0/</url>
      
        <content type="html"><![CDATA[<h1 id="1-自定义"><a href="#1-自定义" class="headerlink" title="1. 自定义"></a>1. 自定义</h1><h2 id="1-1-四种自带线程池"><a href="#1-1-四种自带线程池" class="headerlink" title="1.1. 四种自带线程池"></a>1.1. 四种自带线程池</h2><h3 id="1-1-1-阻塞队列无界-fixed-single"><a href="#1-1-1-阻塞队列无界-fixed-single" class="headerlink" title="1.1.1. 阻塞队列无界 -fixed-single"></a>1.1.1. 阻塞队列无界 -fixed-single</h3><h4 id="1-1-1-1-newFixedThreadPool-nThreads"><a href="#1-1-1-1-newFixedThreadPool-nThreads" class="headerlink" title="1.1.1.1. newFixedThreadPool-nThreads"></a>1.1.1.1. newFixedThreadPool-nThreads</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528065427.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528071859.png" alt="image.png"></p><h4 id="1-1-1-2-newSingleThreadExecutor-1-1"><a href="#1-1-1-2-newSingleThreadExecutor-1-1" class="headerlink" title="1.1.1.2. newSingleThreadExecutor-1-1"></a>1.1.1.2. newSingleThreadExecutor-1-1</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528065509.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528071927.png" alt="image.png"></p><h3 id="1-1-2-最大线程数无界-cached-scheduled"><a href="#1-1-2-最大线程数无界-cached-scheduled" class="headerlink" title="1.1.2. 最大线程数无界 -cached-scheduled"></a>1.1.2. 最大线程数无界 -cached-scheduled</h3><h4 id="1-1-2-1-newCachedThreadPool"><a href="#1-1-2-1-newCachedThreadPool" class="headerlink" title="1.1.2.1. newCachedThreadPool"></a>1.1.2.1. newCachedThreadPool</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528065455.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528072044.png" alt="image.png"></p><h4 id="1-1-2-2-newScheduledThreadPool"><a href="#1-1-2-2-newScheduledThreadPool" class="headerlink" title="1.1.2.2. newScheduledThreadPool"></a>1.1.2.2. newScheduledThreadPool</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528065537.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607092919.png" alt="image.png"></p><h3 id="1-1-3-ForkJoinPool"><a href="#1-1-3-ForkJoinPool" class="headerlink" title="1.1.3. ForkJoinPool"></a>1.1.3. ForkJoinPool</h3><h4 id="1-1-3-1-newWorkStealingPool"><a href="#1-1-3-1-newWorkStealingPool" class="headerlink" title="1.1.3.1. newWorkStealingPool"></a>1.1.3.1. newWorkStealingPool</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607093041.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607094238.png" alt="image.png"></p><p><strong>创建一个拥有多个任务队列的线程池，可以减少连接数，创建当前可用 cpu 数量的线程来并行执行，适用于大耗时的操作，可以并行来执行</strong></p><p>使用了一个无限队列来保存需要执行的任务，而线程的数量则是通过构造函数传入，如果没有向构造函数中传入希望的线程数量，那么当前计算机可用的 CPU 数量会被设置为线程数量作为默认值。</p><p>newWorkStealingPool 会创建一个含有足够多线程的线程池，来维持相应的并行级别，它会通过工作窃取的方式，使得多核的 CPU 不会闲置，总会有活着的线程让 CPU 去运行。</p><p>ForkJoinPool 主要用来使用分治法 (Divide-and-Conquer Algorithm) 来解决问题。ForkJoinPool 的优势在于，可以充分利用多 cpu，多核 cpu 的优势，把一个任务拆分成多个“小任务”分发到不同的 cpu 核心上执行，执行完后再把结果收集到一起返回。典型的应用比如快速排序算法。这里的要点在于，ForkJoinPool 需要使用相对少的线程来处理大量的任务。比如要对 1000 万个数据进行排序，那么会将这个任务分割成两个 500 万的排序任务和一个针对这两组 500 万数据的合并任务。以此类推，对于 500 万的数据也会做出同样的分割处理，到最后会设置一个阈值来规定当数据规模到多少时，停止这样的分割处理。比如，当元素的数量小于 10 时，会停止分割，转而使用插入排序对它们进行排序。</p><p><a href="https://www.cnblogs.com/duanxz/p/5056222.html">https://www.cnblogs.com/duanxz/p/5056222.html</a></p><h2 id="1-2-为什么要自定义⭐️🔴"><a href="#1-2-为什么要自定义⭐️🔴" class="headerlink" title="1.2. 为什么要自定义⭐️🔴"></a>1.2. 为什么要自定义⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105092054.png"></p><h1 id="2-七大参数"><a href="#2-七大参数" class="headerlink" title="2. 七大参数"></a>2. 七大参数</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105090612.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105090933.png"></p><h2 id="2-1-阻塞队列"><a href="#2-1-阻塞队列" class="headerlink" title="2.1. 阻塞队列"></a>2.1. 阻塞队列</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603132223.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603134014.png" alt="image.png"></p><h2 id="2-2-拒绝策略"><a href="#2-2-拒绝策略" class="headerlink" title="2.2. 拒绝策略"></a>2.2. 拒绝策略</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105093008.png"></p><h1 id="3-扩容流程"><a href="#3-扩容流程" class="headerlink" title="3. 扩容流程"></a>3. 扩容流程</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105091648.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105091922.png"></p><h1 id="4-最大线程数配置"><a href="#4-最大线程数配置" class="headerlink" title="4. 最大线程数配置"></a>4. 最大线程数配置</h1><p>[[合理配置线程池的线程数量 - 掘金]]</p><h2 id="4-1-CPU-密集型"><a href="#4-1-CPU-密集型" class="headerlink" title="4.1. CPU 密集型"></a>4.1. CPU 密集型</h2><p>CPU 密集型也就是计算密集型，常常指算法复杂的程序，需要进行大量的逻辑处理与计算，CPU 在此期间是一直在工作的。<br>在这种情况下 CPU 的利用率会非常高，我们的最大核心数设置为 CPU 的核心数即可。<br>这种情况下 CPU 几乎满负荷运行，我们配置的数字在大也没有效果，反而会增大额外的切换开销。<br>在《Java Concurrency in Practice》中，推荐将 CPU 密集型最大线程数设置为 <strong>最大线程数 &#x3D; CPU 核心数 + 1</strong>，这样能发挥最高效率。<br>核心线程数一般会设置为 **核心线程数 &#x3D; 最大线程数 * 20%**。</p><h2 id="4-2-IO-密集型"><a href="#4-2-IO-密集型" class="headerlink" title="4.2. IO 密集型"></a>4.2. IO 密集型</h2><p>IO 密集型是指我们程序更多的工作是在通过磁盘、内存或者是网络读取数据，在 IO 期间我们线程是阻塞的，这期间 CPU 其实也是空闲的，这样我们的操作系统就可以切换其他线程来使用 CPU 资源。<br>通常在进行接口远程调用，数据库数据获取，缓冲数据获取都属于 IO 操作。<br>这时我们线程数可以通过以下公式进行计算：<strong>最大线程数 &#x3D; CPU 核心数 &#x2F; （1 - 阻塞占百分比）</strong>。</p><p>我们很好理解比如在某个请求中，请求时长为 10 秒，调用 IO 时间为 8 秒，这时我们阻塞占百分比就是 80%，有效利用 CPU 占比就是 20%，假设是八核 CPU，我们线程数就是 8 &#x2F; (1 - 80%) &#x3D; 8 &#x2F; 0.2 &#x3D; 40 个。<br>也就是说 我们八核 CPU 在上述情况中，可满负荷运行 40 个线程。这时我们可将最大线程数调整为 40，在系统进行 IO 操作时会去处理其他线程。</p><h2 id="4-3-混合型"><a href="#4-3-混合型" class="headerlink" title="4.3. 混合型"></a>4.3. 混合型</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603155614.png" alt="image.png"></p><h3 id="4-3-1-懒人工具"><a href="#4-3-1-懒人工具" class="headerlink" title="4.3.1. 懒人工具"></a>4.3.1. 懒人工具</h3><p><a href="https://www.javacodegeeks.com/2012/03/threading-stories-about-robust-thread.html">https://www.javacodegeeks.com/2012/03/threading-stories-about-robust-thread.html</a></p><h2 id="4-4-实际考虑因素"><a href="#4-4-实际考虑因素" class="headerlink" title="4.4. 实际考虑因素"></a>4.4. 实际考虑因素</h2><p><a href="https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html">https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html</a><br>[[线程池大小 + 线程数量到底设置多少？ - 腾讯云开发者社区-腾讯云]]<br>[[如何设置线程池参数？美团给出了一个让面试官虎躯一震的回答。 - why技术 - 博客园]]</p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h2 id="5-1-线程池预热"><a href="#5-1-线程池预热" class="headerlink" title="5.1. 线程池预热"></a>5.1. 线程池预热</h2><p>问题一：线程池被创建后里面有线程吗？如果没有的话，你知道有什么方法对线程池进行预热吗？</p><p>线程池被创建后如果没有任务过来，里面是不会有线程的。如果需要预热的话可以调用下面的两个方法：</p><h3 id="5-1-1-全部启动"><a href="#5-1-1-全部启动" class="headerlink" title="5.1.1. 全部启动"></a>5.1.1. 全部启动</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603121737.png" alt="image.png"></p><h3 id="5-1-2-仅启动一个"><a href="#5-1-2-仅启动一个" class="headerlink" title="5.1.2. 仅启动一个"></a>5.1.2. 仅启动一个</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603121744.png" alt="image.png"></p><p>问题二：核心线程数会被回收吗？需要什么设置？</p><p>核心线程数默认是不会被回收的，如果需要回收核心线程数，需要调用下面的方法：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603121831.png" alt="image.png"></p><p>allowCoreThreadTimeOut 该值默认为 false。</p><h2 id="5-2-线程池如何知道一个线程的任务已经执行完成"><a href="#5-2-线程池如何知道一个线程的任务已经执行完成" class="headerlink" title="5.2. 线程池如何知道一个线程的任务已经执行完成"></a>5.2. 线程池如何知道一个线程的任务已经执行完成</h2><h3 id="5-2-1-线程池内部"><a href="#5-2-1-线程池内部" class="headerlink" title="5.2.1. 线程池内部"></a>5.2.1. 线程池内部</h3><p>在线程池内部，当我们把一个任务丢给线程池去执行，线程池会调度工作线程来执行这个任务的 run 方法，run 方法正常结束，也就意味着任务完成了。所以线程池中的工作线程是通过同步调用任务的 run() 方法并且<span style="background-color:#ff00ff">等待 run 方法返回后，再去统计任务的完成数量</span>。</p><h3 id="5-2-2-线程池外部"><a href="#5-2-2-线程池外部" class="headerlink" title="5.2.2. 线程池外部"></a>5.2.2. 线程池外部</h3><h4 id="5-2-2-1-轮询线程池的-isTerminated-方法"><a href="#5-2-2-1-轮询线程池的-isTerminated-方法" class="headerlink" title="5.2.2.1. 轮询线程池的 isTerminated()方法"></a>5.2.2.1. 轮询线程池的 isTerminated()方法</h4><p><span style="background-color:#ff00ff">线程池提供了一个 isTerminated() 方法</span>，可以判断线程池的运行状态，我们可以循环判断 isTerminated() 方法的返回结果来了解线程池的运行状态，一旦线程池的运行状态是 Terminated，意味着线程池中的所有任务都已经执行完了。想要通过这个方法获取状态的前提是，程序中主动调用了线程池的 shutdown() 方法。在实际业务中，一般不会主动去关闭线程池，因此这个方法在实用性和灵活性方面都不是很好。</p><h4 id="5-2-2-2-future-get-会阻塞"><a href="#5-2-2-2-future-get-会阻塞" class="headerlink" title="5.2.2.2. future.get()- 会阻塞"></a>5.2.2.2. future.get()- 会阻塞</h4><p>在线程池中，有一个 submit() 方法，它提供了一个 Future 的返回值，我们通过 Future.get() 方法来获得任务的执行结果，当线程池中的任务没执行完之前， future.get() 方法会一直阻塞，直到任务执行结束。因此，只要 future.get() 方法正常返回，也就意味着传入到线程池中的任务已经执行完成了！</p><h4 id="5-2-2-3-CountDownLatch"><a href="#5-2-2-3-CountDownLatch" class="headerlink" title="5.2.2.3. CountDownLatch"></a>5.2.2.3. CountDownLatch</h4><p>可以引入一个 CountDownLatch 计数器，它可以通过初始化指定一个计数器进行倒计时，其中有两个方法分别是 await() 阻塞线程，以及 countDown() 进行倒计时，一旦倒计时归零，所以被阻塞在 await() 方法的线程都会被释放</p><p>基于这样的原理，我们可以定义一个 CountDownLatch 对象并且计数器为 1，接着在线程池代码块后面调用 await() 方法阻塞主线程，然后，当传入到线程池中的任务执行完成后，调用 countDown() 方法表示任务执行结束。最后，计数器归零 0，唤醒阻塞在 await() 方法的线程。</p><p>   <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603121143.png" alt="image.png"></p><h2 id="5-3-自定义线程池参数设置"><a href="#5-3-自定义线程池参数设置" class="headerlink" title="5.3. 自定义线程池参数设置"></a>5.3. 自定义线程池参数设置</h2><h3 id="5-3-1-降低系统资源消耗"><a href="#5-3-1-降低系统资源消耗" class="headerlink" title="5.3.1. 降低系统资源消耗"></a>5.3.1. 降低系统资源消耗</h3><p>例如 CPU 使用率、操作系统资源消耗、上下文切换开销</p><p>设置一个比较大的队列容量和一个比较小的线程池容量</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603152448.png" alt="image.png"></p><h2 id="5-4-异步化"><a href="#5-4-异步化" class="headerlink" title="5.4. 异步化"></a>5.4. 异步化</h2><h3 id="5-4-1-本地调用异步化"><a href="#5-4-1-本地调用异步化" class="headerlink" title="5.4.1. 本地调用异步化"></a>5.4.1. 本地调用异步化</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603163523.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603164038.png" alt="image.png"></p><h3 id="5-4-2-远程调用异步化"><a href="#5-4-2-远程调用异步化" class="headerlink" title="5.4.2. 远程调用异步化"></a>5.4.2. 远程调用异步化</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603165016.png" alt="image.png"></p><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-尚硅谷周阳"><a href="#6-1-尚硅谷周阳" class="headerlink" title="6.1. 尚硅谷周阳"></a>6.1. 尚硅谷周阳</h2><p><a href="https://www.bilibili.com/video/BV18b411M7xz?t=644.5&amp;p=49">https://www.bilibili.com/video/BV18b411M7xz?t=644.5&amp;p=49</a></p><p>[[jvm juc.xmind]]</p><h2 id="6-2-美团技术"><a href="#6-2-美团技术" class="headerlink" title="6.2. 美团技术"></a>6.2. 美团技术</h2><p><a href="https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html">https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html</a></p>]]></content>
      
      
      <categories>
          
          <category> 线程池 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 线程池 </tag>
            
            <tag> 并发编程 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-RocketMQ-2、消息的清理</title>
      <link href="/2023/01/03/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-2%E3%80%81%E6%B6%88%E6%81%AF%E7%9A%84%E6%B8%85%E7%90%86/"/>
      <url>/2023/01/03/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-2%E3%80%81%E6%B6%88%E6%81%AF%E7%9A%84%E6%B8%85%E7%90%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-消费完后的消息去哪里了？"><a href="#1-消费完后的消息去哪里了？" class="headerlink" title="1. 消费完后的消息去哪里了？"></a>1. 消费完后的消息去哪里了？</h1><p>消息的存储是一直存在于CommitLog中的。而由于CommitLog是以文件为单位（而非消息）存在的，CommitLog的设计是只允许顺序写的，且每个消息大小不定长，所以这决定了消息文件几乎不可能按照消息为单位删除（否则性能会极具下降，逻辑也非常复杂）。所以消息被消费了，消息所占据的物理空间并不会立刻被回收。</p><p>但消息既然一直没有删除，那RocketMQ怎么知道应该投递过的消息就不再投递？——答案是客户端自身维护——客户端拉取完消息之后，在响应体中，broker会返回下一次应该拉取的位置，PushConsumer通过这一个位置，更新自己下一次的pull请求。这样就保证了正常情况下，消息只会被投递一次。</p><h1 id="2-什么时候清理物理消息文件？"><a href="#2-什么时候清理物理消息文件？" class="headerlink" title="2. 什么时候清理物理消息文件？"></a>2. 什么时候清理物理消息文件？</h1><p>那消息文件到底删不删，什么时候删？</p><p>消息存储在CommitLog之后，的确是会被清理的，但是这个清理只会在以下任一条件成立才会批量删除消息文件（CommitLog）：</p><ol><li>消息文件过期（默认72小时），且到达清理时点（默认是凌晨4点），删除过期文件。</li><li>消息文件过期（默认72小时），且磁盘空间达到了水位线（默认75%），删除过期文件。</li><li>磁盘已经达到必须释放的上限（85%水位线）的时候，则开始批量清理文件（无论是否过期），直到空间充足。</li></ol><p>注：若磁盘空间达到危险水位线（默认90%），出于保护自身的目的，broker会拒绝写入服务。</p><blockquote><p>需要注意以下几点：<br> 1）对于RocketMQ系统来说，删除一个1G大小的文件，是一个压力巨大的IO操作。在删除过程 中，系统性能会骤然下降。所以，其默认清理时间点为凌晨4点，访问量最小的时间。也正因如果，我们要保障磁盘空间的空闲率，不要使系统出现在其它时间点删除commitlog文件的情况。<br> 2）官方建议RocketMQ服务的Linux文件系统采用ext4。因为对于文件删除操作，ext4要比ext3性 能更好</p></blockquote><h1 id="3-这样设计带来的好处"><a href="#3-这样设计带来的好处" class="headerlink" title="3. 这样设计带来的好处"></a>3. 这样设计带来的好处</h1><p>消息的物理文件一直存在，消费逻辑只是听客户端的决定而搜索出对应消息进行，这样做，笔者认为，有以下几个好处：</p><ol><li><p>一个消息很可能需要被N个消费组（设计上很可能就是系统）消费，但消息只需要存储一份，消费进度单独记录即可。这给强大的消息堆积能力提供了很好的支持——一个消息无需复制N份，就可服务N个消费组。</p></li><li><p>由于消费从哪里消费的决定权一直都是客户端决定，所以只要消息还在，就可以消费到，这使得RocketMQ可以支持其他传统消息中间件不支持的回溯消费。即我可以通过设置消费进度回溯，就可以让我的消费组重新像放快照一样消费历史消息；或者我需要另一个系统也复制历史的数据，只需要另起一个消费组从头消费即可（前提是消息文件还存在）。</p></li><li><p>消息索引服务。只要消息还存在就能被搜索出来。所以可以依靠消息的索引搜索出消息的各种原信息，方便事后排查问题。</p></li></ol><p>注：在消息清理的时候，由于消息文件默认是 1GB，所以在清理的时候其实是在删除一个大文件操作，这对于 IO 的压力是非常大的，这时候如果有消息写入，写入的耗时会明显变高。这个现象可以在凌晨 4 点（默认删时间时点）后的附近观察得到。</p><h1 id="4-跳过历史消息的处理"><a href="#4-跳过历史消息的处理" class="headerlink" title="4. 跳过历史消息的处理"></a>4. 跳过历史消息的处理</h1><p>由于消息本身是没有过期的概念，只有文件才有过期的概念。那么对于很多业务场景——一个消息如果太老，是无需要被消费的，是不合适的。</p><p>这种需要跳过历史消息的场景，在RocketMQ要怎么实现呢？</p><p>对于一个全新的消费组，PushConsumer默认就是跳过以前的消息而从最尾开始消费的，解析请参看<a href="http://jaskey.github.io/blog/2017/01/25/rocketmq-consume-offset-management//" title="RocketMQ——消息ACK机制及消费进度管理">RocketMQ——消息ACK机制及消费进度管理</a>相关章节。</p><p>但对于已存在的消费组，RocketMQ没有内置的跳过历史消息的实现，但有以下手段可以解决：</p><ol><li><p>自身的消费代码按照日期过滤，太老的消息直接过滤。如：</p><figure class="highlight plaintext"><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 plaintext">@Override<br>public ConsumeConcurrentlyStatus consumeMessage(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context) &#123;<br>    for(MessageExt msg: msgs)&#123;<br>        if(System.currentTimeMillis()-msg.getBornTimestamp()&gt;60*1000) &#123;//一分钟之前的认为过期<br>            continue;//过期消息跳过<br>        &#125;<br>    <br>        //do consume here<br>    <br>    &#125;<br>    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>自身的消费代码代码判断消息的offset和MAX_OFFSET相差很远，认为是积压了很多，直接return CONSUME_SUCCESS过滤。</p><figure class="highlight plaintext"><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 plaintext">@Override<br>public ConsumeConcurrentlyStatus consumeMessage(//<br>    List&lt;MessageExt&gt; msgs, //<br>    ConsumeConcurrentlyContext context) &#123;<br>    long offset = msgs.get(0).getQueueOffset();<br>    String maxOffset = msgs.get(0).getProperty(MessageConst.PROPERTY_MAX_OFFSET);<br>    long diff = Long. parseLong(maxOffset) - offset;<br>    if (diff &gt; 100000) &#123; //消息堆积了10W情况的特殊处理<br>        return ConsumeConcurrentlyStatus. CONSUME_SUCCESS;<br>    &#125;<br>    //do consume here<br>    return ConsumeConcurrentlyStatus. CONSUME_SUCCESS;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>消费者启动前，先调整该消费组的消费进度，再开始消费。可以人工使用控制台命令resetOffsetByTime把消费进度调整到后面，再启动消费。</p></li><li><p>原理同3，但使用代码来控制。代码中调用内部的运维接口</p></li></ol><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><p><a href="https://jaskey.github.io/blog/2017/02/16/rocketmq-clean-commitlog/">https://jaskey.github.io/blog/2017/02/16/rocketmq-clean-commitlog/</a></p>]]></content>
      
      
      <categories>
          
          <category> MQ </category>
          
      </categories>
      
      
        <tags>
            
            <tag> RocketMQ </tag>
            
            <tag> MQ </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-RocketMQ-3、消息ACK机制及消费进度管理</title>
      <link href="/2023/01/03/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-3%E3%80%81%E6%B6%88%E6%81%AF%E7%9A%84%E6%B6%88%E8%B4%B9/"/>
      <url>/2023/01/03/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-3%E3%80%81%E6%B6%88%E6%81%AF%E7%9A%84%E6%B6%88%E8%B4%B9/</url>
      
        <content type="html"><![CDATA[<h1 id="1-消息类型"><a href="#1-消息类型" class="headerlink" title="1. 消息类型"></a>1. 消息类型</h1><h2 id="1-1-顺序消费-顺序消息"><a href="#1-1-顺序消费-顺序消息" class="headerlink" title="1.1. 顺序消费 (顺序消息)"></a>1.1. 顺序消费 (顺序消息)</h2><p><span style="display:none">%%<br>▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230417-1521%%</span>❕ ^gmi3ab</p><p><a href="https://www.bilibili.com/video/BV1L4411y7mn?p=24&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1L4411y7mn?p=24&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>顺序消息指的是，严格按照消息的发送顺序进行消费的消息 (FIFO)。<br>默认情况下生产者会把消息<span style="background-color:#ff00ff">以 Round Robin 轮询方式</span>发送到不同的 Queue 分区队列；而消费消息时会从多个 Queue 上拉取消息，这种情况下的发送和消费是不能保证顺序的。如果将消息仅发送到同一个 Queue 中，消费时也只从这个 Queue 上拉取消息，就严格保证了消息的顺序性。</p><p>当发送和消费参与的 queue 只有一个，则是全局有序；如果多个 queue 参与，则为分区有序，即相对每个 queue，消息都是有序的。</p><p>下面用订单进行分区有序的示例。一个订单的顺序流程是：创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中，消费时，同一个 OrderId 获取到的肯定是同一个队列。</p><h3 id="1-1-1-顺序消息生产"><a href="#1-1-1-顺序消息生产" class="headerlink" title="1.1.1. 顺序消息生产"></a>1.1.1. 顺序消息生产</h3><h3 id="1-1-2-顺序消息消费"><a href="#1-1-2-顺序消息消费" class="headerlink" title="1.1.2. 顺序消息消费"></a>1.1.2. 顺序消息消费</h3><h3 id="1-1-3-能否严格有序"><a href="#1-1-3-能否严格有序" class="headerlink" title="1.1.3. 能否严格有序"></a>1.1.3. 能否严格有序</h3><p><a href="https://dbaplus.cn/news-21-1123-1.html">https://dbaplus.cn/news-21-1123-1.html</a></p><h2 id="1-2-延时消息"><a href="#1-2-延时消息" class="headerlink" title="1.2. 延时消息"></a>1.2. 延时消息</h2><h3 id="1-2-1-使用场景"><a href="#1-2-1-使用场景" class="headerlink" title="1.2.1. 使用场景"></a>1.2.1. 使用场景</h3><p>比如电商里，提交了一个订单就可以发送一个延时消息，1h 后去检查这个订单的状态，如果还是未付款就取消订单释放库存。</p><h3 id="1-2-2-使用限制"><a href="#1-2-2-使用限制" class="headerlink" title="1.2.2. 使用限制"></a>1.2.2. 使用限制</h3><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-comment">// org/apache/rocketmq/store/config/MessageStoreConfig.java</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">String</span> <span class="hljs-variable">messageDelayLevel</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h&quot;</span>;<br></code></pre></td></tr></table></figure><p>现在 RocketMq 并不支持任意时间的延时，需要设置几个固定的延时等级，从 1s 到 2h 分别对应着等级 1 到 18</p><h2 id="1-3-事务消息"><a href="#1-3-事务消息" class="headerlink" title="1.3. 事务消息"></a>1.3. 事务消息</h2><h3 id="1-3-1-流程分析"><a href="#1-3-1-流程分析" class="headerlink" title="1.3.1. 流程分析"></a>1.3.1. 流程分析</h3><p><img src="/img/%E4%BA%8B%E5%8A%A1%E6%B6%88%E6%81%AF.png"></p><p>上图说明了事务消息的大致方案，其中分为两个流程：正常事务消息的发送及提交、事务消息的补偿流程。</p><p>(1) 发送消息（half 消息）<br>(2) 服务端响应消息写入结果<br>(3) 根据发送结果执行本地事务（如果写入失败，此时 half 消息对业务不可见，本地逻辑不执行）<br>(4) 根据本地事务状态执行 Commit 或者 Rollback（Commit 操作生成消息索引，消息对消费者可见）</p><h3 id="1-3-2-使用限制"><a href="#1-3-2-使用限制" class="headerlink" title="1.3.2. 使用限制"></a>1.3.2. 使用限制</h3><ol><li>事务消息不支持延时消息和批量消息。</li><li>为了避免单个消息被检查太多次而导致半队列消息累积，我们默认将单个消息的检查次数限制为 15 次，但是用户可以通过 Broker 配置文件的 <code>transactionCheckMax</code> 参数来修改此限制。如果已经检查某条消息超过 N 次的话（ N &#x3D; <code>transactionCheckMax</code> ） 则 Broker 将丢弃此消息，并在默认情况下同时打印错误日志。用户可以通过重写 <code>AbstractTransactionCheckListener</code> 类来修改这个行为。</li><li>事务消息将在 Broker 配置文件中的参数 <code>transactionMsgTimeout</code> 这样的特定时间长度之后被检查。当发送事务消息时，用户还可以通过设置用户属性 <code>CHECK_IMMUNITY_TIME_IN_SECONDS</code> 来改变这个限制，该参数优先于 <code>transactionMsgTimeout</code> 参数。</li><li>事务性消息可能不止一次被检查或消费。</li><li>提交给用户的目标主题消息可能会失败，目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证，如果希望确保事务消息不丢失、并且事务完整性得到保证，建议使用同步的双重写入机制。</li><li>事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同，事务消息允许反向查询、MQ 服务器能通过它们的生产者 ID 查询到消费者。</li></ol><h1 id="2-消费类型"><a href="#2-消费类型" class="headerlink" title="2. 消费类型"></a>2. 消费类型</h1><p>消费者从 Broker 中获取消息的方式有两种：pull 拉取方式和 push 推动方式。<br>消费者组对于消息消费的模式又分为两种：集群消费 Clustering 和广播消费 Broadcasting。</p><h2 id="2-1-拉取式消费"><a href="#2-1-拉取式消费" class="headerlink" title="2.1. 拉取式消费"></a>2.1. 拉取式消费</h2><h2 id="2-2-推送式消费"><a href="#2-2-推送式消费" class="headerlink" title="2.2. 推送式消费"></a>2.2. 推送式消费</h2><h2 id="2-3-对比"><a href="#2-3-对比" class="headerlink" title="2.3. 对比"></a>2.3. 对比</h2><p>pull：需要应用去实现对关联 Queue 的遍历，实时性差；但便于应用控制消息的拉取<br>push：封装了对关联 Queue 的遍历，实时性强，但会占用较多的系统资源</p><h1 id="3-消费模式"><a href="#3-消费模式" class="headerlink" title="3. 消费模式"></a>3. 消费模式</h1><h2 id="3-1-广播消费"><a href="#3-1-广播消费" class="headerlink" title="3.1. 广播消费"></a>3.1. 广播消费</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104153444.png"><br>广播消费模式下，相同 Consumer Group 的每个 Consumer 实例都接收同一个 Topic 的全量消息。即每条消息都会被发送到 Consumer Group 中的<span style="background-color:#ff00ff">每个 Consumer</span>。</p><h2 id="3-2-集群消费"><a href="#3-2-集群消费" class="headerlink" title="3.2. 集群消费"></a>3.2. 集群消费</h2><h3 id="3-2-1-各种关系⭐️🔴"><a href="#3-2-1-各种关系⭐️🔴" class="headerlink" title="3.2.1. 各种关系⭐️🔴"></a>3.2.1. 各种关系⭐️🔴</h3><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230417-1044%%</span>❕❕ ^r5bwjc</p><h4 id="3-2-1-1-所有主-Broker-分摊所有-Topic-的-MessageQueue"><a href="#3-2-1-1-所有主-Broker-分摊所有-Topic-的-MessageQueue" class="headerlink" title="3.2.1.1. 所有主 Broker 分摊所有 Topic 的 MessageQueue"></a>3.2.1.1. 所有主 Broker 分摊所有 Topic 的 MessageQueue</h4><a href="/2022/12/31/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="分布式专题-MQ-RocketMQ-1、基本原理">分布式专题-MQ-RocketMQ-1、基本原理</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417103817.png" alt="image.png"></p><h4 id="3-2-1-2-订阅关系一致性"><a href="#3-2-1-2-订阅关系一致性" class="headerlink" title="3.2.1.2. 订阅关系一致性"></a>3.2.1.2. 订阅关系一致性</h4><p>由于消息队列 RocketMQ 版的订阅关系主要由 Topic+Tag 共同组成，因此，保持订阅关系一致意味着同一个消费者 Group ID 下所有的 Consumer 实例需在以下方面均保持一致：</p><ul><li>订阅的 Topic 必须一致，例如：Consumer1 订阅 TopicA 和 TopicB，Consumer2 也必须订阅 TopicA 和 TopicB，不能只订阅 TopicA、只订阅 TopicB 或订阅 TopicA 和 TopicC。</li><li>订阅的同一个 Topic 中的 Tag 必须一致，包括 Tag 的数量和 Tag 的顺序，例如：Consumer1 订阅 TopicB 且 Tag 为 Tag1||Tag2，Consumer2 订阅 TopicB 的 Tag 也必须是 Tag1||Tag2，不能只订阅 Tag1、只订阅 Tag2 或者订阅 Tag2||Tag1。</li></ul><p><a href="https://help.aliyun.com/document_detail/43523.html">https://help.aliyun.com/document_detail/43523.html</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417135715.png" alt="image.png"></p><h4 id="3-2-1-3-消费者组与-Topic-多对多"><a href="#3-2-1-3-消费者组与-Topic-多对多" class="headerlink" title="3.2.1.3. 消费者组与 Topic - 多对多"></a>3.2.1.3. 消费者组与 Topic - 多对多</h4><ol><li>如上图所示，一个消费者组可以消费多个 Topic，多个 Tag<br>但同一个消费者组中的消费者必须订阅完全相同的 Topic 和 Tag</li><li>一个 Topic 也可以被多个消费者组消费</li></ol><h4 id="3-2-1-4-消费者与-MessageQueue-1-对多"><a href="#3-2-1-4-消费者与-MessageQueue-1-对多" class="headerlink" title="3.2.1.4. 消费者与 MessageQueue - 1 对多"></a>3.2.1.4. 消费者与 MessageQueue - 1 对多</h4><p>在集群消费模式下，<span style="background-color:#ff00ff">每条消息只需要投递到订阅这个 topic 的 Consumer Group 下的一个实例即可</span>。RocketMQ 采用主动拉取的方式拉取并消费消息，在拉取的时候需要明确指定拉取哪一条 message queue。</p><p>而每当实例的数量有变更，都会触发一次所有实例的负载均衡，这时候会按照 queue 的数量和实例的数量平均分配 queue 给每个实例。</p><p>默认的分配算法是 AllocateMessageQueueAveragely，如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417140947.png"></p><p>还有另外一种平均的算法是 AllocateMessageQueueAveragelyByCircle，也是平均分摊每一条 queue，只是以环状轮流分 queue 的形式，如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417141130.png"></p><p>需要注意的是，集群模式下，<span style="background-color:#ff0000">queue 都是只允许分配给一个实例</span>，这是由于如果多个实例同时消费一个 queue 的消息，<span style="background-color:#ff00ff">由于拉取哪些消息是 consumer 主动控制的</span>，<span style="background-color:#ff00ff">那样会导致同一个消息在不同的实例下被消费多次</span>，<span style="background-color:#ff00ff">所以算法上都是一个 queue 只分给一个 consumer 实例，一个 consumer 实例可以允许同时分到不同的 queue</span>。</p><p>通过增加 consumer 实例去分摊 queue 的消费，可以起到水平扩展的消费能力的作用。而有实例下线的时候，会重新触发负载均衡，这时候原来分配到的 queue 将分配到其他实例上继续消费。</p><p>但是如果 consumer 实例的数量比 message queue 的总数量还多的话，多出来的 consumer 实例将无法分到 queue，也就无法消费到消息，也就无法起到分摊负载的作用了。所以需要控制让 queue 的总数量大于等于 consumer 的数量。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104153533.png"><br>集群消费模式下，相同 Consumer Group 的每个 Consumer 实例平均分摊同一个 Topic 的消息。即每条消息只会被发送到 Consumer Group 中的<span style="background-color:#ff00ff">某个 Consumer</span>。</p><h2 id="3-3-消息进度保存"><a href="#3-3-消息进度保存" class="headerlink" title="3.3. 消息进度保存"></a>3.3. 消息进度保存</h2><p>广播模式：消费进度保存在<span style="background-color:#00ff00">consumer 端</span>。因为广播模式下 consumer group 中每个 consumer 都会消费所有消息，但它们的消费进度是不同。所以 consumer 各自保存各自的消费进度。<br>集群模式：消费进度保存在<span style="background-color:#00ff00">broker 中</span>。consumer group 中的所有 consumer 共同消费同一个 Topic 中的消息，同一条消息只会被消费一次。消费进度会参与到了消费的负载均衡中，故消费进度是需要共享的。下图是 broker 中存放的各个 Topic 的各个 Queue 的消费进度。</p><h1 id="4-Rebalance-机制"><a href="#4-Rebalance-机制" class="headerlink" title="4. Rebalance 机制"></a>4. Rebalance 机制</h1><p>Rebalance 即再均衡，指的是，将⼀个 Topic 下的多个 Queue 在同⼀个 Consumer Group 中的多个 Consumer 间进行重新分配的过程。</p><h2 id="4-1-Rebalance-危害"><a href="#4-1-Rebalance-危害" class="headerlink" title="4.1. Rebalance 危害"></a>4.1. Rebalance 危害</h2><p>Rebalance 的在提升消费能力的同时，也带来一些问题：<br><strong>消费暂停</strong>：在只有一个 Consumer 时，其负责消费所有队列；在新增了一个 Consumer 后会触发 Rebalance 的发生。此时原 Consumer 就需要暂停部分队列的消费，等到这些队列分配给新的 Consumer 后，这些暂停消费的队列才能继续被消费。<br><strong>消费重复</strong>：Consumer 在消费新分配给自己的队列时，必须接着之前 Consumer 提交的消费进度的 offset 继续消费。然而默认情况下，offset 是异步提交的，这个异步性导致提交到 Broker 的 offset 与 Consumer 实际消费的消息并不一致。这个不一致的差值就是可能会重复消费的消息。</p><blockquote><p>同步提交：consumer 提交了其消费完毕的一批消息的 offset 给 broker 后，需要等待 broker 的成功 ACK。当收到 ACK 后，consumer 才会继续获取并消费下一批消息。在等待 ACK 期间，consumer 是阻塞的。<br>异步提交：consumer 提交了其消费完毕的一批消息的 offset 给 broker 后，不需要等待 broker 的成功 ACK。consumer 可以直接获取并消费下一批消息。对于一次性读取消息的数量，需要根据具体业务场景选择一个相对均衡的是很有必要的。因为数量过大，系统性能提升了，但产生重复消费的消息数量可能会增加；数量过小，系统性能会下降，但被重复消费的消息数量可能会减少。</p></blockquote><p><strong>消费突刺</strong>：由于 Rebalance 可能导致重复消费，如果需要重复消费的消息过多，或者因为 Rebalance 暂停时间过长从而导致积压了部分消息。那么有可能会导致在 Rebalance 结束之后瞬间需要消费很多消息。</p><h1 id="5-Queue-分配算法"><a href="#5-Queue-分配算法" class="headerlink" title="5. Queue 分配算法"></a>5. Queue 分配算法</h1><p><span style="background-color:#ff00ff">一个 Topic 中的 Queue 只能由 Consumer Group 中的一个 Consumer 进行消费，而一个 Consumer 可以同时消费多个 Queue 中的消息。</span>那么 Queue 与 Consumer 间的配对关系是如何确定的，即 Queue 要分配给哪个 Consumer 进行消费，也是有算法策略的。常见的有四种策略。这些策略是通过在创建 Consumer 时的构造器传进去的。</p><h2 id="5-1-平均分配策略"><a href="#5-1-平均分配策略" class="headerlink" title="5.1. 平均分配策略"></a>5.1. 平均分配策略</h2><h2 id="5-2-环形平均策略"><a href="#5-2-环形平均策略" class="headerlink" title="5.2. 环形平均策略"></a>5.2. 环形平均策略</h2><h2 id="5-3-一致性-hash-策略"><a href="#5-3-一致性-hash-策略" class="headerlink" title="5.3. 一致性 hash 策略"></a>5.3. 一致性 hash 策略</h2><h2 id="5-4-同机房策略"><a href="#5-4-同机房策略" class="headerlink" title="5.4. 同机房策略"></a>5.4. 同机房策略</h2><h2 id="5-5-对比"><a href="#5-5-对比" class="headerlink" title="5.5. 对比"></a>5.5. 对比</h2><p>一致性 hash 算法存在的问题：<br>两种平均分配策略的分配效率较高，一致性 hash 策略的较低。因为一致性 hash 算法较复杂。另外，一致性 hash 策略分配的结果也很大可能上存在不平均的情况。<br>一致性 hash 算法存在的意义：<br>其可以<span style="background-color:#00ff00">有效减少由于消费者组扩容或缩容所带来的大量的 Rebalance</span>。<br>一致性 hash 算法的应用场景：<br>Consumer 数量变化较频繁的场景。</p><h1 id="6-至少一次原则"><a href="#6-至少一次原则" class="headerlink" title="6. 至少一次原则"></a>6. 至少一次原则</h1><p><span style="background-color:#ff00ff">RocketMQ 有一个原则：每条消息必须要被成功消费一次。</span><br>那么什么是成功消费呢？Consumer 在消费完消息后会向其消费进度记录器提交其消费消息的 offset， offset 被成功记录到记录器中，那么这条消费就被成功消费了。<br>什么是消费进度记录器？ 对于广播消费模式来说，Consumer 本身就是消费进度记录器。对于集群消费模式来说，Broker 是消费进度记录器。</p><h1 id="7-offset-管理"><a href="#7-offset-管理" class="headerlink" title="7. offset 管理"></a>7. offset 管理</h1><h2 id="7-1-重试队列"><a href="#7-1-重试队列" class="headerlink" title="7.1. 重试队列"></a>7.1. 重试队列</h2><p>当 rocketMQ 对消息的消费出现异常时，会将发生异常的消息的 offset 提交到 Broker 中的重试队列。系统在发生消息消费异常时会为当前的 topic@group 创建一个重试队列，该队列以%RETRY% 开头，到达重试时间后进行消费重试。</p><h1 id="8-重复消费-消费幂等"><a href="#8-重复消费-消费幂等" class="headerlink" title="8. 重复消费 (消费幂等)"></a>8. 重复消费 (消费幂等)</h1><h2 id="8-1-什么是消费幂等"><a href="#8-1-什么是消费幂等" class="headerlink" title="8.1. 什么是消费幂等"></a>8.1. 什么是消费幂等</h2><p>当出现消费者对某条消息重复消费的情况时，重复消费的结果与消费一次的结果是相同的，并且多次消费并未对业务系统产生任何负面影响，那么这个消费过程就是消费幂等的。<br><span style="background-color:#00ff00">幂等：若某操作执行多次与执行一次对系统产生的影响是相同的，则称该操作是幂等的。</span><br>在互联网应用中，尤其在网络不稳定的情况下，消息很有可能会出现重复发送或重复消费。如果重复的消息可能会影响业务处理，那么就应该对消息做幂等处理。</p><h2 id="8-2-消息重复的场景分析"><a href="#8-2-消息重复的场景分析" class="headerlink" title="8.2. 消息重复的场景分析"></a>8.2. 消息重复的场景分析</h2><p>什么情况下可能会出现消息被重复消费呢？最常见的有以下三种情况：</p><h3 id="8-2-1-发送时消息重复"><a href="#8-2-1-发送时消息重复" class="headerlink" title="8.2.1. 发送时消息重复"></a>8.2.1. 发送时消息重复</h3><p>当一条消息已被成功发送到 Broker 并完成持久化，此时出现了网络闪断，从而导致 Broker 对 Producer 应答失败。如果此时 Producer 意识到消息发送失败并尝试再次发送消息，此时 Broker 中就可能会出现两条内容相同并且 Message ID 也相同的消息，那么后续 Consumer 就一定会消费两次该消息。</p><h3 id="8-2-2-消费时消息重复"><a href="#8-2-2-消费时消息重复" class="headerlink" title="8.2.2. 消费时消息重复"></a>8.2.2. 消费时消息重复</h3><p>消息已投递到 Consumer 并完成业务处理，当 Consumer 给 Broker 反馈应答时网络闪断，Broker 没有接收到消费成功响应。为了保证消息至少被消费一次的原则，Broker 将在网络恢复后再次尝试投递之前已被处理过的消息。此时消费者就会收到与之前处理过的内容相同、Message ID 也相同的消息。</p><h3 id="8-2-3-Rebalance-时消息重复"><a href="#8-2-3-Rebalance-时消息重复" class="headerlink" title="8.2.3. Rebalance 时消息重复"></a>8.2.3. Rebalance 时消息重复</h3><p>当 Consumer Group 中的 Consumer 数量发生变化时，或其订阅的 Topic 的 Queue 数量发生变化时，会触发 Rebalance，此时 Consumer 可能会收到曾经被消费过的消息。</p><h2 id="8-3-通用解决方案"><a href="#8-3-通用解决方案" class="headerlink" title="8.3. 通用解决方案"></a>8.3. 通用解决方案</h2><h3 id="8-3-1-两要素"><a href="#8-3-1-两要素" class="headerlink" title="8.3.1. 两要素"></a>8.3.1. 两要素</h3><p>幂等解决方案的设计中涉及到两项要素：<span style="background-color:#00ff00">幂等令牌</span>、<span style="background-color:#00ff00">唯一性处理</span>。只要充分利用好这两要素，就可以设计出好的幂等解决方案。<br><strong>幂等令牌</strong>：是生产者和消费者两者中的既定协议，通常指具备唯⼀业务标识的字符串。例如，<span style="background-color:#ff00ff">订单号、流水号。一般由 Producer 随着消息一同发送来的。</span><br><strong>唯一性处理</strong>：服务端通过采用⼀定的算法策略，保证同⼀个业务逻辑不会被重复执行成功多次。<br>例如，对同一笔订单的多次支付操作，只会成功一次。</p><h3 id="8-3-2-解决方案"><a href="#8-3-2-解决方案" class="headerlink" title="8.3.2. 解决方案"></a>8.3.2. 解决方案</h3><p>对于常见的系统，幂等性操作的通用性解决方案是：</p><ol><li>首先通过缓存去重。在缓存中如果已经存在了某幂等令牌，则说明本次操作是重复性操作；若缓存没有命中，则进入下一步。</li><li>在唯一性处理之前，先在数据库中查询幂等令牌作为索引的数据是否存在。若存在，则说明本次操作为重复性操作；若不存在，则进入下一步。</li><li>在同一事务中完成三项操作：唯一性处理后，将幂等令牌写入到缓存，并将幂等令牌作为唯一索引的数据写入到 DB 中。</li></ol><p>第 1 步已经判断过是否是重复性操作了，为什么第 2 步还要再次判断？能够进入第 2 步，说明已经不是重复操作了，第 2 次判断是否重复？ 当然不重复。一般缓存中的数据是具有有效期的。缓存中数据的有效期一旦过期，就是发生缓存穿透，使请求直接就到达了 DBMS。</p><h3 id="8-3-3-解决方案举例"><a href="#8-3-3-解决方案举例" class="headerlink" title="8.3.3. 解决方案举例"></a>8.3.3. 解决方案举例</h3><p>以支付场景为例：</p><ol><li>当支付请求到达后，首先在 Redis 缓存中却获取<span style="background-color:#00ff00">key 为支付流水号的缓存</span>value。若 value 不空，则说明本次支付是重复操作，业务系统直接返回调用侧重复支付标识；若 value 为空，则进入下一步操作</li><li>到 DBMS 中根据支付流水号查询是否存在相应实例。若存在，则说明本次支付是重复操作，业务系统直接返回调用侧重复支付标识；若不存在，则说明本次操作是首次操作，进入下一步完成唯一性处理</li><li>在分布式事务中完成三项操作：</li></ol><p>完成支付任务；<br>将当前支付流水号作为 key，任意字符串作为 value，通过 set(key, value, expireTime) 将数<br>据写入到 Redis 缓存；<br>将当前支付流水号作为主键，与其它相关数据共同写入到 DBMS。</p><h2 id="8-4-消费幂等的实现"><a href="#8-4-消费幂等的实现" class="headerlink" title="8.4. 消费幂等的实现"></a>8.4. 消费幂等的实现</h2><p>消费幂等的解决方案很简单：为消息指定不会重复的唯一标识。因为 Message ID 有可能出现重复的情况，所以真正安全的幂等处理，<span style="background-color:#00ff00">不建议以 Message ID 作为处理依据。最好的方式是以业务唯一标识作为幂等处理的关键依据</span>，而业务的唯一标识可以通过消息 Key 设置。<br>以支付场景为例，可以将消息的 Key 设置为订单号，作为幂等处理的依据。具体代码示例如下：</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-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Message</span>(); <br>message.setKey(<span class="hljs-string">&quot;ORDERID_100&quot;</span>); <br><span class="hljs-type">SendResult</span> <span class="hljs-variable">sendResult</span> <span class="hljs-operator">=</span> producer.send(messag<br></code></pre></td></tr></table></figure><p>消费者收到消息时可以根据消息的 Key 即订单号来实现消费幂等：</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">consumer.registerMessageListener(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageListenerConcurrently</span>() &#123;  <br>   <span class="hljs-meta">@Override</span> <span class="hljs-keyword">public</span> ConsumeConcurrentlyStatus <span class="hljs-title function_">consumeMessage</span> <span class="hljs-params">(List &lt; MessageExt &gt; msgs, ConsumeConcurrentlyContext  </span><br><span class="hljs-params">   context)</span>&#123;  <br>      <span class="hljs-keyword">for</span> (MessageExt msg : msgs) &#123;  <br>         <span class="hljs-type">String</span> <span class="hljs-variable">key</span> <span class="hljs-operator">=</span> msg.getKeys(); <span class="hljs-comment">// 根据业务唯一标识Key做幂等处理 // ……  </span><br>         &#125;  <br>      <span class="hljs-keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS; &#125;   <br>&#125;);<br></code></pre></td></tr></table></figure><h1 id="9-消息堆积与消费延迟"><a href="#9-消息堆积与消费延迟" class="headerlink" title="9. 消息堆积与消费延迟"></a>9. 消息堆积与消费延迟</h1><h2 id="9-1-概念"><a href="#9-1-概念" class="headerlink" title="9.1. 概念"></a>9.1. 概念</h2><p>消息处理流程中，如果 Consumer 的消费速度跟不上 Producer 的发送速度，MQ 中未处理的消息会越来越多（进的多出的少），这部分消息就被称为堆积消息。消息出现堆积进而会造成消息的消费延迟。<br>以下场景需要重点关注消息堆积和消费延迟问题：</p><ul><li>业务系统上下游能力不匹配造成的持续堆积，且无法自行恢复。</li><li>业务系统对消息的消费实时性要求较高，即使是短暂的堆积造成的消费延迟也无法接受。</li></ul><h2 id="9-2-产生原因分析"><a href="#9-2-产生原因分析" class="headerlink" title="9.2. 产生原因分析"></a>9.2. 产生原因分析</h2><h3 id="9-2-1-消息拉取"><a href="#9-2-1-消息拉取" class="headerlink" title="9.2.1. 消息拉取"></a>9.2.1. 消息拉取</h3><p>Consumer 通过长轮询 Pull 模式批量拉取的方式从服务端获取消息，将拉取到的消息缓存到本地缓冲队列中。对于拉取式消费，在内网环境下会有很高的吞吐量，所以这一阶段一般不会成为消息堆积的瓶颈。<br>一个单线程单分区的低规格主机 (Consumer，4C8G)，其可达到几万的 TPS。如果是多个分区多个线程，则可以轻松达到几十万的 TPS。</p><h3 id="9-2-2-消息消费"><a href="#9-2-2-消息消费" class="headerlink" title="9.2.2. 消息消费"></a>9.2.2. 消息消费</h3><p>Consumer 将本地缓存的消息提交到消费线程中，使用业务消费逻辑对消息进行处理，处理完毕后获取到一个结果。这是真正的消息消费过程。此时 Consumer 的消费能力就完全依赖于消息的消费耗时和消费并发度了。如果由于业务处理逻辑复杂等原因，导致处理单条消息的耗时较长，则整体的消息吞吐量肯定不会高，此时就会导致 Consumer 本地缓冲队列达到上限，停止从服务端拉取消息。</p><h3 id="9-2-3-结论"><a href="#9-2-3-结论" class="headerlink" title="9.2.3. 结论"></a>9.2.3. 结论</h3><p>消息堆积的主要瓶颈在于客户端的消费能力，而消费能力由消费耗时和消费并发度决定。注意，消费耗时的优先级要高于消费并发度。即在保证了消费耗时的合理性前提下，再考虑消费并发度问题。</p><h2 id="9-3-消费耗时"><a href="#9-3-消费耗时" class="headerlink" title="9.3. 消费耗时"></a>9.3. 消费耗时</h2><p>影响消息处理时长的主要因素是代码逻辑。而代码逻辑中可能会影响处理时长代码主要有两种类型：<br>CPU 内部计算型代码和外部 I&#x2F;O 操作型代码。<br>通常情况下代码中如果没有复杂的递归和循环的话，内部计算耗时相对外部 I&#x2F;O 操作来说几乎可以忽略。所以<span style="background-color:#ff0000">外部 IO 型代码</span>是影响消息处理时长的主要症结所在。</p><blockquote><p>外部 IO 操作型代码举例：</p><ul><li>读写外部数据库，例如对远程 MySQL 的访问</li><li>读写外部缓存系统，例如对远程 Redis 的访问</li><li>下游系统调用，例如 Dubbo 的 RPC 远程调用，Spring Cloud 的对下游系统的 Http 接口调用</li></ul></blockquote><blockquote><p>关于下游系统调用逻辑需要进行提前梳理，掌握每个调用操作预期的耗时，这样做是为了能够判断消费逻辑中 IO 操作的耗时是否合理。通常消息堆积是由于下游系统出现了服务异常或达到了 DBMS 容量限制，导致消费耗时增加。<br>服务异常，并不仅仅是系统中出现的类似 500 这样的代码错误，而可能是更加隐蔽的问题。例如，网络带宽问题。<br>达到了 DBMS 容量限制，其也会引发消息的消费耗时增加。</p></blockquote><h2 id="9-4-消费并发度"><a href="#9-4-消费并发度" class="headerlink" title="9.4. 消费并发度"></a>9.4. 消费并发度</h2><h2 id="9-5-如何避免"><a href="#9-5-如何避免" class="headerlink" title="9.5. 如何避免"></a>9.5. 如何避免</h2><p>为了避免在业务使用时出现非预期的消息堆积和消费延迟问题，需要在前期设计阶段对整个业务逻辑进行完善的排查和梳理。其中最重要的就是梳理消息的消费耗时和设置消息消费的并发度。</p><h3 id="9-5-1-梳理消息的消费耗时"><a href="#9-5-1-梳理消息的消费耗时" class="headerlink" title="9.5.1. 梳理消息的消费耗时"></a>9.5.1. 梳理消息的消费耗时</h3><p>通过压测获取消息的消费耗时，并对耗时较高的操作的代码逻辑进行分析。梳理消息的消费耗时需要关注以下信息：<br><span style="background-color:#ffff00">消息消费逻辑的计算复杂度是否过高，代码是否存在无限循环和递归等缺陷。</span><br><span style="background-color:#ffff00">消息消费逻辑中的 I&#x2F;O 操作是否是必须的，能否用本地缓存等方案规避。</span><br><span style="background-color:#ffff00">消费逻辑中的复杂耗时的操作是否可以做异步化处理。如果可以，是否会造成逻辑错乱。</span></p><h3 id="9-5-2-设置消费并发度"><a href="#9-5-2-设置消费并发度" class="headerlink" title="9.5.2. 设置消费并发度"></a>9.5.2. 设置消费并发度</h3><p>对于消息消费并发度的计算，可以通过以下两步实施：<br>逐步调大单个 Consumer 节点的线程数，并观测节点的系统指标，得到单个节点最优的消费线程数和消息吞吐量。<br>根据上下游链路的流量峰值计算出需要设置的节点数<br>节点数 &#x3D; 流量峰值 &#x2F; 单个节点消息吞吐量</p><h1 id="10-消息刷盘"><a href="#10-消息刷盘" class="headerlink" title="10. 消息刷盘"></a>10. 消息刷盘</h1><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/rocketmq_design_2.png" alt="img"></p><h1 id="11-保证消费成功"><a href="#11-保证消费成功" class="headerlink" title="11. 保证消费成功"></a>11. 保证消费成功</h1><p>PushConsumer 为了保证消息肯定消费成功，只有使用方明确表示消费成功，RocketMQ 才会认为消息消费成功。中途断电，抛出异常等都不会认为成功——即都会重新投递。<br>消费的时候，我们需要注入一个消费回调，具体 sample 代码如下：</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">consumer.registerMessageListener(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageListenerConcurrently</span>() &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> ConsumeConcurrentlyStatus <span class="hljs-title function_">consumeMessage</span><span class="hljs-params">(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context)</span> &#123;<br>        System.out.println(Thread.currentThread().getName() + <span class="hljs-string">&quot; Receive New Messages: &quot;</span> + msgs);<br>        doMyJob();<span class="hljs-comment">//执行真正消费</span><br>        <span class="hljs-keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;<br>    &#125;<br>&#125;);<br></code></pre></td></tr></table></figure><p>业务实现消费回调的时候，当且仅当此回调函数返回 <code>ConsumeConcurrentlyStatus.CONSUME_SUCCESS</code>，RocketMQ 才会认为这批消息（默认是 1 条）是消费完成的。（具体如何 ACK 见后面章节）<br>如果这时候消息消费失败，例如数据库异常，余额不足扣款失败等一切业务认为消息需要重试的场景，只要返回 <code>ConsumeConcurrentlyStatus.RECONSUME_LATER</code>，RocketMQ 就会认为这批消息消费失败了。<br>为了保证消息是肯定被至少消费成功一次，RocketMQ 会把这批消息重发回 Broker（topic 不是原 topic 而是这个消费租的 RETRY topic），在延迟的某个时间点（默认是 10 秒，业务可设置）后，再次投递到这个 ConsumerGroup。而如果一直这样重复消费都持续失败到一定次数（<span style="background-color:#00ff00">默认 16 次</span>），就会投递到 DLQ 死信队列。应用可以监控死信队列来做人工干预。</p><h1 id="12-启动的时候从哪里消费"><a href="#12-启动的时候从哪里消费" class="headerlink" title="12. 启动的时候从哪里消费"></a>12. 启动的时候从哪里消费</h1><p>当新实例启动的时候，PushConsumer 会拿到本消费组 broker 已经记录好的消费进度（consumer offset），按照这个进度发起自己的第一次 Pull 请求。<br>如果这个消费进度在 Broker 并没有存储起来，证明这个是一个全新的消费组，这时候客户端有几个策略可以选择：</p><figure class="highlight plaintext"><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 plaintext">CONSUME_FROM_LAST_OFFSET //默认策略，从该队列最尾开始消费，即跳过历史消息<br>CONSUME_FROM_FIRST_OFFSET //从队列最开始开始消费，即历史消息（还储存在broker的）全部消费一遍<br>CONSUME_FROM_TIMESTAMP//从某个时间点开始消费，和setConsumeTimestamp()配合使用，默认是半个小时以前<br></code></pre></td></tr></table></figure><p>所以，社区中经常有人问：“为什么我设了 <code>CONSUME_FROM_LAST_OFFSET</code>，历史的消息还是被消费了”？ 原因就在于只有全新的消费组才会使用到这些策略，老的消费组都是按已经存储过的消费进度继续消费。<br>对于老消费组想跳过历史消息需要自身做过滤，或者使用先修改消费进度。</p><h1 id="13-无法避免重复消费"><a href="#13-无法避免重复消费" class="headerlink" title="13. 无法避免重复消费"></a>13. 无法避免重复消费</h1><p>RocketMQ 是以 consumer group+queue 为单位是管理消费进度的，以一个 consumer offset 标记<span style="background-color:#00ff00">这个消费组在这条 queue 上的消费进度</span>。<br>如果某已存在的消费组出现了新消费实例的时候，依靠这个组的消费进度，就可以判断第一次是从哪里开始拉取的。<br>每次消息成功后，本地的消费进度会被更新，然后由定时器定时同步到 broker，以此持久化消费进度。<br>但是每次记录消费进度的时候，只会把一批消息中最小的 offset 值为消费进度值，如下图：<br><img src="https://jaskey.github.io/images/rocketmq/rocketmq-ack.png" alt="message ack" title="message ack"></p><p>这种方式和传统的一条 message 单独 ack 的方式有本质的区别。性能上提升的同时，会带来一个潜在的重复问题——由于消费进度只是记录了一个下标，就可能出现拉取了 100 条消息如 2101-2200 的消息，后面 99 条都消费结束了，只有 2101 消费一直没有结束的情况。<br>在这种情况下，RocketMQ 为了保证消息肯定被消费成功，消费进度职能维持在 2101，直到 2101 也消费结束了，本地的消费进度才能标记 2200 消费结束了（注：consumerOffset&#x3D;2201）。<br>在这种设计下，就有消费大量重复的风险。如 2101 在还没有消费完成的时候消费实例突然退出（机器断电，或者被 kill）。这条 queue 的消费进度还是维持在 2101，当 queue 重新分配给新的实例的时候，新的实例从 broker 上拿到的消费进度还是维持在 2101，这时候就会又从 2101 开始消费，2102-2200 这批消息实际上已经被消费过还是会投递一次。<br><span style="background-color:#ff00ff">对于这个场景，RocketMQ 暂时无能为力，所以业务必须要保证消息消费的幂等性，这也是 RocketMQ 官方多次强调的态度。</span><br>实际上，从源码的角度上看，RocketMQ 可能是考虑过这个问题的，截止到 3.2.6 的版本的源码中，可以看到为了缓解这个问题的影响面，<code>DefaultMQPushConsumer</code> 中有个配置 <code>consumeConcurrentlyMaxSpan</code></p><figure class="highlight plaintext"><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 plaintext">/**<br> * Concurrently max span offset.it has no effect on sequential consumption<br> */<br>private int consumeConcurrentlyMaxSpan = 2000;<br></code></pre></td></tr></table></figure><p>这个值默认是 2000，当 RocketMQ 发现本地缓存的消息的最大值 - 最小值差距大于这个值（2000）的时候，会触发流控——也就是说如果头尾都卡住了部分消息，达到了这个阈值就不再拉取消息。<br>但作用实际很有限，像刚刚这个例子，2101 的消费是死循环，其他消费非常正常的话，是无能为力的。一旦退出，在不人工干预的情况下，2101 后所有消息全部重复!</p><h1 id="14-实战经验"><a href="#14-实战经验" class="headerlink" title="14. 实战经验"></a>14. 实战经验</h1><h1 id="15-参考与感谢"><a href="#15-参考与感谢" class="headerlink" title="15. 参考与感谢"></a>15. 参考与感谢</h1><p><a href="https://jaskey.github.io/blog/2017/01/25/rocketmq-consume-offset-management/">https://jaskey.github.io/blog/2017/01/25/rocketmq-consume-offset-management/</a></p><a href="/2022/12/31/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="分布式专题-MQ-RocketMQ-1、基本原理">分布式专题-MQ-RocketMQ-1、基本原理</a>]]></content>
      
      
      <categories>
          
          <category> MQ </category>
          
      </categories>
      
      
        <tags>
            
            <tag> RocketMQ </tag>
            
            <tag> MQ </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-MySQL-1、基本原理</title>
      <link href="/2022/12/31/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2022/12/31/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-SQL-分类"><a href="#1-SQL-分类" class="headerlink" title="1. SQL 分类"></a>1. SQL 分类</h1><p><a href="https://www.jianshu.com/p/671a2d54dca0">https://www.jianshu.com/p/671a2d54dca0</a></p><p>DQL：查询<br>DML：增删改<br>DDL： (Data Definition Language 数据定义语言）用于操作对象及对象本身，这种对象包括数据库,表对象，及视图对象<br>DCL：（Data Control Language 数据控制语句） 用于操作数据库对象的权限，比如：<br>greate: 分配权限给用户<br>revoke: 废除数据库中某用户的权限</p><h1 id="2-MySQL-结构"><a href="#2-MySQL-结构" class="headerlink" title="2. MySQL 结构"></a>2. MySQL 结构</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406131437.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230414163354.png" alt="image.png"></p><h1 id="3-内部原理"><a href="#3-内部原理" class="headerlink" title="3. 内部原理"></a>3. 内部原理</h1><p>[[Mysql面试题#^jerwaj]]</p><p><a href="https://learnku.com/docs/daily-qa/2021-07-16-why-do-redo-logs-need-to-be-submitted-in-two-phases/11200">https://learnku.com/docs/daily-qa/2021-07-16-why-do-redo-logs-need-to-be-submitted-in-two-phases/11200</a></p><p><a href="https://blog.csdn.net/weixin_40471676/article/details/119732738">https://blog.csdn.net/weixin_40471676&#x2F;article&#x2F;details&#x2F;119732738</a></p><h2 id="3-1-查询过程"><a href="#3-1-查询过程" class="headerlink" title="3.1. 查询过程"></a>3.1. 查询过程</h2><p><a href="https://xiaolincoding.com/mysql/base/how_select.html#mysql-%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84">https://xiaolincoding.com/mysql/base/how_select.html#mysql-%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84</a></p><p>[[执行一条 select 语句，期间发生了什么？  小林coding]]</p><ol><li>连接器：建立连接，管理连接、校验用户身份；</li><li>查询缓存：查询语句如果命中查询缓存则直接返回，否则继续往下执行。MySQL 8.0 已删除该模块；</li><li>解析 SQL，通过解析器对 SQL 查询语句进行词法分析、语法分析，然后构建语法树，方便后续模块读取表名、字段、语句类型；</li><li>执行 SQL：执行 SQL 共有三个阶段：<ol><li>预处理阶段：检查表或字段是否存在；将 <code>select *</code> 中的 <code>*</code> 符号扩展为表上的所有列。</li><li>优化阶段：基于查询成本的考虑， 选择查询成本最小的执行计划；</li><li>执行阶段：根据执行计划执行 SQL 查询语句，从存储引擎读取记录，返回给客户端；</li></ol></li></ol><h2 id="3-2-更新过程"><a href="#3-2-更新过程" class="headerlink" title="3.2. 更新过程"></a>3.2. 更新过程</h2><p><a href="https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-undo-log">https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-undo-log</a><br>查询语句的那一套流程，更新语句也是同样会走一遍：</p><ol><li>客户端先通过连接器建立连接，连接器自会判断用户身份；</li><li>因为这是一条 update 语句，所以不需要经过查询缓存，但是表上有更新语句，是会把整个表的查询缓存清空的，所以说查询缓存很鸡肋，在 MySQL 8.0 就被移除这个功能了；</li><li>解析器会通过词法分析识别出关键字 update，表名等等，构建出语法树，接着还会做语法分析，判断输入的语句是否符合 MySQL 语法；</li><li>预处理器会判断表和字段是否存在；</li><li>优化器确定执行计划，因为 where 条件中的 id 是主键索引，所以决定要使用 id 这个索引；</li><li>执行器负责具体执行，找到这一行，然后更新。</li></ol><p>不过，更新语句的流程会涉及到 undo log（回滚日志）、redo log（重做日志） 、binlog （归档日志）这三种日志：</p><blockquote><ul><li><strong>undo log（回滚日志）</strong>：是 Innodb 存储引擎层生成的日志，实现了事务中的 <strong>原子性</strong>，主要 <strong>用于事务回滚和 MVCC</strong>。</li><li><strong>redo log（重做日志）</strong>：是 Innodb 存储引擎层生成的日志，实现了事务中的 <strong>持久性</strong>，主要 <strong>用于掉电等故障恢复</strong>；</li><li><strong>binlog （归档日志）</strong>：是 Server 层生成的日志，主要 <strong>用于数据备份和主从复制</strong></li></ul></blockquote><a href="/2023/02/14/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-3%E3%80%81redolog-undolog-binlog/" title="MySQL-3、redolog-undolog-binlog">MySQL-3、redolog-undolog-binlog</a><p>具体更新一条记录 <code>UPDATE t_user SET name = &#39;xiaolin&#39; WHERE id = 1;</code> 的流程如下:</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301114657.png" alt="image.png"></p><ol><li>执行器负责具体执行，会调用存储引擎的接口，通过主键索引树搜索获取 id &#x3D; 1 这一行记录：<ul><li>如果 id&#x3D;1 这一行所在的数据页本来就在 buffer pool 中，就直接返回给执行器更新；</li><li>如果记录不在 buffer pool，将数据页从磁盘读入到 buffer pool，返回记录给执行器。</li></ul></li><li>执行器得到聚簇索引记录后，会看一下更新前的记录和更新后的记录是否一样：<ul><li>如果一样的话就不进行后续更新流程；</li><li>如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层，让 InnoDB 真正的执行更新记录的操作；</li></ul></li><li><span style="background-color:#ff00ff">开启事务， InnoDB 层更新记录前</span>，首先要记录相应的 undo log，因为这是更新操作，需要把被更新的列的旧值记下来，也就是要生成一条 undo log，undo log 会写入 Buffer Pool 中的 Undo 页面，不过在内存修改该 Undo 页面后，需要记录对应的 redo log。</li><li>InnoDB 层开始更新记录，会<span style="background-color:#ff00ff">先更新内存（同时标记为脏页）</span>，然后将记录写到 redo log buffer(prepare 状态) 里面，这个时候更新就算完成了。为了减少磁盘 I&#x2F;O，不会立即将脏页写入磁盘，后续由后台线程选择一个合适的时机将脏页写入到磁盘。<span style="background-color:#ff00ff">这就是 <strong>WAL 技术</strong>，MySQL 的写操作并不是立刻写到磁盘上，而是先写 redo 日志，然后在合适的时间再将修改的行数据写到磁盘上。</span></li><li>至此，一条记录更新完了。</li><li>在一条更新语句执行完成后，然后开始记录该语句对应的 binlog，此时记录的 binlog 会被保存到 binlog cache，并没有刷新到硬盘上的 binlog 文件，在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。</li><li>事务提交（为了方便说明，这里不说组提交的过程，只说两阶段提交）：<ul><li><strong>prepare 阶段</strong>：将 redo log 对应的事务状态设置为 prepare，然后将 redo log 刷新到硬盘；</li><li><strong>commit 阶段</strong>：将 binlog 刷新到磁盘，接着调用引擎的提交事务接口，将 redo log 状态设置为 commit（将事务设置为 commit 状态后，刷入到磁盘 redo log 文件）；</li></ul></li><li>至此，一条更新语句执行完成</li></ol><h2 id="3-3-查询缓存"><a href="#3-3-查询缓存" class="headerlink" title="3.3. 查询缓存"></a>3.3. 查询缓存</h2><p><a href="https://www.modb.pro/db/325897">https://www.modb.pro/db/325897</a><br><a href="https://www.zhihu.com/question/565486816/answer/2822879610">https://www.zhihu.com/question/565486816/answer/2822879610</a><br>如果查询缓存中存在这条 SQL 的结果集缓存，直接取出返回客户端，前面说过，表上有更新的时候，这个表相关的查询缓存都会失效，所以查询缓存不建议使用，在 MySQL 8.0 版本把查询缓存删除了</p><p>对于更新比较频繁的表，查询缓存的命中率很低的，因为只要一个表有更新操作，那么这个表的查询缓存就会被清空。如果刚缓存了一个查询结果很大的数据，还没被使用的时候，刚好这个表有更新操作，查询缓冲就被清空了，相当于缓存了个寂寞。</p><p>所以，MySQL 8.0 版本直接将查询缓存删掉了，也就是说 MySQL 8.0 开始，执行一条 SQL 查询语句，不会再走到查询缓存这个阶段了。</p><p><a href="https://www.bilibili.com/video/BV1zJ411M7TB?p=74&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zJ411M7TB?p=74&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="3-4-Buffer-Pool"><a href="#3-4-Buffer-Pool" class="headerlink" title="3.4. Buffer Pool"></a>3.4. Buffer Pool</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230414-1637%%</span>❕ ^w5k1hd</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228104916.png" alt="image.png"></p><h3 id="3-4-1-Buffer-Pool-有多大"><a href="#3-4-1-Buffer-Pool-有多大" class="headerlink" title="3.4.1. Buffer Pool 有多大"></a>3.4.1. Buffer Pool 有多大</h3><p>Buffer Pool 是在 MySQL 启动的时候，向操作系统申请的一片连续的内存空间，默认配置下 Buffer Pool 只有 <code>128MB</code> 。</p><p>可以通过调整 <code>innodb_buffer_pool_size</code> 参数来设置 Buffer Pool 的大小，一般建议设置成可用物理内存的 60%~80%。</p><h3 id="3-4-2-缓存内容"><a href="#3-4-2-缓存内容" class="headerlink" title="3.4.2. 缓存内容"></a>3.4.2. 缓存内容</h3><p>InnoDB 会把存储的数据划分为若干个「页」，以页作为磁盘和内存交互的基本单位，一个页的默认大小为 16KB。因此，Buffer Pool 同样需要按「页」来划分。</p><p>在 MySQL 启动的时候，<strong>InnoDB 会为 Buffer Pool 申请一片连续的内存空间，然后按照默认的 <code>16KB</code> 的大小划分出一个个的页， Buffer Pool 中的页就叫做缓存页</strong>。此时这些缓存页都是空闲的，之后随着程序的运行，才会有磁盘上的页被缓存到 Buffer Pool 中。</p><p>所以，MySQL 刚启动的时候，你会观察到使用的虚拟内存空间很大，而使用到的物理内存空间却很小，这是因为只有这些虚拟内存被访问后，操作系统才会触发缺页中断，申请物理内存，接着将虚拟地址和物理地址建立映射关系。</p><p>Buffer Pool 除了缓存「索引页」和「数据页」，还包括了 Undo 页，插入缓存、自适应哈希索引、锁信息等等。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230406181315.png" alt="image.png"></p><blockquote><p>Undo 页是记录什么？</p></blockquote><p>开启事务后，InnoDB 层更新记录前，首先要记录相应的 undo log，如果是更新操作，需要把被更新的列的旧值记下来，也就是要生成一条 undo log，undo log 会写入 Buffer Pool 中的 Undo 页面。</p><blockquote><p>查询一条记录，就只需要缓冲一条记录吗？当然不是！</p></blockquote><p>当我们查询一条记录时，InnoDB 是会把整个页的数据加载到 Buffer Pool 中，将页加载到 Buffer Pool 后，再通过页里的「页目录」去定位到某条具体的记录。</p><h3 id="3-4-3-与-redo-log-关系"><a href="#3-4-3-与-redo-log-关系" class="headerlink" title="3.4.3. 与 redo log 关系"></a>3.4.3. 与 redo log 关系</h3><a href="/2023/02/14/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-3%E3%80%81redolog-undolog-binlog/" title="MySQL-3、redolog-undolog-binlog">MySQL-3、redolog-undolog-binlog</a><h1 id="4-事务原理"><a href="#4-事务原理" class="headerlink" title="4. 事务原理"></a>4. 事务原理</h1><a href="/2023/02/13/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/MySQL-2%E3%80%81%E4%BA%8B%E5%8A%A1-MVCC-LBCC/" title="MySQL-2、事务-MVCC-LBCC">MySQL-2、事务-MVCC-LBCC</a><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230514195437.png" alt="image.png"></p><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p><a href="https://blog.csdn.net/J080624/article/details/128112914">https://blog.csdn.net/J080624/article/details/128112914</a></p><h2 id="7-1-马士兵"><a href="#7-1-马士兵" class="headerlink" title="7.1. 马士兵"></a>7.1. 马士兵</h2><p><a href="https://www.bilibili.com/video/BV1X3411875S/?spm_id_from=333.788.b_7265636f5f6c697374.5&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1X3411875S/?spm_id_from=333.788.b_7265636f5f6c697374.5&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="8-Old"><a href="#8-Old" class="headerlink" title="8. Old"></a>8. Old</h1><h2 id="8-1-page"><a href="#8-1-page" class="headerlink" title="8.1. page"></a>8.1. page</h2><h2 id="8-2-一行数据超过-16k"><a href="#8-2-一行数据超过-16k" class="headerlink" title="8.2. 一行数据超过 16k"></a>8.2. 一行数据超过 16k</h2><p><a href="https://blog.csdn.net/star_xing123/article/details/107380438">https://blog.csdn.net/star_xing123&#x2F;article&#x2F;details&#x2F;107380438</a></p><h2 id="8-3-双写落盘"><a href="#8-3-双写落盘" class="headerlink" title="8.3. 双写落盘"></a>8.3. 双写落盘</h2><h3 id="8-3-1-CheckPoint"><a href="#8-3-1-CheckPoint" class="headerlink" title="8.3.1. CheckPoint"></a>8.3.1. CheckPoint</h3><p><a href="https://www.cnblogs.com/chenpingzhao/p/5107480.html">https://www.cnblogs.com/chenpingzhao/p/5107480.html</a></p><p><a href="https://www.cnblogs.com/wy123/p/8353245.html">https://www.cnblogs.com/wy123/p/8353245.html</a></p><p>思考一下这个场景：如果重做日志可以无限地增大，同时缓冲池也足够大，那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时，完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。</p><p>因此 Checkpoint（检查点）技术就诞生了，目的是解决以下几个问题：1、缩短数据库的恢复时间；2、缓冲池不够用时，将脏页刷新到磁盘；3、重做日志不可用时，刷新脏页。</p><p>脏页落盘的时机采用 CheckPoint 检查点机制以下机制都可通过参数控制</p><p>sharp checkpoint：强制落盘。把内存中所有的脏页都执行落盘操作。只有当关闭数据库之前才会执行。<br>​<br>fuzzy checkpoint：没落落盘。把一部分脏页执行落盘操作<br>  1.Master Thrad Checkpoint 主线程定时将脏页写入磁盘每秒或每 10s 执行一次脏页。<br>  2.FLUSH_LRU_LIST buffer pool 有脏页换出，执行落盘<br>  3.Async&#x2F;Sync Flush checkpoint 当 redo log 快写满的时候执行落盘<br>    a.当 redo log 超过 75% 小于 90% 会执行异步落盘<br>    b.当 redo log 超过 90%，会执行同步落盘操作。会阻塞写操作。<br>  4.Dirty Page too much checkpoint 如果 buffer pool 中脏页太多，脏页率超过 75% 执行落盘</p><ul><li>那么其实建立普通索引是不合适的，因为写的过程，虽然利用了 change buffer 暂时提高了写的性能，但是在读的时候还是需要磁盘 IO。可以考虑建立唯一索引，在索引写的时候，就提前读取数据到缓冲池中，提高读的性能。</li></ul><ol><li>适合建立二级非唯一性索引：利用普通索引的 change buffer 特性，当业务场景中的写远大于读时，常见场景为日志表，当某些列必须建立索引时，可以考虑建立普通索引，提高写入性能。</li><li>适合建立二级唯一性索引：如果业务场景的写之后立即伴随读，如果列的值是唯一的</li></ol><h3 id="8-3-2-业务实践"><a href="#8-3-2-业务实践" class="headerlink" title="8.3.2. 业务实践"></a>8.3.2. 业务实践</h3><ol><li>被动：在后续的真正需要读这个非唯一索引时，把索引的数据页从磁盘读取到内存中，再通过 change buffer 做一个 merge 操作，merge 操作以后，内存中的数据页就是最新的了。</li><li>主动：innoDB 引擎中有线程会主动的定期做 merge 操作</li></ol><p>那么什么时候会真正更新数据页呢？有两种情况会触发：</p><p>关于 change buffer 的一些思考 1. 既然是缓存索引更新操作，那也就是说，索引数据并不是最新的，那不是会造成数据不一致的情况？ 当需要用到的索引在 change buffer 里有尚未应用的更新动作，会马上进行更新。 2. 为什么 change buffer 只对二级非唯一索引起效？ 因为对于唯一索引，所有的索引维护更新，都需要到索引上去判断唯一性，而判断唯一性，就需要把索引块读到 buffer pool 里，这也就跳过了 change buffer 的使用前提：索引块不在 buffer pool 里。 3.change buffer 的低效或瓶颈场景或弊端？ 1)change buffer 会占用 buffer pool 的空间，也就是直接减少了 buffer pool 的可用空间； 2) 如果 buffer pool 命中率很高，也就是说数据块和索引块都能在 buffer pool 里找到，也就没有 change buffer 什么事了； 3) 如果经常操作的表只有少量的二级非唯一索引，也就是说索引更新所带来的压力并不明显，那么 change buffer 的作用也微乎其微； 4)change buffer 里的数据也是脏数据，同样受到 redo log 的保护，同时在推进 redo log check point 时，除了会先将 buffer pool 的数据刷盘，同时也会先将 change buffer 的数据合并和应用到索引上，所以如果 redo log 文件大小设置不合理，导致 redo 文件经常写满，频繁触发写盘，那么 change buffer 的数据也就相当于还没捂热就写出了，不仅没有起到很好的缓存作用，反而因为多了一道先缓存到 change buffer 的开销; 5) 如果一个二级索引做了 DML 以后，马上又对操作的数据进行读操作，那么就等于需要将数据读入 buffer pool，这会触发 change buffer 的 merge 并且应用在二级索引上，那这种场景下还不如不要 change buffer，直接将索引数据读入 buffer pool。</p><p>针对随机 IO 的情况，MySQL 利用 change buffer 来避免维护二级非唯一索引时的随机 IO 读。原理是这样的：当对一个拥有二级非唯一索引的列做 DML 时，如果该列值对应的索引块不在 buffer pool 里，就先把这个索引块维护的动作先存储在 change buffer 里，而不去采用随机 IO 读的方式去对应的索引块读到 buffer pool 里。而索引块维护操作，是由后台线程在一定时间间隔来循环地将 change buffer 里的索引维护动作合并，再进行批量的索引更新。合并可以将邻接的块一起操作，以及批量的更新，明显是比每次 DML 都更新索引要高效。 change buffer 属于 buffer pool，空间从 buffer pool 分配，分配百分比由参数 innodb_change_buffer_max_size 控制，默认是 25，即 25%，最大值可取 50；而为了防止 crash 而丢失 change buffer 的数据，在 system 表空间上也分配空间来存 change buffer 的数据，所以重启以后，可以将 system 表空间的 change buffer 相关数据读回 buffer 里。同时，change buffer 的数据也会写 redo，同样具有 crash safe 的特性。而 change buffer 在较低版本的 MySQL 里，雏形是 insert buffer，也就是只缓存 insert 相关的二级非唯一索引的维护动作；而 MySQL5.6 以后，change buffer 可以缓存所有 DML 的二级非唯一索引维护动作，可以通过设置 innodb_change_buffering 来选择缓存的操作，可取值 all,none,inserts,deletes,changes,purges, 各取值含义是： all: 缓存所有二级非唯一索引的维护动作，当然是相关索引块不在 buffer pool 的情况下。 none: 不使用 change buffer 的任何功能。 inserts,deletes,changes: 分别代表 insert,delete,update。 purges: 后台的物理删除操作所触发的二级非唯一索引的维护动作，也可以先缓存下来。</p><p>MySQL 的二级索引分为唯一和非唯一两种，而对一个拥有二级索引的列做 DML，同时需要维护更新这个列上的索引。维护更新索引的这个操作，也就是根据列上的值，按照索引排序规则，将该列对应的索引块进行相关维护。而在做维护的时候，自然是要把索引块从磁盘读到 buffer pool 里。那么最理想的情况就是需要维护的索引块已经在 buffer pool 里了，就不需要再去磁盘里找出相应的索引块，也就节省了一些随机 IO。</p><p><a href="https://blog.csdn.net/qq_36652619/article/details/89460786">https://blog.csdn.net/qq_36652619&#x2F;article&#x2F;details&#x2F;89460786</a></p><p><a href="https://blog.csdn.net/weixin_39004901/article/details/102456334?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~default-15.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~default-15.no_search_link">https://blog.csdn.net/weixin_39004901&#x2F;article&#x2F;details&#x2F;102456334?utm_medium&#x3D;distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-15.no_search_link&amp;depth_1-utm_source&#x3D;distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-15.no_search_link</a></p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> MySQL日志 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-RocketMQ-1、基本原理</title>
      <link href="/2022/12/31/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2022/12/31/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-历史"><a href="#1-历史" class="headerlink" title="1. 历史"></a>1. 历史</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404090718.png" alt="image.png"></p><p>Kafka：<span style="background-color:#ff00ff">一个 topic 中每个 Queue 都是一个单独的文件</span>，所有消息都存在其中<br>RocketMQ：所有 topic 所有 Queue 的消息都放在一种文件中，即 CommitLog，默认大小为 1G。但又为每个 topic 创建一个目录，然后为每个 Queue 创建一个文件，即 ConsumeQueue，用来存储索引信息。存储使用 CommitLog，查询使用 ConsumeQueue。实现了读写分离。</p><h1 id="2-基本概念"><a href="#2-基本概念" class="headerlink" title="2. 基本概念"></a>2. 基本概念</h1><h2 id="2-1-消息（Message）"><a href="#2-1-消息（Message）" class="headerlink" title="2.1. 消息（Message）"></a>2.1. 消息（Message）</h2><p>消息是指，消息系统所传输信息的物理载体，生产和消费数据的最小单位，每条消息必须属于一个主题。</p><h2 id="2-2-主题（Topic）-消费者-消费者组"><a href="#2-2-主题（Topic）-消费者-消费者组" class="headerlink" title="2.2. 主题（Topic）- 消费者 - 消费者组"></a>2.2. 主题（Topic）- 消费者 - 消费者组</h2><p>Topic 表示一类消息的集合，每个主题包含若干条消息，每条消息只能属于一个主题，是 RocketMQ 进行消息订阅的基本单位。 topic:message 1:n           <span style="background-color:#00ff00">message : topic 1:1</span><br>一个生产者可以同时发送多种 Topic 的消息；而<span style="background-color:#ff00ff">一个消费者</span>只对某种特定的 Topic 感兴趣，即<span style="background-color:#ff00ff">只可以订阅和消费一种 Topic 的消息</span>。 producer:topic 1:n        <span style="background-color:#00ff00">consumer:topic 1:1</span></p><h2 id="2-3-队列（Queue）-同消费组只能-1-个消费者"><a href="#2-3-队列（Queue）-同消费组只能-1-个消费者" class="headerlink" title="2.3. 队列（Queue）- 同消费组只能 1 个消费者"></a>2.3. 队列（Queue）- 同消费组只能 1 个消费者</h2><p>存储消息的物理实体。一个 Topic 中可以包含多个 Queue，每个 Queue 中存放的就是该 Topic 的消息。一个 Topic 的 Queue 也被称为一个 Topic 中消息的分区（Partition）。<br><span style="background-color:#ffff00">一个 Topic 的 Queue 中的消息只能被一个消费者组中的一个消费者消费</span>。<span style="background-color:#ff00ff">一个 Queue 中的消息不允许同一个消费者组中的多个消费者同时消费。</span></p><p>在学习参考其它相关资料时，还会看到一个概念：分片（Sharding）。分片不同于分区。在 RocketMQ 中，分片指的是存放相应 Topic 的 Broker。每个分片中会创建出相应数量的分区，即 Queue，每个 Queue 的大小都是相同的。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103213426.png"></p><h2 id="2-4-消息标识（MessageId-x2F-Key）"><a href="#2-4-消息标识（MessageId-x2F-Key）" class="headerlink" title="2.4. 消息标识（MessageId&#x2F;Key）"></a>2.4. 消息标识（MessageId&#x2F;Key）</h2><p>RocketMQ 中每个消息拥有唯一的 MessageId，且可以携带具有业务标识的 Key，以方便对消息的查询。<br><span style="background-color:#00ff00">不过需要注意的是，MessageId 有两个</span>：<span style="background-color:#ffff00">在生产者 send() 消息时会自动生成一个 MessageId</span>（msgId)，<span style="background-color:#ffff00">当消息到达 Broker 后，Broker 也会自动生成一个 MessageId(offsetMsgId)</span>。msgId、offsetMsgId 与 key 都称为消息标识。<br><strong>msgId</strong>：由 producer 端生成，其生成规则为：<br>&#x3D;&#x3D;producerIp + 进程 pid + MessageClientIDSetter 类的 ClassLoader 的 hashCode + 当前时间 + AutomicInteger 自增计数器&#x3D;&#x3D;<br><strong>offsetMsgId</strong>：由 broker 端生成，其生成规则为：&#x3D;&#x3D;brokerIp + 物理分区的 offset（Queue 中的偏移量）&#x3D;&#x3D;<br><strong>key</strong>：由用户指定的业务相关的唯一标识</p><h1 id="3-系统组成"><a href="#3-系统组成" class="headerlink" title="3. 系统组成"></a>3. 系统组成</h1><h2 id="3-1-Producer"><a href="#3-1-Producer" class="headerlink" title="3.1. Producer"></a>3.1. Producer</h2><p>消息生产者，负责生产消息。Producer 通过 MQ 的负载均衡模块选择相应的 Broker 集群队列进行消息投递，投递的过程支持快速失败并且低延迟。<br>例如，业务系统产生的日志写入到 MQ 的过程，就是消息生产的过程。再如，电商平台中用户提交的秒杀请求写入到 MQ 的过程，就是消息生产的过程</p><h2 id="3-2-Consumer"><a href="#3-2-Consumer" class="headerlink" title="3.2. Consumer"></a>3.2. Consumer</h2><p>RocketMQ 中的消息消费者都是以消费者组（Consumer Group）的形式出现的。消费者组是同一类消费者的集合，这类 Consumer 消费的是同一个 Topic 类型的消息。消费者组使得在消息消费方面，实现负载均衡（将一个 Topic 中的不同的 Queue 平均分配给同一个 Consumer Group 的不同的 Consumer，注意，并不是将消息负载均衡）和容错（一个 Consmer 挂了，该 Consumer Group 中的其它 Consumer 可以接着消费原 Consumer 消费的 Queue）的目标变得非常容易。<br><span style="background-color:#00ff00">消费者组中 Consumer 的数量应该小于等于订阅 Topic 的 Queue 数量。如果超出 Queue 数量，则多出的 Consumer 将不能消费消息。</span></p><p>不过，<span style="background-color:#ff00ff">一个 Topic 类型的消息可以被多个消费者组同时消费</span>。<br><strong>注意</strong><br><span style="background-color:#ffff00">1）消费者组只能消费一个 Topic 的消息，不能同时消费多个 Topic 消息 </span><br><span style="background-color:#ffff00">2）一个消费者组中的消费者必须订阅完全相同的 Topic</span></p><h2 id="3-3-Broker"><a href="#3-3-Broker" class="headerlink" title="3.3. Broker"></a>3.3. Broker</h2><h3 id="3-3-1-功能介绍"><a href="#3-3-1-功能介绍" class="headerlink" title="3.3.1. 功能介绍"></a>3.3.1. 功能介绍</h3><p>Broker 充当着消息中转角色，<span style="background-color:#00ff00">负责存储消息、转发消息</span>。Broker 在 RocketMQ 系统中负责接收并存储从生产者发送来的消息，同时为消费者的拉取请求作准备。Broker 同时也存储着消息相关的元数据，<span style="background-color:#00ff00">包括消费者组消费进度偏移 offset、主题、队列等</span>。</p><h3 id="3-3-2-模块构成"><a href="#3-3-2-模块构成" class="headerlink" title="3.3.2. 模块构成"></a>3.3.2. 模块构成</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104084614.png"><br><strong>Remoting Module</strong>：整个 Broker 的实体，负责处理来自 clients 端的请求。而这个 Broker 实体则由以下模块构成。<br><strong>Client Manager</strong>：客户端管理器。负责接收、解析客户端 (Producer&#x2F;Consumer) 请求，管理客户端。例如，维护 Consumer 的 Topic 订阅信息<br><strong>Store Service</strong>：存储服务。提供方便简单的 API 接口，处理消息存储到物理硬盘和消息查询功能。<br><strong>HA Service</strong>：高可用服务，提供 Master Broker 和 Slave Broker 之间的数据同步功能。<br><strong>Index Service</strong>：索引服务。根据特定的 Message key，对投递到 Broker 的消息进行索引服务，同时也提供根据 Message Key 对消息进行快速查询的功能。</p><h3 id="3-3-3-集群部署"><a href="#3-3-3-集群部署" class="headerlink" title="3.3.3. 集群部署"></a>3.3.3. 集群部署</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104084837.png"></p><p>为了增强 Broker 性能与吞吐量，Broker 一般都是以集群形式出现的。各集群节点中可能存放着<span style="background-color:#ff00ff">相同 Topic 的不同 Queue，即所有主 Broker 分摊所有 Topic 的所有 messageQueue</span>。<br>不过，这里有个问题，如果某 Broker 节点宕机，如何保证数据不丢失呢？其解决<br>方案是，<span style="background-color:#ff00ff">将每个 Broker 集群节点进行横向扩展，即将 Broker 节点再建为一个 HA 集群，解决单点问题。</span><br><span style="background-color:#00ff00">Broker 节点集群是一个主从集群，即集群中具有 Master 与 Slave 两种角色。Master 负责处理读写操作请求，Slave 负责对 Master 中的数据进行备份。当 Master 挂掉了，Slave 则会自动切换为 Master 去工作。所以这个 Broker 集群是<font color=#ff0000>主备集群</font>。</span>一个 Master 可以包含多个 Slave，但一个 Slave 只能隶属于一个 Master。 Master 与 Slave 的对应关系是通过<span style="background-color:#ff00ff">指定相同的 BrokerName、不同的 BrokerId 来确定的</span>。BrokerId 为 0 表 示 Master，非 0 表示 Slave。每个 Broker 与 NameServer 集群中的所有节点建立长连接，定时注册 Topic 信息到所有 NameServer。</p><h2 id="3-4-NameServer"><a href="#3-4-NameServer" class="headerlink" title="3.4. NameServer"></a>3.4. NameServer</h2><p>NameServer 是一个 <strong>Broker 与 Topic 路由的注册中心</strong>，支持 Broker 的动态注册与发现。<br>RocketMQ 的思想来自于 Kafka，而 Kafka 是依赖了 Zookeeper 的。所以，在 RocketMQ 的早期版本，即在 MetaQ v1.0 与 v2.0 版本中，也是依赖于 Zookeeper 的。从 MetaQ v3.0，即 RocketMQ 开始<span style="background-color:#ffff00">去掉了 Zookeeper 依赖</span>，<span style="background-color:#00ff00">使用了自己的 NameServer</span>。</p><p>主要包括两个功能：<br><strong>Broker 管理</strong>：接受 Broker 集群的注册信息并且保存下来作为路由信息的基本数据；<span style="background-color:#00ff00">提供心跳检测机制</span>，检查 Broker 是否还存活。<br><strong>路由信息管理</strong>：每个 NameServer 中都保存着 Broker 集群的整个路由信息和用于客户端查询的队列信息。<span style="background-color:#00ff00">Producer 和 Conumser 通过 NameServer 可以获取整个 Broker 集群的路由信息，从而进行消息的投递和消费</span>。</p><h3 id="3-4-1-路由注册"><a href="#3-4-1-路由注册" class="headerlink" title="3.4.1. 路由注册"></a>3.4.1. 路由注册</h3><p>NameServer 通常也是以集群的方式部署，不过，NameServer 是<span style="background-color:#00ff00">无状态的</span>，即 NameServer 集群中的各个节点间是无差异的，<span style="background-color:#ff00ff">各节点间相互不进行信息通讯</span>。那各节点中的数据是如何进行数据同步的呢？<font color=#ff0000>在 Broker 节点启动时，轮询 NameServer 列表，与<span style="background-color:#00ff00">每个 NameServer 节点</span>建立长连接，发起注册请求。在 NameServer 内部维护着⼀个 Broker 列表，用来动态存储 Broker 的信息。</font></p><blockquote><p>注意，这是与其它像 zk、Eureka、Nacos 等注册中心不同的地方。<br>这种 NameServer 的无状态方式，有什么优缺点：<br>优点：NameServer 集群搭建简单，扩容简单。<br>缺点：对于 Broker，<span style="background-color:#ffff00">必须明确指出所有 NameServer 地址</span>。否则未指出的将不会去注册。也正因为如此，NameServer 并不能随便扩容。因为，若 Broker 不重新配置，新增的 NameServer 对于 Broker 来说是不可见的，其不会向这个 NameServer 进行注册。</p></blockquote><p>Broker 节点为了证明自己是活着的，为了维护与 NameServer 间的长连接，会将最新的信息以心跳包的方式上报给 NameServer，<span style="background-color:#00ff00">每 30 秒发送一次心跳</span>。<span style="background-color:#ff00ff">心跳包中包含 BrokerId、Broker 地址 (IP+Port)、 Broker 名称、Broker 所属集群名称等等</span>。NameServer 在接收到心跳包后，会更新心跳时间戳，记录这个 Broker 的最新存活时间。</p><h3 id="3-4-2-路由剔除"><a href="#3-4-2-路由剔除" class="headerlink" title="3.4.2. 路由剔除"></a>3.4.2. 路由剔除</h3><p>NameServer 中有⼀个定时任务，<span style="background-color:#00ff00">每隔 10 秒</span>就会扫描⼀次 Broker 表，查看每一个 Broker 的最新心跳时间戳距离当前时间<span style="background-color:#ffff00">是否超过 120 秒</span>，如果超过，则会判定 Broker 失效，然后将其从 Broker 列表中剔除。<br>扩展：对于 RocketMQ 日常运维工作，例如 Broker 升级，需要停掉 Broker 的工作。OP 需要怎么做？OP 需要将 Broker 的读写权限禁掉。一旦 client(Consumer 或 Producer) 向 broker 发送请求，都会收到 broker 的 NO_PERMISSION 响应，然后 client 会进行对其它 Broker 的重试。当 OP 观察到这个 Broker 没有流量后，再关闭它，实现 Broker 从 NameServer 的移除。</p><h3 id="3-4-3-路由发现"><a href="#3-4-3-路由发现" class="headerlink" title="3.4.3. 路由发现"></a>3.4.3. 路由发现</h3><p>RocketMQ 的<span style="background-color:#ff00ff">路由发现采用的是 Pull 模型</span>。当 Topic 路由信息出现变化时，NameServer 不会主动推送给客户端，而是客户端定时拉取主题最新的路由。默认<span style="background-color:#00ff00">客户端每 30 秒会拉取一次</span>最新的路由。</p><blockquote><p>扩展：<br>1）Push 模型：推送模型。其实时性较好，是一个“发布 - 订阅”模型，需要维护一个长连接。而长连接的维护是需要资源成本的。该模型适合于的场景： 实时性要求较高 Client 数量不多，Server 数据变化较频繁<br>2）Pull 模型：拉取模型。存在的问题是，实时性较差。<br>3）Long Polling 模型：长轮询模型。其是对 Push 与 Pull 模型的整合，充分利用了这两种模型的优势，屏蔽了它们的劣势。</p></blockquote><p>对比 Nacos：<a href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-6%E3%80%81Nacos/" title="服务注册与发现-6、Nacos">服务注册与发现-6、Nacos</a></p><h3 id="3-4-4-客户端-NameServer-选择策略"><a href="#3-4-4-客户端-NameServer-选择策略" class="headerlink" title="3.4.4. 客户端 NameServer 选择策略"></a>3.4.4. 客户端 NameServer 选择策略</h3><blockquote><p>这里的客户端指的是 Producer 与 Consumer</p></blockquote><p>客户端在配置时必须要写上 NameServer 集群的地址，那么客户端到底连接的是哪个 NameServer 节点呢？客户端<span style="background-color:#00ff00">首先会生产一个随机数，然后再与 NameServer 节点数量取模</span>，此时得到的就是所要连接的节点索引，然后就会进行连接。<span style="background-color:#00ff00">如果连接失败，则会采用 round-robin 策略，逐个尝试着去连接其它节点</span>。<br>首先采用的是随机策略进行的选择，失败后采用的是轮询策略。</p><blockquote><p>扩展：Zookeeper Client 是如何选择 Zookeeper Server 的？ 简单来说就是，经过两次 Shuffle，然后选择第一台 Zookeeper Server。详细说就是，将配置文件中的 zk server 地址进行第一次 Shuffle，然后随机选择一个。这个选择出的一般都是一个 hostname。然后获取到该 hostname 对应的所有 ip，再对这些 ip 进行第二次 Shuffle，从 Shuffle 过的结果中取第一个 server 地址进行连接。</p></blockquote><h1 id="4-工作流程"><a href="#4-工作流程" class="headerlink" title="4. 工作流程"></a>4. 工作流程</h1><h2 id="4-1-具体流程"><a href="#4-1-具体流程" class="headerlink" title="4.1. 具体流程"></a>4.1. 具体流程</h2><p>1）启动 NameServer，NameServer 启动后开始监听端口，等待 Broker、Producer、Consumer 连接。<br>2）启动 Broker 时，Broker 会<span style="background-color:#ff00ff">与所有的 NameServer 建立并保持长连接</span>，然后每 &#x3D;&#x3D;30 秒&#x3D;&#x3D;向 NameServer 定时发送心跳包。<br>3）发送消息前，可以先创建 Topic，创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上，当然，在创建 Topic 时也会将 Topic 与 Broker 的关系写入到 NameServer 中。不过，这步是可选的，也可以在发送消息时自动创建 Topic。<br>4）Producer 发送消息，启动时先跟 NameServer 集群中的其中一台建立长连接，并从 NameServer 中获取路由信息，即当前发送的 Topic 消息的 Queue 与 Broker 的地址（IP+Port）的映射关系。然后根据算法策略从队选择一个 Queue，与队列所在的 Broker 建立长连接从而向 Broker 发消息。当然，在获取到路由信息后，Producer 会首先将路由信息缓存到本地，再&#x3D;&#x3D;每 30 秒从 NameServer 更新一次路由信息&#x3D;&#x3D;。<br>5）Consumer 跟 Producer 类似，跟其中一台 NameServer 建立长连接，获取其所订阅 Topic 的路由信息，然后根据算法策略从路由信息中获取到其所要消费的 Queue，然后直接跟 Broker 建立长连接，开始消费其中的消息。Consumer 在获取到路由信息后，同样也会每 30 秒从 NameServer 更新一次路由信息。<span style="background-color:#ffff00">不过不同于 Producer 的是，Consumer 还会向 Broker 发送心跳，以确保 Broker 的存活状态。</span></p><h2 id="4-2-Topic-的创建模式"><a href="#4-2-Topic-的创建模式" class="headerlink" title="4.2. Topic 的创建模式"></a>4.2. Topic 的创建模式</h2><p>手动创建 Topic 时，有两种模式：<br><strong>集群模式</strong>：该模式下创建的 Topic 在该集群中，所有 Broker 中的 Queue 数量是相同的。<br><strong>Broker 模式</strong>：该模式下创建的 Topic 在该集群中，每个 Broker 中的 Queue 数量可以不同。</p><blockquote><p>自动创建 Topic 时，默认采用的是 Broker 模式，会为每个 Broker 默认创建 4 个 Queue。</p></blockquote><h2 id="4-3-读-x2F-写队列"><a href="#4-3-读-x2F-写队列" class="headerlink" title="4.3. 读&#x2F;写队列"></a>4.3. 读&#x2F;写队列</h2><p><span style="background-color:#00ff00">按数量大的创建 queue</span></p><p>从物理上来讲，读&#x2F;写队列是同一个队列。所以，不存在读&#x2F;写队列数据同步问题。读&#x2F;写队列是逻辑上进行区分的概念。一般情况下，读&#x2F;写队列数量是相同的。<br>例如，创建 Topic 时设置的写队列数量为 8，读队列数量为 4，此时系统会创建 8 个 Queue，分别是 0 1 2 3 4 5 6 7。Producer 会将消息写入到这 8 个队列，但 Consumer 只会消费 0 1 2 3 这 4 个队列中的消息，4 5 6 7 中的消息是不会被消费到的。<br>再如，创建 Topic 时设置的写队列数量为 4，读队列数量为 8，此时系统会创建 8 个 Queue，分别是 0 1 2 3 4 5 6 7。Producer 会将消息写入到 0 1 2 3 这 4 个队列，但 Consumer 只会消费 0 1 2 3 4 5 6 7 这 8 个队列中的消息，但是 4 5 6 7 中是没有消息的。此时假设 Consumer Group 中包含两个 Consuer，Consumer1 消 费 0 1 2 3，而 Consumer2 消费 4 5 6 7。但实际情况是，Consumer2 是没有消息可消费的。<br>也就是说，当读&#x2F;写队列数量设置不同时，总是有问题的。那么，为什么要这样设计呢？<br>其这样设计的目的是为了，<span style="background-color:#00ff00">方便 Topic 的 Queue 的缩容</span>。<br>例如，原来创建的 Topic 中包含 16 个 Queue，如何能够使其 Queue 缩容为 8 个，还不会丢失消息？可以动态修改写队列数量为 8，读队列数量不变。此时新的消息只能写入到前 8 个队列，而消费者消费的却是 16 个队列中的数据。<span style="background-color:#00ff00">当发现<font color=#ff0000>后 8 个 Queue</font>中的消息消费完毕后</span>，就可以再将读队列数量动态设置为 8。整个缩容过程，没有丢失任何消息。<br>perm 用于设置对当前创建 Topic 的操作权限：2 表示只写，4 表示只读，6 表示读写。</p><h1 id="5-底层原理"><a href="#5-底层原理" class="headerlink" title="5. 底层原理"></a>5. 底层原理</h1><h2 id="5-1-数据复制与刷盘策略⭐️🔴"><a href="#5-1-数据复制与刷盘策略⭐️🔴" class="headerlink" title="5.1. 数据复制与刷盘策略⭐️🔴"></a>5.1. 数据复制与刷盘策略⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104122805.png"></p><h3 id="5-1-1-复制策略"><a href="#5-1-1-复制策略" class="headerlink" title="5.1.1. 复制策略"></a>5.1.1. 复制策略</h3><pre><code>复制策略是Broker的Master与Slave间的数据同步方式。分为同步复制与异步复制：</code></pre><p><strong>同步复制</strong>：消息写入 master 后，master 会等待 slave 同步数据成功后才向 producer 返回成功 ACK<br>       <strong>异步复制</strong>：消息写入 master 后，master 立即向 producer 返回成功 ACK，无需等待 slave 同步数据成功异步复制策略会降低系统的写入延迟，RT 变小，提高了系统的吞吐量</p><h3 id="5-1-2-刷盘策略"><a href="#5-1-2-刷盘策略" class="headerlink" title="5.1.2. 刷盘策略"></a>5.1.2. 刷盘策略</h3><pre><code>刷盘策略指的是broker中消息的落盘方式，即消息发送到broker内存后消息持久化到磁盘的方式。</code></pre><p>分为同步刷盘与异步刷盘：</p><p><strong>同步刷盘</strong>：当消息持久化到 broker 的磁盘后才算是消息写入成功。<br><strong>异步刷盘</strong>：当消息写入到 broker 的内存后即表示消息写入成功，无需等待消息持久化到磁盘。</p><blockquote><p>1）异步刷盘策略会降低系统的写入延迟，RT 变小，提高了系统的吞吐量<br>2）消息写入到 Broker 的内存，一般是写入到了 PageCache<br>3）对于异步刷盘策略，消息会写入到 PageCache 后立即返回成功 ACK。但并不会立即做落盘操作，而是当 PageCache 到达一定量时会自动进行落盘。</p></blockquote><h2 id="5-2-Broker-集群模式"><a href="#5-2-Broker-集群模式" class="headerlink" title="5.2. Broker 集群模式"></a>5.2. Broker 集群模式</h2><p>根据 Broker 集群中各个节点间关系的不同，Broker 集群可以分为以下几类：</p><h3 id="5-2-1-单-Master"><a href="#5-2-1-单-Master" class="headerlink" title="5.2.1. 单 Master"></a>5.2.1. 单 Master</h3><p>只有一个 broker（其本质上就不能称为集群）。这种方式也只能是在测试时使用，生产环境下不能使用，因为存在单点问题。</p><h3 id="5-2-2-多-Master"><a href="#5-2-2-多-Master" class="headerlink" title="5.2.2. 多 Master"></a>5.2.2. 多 Master</h3><p>broker 集群仅由多个 master 构成，不存在 Slave。同一 Topic 的各个 Queue 会平均分布在各个 master 节点上。<br>优点：配置简单，单个 Master 宕机或重启维护对应用无影响，在磁盘配置为 RAID10 时，即使机器宕机不可恢复情况下，由于 RAID10 磁盘非常可靠，消息也不会丢（异步刷盘丢失少量消息，同步刷盘一条不丢），性能最高；<br>缺点：<span style="background-color:#ff00ff">单台机器宕机期间，这台机器上未被消费的消息在机器恢复之前不可订阅（不可消费），消息实时性会受到影响。</span></p><blockquote><p>以上优点的前提是，这些 Master 都配置了 RAID 磁盘阵列。如果没有配置，一旦出现某 Master 宕机，则会发生大量消息丢失的情况。</p></blockquote><h3 id="5-2-3-多-Master-多-Slave-模式-异步复制"><a href="#5-2-3-多-Master-多-Slave-模式-异步复制" class="headerlink" title="5.2.3. 多 Master 多 Slave 模式 - 异步复制"></a>5.2.3. 多 Master 多 Slave 模式 - 异步复制</h3><p>broker 集群由多个 master 构成，每个 master 又配置了多个 slave（在配置了 RAID 磁盘阵列的情况下，一个 master 一般配置一个 slave 即可）。master 与 slave 的关系是主备关系，即 master 负责处理消息的读写请求，而 slave 仅负责消息的备份与 master 宕机后的角色切换。<br>异步复制即前面所讲的复制策略中的异步复制策略，即消息写入 master 成功后，master 立即向 producer 返回成功 ACK，无需等待 slave 同步数据成功。<br>该模式的最大特点之一是，当 master 宕机后 slave 能够自动切换为 master。<span style="background-color:#ff00ff">不过由于 slave 从 master 的同步具有短暂的延迟（毫秒级），所以当 master 宕机后，这种异步复制方式可能会存在少量消息的丢失问题。</span></p><blockquote><p>Slave 从 Master 同步的延迟越短，其可能丢失的消息就越少。对于 Master 的 RAID 磁盘阵列，若使用的也是异步复制策略，同样也存在延迟问题，同样也可能会丢失消息。但 RAID 阵列的秘诀是微秒级的（因为是由硬盘支持的），所以其丢失的数据量会更少。</p></blockquote><h3 id="5-2-4-多-Master-多-Slave-模式-同步双写"><a href="#5-2-4-多-Master-多-Slave-模式-同步双写" class="headerlink" title="5.2.4. 多 Master 多 Slave 模式 - 同步双写"></a>5.2.4. 多 Master 多 Slave 模式 - 同步双写</h3><p>该模式是多 Master 多 Slave 模式的同步复制实现。所谓同步双写，指的是消息写入 master 成功后，master 会等待 slave 同步数据成功后才向 producer 返回成功 ACK，即 master 与 slave 都要写入成功后才会返回成功 ACK，也即双写。<br>该模式与异步复制模式相比，优点是消息的安全性更高，不存在消息丢失的情况。但单个消息的 RT 略高，从而导致性能要略低（大约低 10%）。<br>该模式存在一个大的问题：对于目前的版本，Master 宕机后，Slave 不会自动切换到 Master。</p><h2 id="5-3-消息的生产"><a href="#5-3-消息的生产" class="headerlink" title="5.3. 消息的生产"></a>5.3. 消息的生产</h2><h3 id="5-3-1-消息的生产过程"><a href="#5-3-1-消息的生产过程" class="headerlink" title="5.3.1. 消息的生产过程"></a>5.3.1. 消息的生产过程</h3><ul><li>Producer 发送消息之前，会先向 NameServer 发出获取消息 Topic 的路由信息的请求</li><li>NameServer 返回该 Topic 的路由表及 Broker 列表</li><li>Producer 根据代码中指定的 Queue 选择策略，从 Queue 列表中选出一个队列，用于后续存储消息</li><li>Producer 对消息做一些特殊处理，例如，消息本身超过 4M，则会对其进行压缩</li><li>Producer 向选择出的 Queue 所在的 Broker 发出 RPC 请求，将消息发送到选择出的 Queue</li></ul><h3 id="5-3-2-Producer-的负载均衡"><a href="#5-3-2-Producer-的负载均衡" class="headerlink" title="5.3.2. Producer 的负载均衡"></a>5.3.2. Producer 的负载均衡</h3><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230417-1500%%</span>❕ ^sjwepe</p><p>Producer 端，每个实例在发消息的时候，默认会轮询所有的 message queue 发送，以达到让消息平均落在不同的 queue 上。而由于 queue 可以散落在不同的 broker，所以消息就发送到不同的 broker 下，如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417145613.png"></p><p>图中箭头线条上的标号代表顺序，发布方会把第一条消息发送至 Queue 0，然后第二条消息发送至 Queue 1，以此类推。</p><h3 id="5-3-3-Queue-选择算法"><a href="#5-3-3-Queue-选择算法" class="headerlink" title="5.3.3. Queue 选择算法"></a>5.3.3. Queue 选择算法</h3><h4 id="5-3-3-1-轮询算法-默认"><a href="#5-3-3-1-轮询算法-默认" class="headerlink" title="5.3.3.1. 轮询算法 - 默认"></a>5.3.3.1. 轮询算法 - 默认</h4><p>默认选择算法。该算法保证了每个 Queue 中可以均匀的获取到消息。<br><span style="background-color:#ffff00">该算法存在一个问题：由于某些原因，在某些 Broker 上的 Queue 可能投递延迟较严重。从而导致 Producer 的缓存队列中出现较大的消息积压，影响消息的投递性能。</span></p><h4 id="5-3-3-2-最小投递延迟算法"><a href="#5-3-3-2-最小投递延迟算法" class="headerlink" title="5.3.3.2. 最小投递延迟算法"></a>5.3.3.2. 最小投递延迟算法</h4><p>该算法会统计每次消息投递的时间延迟，然后根据统计出的结果将消息投递到时间延迟最小的 Queue。<br>如果延迟相同，则采用轮询算法投递。该算法可以有效提升消息的投递性能。<br><span style="background-color:#ffff00">该算法也存在一个问题：消息在 Queue 上的分配不均匀。投递延迟小的 Queue 其可能会存在大量的消息。而对该 Queue 的消费者压力会增大，降低消息的消费能力，可能会导致 MQ 中消息的堆积。</span></p><h2 id="5-4-消息的存储"><a href="#5-4-消息的存储" class="headerlink" title="5.4. 消息的存储"></a>5.4. 消息的存储</h2><p>RocketMQ 消息的存储是由 ConsumeQueue 和 CommitLog 配合完成的，消息真正的物理存储文件是 CommitLog，<span style="background-color:#ff00ff">ConsumeQueue 是消息的逻辑队列，类似数据库的索引文件，存储的是指向物理存储的地址</span>。<span style="background-color:#00ff00">每个 Topic 下的每个 Message Queue 都有一个对应的 ConsumeQueue 文件。</span></p><p><a href="https://www.bilibili.com/video/BV173411H7JR/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV173411H7JR/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405163417.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405163712.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405164151.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405164228.png" alt="image.png"></p><p>RocketMQ 中的消息存储在本地文件系统中，这些相关文件默认在当前用户主目录下的 store 目录中。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104130435.png"><br>abort：该文件在 Broker 启动后会自动创建，正常关闭 Broker，该文件会自动消失。若在没有启动<br>Broker 的情况下，发现这个文件是存在的，则说明之前 Broker 的关闭是非正常关闭。<br>checkpoint：其中存储着 commitlog、consumequeue、index 文件的最后刷盘时间戳<br><span style="background-color:#00ff00">commitlog：其中存放着 commitlog 文件，而消息是写在 commitlog 文件中的</span><br>config：存放着 Broker 运行期间的一些配置数据<br>consumequeue：其中存放着 consumequeue 文件，队列就存放在这个目录中<br>index：其中存放着消息索引文件 indexFile<br>lock：运行期间使用到的全局资源锁</p><h3 id="5-4-1-commitlog"><a href="#5-4-1-commitlog" class="headerlink" title="5.4.1. commitlog"></a>5.4.1. commitlog</h3><p>说明：在很多资料中 commitlog 目录中的文件简单就称为 commitlog 文件。但在源码中，该文件被命名为 mappedFile。</p><h4 id="5-4-1-1-目录与文件"><a href="#5-4-1-1-目录与文件" class="headerlink" title="5.4.1.1. 目录与文件"></a>5.4.1.1. 目录与文件</h4><p>commitlog 目录中存放着很多的 mappedFile 文件，<span style="background-color:#ff00ff">当前 Broker 中的所有消息都是落盘到这些 mappedFile 文件中的</span>。mappedFile 文件大小为 1G（小于等于 1G），文件名由 20 位十进制数构成，表示当前文件的第一条消息的起始位移偏移量。<br>第一个文件名一定是 20 位 0 构成的。因为第一个文件的第一条消息的偏移量 commitlog offset 为 0 当第一个文件放满时，则会自动生成第二个文件继续存放消息。假设第一个文件大小是 1073741820 字节（1G &#x3D; 1073741824 字节），则第二个文件名就是 00000000001073741824。 以此类推，第 n 个文件名应该是前 n-1 个文件大小之和。 一个 Broker 中所有 mappedFile 文件的 commitlog offset 是连续的<br>需要注意的是，<span style="background-color:#00ff00">一个 Broker 中仅包含一个 commitlog 目录，所有的 mappedFile 文件都是存放在该目录中的。即无论当前 Broker 中存放着多少 Topic 的消息，这些消息都是被顺序写入到了 mappedFile 文件中的</span>。也就是说，<span style="background-color:#ffff00">这些消息在 Broker 中存放时并没有被按照 Topic 进行分类存放。</span><span style="background-color:#ff00ff">mappedFile 文件是顺序读写的文件，所有其访问效率很高</span> 无论是 SSD 磁盘还是 SATA 磁盘，通常情况下，顺序存取效率都会高于随机存取。</p><h4 id="5-4-1-2-消息单元"><a href="#5-4-1-2-消息单元" class="headerlink" title="5.4.1.2. 消息单元"></a>5.4.1.2. 消息单元</h4><h3 id="5-4-2-consumequeue"><a href="#5-4-2-consumequeue" class="headerlink" title="5.4.2. consumequeue"></a>5.4.2. consumequeue</h3><h4 id="5-4-2-1-目录与文件"><a href="#5-4-2-1-目录与文件" class="headerlink" title="5.4.2.1. 目录与文件"></a>5.4.2.1. 目录与文件</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104132355.png"></p><p>为了提高效率，会为<span style="background-color:#ff00ff">每个 Topic</span> 在~&#x2F;store&#x2F;consumequeue 中创建一个目录，目录名为 Topic 名称。在该 Topic 目录下，<span style="background-color:#ff00ff">会再为每个该 Topic 的 Queue 建立一个目录</span>，目录名为 queueId。每个目录中存放着若干 consumequeue 文件，<span style="background-color:#ff00ff">consumequeue 文件是 commitlog 的索引文件</span>，可以根据 consumequeue 定位到具体的消息。<br>对比：Kafka 每个 topic 一个文件</p><h4 id="5-4-2-2-索引条目"><a href="#5-4-2-2-索引条目" class="headerlink" title="5.4.2.2. 索引条目"></a>5.4.2.2. 索引条目</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104132419.png"></p><p>每个 consumequeue 文件<span style="background-color:#ff00ff">可以包含 30w 个索引条目</span>，每个索引条目包含了三个消息重要属性：<br><span style="background-color:#00ff00">消息在 mappedFile 文件中的偏移量 CommitLog Offset</span><br><span style="background-color:#00ff00">消息长度</span><br><span style="background-color:#00ff00">消息 Tag 的 hashcode 值</span><br>这三个属性占 20 个字节，所以每个文件的大小是固定的 30w * 20 字节。<br><span style="background-color:#ffff00">一个 consumequeue 文件中所有消息的 Topic 一定是相同的</span>。但每条消息的 Tag 可能是不同的。</p><h3 id="5-4-3-对文件的读写"><a href="#5-4-3-对文件的读写" class="headerlink" title="5.4.3. 对文件的读写"></a>5.4.3. 对文件的读写</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104132607.png"></p><h4 id="5-4-3-1-消息写入"><a href="#5-4-3-1-消息写入" class="headerlink" title="5.4.3.1. 消息写入"></a>5.4.3.1. 消息写入</h4><p>一条消息进入到 Broker 后经历了以下几个过程才最终被持久化。</p><ul><li>Broker 根据 queueId，获取到该消息对应索引条目要在 consumequeue 目录中的写入偏移量，即 QueueOffset</li><li>将 queueId、queueOffset 等数据，与消息一起封装为消息单元</li><li>将消息单元写入到 commitlog</li><li>同时，形成消息索引条目</li><li>将消息索引条目分发到相应的 consumequeue</li></ul><h4 id="5-4-3-2-消息拉取"><a href="#5-4-3-2-消息拉取" class="headerlink" title="5.4.3.2. 消息拉取"></a>5.4.3.2. 消息拉取</h4><p>当 Consumer 来拉取消息时会经历以下几个步骤：</p><ul><li>Consumer 获取到其要消费消息所在 Queue 的消费偏移量 offset，计算出其要消费消息的<br>消息 offset</li></ul><blockquote><p><span style="background-color:#00ff00">消费 offset 即消费进度</span>，consumer 对某个 Queue 的消费 offset，即消费到了该 Queue 的第几 条消息 <span style="background-color:#00ff00">消息 offset &#x3D; 消费 offset + 1</span></p></blockquote><ul><li>Consumer 向 Broker 发送拉取请求，其中会包含其要拉取消息的 Queue、消息 offset 及消息 Tag。</li><li>Broker 计算在该 consumequeue 中的 queueOffset。 <span style="background-color:#ffff00">queueOffset &#x3D; 消息 offset * 20 字节</span></li><li>从该 queueOffset 处开始向后查找第一个指定 Tag 的索引条目。</li><li>解析该索引条目的前 8 个字节，即可定位到该消息在 commitlog 中的 commitlog offset</li><li>从对应 commitlog offset 中读取消息单元，并发送给 Consumer</li></ul><h4 id="5-4-3-3-性能提示⭐️🔴⭐️🔴"><a href="#5-4-3-3-性能提示⭐️🔴⭐️🔴" class="headerlink" title="5.4.3.3. 性能提示⭐️🔴⭐️🔴"></a>5.4.3.3. 性能提示⭐️🔴⭐️🔴</h4><p>RocketMQ 中，无论是消息本身还是消息索引，都是存储在磁盘上的。其不会影响消息的消费吗？当然不会。其实 RocketMQ 的性能在目前的 MQ 产品中性能是非常高的。因为系统通过一系列相关机制大大提升了性能。<br>首先，RocketMQ 对文件的读写操作是通过<span style="background-color:#ff00ff">mmap 零拷贝</span>进行的，将对文件的操作转化为直接对内存地址进行操作，从而极大地提高了文件的读写效率。<br>其次，<span style="background-color:#ff00ff">consumequeue 中的数据是顺序存放的</span>，还引入了 PageCache 的预读取机制，使得对<br>consumequeue 文件的读取几乎接近于内存读取，即使在有消息堆积情况下也不会影响性能。</p><blockquote><p>PageCache 机制，页缓存机制，是 OS 对文件的缓存机制，用于加速对文件的读写操作。一般来说，程序对文件进行顺序读写的速度几乎接近于内存读写速度，主要原因是由于 OS 使用 PageCache 机制对读写访问操作进行性能优化，将一部分的内存用作 PageCache。<br>写操作：OS 会先将数据写入到 PageCache 中，随后会以异步方式由 pdæ ush（page dirty æ ush) 内核线程将 Cache 中的数据刷盘到物理磁盘<br>读操作：若用户要读取数据，其首先会从 PageCache 中读取，若没有命中，则 OS 在从物理磁 盘上加载该数据到 PageCache 的同时，也会顺序对其相邻数据块中的数据进行预读取。</p></blockquote><p>RocketMQ 中可能会影响性能的是对 commitlog 文件的读取。因为对 commitlog 文件来说，读取消息时会产生大量的随机访问，而随机访问会严重影响性能。不过，如果选择合适的系统 IO 调度算法，比如设置调度算法为 Deadline（采用 SSD 固态硬盘的话），随机读的性能也会有所提升。</p><h2 id="5-5-消息的消费"><a href="#5-5-消息的消费" class="headerlink" title="5.5. 消息的消费"></a>5.5. 消息的消费</h2><a href="/2023/01/03/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-3%E3%80%81%E6%B6%88%E6%81%AF%E7%9A%84%E6%B6%88%E8%B4%B9/" title="分布式专题-MQ-RocketMQ-3、消息的消费">分布式专题-MQ-RocketMQ-3、消息的消费</a><h2 id="5-6-幂等性"><a href="#5-6-幂等性" class="headerlink" title="5.6. 幂等性"></a>5.6. 幂等性</h2><p><a href="https://www.bilibili.com/video/BV1L4411y7mn?t=155.5&amp;p=100">https://www.bilibili.com/video/BV1L4411y7mn?t=155.5&amp;p=100</a></p><h1 id="6-面试题"><a href="#6-面试题" class="headerlink" title="6. 面试题"></a>6. 面试题</h1><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">001</span>-MQ/MQ.docx<br></code></pre></td></tr></table></figure><h2 id="6-1-RocketMQ-如何保证消息有序性"><a href="#6-1-RocketMQ-如何保证消息有序性" class="headerlink" title="6.1. RocketMQ 如何保证消息有序性"></a>6.1. RocketMQ 如何保证消息有序性</h2><p><a href="https://www.bilibili.com/video/BV1HL4y1x7j2/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1HL4y1x7j2/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><a href="/2023/01/03/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RocketMQ-3%E3%80%81%E6%B6%88%E6%81%AF%E7%9A%84%E6%B6%88%E8%B4%B9/" title="分布式专题-MQ-RocketMQ-3、消息的消费">分布式专题-MQ-RocketMQ-3、消息的消费</a><h2 id="6-2-RocketMQ-和-Kafka-的区别和相同点"><a href="#6-2-RocketMQ-和-Kafka-的区别和相同点" class="headerlink" title="6.2. RocketMQ 和 Kafka 的区别和相同点"></a>6.2. RocketMQ 和 Kafka 的区别和相同点</h2><p><a href="https://www.bilibili.com/video/BV1AT411G7Hd/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1AT411G7Hd/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202739.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202753.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202810.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202830.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202854.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202907.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202918.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202929.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202940.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230402202951.png" alt="image.png"></p><p>失败重试、延时定时消息、分布式事务、消息查询</p><h2 id="6-3-RocketMQ-如何保证高可用⭐️🔴"><a href="#6-3-RocketMQ-如何保证高可用⭐️🔴" class="headerlink" title="6.3. RocketMQ 如何保证高可用⭐️🔴"></a>6.3. RocketMQ 如何保证高可用⭐️🔴</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230417-0753%%</span>❕ ^06jcfj</p><p><a href="https://www.bilibili.com/video/BV1YT411z7GV?p=36&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1YT411z7GV?p=36&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417082518.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404072746.png" alt="image.png"></p><h3 id="6-3-1-架构层面"><a href="#6-3-1-架构层面" class="headerlink" title="6.3.1. 架构层面"></a>6.3.1. 架构层面</h3><p>避免用单节点或者简单的一主一从架构，可以采取多主从的架构，并且主从之间采用同步复制的方式进行<span style="background-color:#ff00ff">数据双写</span>。</p><h3 id="6-3-2-刷盘策略"><a href="#6-3-2-刷盘策略" class="headerlink" title="6.3.2. 刷盘策略"></a>6.3.2. 刷盘策略</h3><p>RocketMQ 默认的异步刷盘，可以<span style="background-color:#ff00ff">改成同步刷盘</span> SYNC_FLUSH。</p><h3 id="6-3-3-生产消息的高可用"><a href="#6-3-3-生产消息的高可用" class="headerlink" title="6.3.3. 生产消息的高可用"></a>6.3.3. 生产消息的高可用</h3><p>当消息发送失败了，在消息重试的时候，会尽量规避上一次发送的 Broker，选择还没推送过该消息的 Broker，以增大消息发送的成功率。</p><h3 id="6-3-4-消费消息的高可用"><a href="#6-3-4-消费消息的高可用" class="headerlink" title="6.3.4. 消费消息的高可用"></a>6.3.4. 消费消息的高可用</h3><p>消费者获取到消息之后，可以等到整个业务处理完成，再进行 CONSUME_SUCCESS 状态确认，如果业务处理过程中发生了异常那么就会触发 broker 的重试机制。</p><p>对比 Rabbitmq：<a href="/2023/03/08/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98-MQ-RabbitMQ-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="分布式专题-MQ-RabbitMQ-1、基本原理">分布式专题-MQ-RabbitMQ-1、基本原理</a></p><h2 id="6-4-RocketMQ-的存储机制"><a href="#6-4-RocketMQ-的存储机制" class="headerlink" title="6.4. RocketMQ 的存储机制"></a>6.4. RocketMQ 的存储机制</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230403214018.png" alt="image.png"></p><h3 id="6-4-1-CommitLog"><a href="#6-4-1-CommitLog" class="headerlink" title="6.4.1. CommitLog"></a>6.4.1. CommitLog</h3><p>消息生产者发送消息到 broker，都是会<span style="background-color:#ff00ff">按照顺序存储在 CommitLog 文件中</span>，每个 commitLog 文件的大小为 1G。<span style="background-color:#ff00ff">同时有一个异步线程监听 CommitLog 文件</span>，有内容写入就会生成索引文件写入 ConsumeQueue 中。<br><strong>CommitLog</strong> - 存储所有的消息元数据，包括 Topic、QueueId 以及 message<br><strong>CosumerQueue</strong> - 消费逻辑队列：监听存储消息在 CommitLog 的 offset<br><strong>IndexFile</strong> - 索引文件：存储消息的 key 和时间戳等信息，使得 RocketMq 可以采用 key 和时间区间来查询消息 <br>也就是说，<span style="background-color:#ff00ff">rocketMq 将消息均存储在 CommitLog 中，并分别提供了 CosumerQueue 和 IndexFile 两个索引，来快速检索消息</span>。</p><h3 id="6-4-2-ConsumeQueue"><a href="#6-4-2-ConsumeQueue" class="headerlink" title="6.4.2. ConsumeQueue"></a>6.4.2. ConsumeQueue</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230403214223.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230104132419.png"></p><h3 id="6-4-3-IndexFile"><a href="#6-4-3-IndexFile" class="headerlink" title="6.4.3. IndexFile"></a>6.4.3. IndexFile</h3><p><a href="https://www.modb.pro/db/181270">https://www.modb.pro/db/181270</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417210422.png" alt="image.png"></p><h3 id="6-4-4-ReputMessageServiceThreadLoop"><a href="#6-4-4-ReputMessageServiceThreadLoop" class="headerlink" title="6.4.4. ReputMessageServiceThreadLoop"></a>6.4.4. ReputMessageServiceThreadLoop</h3><p>每 1ms 扫描一次 <code>CommitLog</code> 文件，生成新插入内容的索引，写入到 <code>ConsumeQueue</code> 中</p><h2 id="6-5-为什么-Rocketmq-性能高⭐️🔴⭐️🔴"><a href="#6-5-为什么-Rocketmq-性能高⭐️🔴⭐️🔴" class="headerlink" title="6.5. 为什么 Rocketmq 性能高⭐️🔴⭐️🔴"></a>6.5. 为什么 Rocketmq 性能高⭐️🔴⭐️🔴</h2><p> <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230403225513.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404145132.png" alt="image.png"></p><h3 id="6-5-1-顺序写"><a href="#6-5-1-顺序写" class="headerlink" title="6.5.1. 顺序写"></a>6.5.1. 顺序写</h3><p>顺序写比随机写的性能会高很多，不会有大量寻址的过程</p><h3 id="6-5-2-异步刷盘"><a href="#6-5-2-异步刷盘" class="headerlink" title="6.5.2. 异步刷盘"></a>6.5.2. 异步刷盘</h3><p>相比较于同步刷盘，异步刷盘的性能会高很多</p><h3 id="6-5-3-零拷贝"><a href="#6-5-3-零拷贝" class="headerlink" title="6.5.3. 零拷贝"></a>6.5.3. 零拷贝</h3><p>使用 mmap 的方式进行零拷贝，提高了数据传输的效率</p><h3 id="6-5-4-CompletableFuture-提升同步双写性能"><a href="#6-5-4-CompletableFuture-提升同步双写性能" class="headerlink" title="6.5.4. CompletableFuture 提升同步双写性能"></a>6.5.4. CompletableFuture 提升同步双写性能</h3><p>FutureTask<br><span style="display:none"></p><ul><li><input disabled="" type="checkbox"> 🚩 - CompleteFutureTask - 🏡 2023-04-04 14:45</span></li></ul><p>#todo</p><h3 id="6-5-5-Commitlog-写入时锁的配置"><a href="#6-5-5-Commitlog-写入时锁的配置" class="headerlink" title="6.5.5. Commitlog 写入时锁的配置"></a>6.5.5. Commitlog 写入时锁的配置</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404152958.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404153519.png" alt="image.png"></p><p><span style="background-color:#ff00ff">默认是异步刷盘，所以默认使用的是自旋锁</span></p><h3 id="6-5-6-读写分离之对外内存机制"><a href="#6-5-6-读写分离之对外内存机制" class="headerlink" title="6.5.6. 读写分离之对外内存机制"></a>6.5.6. 读写分离之对外内存机制</h3><h2 id="6-6-让你来设计一个消息队列，你会怎么设计"><a href="#6-6-让你来设计一个消息队列，你会怎么设计" class="headerlink" title="6.6. 让你来设计一个消息队列，你会怎么设计"></a>6.6. 让你来设计一个消息队列，你会怎么设计</h2><h3 id="6-6-1-数据存储角度"><a href="#6-6-1-数据存储角度" class="headerlink" title="6.6.1. 数据存储角度"></a>6.6.1. 数据存储角度</h3><p>理论上，从速度来看，分布式文件系统&gt;分布式 KV（持久化）&gt;数据库，而可靠性却截然相反，如果追求性能可以基于文件系统的顺序写。</p><h3 id="6-6-2-高可用角度"><a href="#6-6-2-高可用角度" class="headerlink" title="6.6.2. 高可用角度"></a>6.6.2. 高可用角度</h3><p>分区 + 复制 + 选举的思想</p><h3 id="6-6-3-网络框架角度"><a href="#6-6-3-网络框架角度" class="headerlink" title="6.6.3. 网络框架角度"></a>6.6.3. 网络框架角度</h3><p>选用高效的 Netty 框架，producer 同步异步发送消息，consumer 同步异步接收消息。同步能够保证结果，异步能够保证性能。</p><h2 id="6-7-有几百万消息持续积压几小时，怎么解决⭐️🔴"><a href="#6-7-有几百万消息持续积压几小时，怎么解决⭐️🔴" class="headerlink" title="6.7. 有几百万消息持续积压几小时，怎么解决⭐️🔴"></a>6.7. 有几百万消息持续积压几小时，怎么解决⭐️🔴</h2><p>发生了线上故障，几千万条数据在 MQ 里积压很久。是修复 consumer 的问题，让他恢复消费速度，然后等待几个小时消费完毕？这是个解决方案。不过有时候我们还会进行临时紧急扩容。<br>一个消费者一秒是 1000 条，一秒 3 个消费者是 3000 条，一分钟是 18 万条。1000 多万条，所以如果积压了几百万到上千万的数据，即使消费者恢复了，也需要大概 1 小时的时间才能恢复过来。<br>一般这个时候，只能操作临时紧急扩容了，具体操作步骤和思路如下：<br>先修复 consumer 的问题，确保其恢复消费速度，然后将现有 consumer 都停掉。<br><span style="background-color:#ff00ff">新建一个 topic，partition 是原来的 10 倍，临时建立好原先 10 倍或者 20 倍的 queue 数量。然后写一个临时的分发数据的 consumer 程序，这个程序部署上去消费积压的数据，消费之后不做耗时的处理，直接均匀轮询写入临时建立好的 10 倍数量的 queue。</span><br>接着临时征用 10 倍的机器来部署 consumer，每一批 consumer 消费一个临时 queue 的数据。<br>这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍，以正常的 10 倍速度来消费数据。<br>等快速消费完积压数据之后，再恢复原先部署架构，重新用原先的 consumer 机器来消费消息。</p><h2 id="6-8-什么是路由注册？RocketMQ-如何进行路由注册？"><a href="#6-8-什么是路由注册？RocketMQ-如何进行路由注册？" class="headerlink" title="6.8. 什么是路由注册？RocketMQ 如何进行路由注册？"></a>6.8. <strong>什么是路由注册？RocketMQ 如何进行路由注册？</strong></h2><p>RocketMQ 的路由注册是通过 Broker 向 NameServer 发送心跳包实现的，首先 Broker <span style="background-color:#ff00ff">每隔 30s</span> 向 NameServer 发送心跳语句，NameServer <span style="background-color:#ff00ff">每隔 10 秒</span>扫描 <code>BrokerLiveTable</code>，检查上次心跳时间与当前时间时差是否<span style="background-color:#ff00ff">超过 120 秒</span>，超过则路由踢除。<br>RocketMQ 的<span style="background-color:#ff00ff">路由发现采用的是 Pull 模型</span>。当 Topic 路由信息出现变化时，NameServer 不会主动推送给客户端，而是客户端定时拉取主题最新的路由。默认<span style="background-color:#00ff00">客户端每 30 秒会拉取一次</span>最新的路由。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404183553.png" alt="image.png"></p><p>对比 Nacos：<a href="/2023/03/11/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-6%E3%80%81Nacos/" title="服务注册与发现-6、Nacos">服务注册与发现-6、Nacos</a></p><h2 id="6-9-RocketMQ-的总体架构，以及每个组件的功能？"><a href="#6-9-RocketMQ-的总体架构，以及每个组件的功能？" class="headerlink" title="6.9. RocketMQ 的总体架构，以及每个组件的功能？"></a>6.9. <strong>RocketMQ 的总体架构，以及每个组件的功能？</strong></h2><p>RocketMQ 一共由四个部分组成：NameServer、Broker、Producer、Consumer，它们分别对应着发现、存、发、收四个功能。这四部分的功能很像邮政系统，Producer 相当于负责发送信件的发件人，Consumer 相当于负责接收信件的收件人，Broker 相当于负责暂存信件传输的邮局，NameServer 相当于负责协调各个地方邮局的管理机构。一般情况下，为了保证高可用，每一部分都是以集群形式部署的。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404193227.png" alt="image.png"></p><h2 id="6-10-讲一讲-RocketMQ-中的分布式事务及实现"><a href="#6-10-讲一讲-RocketMQ-中的分布式事务及实现" class="headerlink" title="6.10. 讲一讲 RocketMQ 中的分布式事务及实现"></a>6.10. 讲一讲 RocketMQ 中的分布式事务及实现</h2><a href="/2022/12/29/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-2%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/" title="事务-2、分布式事务">事务-2、分布式事务</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230404193818.png" alt="image.png"></p><h2 id="6-11-丢数情况"><a href="#6-11-丢数情况" class="headerlink" title="6.11. 丢数情况"></a>6.11. 丢数情况</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405144327.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405144556.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405144633.png" alt="image.png"></p><h2 id="6-12-讲一讲-RocketMQ-中事务回查机制的实现"><a href="#6-12-讲一讲-RocketMQ-中事务回查机制的实现" class="headerlink" title="6.12. 讲一讲 RocketMQ 中事务回查机制的实现"></a>6.12. 讲一讲 RocketMQ 中事务回查机制的实现</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230405180200.png" alt="image.png"></p><h2 id="6-13-订阅关系不一致问题"><a href="#6-13-订阅关系不一致问题" class="headerlink" title="6.13. 订阅关系不一致问题"></a>6.13. 订阅关系不一致问题</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230417-0901%%</span>❕ ^nsewfi</p><p>RocketMQ 同一个消费组内的消费者订阅不同 tag，会有问题吗？<br><a href="https://www.modb.pro/db/214714">https://www.modb.pro/db/214714</a><br>会出现丢消息的问题</p><h3 id="6-13-1-不一致情况分-3-种"><a href="#6-13-1-不一致情况分-3-种" class="headerlink" title="6.13.1. 不一致情况分 3 种"></a>6.13.1. 不一致情况分 3 种</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230417085929.png" alt="image.png"></p><ul><li>消费组 1 的 Consumer1 和 Consumer2 都订阅了 Topic1，但是订阅的 Tag 不一致。</li><li>消费组 2 的 Consumer1 和 Consumer2 订阅的 Topic 不一致。</li><li>消费组 3 的 Consumer1 和 Consumer2 订阅的 Topic 和 Tag 都一致，但是订阅 Tag 的顺序不一致。</li></ul><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><p><a href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/RocketMQ%20%E5%AE%9E%E6%88%98%E4%B8%8E%E8%BF%9B%E9%98%B6%EF%BC%88%E5%AE%8C%EF%BC%89/02%20RocketMQ%20%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E6%89%AB%E7%9B%B2%E7%AF%87.md****">技术文章摘抄</a></p><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230403-1840%%</span>❕ ^axhrcj</p><h2 id="8-1-尚硅谷"><a href="#8-1-尚硅谷" class="headerlink" title="8.1. 尚硅谷"></a>8.1. 尚硅谷</h2><h3 id="8-1-1-视频"><a href="#8-1-1-视频" class="headerlink" title="8.1.1. 视频"></a>8.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1cf4y157sz/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1cf4y157sz/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-1-2-资料"><a href="#8-1-2-资料" class="headerlink" title="8.1.2. 资料"></a>8.1.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">001</span>-MQ/尚硅谷分布式消息系统RocketMQ<br></code></pre></td></tr></table></figure><h2 id="8-2-黑马程序员"><a href="#8-2-黑马程序员" class="headerlink" title="8.2. 黑马程序员"></a>8.2. 黑马程序员</h2><h3 id="8-2-1-视频"><a href="#8-2-1-视频" class="headerlink" title="8.2.1. 视频"></a>8.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1L4411y7mn/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1L4411y7mn/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-2-2-资料"><a href="#8-2-2-资料" class="headerlink" title="8.2.2. 资料"></a>8.2.2. 资料</h3><p><a href="https://github.com/DillonDong/notes/tree/master/RocketMQ">https://github.com/DillonDong/notes/tree/master/RocketMQ</a></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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">005</span>-分布式专题/资料-全面解剖RocketMQ和项目实战<br></code></pre></td></tr></table></figure><h2 id="8-3-马士兵-带面试题"><a href="#8-3-马士兵-带面试题" class="headerlink" title="8.3. 马士兵 - 带面试题"></a>8.3. 马士兵 - 带面试题</h2><h3 id="8-3-1-视频-1"><a href="#8-3-1-视频-1" class="headerlink" title="8.3.1. 视频 1"></a>8.3.1. 视频 1</h3><p><a href="https://www.bilibili.com/video/BV1YT411z7GV?p=37&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1YT411z7GV?p=37&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-3-2-资料-1"><a href="#8-3-2-资料-1" class="headerlink" title="8.3.2. 资料 1"></a>8.3.2. 资料 1</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">001</span>-MQ/马士兵-MQ.docx<br></code></pre></td></tr></table></figure><h3 id="8-3-3-视频-2"><a href="#8-3-3-视频-2" class="headerlink" title="8.3.3. 视频 2"></a>8.3.3. 视频 2</h3><p><a href="https://www.bilibili.com/video/BV1kB4y1D7Q2?p=23&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1kB4y1D7Q2?p=23&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-3-4-资料-2"><a href="#8-3-4-资料-2" class="headerlink" title="8.3.4. 资料 2"></a>8.3.4. 资料 2</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">001</span>-MQ/马士兵-RocketMQ-笔记.docx<br></code></pre></td></tr></table></figure><h2 id="8-4-网络笔记"><a href="#8-4-网络笔记" class="headerlink" title="8.4. 网络笔记"></a>8.4. 网络笔记</h2><p><a href="https://www.cnblogs.com/starcrm/p/13063833.html">https://www.cnblogs.com/starcrm/p/13063833.html</a><br><a href="https://www.codenong.com/cs109783051/">https://www.codenong.com/cs109783051/</a></p>]]></content>
      
      
      <categories>
          
          <category> MQ </category>
          
      </categories>
      
      
        <tags>
            
            <tag> RocketMQ </tag>
            
            <tag> MQ </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式专题-2、分布式事务</title>
      <link href="/2022/12/29/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-2%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/"/>
      <url>/2022/12/29/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E4%BA%8B%E5%8A%A1-2%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/</url>
      
        <content type="html"><![CDATA[<h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221230211206.png"></p><p>分布式系统会把一个应用系统拆分为可独立部署的多个服务，因此需要服务与服务之间远程协作才能完成事务操作，这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务，例如用户注册送积分 事务、创建订单减库存事务，银行转账事务等都是分布式事务。</p><h1 id="2-产生的场景"><a href="#2-产生的场景" class="headerlink" title="2. 产生的场景"></a>2. 产生的场景</h1><p>典型的场景就是微服务架构 <span style="background-color:#00ff00">微服务之间通过远程调用完成事务操作</span>。 比如：订单微服务和库存微服务，下单的同时订单微服务请求库存微服务减库存。 简言之：跨 JVM 进程产生分布式事务。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221230211316.png"></p><p><span style="background-color:#00ff00">单体系统访问多个数据库实例</span> 当单体系统需要访问多个数据库（实例）时就会产生分布式事务。 比如：用户信息和订单信息分别在两个 MySQL 实例存储，用户管理系统删除用户信息，需要分别删除用户信息及用户的订单信 息，由于数据分布在不同的数据实例，需要通过不同的数据库链接去操作数据，此时产生分布式事务。 简言之：跨 数据库实例产生分布式事务。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221230211415.png"></p><p><span style="background-color:#00ff00">多服务访问同一个数据库实例</span> 比如：订单微服务和库存微服务即使访问同一个数据库也会产生分布式事务，原 因就是跨 JVM 进程，两个微服务持有了不同的数据库链接进行数据库操作，此时产生分布式事务。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221230211437.png"></p><h1 id="3-基础理论"><a href="#3-基础理论" class="headerlink" title="3. 基础理论"></a>3. 基础理论</h1><h2 id="3-1-CAP-理论"><a href="#3-1-CAP-理论" class="headerlink" title="3.1. CAP 理论"></a>3.1. CAP 理论</h2><p>CAP 是 Consistency、Availability、Partition tolerance 三个词语的缩写，分别表示一致性、可用性、分区容忍性。</p><h3 id="3-1-1-CAP-组合方式"><a href="#3-1-1-CAP-组合方式" class="headerlink" title="3.1.1. CAP 组合方式"></a>3.1.1. CAP 组合方式</h3><p>1）AP： 放弃一致性，追求分区容忍性和可用性。这是很多分布式系统设计时的选择。 例如： 上边的商品管理，完全可以实现 AP，前提是只要用户可以接受所查询的到数据在一定时间内不是最新的即可。 通常实现 AP 都会保证最终一致性，后面讲的 BASE 理论就是根据 AP 来扩展的，一些业务场景 比如：订单退款，今 日退款成功，明日账户到账，只要用户可以接受在一定时间内到账即可。</p><p>2）CP： 放弃可用性，追求一致性和分区容错性，我们的 zookeeper 其实就是追求的强一致，又比如跨行转账，一次转账请 求要等待双方银行系统都完成整个事务才算完成。</p><p>3）CA： 放弃分区容忍性，即不进行分区，不考虑由于网络不通或结点挂掉的问题，则可以实现一致性和可用性。那么系统 将不是一个标准的分布式系统，我们最常用的关系型数据就满足了 CA。</p><h3 id="3-1-2-总结"><a href="#3-1-2-总结" class="headerlink" title="3.1.2. 总结"></a>3.1.2. 总结</h3><p>通过上面我们已经学习了 CAP 理论的相关知识，CAP 是一个已经被证实的理论：一个分布式系统最多只能同时满足 一致性（Consistency）、可用性（Availability）和分区容忍性（Partition tolerance）这三项中的两项。它可以作 为我们进行架构设计、技术选型的考量标准。对于多数大型互联网应用的场景，结点众多、部署分散，而且现在的 集群规模越来越大，所以节点故障、网络故障是常态，而且要保证服务可用性达到 N 个 9（99.99..%），并要达到良 好的响应性能来提高用户体验，因此一般都会做出如下选择：保证 P 和 A，舍弃 C 强一致，保证最终一致性。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230316224037.png" alt="image.png"></p><h2 id="3-2-BASE-理论"><a href="#3-2-BASE-理论" class="headerlink" title="3.2. BASE 理论"></a>3.2. BASE 理论</h2><h3 id="3-2-1-强一致性和最终一致性"><a href="#3-2-1-强一致性和最终一致性" class="headerlink" title="3.2.1. 强一致性和最终一致性"></a>3.2.1. 强一致性和最终一致性</h3><p>CAP 理论告诉我们一个分布式系统最多只能同时满足一致性（Consistency）、可用性（Availability）和分区容忍性（Partition tolerance）这三项中的两项，<span style="background-color:#00ff00">其中 AP 在实际应用中较多，AP 即舍弃一致性，保证可用性和分区容忍性</span>，但是在实际生产中很多场景都要实现一致性，比如前边我们举的例子主数据库向从数据库同步数据，即使不要一致性，但是最终也要将数据同步成功来保证数据一致，这种一致性和 CAP 中的一致性不同，CAP 中的一致性要求 在任何时间查询每个结点数据都必须一致，它强调的是强一致性，但是最终一致性是<span style="background-color:#00ff00">允许可以在一段时间内每个结点的数据不一致，但是经过一段时间每个结点的数据必须一致</span>，它强调的是最终数据的一致性。</p><h3 id="3-2-2-Base-理论"><a href="#3-2-2-Base-理论" class="headerlink" title="3.2.2. Base 理论"></a>3.2.2. Base 理论</h3><p>BASE 是 <strong>Basically Available</strong>(基本可用)、<strong>Soft state</strong>(软状态) 和 <strong>Eventually consistent</strong> (最终一致性) 三个短语的缩写。<span style="background-color:#00ff00">BASE 理论是对 CAP 中 AP 的一个扩展，通过牺牲强一致性来获得可用性，当出现故障允许部分不可用但要保证核心功能可用，允许数据在一段时间内是不一致的，但最终达到一致状态。满足 BASE 理论的事务，我们称之为“柔性事务”</span>。</p><p><strong>基本可用</strong>: 分布式系统在出现故障时，允许损失部分可用功能，保证核心功能可用。        如，电商网站交易付款出现问题了，商品依然可以正常浏览。<br><strong>软状态</strong>: 由于不要求强一致性，所以 BASE 允许系统中存在中间状态（也叫软状态），这个状态不影响系统可用性，如订单的 “ 支付中 “、“数据同步中”等状态，待数据最终一致后状态改为“成功”状态。<br><strong>最终一致</strong>: 最终一致是指经过一段时间后，所有节点数据都将会达到一致。如订单的 “ 支付中 “ 状态，最终会变为“支付成功”或者 “ 支付失败 “，使订单状态与实际交易结果达成一致，但需要一定时间的延迟、等待。</p><h1 id="4-分布式事务解决方案"><a href="#4-分布式事务解决方案" class="headerlink" title="4. 分布式事务解决方案"></a>4. 分布式事务解决方案</h1><p><span style="display:none">%%<br>▶20.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230228-2210%%</span> ^7zc3an</p><h2 id="4-1-2PC"><a href="#4-1-2PC" class="headerlink" title="4.1. 2PC"></a>4.1. 2PC</h2><h3 id="4-1-1-什么是-2PC"><a href="#4-1-1-什么是-2PC" class="headerlink" title="4.1.1. 什么是 2PC"></a>4.1.1. 什么是 2PC</h3><p>2PC 即两阶段提交协议，是将整个事务流程分为两个阶段，准备阶段（Prepare phase）、提交阶段（commit phase），2 是指两个阶段，P 是指准备阶段，C 是指提交阶段。</p><p>在计算机中部分关系数据库如 Oracle、MySQL 支持两阶段提交协议，如下图：</p><ol><li><p>准备阶段（Prepare phase）：事务管理器给每个参与者发送 Prepare 消息，每个数据库参与者在本地执行事务，并写本地的 Undo&#x2F;Redo 日志，此时事务没有提交。 （Undo 日志是记录修改前的数据，用于数据库回滚，Redo 日志是记录修改后的数据，用于提交事务后写入数据文件）</p></li><li><p>提交阶段（commit phase）：如果事务管理器收到了参与者的执行失败或者超时消息时，直接给每个参与者发送回滚 (Rollback) 消息；否则，发送提交 (Commit) 消息；参与者根据事务管理器的指令执行提交或者回滚操作，并释放事务处理过程中使用的锁资源。<span style="background-color:red">注意: 必须在最后阶段释放锁资源</span>。下图展示了 2PC 的两个阶段，分成功和失败两个情况说明：</p><p>成功情况：</p></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231120954.png"></p><p>  失败情况：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231121044.png"></p><h3 id="4-1-2-2PC-实现方案"><a href="#4-1-2-2PC-实现方案" class="headerlink" title="4.1.2. 2PC 实现方案"></a>4.1.2. 2PC 实现方案</h3><h4 id="4-1-2-1-XA-方案"><a href="#4-1-2-1-XA-方案" class="headerlink" title="4.1.2.1. XA 方案"></a>4.1.2.1. XA 方案</h4><p>2PC 的传统方案是在数据库层面实现的，如 Oracle、MySQL 都支持 2PC 协议。为了让大家更明确 XA 方案的内容程，下面新用户注册送积分为例来说明：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231122224.png"></p><h5 id="4-1-2-1-1-DTP-模型角色"><a href="#4-1-2-1-1-DTP-模型角色" class="headerlink" title="4.1.2.1.1. DTP 模型角色"></a>4.1.2.1.1. DTP 模型角色</h5><p>**AP(Application Program)**：即应用程序，可以理解为使用 DTP 分布式事务的程序。<br>**RM(Resource Manager)**：即资源管理器，可以理解为事务的参与者，<span style="background-color:#00ff00">一般情况下是指一个数据库实例</span>，通过资源管理器对该数据库进行控制，<span style="background-color:#00ff00">资源管理器控制着分支事务</span>。<br>**TM(Transaction Manager)**：事务管理器，负责协调和管理事务，事务管理器控制着全局事务，管理事务生命周期，并协调各个 RM。全局事务是指分布式事务处理环境中，需要操作多个数据库共同完成一个工作，这个工作即是一个全局事务。</p><p>DTP 模型定义 TM 和 RM 之间通讯的接口规范叫 XA，<span style="background-color:#00ff00">简单理解为数据库提供的 2PC 接口协议</span>，基于数据库的 XA 协议来实现 2PC 又称为 XA 方案。</p><p>以上三个角色之间的交互方式如下：</p><p>1）TM 向 AP 提供应用程序编程接口，<strong>AP 通过 TM 提交及回滚事务</strong>。<br>2）TM 交易中间件 <strong>通过 XA 接口来通知 RM 数据库</strong> 事务的开始、结束以及提交、回滚等。</p><p>总结： 整个 2PC 的事务流程涉及到三个角色 AP、RM、TM。AP 指的是使用 2PC 分布式事务的应用程序；RM 指的是资源管理器，它控制着分支事务；TM 指的是事务管理器，它控制着整个全局事务。</p><h5 id="4-1-2-1-2-执行流程如下"><a href="#4-1-2-1-2-执行流程如下" class="headerlink" title="4.1.2.1.2. 执行流程如下"></a>4.1.2.1.2. 执行流程如下</h5><p>1、 应用程序（AP）持有用户库和积分库两个数据源。<br>2、应用程序（AP）通过 TM 通知用户库 RM 新增用户，同时通知积分库 RM 为该用户新增积分，RM 此时并未提交事务，此时<span style="background-color:#ff00ff">用户和积分资源锁定</span>。<br>3、TM 收到执行回复，只要有一方失败则分别向其他 RM 发起回滚事务，回滚完毕，资源锁释放。<br>4、TM 收到执行回复，全部成功，此时向所有 RM 发起提交事务，提交完毕，资源锁释放。</p><h5 id="4-1-2-1-3-XA-方案的问题"><a href="#4-1-2-1-3-XA-方案的问题" class="headerlink" title="4.1.2.1.3. XA 方案的问题"></a>4.1.2.1.3. XA 方案的问题</h5><p><span style="background-color:#ffff00">1、需要本地数据库支持 XA 协议。</span><br><span style="background-color:#ffff00">2、资源锁需要等到两个阶段结束才释放，性能较差。</span></p><h4 id="4-1-2-2-Seata-方案"><a href="#4-1-2-2-Seata-方案" class="headerlink" title="4.1.2.2. Seata 方案"></a>4.1.2.2. Seata 方案</h4><h5 id="4-1-2-2-1-什么是-Seata"><a href="#4-1-2-2-1-什么是-Seata" class="headerlink" title="4.1.2.2.1. 什么是 Seata"></a>4.1.2.2.1. 什么是 Seata</h5><p>Seata 是由阿里中间件团队发起的开源项目 Fescar，后更名为 Seata，它是一个是开源的分布式事务框架。 传统 2PC 的问题在 Seata 中得到了解决，<span style="background-color:#00ff00">它通过对本地关系数据库的分支事务的协调来驱动完成全局事务</span>，工作在应用层的中间件。主要优点是<span style="background-color:#00ff00">性能较好，且不长时间占用连接资源，它以高效并且对业务 0 侵入的方式解决微服务场景下面临的分布式事务问题</span>，它目前提供<span style="background-color:#ffff00">AT 模式 (即 2PC) 及 TCC 模式</span>的分布式事务解决方案。</p><h5 id="4-1-2-2-2-Seata-原理"><a href="#4-1-2-2-2-Seata-原理" class="headerlink" title="4.1.2.2.2. Seata 原理"></a>4.1.2.2.2. Seata 原理</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231133229.png"><br>**Transaction Coordinator (TC)**： 事务协调器，<span style="background-color:#ff00ff">它是独立的中间件，需要独立部署运行</span>，它维护全局事务的运行状态，接收 TM 指令发起全局事务的提交与回滚，负责与 RM 通信协调各个分支事务的提交或回滚。<br>**Transaction Manager (TM)**： 事务管理器，<span style="background-color:#00ff00">TM 需要嵌入应用程序中工作</span>，它负责开启一个全局事务，并<span style="background-color:#ff00ff">最终向 TC 发起全局提交或全局回滚的指令</span>。<br>**Resource Manager (RM)**： 控制分支事务，负责分支注册、状态汇报，并接收事务协调器 TC 的指令，驱动分支（本地）事务的提交和回滚。</p><h5 id="4-1-2-2-3-流程分析"><a href="#4-1-2-2-3-流程分析" class="headerlink" title="4.1.2.2.3. 流程分析"></a>4.1.2.2.3. 流程分析</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231133840.png"></p><p>具体的执行流程如下：</p><ol><li>用户服务的 TM 向 TC 申请开启一个全局事务，全局事务创建成功并生成一个全局唯一的 XID。</li><li>用户服务的 RM 向 TC 注册 分支事务，该分支事务在用户服务执行新增用户逻辑，并将其纳入 XID 对应全局事务的管辖。</li><li>用户服务执行分支事务，向用户表插入一条记录。</li><li>逻辑执行到<span style="background-color:#00ff00">远程调用积分服务</span>时 (<strong>XID 在微服务调用链路的上下文中传播</strong>)。积分服务的 RM 向 TC 注册分支事务，该分支事务执行增加积分的逻辑，并将其纳入 XID 对应全局事务的管辖。</li><li>积分服务执行分支事务，向积分记录表插入一条记录，执行完毕后，返回用户服务。</li><li>至此用户服务分支事务执行完毕。</li><li>TM 向 TC 发起针对 XID 的全局提交或回滚决议。</li><li>TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。</li></ol><p>详细流程见：<a href="/2023/06/12/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-6%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BB%84%E4%BB%B6/" title="面试专题-6、分布式组件">面试专题-6、分布式组件</a></p><h5 id="4-1-2-2-4-传统-2PC-的差别⭐️🔴"><a href="#4-1-2-2-4-传统-2PC-的差别⭐️🔴" class="headerlink" title="4.1.2.2.4. 传统 2PC 的差别⭐️🔴"></a>4.1.2.2.4. 传统 2PC 的差别⭐️🔴</h5><p><a href="https://www.bilibili.com/video/BV1FJ411A7mV?p=9&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1FJ411A7mV?p=9&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><ol><li>架构层次方面，传统 2PC 方案的 RM 实际上是在数据库层，RM 本质上就是数据库自身，通过 XA 协议实现，而 Seata 的 <span style="background-color:#ffff00">RM(TM) 是以 jar 包的形式作为中间件层部署在应用程序这一侧</span>的。❕<span style="display:none">%%<br>▶3.🏡⭐️◼️理解传统 2PC 与 seata AT 模式的区别 ?🔜MSTM📝 AT 模式中传统 2PC TM 的事务协调工作交给了 TC，TM 负责开启全局事务。以 jar 包的方式嵌入的应用中◼️⭐️-point-20230228-1639%%</span></li><li>两阶段提交方面，传统 2PC 无论第二阶段的决议是 commit 还是 rollback，事务性资源的锁都要保持到 Phase2 完成才释放。<span style="background-color:#ff00ff">而 Seata 的做法是在 Phase1 就将本地事务提交，这样就可以省去 Phase2 持锁的时间，整体提高效</span></li></ol><p><a href="https://www.jianshu.com/p/044e95223a17">https://www.jianshu.com/p/044e95223a17</a></p><p><strong>XA 方案的 RM 实际上是在数据库层，RM 本质上就是数据库自身（通过提供支持 XA 的驱动程序来供应用使用）。而 Seata 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的，不依赖与数据库本身对协议的支持，当然也不需要数据库支持 XA 协议</strong>。这点对于微服务化的架构来说是非常重要的：应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。</p><p>另外，**XA 方案无论 Phase2 的决议是 commit 还是 rollback，事务性资源的锁都要保持到 Phase2 完成才释放。而对于 Seata，将锁分为了本地锁和全局锁，本地锁由本地事务管理，在分支事务 Phase1 结束时就直接释放。而全局锁由 TC 管理，在决议 Phase2 全局提交时，全局锁马上可以释放。只有在决议全局回滚的情况下，全局锁才被持有至分支的 Phase2 结束。因此，Seata 对于资源的占用时间要少的多。</p><h5 id="4-1-2-2-5-使用要素"><a href="#4-1-2-2-5-使用要素" class="headerlink" title="4.1.2.2.5. 使用要素"></a>4.1.2.2.5. 使用要素</h5><ol><li>全局事务开始使用 @GlobalTransactional 标识 。</li><li>每个本地事务方案仍然使用@Transactional 标识。</li><li>每个数据都需要创建 undo_log 表，此表是 seata 保证本地事务一致性的关键。</li></ol><h2 id="4-2-TCC"><a href="#4-2-TCC" class="headerlink" title="4.2. TCC"></a>4.2. TCC</h2><h3 id="4-2-1-什么是-TCC-事务"><a href="#4-2-1-什么是-TCC-事务" class="headerlink" title="4.2.1. 什么是 TCC 事务"></a>4.2.1. 什么是 TCC 事务</h3><p>TCC 是 Try、Confirm、Cancel 三个词语的缩写，TCC 要求每个分支事务实现三个操作：预处理 Try、确认 Confirm、撤销 Cancel。Try 操作做业务检查及资源预留，Confirm 做业务确认操作，Cancel 实现一个与 Try 相反的操作即回滚操作。TM 首先发起所有的分支事务的 try 操作，任何一个分支事务的 try 操作执行失败，TM 将会发起所有分支事务的 Cancel 操作，若 try 操作全部成功，TM 将会发起所有分支事务的 Confirm 操作，其中 Confirm&#x2F;Cancel 操作若执行失败，TM 会进行重试。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228152526.png" alt="image.png"></p><p><strong>TCC 分为三个阶段</strong>：</p><ol><li>Try 阶段是做业务检查 (一致性) 及资源预留 (隔离)，此阶段仅是一个初步操作，它和后续的 Confirm 一起才能真正构成一个完整的业务逻辑。</li><li>Confirm 阶段是做确认提交，Try 阶段所有分支事务执行成功后开始执行 Confirm。通常情况下，采用 TCC 则认为 Confirm 阶段是不会出错的。即：只要 Try 成功，Confirm 一定成功。若 Confirm 阶段真的出错了，需引入重试机制或人工处理。</li><li>Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消，预留资源释放。通常情况下，采用 TCC 则认为 Cancel 阶段也是一定成功的。若 Cancel 阶段真的出错了，需引入重试机制或人工处理。</li></ol><p>TM 事务管理器可以实现为独立的服务，也可以让全局事务发起方充当 TM 的角色，TM 独立出来是为了成为公用组件，是为了考虑系统结构和软件复用。</p><p>TM 在发起全局事务时生成全局事务记录，全局事务 ID 贯穿整个分布式事务调用链条，用来记录事务上下文，追踪和记录状态，由于 Confirm 和 cancel 失败需进行重试，因此需要实现为幂等，幂等性是指同一个操作无论请求多少次，其结果都相同。</p><h3 id="4-2-2-TCC-需要注意三种异常处理分别是空回滚、幂等、悬挂"><a href="#4-2-2-TCC-需要注意三种异常处理分别是空回滚、幂等、悬挂" class="headerlink" title="4.2.2. TCC 需要注意三种异常处理分别是空回滚、幂等、悬挂"></a>4.2.2. TCC 需要注意三种异常处理分别是空回滚、幂等、悬挂</h3><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1110%%</span>❕ ^4qeujw</p><h4 id="4-2-2-1-空回滚"><a href="#4-2-2-1-空回滚" class="headerlink" title="4.2.2.1. 空回滚"></a>4.2.2.1. 空回滚</h4><p><span style="background-color:#ff00ff">在没有调用 TCC 资源 Try 方法的情况下，调用了二阶段的 Cancel 方法</span>，Cancel 方法需要识别出这是一个空回滚，然后直接返回成功。<span style="background-color:#00ff00">出现原因是当一个分支事务所在服务宕机或网络异常，分支事务调用记录为失败，这个时候其实是没有执行 Try 阶段，当故障恢复后，分布式事务进行回滚则会调用二阶段的 Cancel 方法，从而形成空回滚。</span>解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行，如果执行了，那就是正常回滚；如果没执行，那就是空回滚。前面已经说过 TM 在发起全局事务时生成全局事务记录，全局事务 ID 贯穿整个分布式事务调用链条。<span style="background-color:#00ff00">再额外增加一张分支事务记录表，其中有全局事务 ID 和分支事务 ID，第一阶段 Try 方法里会插入一条记录，表示一阶段执行了。</span>Cancel 接口里读取该记录，如果该记录存在，则正常回滚；如果该记录不存在，则是空回滚。</p><h4 id="4-2-2-2-幂等"><a href="#4-2-2-2-幂等" class="headerlink" title="4.2.2.2. 幂等"></a>4.2.2.2. 幂等</h4><p>通过前面介绍已经了解到，为了保证 TCC 二阶段提交重试机制不会引发数据不一致，要求 TCC 的二阶段 Try、 Confirm 和 Cancel 接口保证幂等，这样不会重复使用或者释放资源。如果幂等控制没有做好，很有可能导致数据不一致等严重问题。<span style="background-color:#ff00ff">解决思路在上述“分支事务记录”中增加执行状态，每次执行前都查询该状态。</span></p><h4 id="4-2-2-3-悬挂"><a href="#4-2-2-3-悬挂" class="headerlink" title="4.2.2.3. 悬挂"></a>4.2.2.3. 悬挂</h4><p>悬挂就是对于一个分布式事务，其<span style="background-color:#ff00ff">二阶段 Cancel 接口比 Try 接口先执行</span>。 出现原因是在 RPC 调用分支事务 try 时，先注册分支事务，再执行 RPC 调用，如果此时 RPC 调用的网络发生拥堵， 通常 RPC 调用是有超时时间的，RPC 超时以后，TM 就会通知 RM 回滚该分布式事务，可能回滚完成后，RPC 请求才到达参与者真正执行，而一个 Try 方法预留的业务资源，只有该分布式事务才能使用，该分布式事务第一阶段预留的业务资源就再也没有人能够处理了，对于这种情况，我们就称为悬挂，即业务资源预留后没法继续处理。 解决思路是如果二阶段执行完成，那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下，“分支 事务记录”表中是否已经有二阶段事务记录，如果有则不执行 Try。</p><h3 id="4-2-3-小结"><a href="#4-2-3-小结" class="headerlink" title="4.2.3. 小结"></a>4.2.3. 小结</h3><p>如果拿 TCC 事务的处理流程与 2PC 两阶段提交做比较，<span style="background-color:#00ff00">2PC 通常都是在跨库的 DB 层面，而 TCC 则在应用层面的处理，需要通过业务逻辑来实现</span>。这种分布式事务的实现方式的优势在于，可以让应用自己定义数据操作的粒度，使得降低锁冲突、提高吞吐量成为可能。</p><p>而不足之处则在于<span style="background-color:#ffff00">对应用的侵入性非常强，业务逻辑的每个分支都需要实现 try、confifirm、cancel 三个操作</span>。此外，其实现难度也比较大，需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。</p><h3 id="4-2-4-使用案例-Hmily"><a href="#4-2-4-使用案例-Hmily" class="headerlink" title="4.2.4. 使用案例 Hmily"></a>4.2.4. 使用案例 Hmily</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228200919.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230228200953.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302070252.png" alt="image.png"></p><h2 id="4-3-可靠消息最终一致性"><a href="#4-3-可靠消息最终一致性" class="headerlink" title="4.3. 可靠消息最终一致性"></a>4.3. 可靠消息最终一致性</h2><h3 id="4-3-1-可靠消息最终一致性事务"><a href="#4-3-1-可靠消息最终一致性事务" class="headerlink" title="4.3.1. 可靠消息最终一致性事务"></a>4.3.1. 可靠消息最终一致性事务</h3><p>可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息，事务参与方 (消息消费者)<span style="background-color:#00ff00">一定能够接收消息并处理事务成功，此方案强调的是只要消息发给事务参与方最终事务要达到一致。</span></p><p>此方案是利用消息中间件完成，如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231162219.png"></p><p>事务发起方（消息生产方）将消息发给消息中间件，事务参与方从消息中间件接收消息，事务发起方和消息中间件之间，事务参与方（消息消费方）和消息中间件之间都是通过网络通信，由于网络通信的不确定性会导致分布式事务问题。</p><p>因此可靠消息最终一致性方案要解决以下几个问题：</p><ol><li><p>本地事务与消息发送的原子性问题<br>   本地事务与消息发送的原子性问题即：事务发起方在本地事务执行成功后消息必须发出去，否则就丢弃消息。即实现本地事务和消息发送的原子性，要么都成功，要么都失败。<span style="background-color:#ff0000">本地事务与消息发送的原子性问题</span>是实现可靠消息最终一致性方案的关键问题。</p></li><li><p>事务参与方接收消息的可靠性<br>   事务参与方必须能够从消息队列接收到消息，如果接收消息失败可以重复接收消息。</p></li><li><p>消息重复消费的问题<br>   由于网络 2 的存在，若某一个消费节点超时但是消费成功，此时消息中间件会重复投递此消息，就导致了消息的重复消费。 要解决消息重复消费的问题<span style="background-color:#ffff00">就要实现事务参与方的方法幂等性</span>。</p></li></ol><h3 id="4-3-2-解决方案"><a href="#4-3-2-解决方案" class="headerlink" title="4.3.2. 解决方案"></a>4.3.2. 解决方案</h3><h4 id="4-3-2-1-本地消息表方案"><a href="#4-3-2-1-本地消息表方案" class="headerlink" title="4.3.2.1. 本地消息表方案"></a>4.3.2.1. 本地消息表方案</h4><p>本地消息表这个方案最初是 eBay 提出的，此方案的核心是<span style="background-color:#00ff00">通过本地事务保证数据业务操作和消息的一致性</span>，具体过程：通过 <strong>定时任务</strong> 将消息发送至消息中间件，待确认消息发送给消费方成功再将消息删除。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231163152.png"></p><h4 id="4-3-2-2-RocketMQ"><a href="#4-3-2-2-RocketMQ" class="headerlink" title="4.3.2.2. RocketMQ"></a>4.3.2.2. RocketMQ</h4><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230404-1936%%</span>❕ ^8uae29</p><h5 id="4-3-2-2-1-事务消息是什么"><a href="#4-3-2-2-1-事务消息是什么" class="headerlink" title="4.3.2.2.1. 事务消息是什么"></a>4.3.2.2.1. 事务消息是什么</h5><p>RocketMQ 事务消息设计则主要是为了<span style="background-color:#ff00ff">解决 Producer 端的消息发送与本地事务执行的原子性问题</span>，RocketMQ 的 设计中 <span style="background-color:#ff0000">broker 与 producer 端的双向通信能力</span>，使得 broker 天生可以作为一个事务协调者存在；而 RocketMQ 本身提供的存储机制为事务消息提供了持久化能力；RocketMQ 的<span style="background-color:#00ff00">高可用机制以及可靠消息设计</span>则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。<br>在 RocketMQ 4.3 后实现了完整的事务消息，<span style="background-color:#00ff00">实际上是对本地消息表的一个封装，将本地消息表移动到了 MQ 内部</span>，<span style="background-color:#00ff00">解决 Producer 端的消息发送与本地事务执行的原子性问题</span>。</p><h5 id="4-3-2-2-2-执行流程"><a href="#4-3-2-2-2-执行流程" class="headerlink" title="4.3.2.2.2. 执行流程"></a>4.3.2.2.2. 执行流程</h5><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230228-1946%%</span> ^2z2afv<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231174316.png"></p><p>为方便理解我们还以注册送积分的例子来描述整个流程。 Producer 即 MQ 发送方，本例中是用户服务，负责新增用户。MQ 订阅方即消息消费方，本例中是积分服务，负责新增积分。</p><p>1、Producer 发送事务消息<br>     Producer （MQ 发送方）发送事务消息至 MQ Server，MQ Server 将消息状态标记为<span style="background-color:#00ff00">Prepared（预备状态）</span>，注意此时这条消息消费者（MQ 订阅方）是无法消费到的。<br>    本例中，Producer 发送 ”增加积分消息“ 到 MQ Server。</p><p>2、MQ Server 回应消息发送成功<br>  MQ Server 接收到 Producer 发送给的消息则回应发送成功表示 MQ 已接收到消息。</p><p>3、Producer 执行本地事务⭐️🔴<br><span style="display:none">%%<br>▶13.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1216%%</span>❕ ^x1o35a</p><pre><code>Producer 端执行业务代码逻辑，通过本地数据库事务控制。本例中，Producer 执行添加用户操作。</code></pre><p>RoacketMQ 提供 RocketMQLocalTransactionListener 接口。实现该方案时，<span style="background-color:#ff00ff">需要编写 RocketMQLocalTransactionListener 接口实现类，实现执行本地事务和事务回查两个方法。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230301103952.png" alt="image.png"></p><p>4、消息投递⭐️🔴<br>    若 Producer 本地事务执行成功则自动向 MQServer 发送<span style="background-color:#00ff00">commit 消息</span>，MQ Server 接收到 commit 消息后将”增加积分消息“ 状态标记为 <strong>可消费</strong>，此时 MQ 订阅方（积分服务）即正常消费消息；</p><p>若 Producer 本地事务执行失败则<span style="background-color:#ff00ff">自动向 MQServer 发送 rollback 消息</span>，MQ Server 接收到 rollback 消息后 将<span style="background-color:#ffff00">删除”增加积分消息“</span> 。</p><p>MQ 订阅方（积分服务）消费消息，<span style="background-color:#ff00ff">消费成功则向 MQ 回应 ack，否则将重复接收消息。</span>这里 ack 默认自动回应，即程序执行正常则自动回应 ack。</p><p>5、事务回查⭐️🔴</p><p>如果执行 Producer 端本地事务过程中，执行端挂掉，或者超时，MQ Server 将会不停的<span style="background-color:#ff00ff">询问同组的其他 Producer</span>来获取事务执行状态，这个过程叫事务回查。MQ Server 会根据事务回查结果来决定是否投递消息。</p><p>以上主干流程已由 RocketMQ 实现，对用户侧来说，用户需要分别实现<span style="background-color:#00ff00">本地事务执行以及本地事务回查方法</span>，因此只需关注本地事务的执行状态即可。</p><h5 id="4-3-2-2-3-小结"><a href="#4-3-2-2-3-小结" class="headerlink" title="4.3.2.2.3. 小结"></a>4.3.2.2.3. 小结</h5><p>可靠消息最终一致性就是保证消息从生产方经过消息中间件传递到消费方的一致性，本案例使用了 RocketMQ 作为消息中间件，RocketMQ 主要解决了两个功能：<br><span style="background-color:#ffff00">1、本地事务与消息发送的原子性问题。 </span><br><span style="background-color:#ffff00">2、事务参与方接收消息的可靠性。 </span></p><p>可靠消息最终一致性事务适合<span style="background-color:#ff00ff">执行周期长且实时性要求不高的场景</span>。引入消息机制后，<strong>同步的事务操作变为基于消息的异步执行操作</strong>, 避免了分布式事务中的同步阻塞操作的影响，并实现了两个服务的解耦。</p><h2 id="4-4-最大努力通知"><a href="#4-4-最大努力通知" class="headerlink" title="4.4. 最大努力通知"></a>4.4. 最大努力通知</h2><h3 id="4-4-1-是什么"><a href="#4-4-1-是什么" class="headerlink" title="4.4.1. 是什么"></a>4.4.1. 是什么</h3><p>目标：发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。<br>具体包括：<br>1、有一定的消息重复通知机制。 因为接收通知方可能没有接收到通知，此时要有一定的机制对消息重复通知。<br>2、消息校对机制。 如果尽最大努力也没有通知到接收方，或者接收方消费消息后要再次消费，此时可由接收方主动向通知方查询消息信息来满足需求。</p><h3 id="4-4-2-解决方案"><a href="#4-4-2-解决方案" class="headerlink" title="4.4.2. 解决方案"></a>4.4.2. 解决方案</h3><p>通过对最大努力通知的理解，采用 MQ 的<span style="background-color:#00ff00">ack 机制</span>就可以实现最大努力通知。</p><h4 id="4-4-2-1-方案-1-企业内部应用"><a href="#4-4-2-1-方案-1-企业内部应用" class="headerlink" title="4.4.2.1. 方案 1- 企业内部应用"></a>4.4.2.1. 方案 1- 企业内部应用</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231183254.png"></p><p>本方案是利用 MQ 的 ack 机制<span style="background-color:#ff00ff">由 MQ 向接收通知方发送通知</span>，流程如下：</p><p>1、发起通知方将通知发给 MQ。<br>使用普通消息机制将通知发给 MQ。<br>注意：如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。（后边会讲）<br>2、接收通知方 <strong>监听 MQ</strong>。<br>3、接收通知方接收消息，业务处理完成回应 ack。<br>4、接收通知方 <strong>若没有回应 ack 则 MQ 会重复通知</strong>。<br>MQ 会按照间隔 1min、5min、10min、30min、1h、2h、5h、10h 的方式，逐步拉大通知间隔 （如果 MQ 采用 RocketMQ，在 broker 中可进行配置），直到达到通知要求的时间窗口上限。<br>5、接收通知方可通过消息校对接口来校对消息的一致性。</p><h4 id="4-4-2-2-方案-2-通知外部应用"><a href="#4-4-2-2-方案-2-通知外部应用" class="headerlink" title="4.4.2.2. 方案 2- 通知外部应用"></a>4.4.2.2. 方案 2- 通知外部应用</h4><p>本方案也是利用 MQ 的 ack 机制，与方案 1 不同的是<span style="background-color:#ff00ff">由应用程序向接收通知方发送通知</span>，如下图：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221231183847.png"></p><p><strong>方案 1 和方案 2 的不同点</strong></p><p>1、方案 1 中接收通知方与 MQ 接口，即 <strong>接收通知方可以案监听 MQ</strong>，此方案<span style="background-color:#00ff00">主要应用于同一个企业内部应用之间的通知</span>。</p><p>2、方案 2 中由通知程序与 MQ 接口，<span style="background-color:#ffff00">通知程序监听 MQ</span>，收到 MQ 的消息后由通知程序通过互联网接口协议调用接收通知方。此方案<span style="background-color:#00ff00">主要应用于外部应用之间的通知，例如支付宝、微信的支付结果通知。</span></p><h3 id="4-4-3-最大努力通知与可靠消息一致性有什么不同"><a href="#4-4-3-最大努力通知与可靠消息一致性有什么不同" class="headerlink" title="4.4.3. 最大努力通知与可靠消息一致性有什么不同"></a>4.4.3. 最大努力通知与可靠消息一致性有什么不同</h3><p>1、解决方案思想不同</p><p>可靠消息一致性，发起通知方需要保证将消息发出去，并且将消息发到接收通知方，<span style="background-color:#00ff00">消息的可靠性关键由<font color=#ff0000>发起通知方</font>来保证</span>。</p><p>最大努力通知，发起通知方尽最大的努力将业务处理结果通知为接收通知方，但是可能消息接收不到，此时需要接收通知方主动调用发起通知方的接口查询业务处理结果，<span style="background-color:#00ff00">通知的可靠性关键在<font color=#ff0000>接收通知方</font></span>。</p><p>2、两者的业务应用场景不同</p><p>可靠消息一致性关注的是<span style="background-color:#ff00ff">交易过程的事务一致，以异步的方式完成交易</span>。<br>最大努力通知关注的是<span style="background-color:#ff00ff">交易后的通知事务，即将交易结果可靠的通知出去</span>。</p><p>3、技术解决方向不同</p><p>可靠消息一致性要解决消息从发出到接收的一致性，即消息发出并且被接收到。</p><p>最大努力通知无法保证消息从发出到接收的一致性，只提供消息接收的可靠性机制。可靠机制是，最大努力的将消息通知给接收方，当消息无法被接收方接收时，由接收方主动查询消息（业务处理结果）</p><h1 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h1><p>分布式事务对比分析: 在学习各种分布式事务的解决方案后，我们了解到各种方案的优缺点：<br>2PC 最大的诟病是一个阻塞协议。RM 在执行分支事务后需要等待 TM 的决定，此时服务会阻塞并锁定资源。由于其阻塞机制和最差时间复杂度高，因此，这种设计不能适应随着事务涉及的服务数量增加而扩展的需要，很难用于并发较高以及子事务生命周期较长 (long-running transactions) 的分布式服务中。<br>如果拿 TCC 事务的处理流程与 2PC 两阶段提交做比较，2PC 通常都是在跨库的 DB 层面，而 TCC 则在应用层面的处理，需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于，可以让应用自己定义数据操作的粒度，使得降低锁冲突、提高吞吐量成为可能。而不足之处则在于对应用的侵入性非常强，业务逻辑的每个分支都需要实现 try、confirm、cancel 三个操作。此外，其实现难度也比较大，需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。&#x3D;&#x3D;典型的使用场景：满，登录送优惠券等&#x3D;&#x3D;。<br>可靠消息最终一致性事务适合<span style="background-color:#00ff00">执行周期长且实时性要求不高的场景</span>。引入消息机制后，同步的事务操作变为基于消息执行的异步操作，避免了分布式事务中的同步阻塞操作的影响，并实现了两个服务的解耦。&#x3D;&#x3D;典型的使用场景：注册送积分，登录送优惠券等&#x3D;&#x3D;。<br>最大努力通知是分布式事务中要求最低的一种。适用于一些最终一致性时间敏感度低的业务；允许发起通知方处理业务失败，在接收通知方收到通知后积极进行失败处理，无论发起通知方如何处理结果都会不影响到接收通知方的后续处理；发起通知方需提供查询执行情况接口，用于接收通知方校对结果。&#x3D;&#x3D;典型的使用场景：银行通知、支付结果通知等&#x3D;&#x3D;。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302072650.png" alt="image.png"></p><p>总结： 在条件允许的情况下，我们尽可能选择本地事务单数据源，因为它减少了网络交互带来的性能损耗，且避免了数据弱一致性带来的种种问题。若某系统频繁且不合理的使用分布式事务，应首先从整体设计角度观察服务的拆分是否合理，是否高内聚低耦合？是否粒度太小？分布式事务一直是业界难题，因为网络的不确定性，而且我们习惯于拿分布式事务与单机事务 ACID 做对比。 无论是数据库层的 XA、还是应用层 TCC、可靠消息、最大努力通知等方案，都没有完美解决分布式事务问题，它们不过是各自在性能、一致性、可用性等方面做取舍，寻求某些场景偏好下的权衡。</p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230405-0754%%</span>❕ ^ylefmx</p><h2 id="6-1-注册账号案例分析"><a href="#6-1-注册账号案例分析" class="headerlink" title="6.1. 注册账号案例分析"></a>6.1. 注册账号案例分析</h2><h3 id="6-1-1-业务流程"><a href="#6-1-1-业务流程" class="headerlink" title="6.1.1. 业务流程"></a>6.1.1. 业务流程</h3><p>采用用户、账号分离设计 (这样设计的好处是，当用户的业务信息发生变化时，不会影响的认证、授权等系统机制)，因此需要保证用户信息与账号信息的一致性。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230101132144.png"></p><h3 id="6-1-2-解决方案分析"><a href="#6-1-2-解决方案分析" class="headerlink" title="6.1.2. 解决方案分析"></a>6.1.2. 解决方案分析</h3><p>针对注册业务，如果用户与账号信息不一致，则会导致严重问题，因此该业务对一致性要求较为严格，即当用户服务和账号服务任意一方出现问题都需要回滚事务。</p><p>根据上述需求进行解决方案分析：</p><p><strong>1、采用可靠消息一致性方案</strong></p><p>可靠消息一致性要求只要消息发出，事务参与者接到消息就要将事务执行成功，<span style="background-color:#ffff00">不存在回滚的要求</span>，所以不适用。</p><p><strong>2、采用最大努力通知方案</strong></p><p>最大努力通知表示发起通知方执行完本地事务后将结果通知给事务参与者，即使事务参与者执行业务处理失败，发起通知方<span style="background-color:#ffff00">也不会回滚事务</span>，所以不适用。</p><p><strong>3、采用 Seata 实现 2PC</strong></p><p>在用户中心发起全局事务，统一账户服务为事务参与者，用户中心和统一账户服务只要有一方出现问题则全局事务回滚，符合要求。</p><p>实现方法如下：</p><pre><code>1、用户中心添加用户信息，开启全局事务2、统一账号服务添加账号信息，作为事务参与者3、其中一方执行失败Seata对SQL进行逆操作删除用户信息和账号信息，实现回滚。</code></pre><p><strong>4、采用 Hmily 实现 TCC</strong></p><p>TCC 也可以实现用户中心和统一账户服务只要有一方出现问题则全局事务回滚，符合要求。</p><p>实现方法如下：</p><pre><code>1、用户中心try：添加用户，状态为不可用confifirm：更新用户状态为可用cancel：删除用户2、统一账号服务try：添加账号，状态为不可用confifirm：更新账号状态为可用cancel：删除账号</code></pre><h2 id="6-2-存管开户"><a href="#6-2-存管开户" class="headerlink" title="6.2. 存管开户"></a>6.2. 存管开户</h2><h3 id="6-2-1-业务流程"><a href="#6-2-1-业务流程" class="headerlink" title="6.2.1. 业务流程"></a>6.2.1. 业务流程</h3><p>根据政策要求，P2P 业务必须让银行存管资金，用户的资金在银行存管系统的账户中，而不在 P2P 平台中，因此用户要在银行存管系统开户。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230101132601.png"><br>用户向用户中心提交开户资料，用户中心生成开户请求号并重定向至银行存管系统开户页面。用户设置存管密码并确认开户后，银行存管立即返回“请求已受理”。在某一时刻，银行存管系统处理完该开户请求后，将调用回调地址 通知处理结果，若通知失败，则按一定策略重试通知。同时，银行存管系统应提供开户结果查询的接口，供用户中心校对结果。</p><h3 id="6-2-2-解决方案分析"><a href="#6-2-2-解决方案分析" class="headerlink" title="6.2.2. 解决方案分析"></a>6.2.2. 解决方案分析</h3><p>P2P 平台的用户中心与银行存管系统之间属于跨系统交互，银行存管系统属于外部系统，用户中心无法干预银行存管系统，所以用户中心只能在收到银行存管系统的业务处理结果通知后积极处理，开户后的使用情况完全由用户中心来控制。 根据上述需求进行解决方案分析：</p><p><strong>1、采用 Seata 实现 2PC</strong></p><p>需要侵入银行存管系统的数据库，由于它是外部系统，所以不适用。</p><p><strong>2、采用 Hmily 实现 TCC</strong></p><p>TCC 侵入性更强，所以不适用。</p><p><strong>3、基于 MQ 的可靠消息一致性</strong></p><p>如果让银行存管系统监听 MQ 则不合适 ，因为它是外部系统。</p><p>如果银行存管系统将消息发给 MQ 用户中心监听 MQ 是可以的，但是由于相对银行存管系统来说用户中心属于外部系统，银行存管系统是不会让外部系统直接监听自己的 MQ 的，基于 MQ 的通信协议也不方便外部系统间的交互，</p><p>所以本方案不合适。</p><p><strong>4、最大努力通知方案</strong></p><p>银行存管系统内部使用 MQ，银行存管系统处理完业务后将处理结果发给 MQ，由银行存管的通知程序专门发送通知，并且采用互联网协议通知给第三方系统（用户中心）。</p><p>下图中发起通知即银行存管系统：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230101133303.png"></p><h2 id="6-3-满标审核"><a href="#6-3-满标审核" class="headerlink" title="6.3. 满标审核"></a>6.3. 满标审核</h2><h3 id="6-3-1-业务流程"><a href="#6-3-1-业务流程" class="headerlink" title="6.3.1. 业务流程"></a>6.3.1. 业务流程</h3><p>在借款人标的募集够所有的资金后，P2P 运营管理员审批该标的，触发放款，并开启还款流程。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230101133538.png"></p><h3 id="6-3-2-解决方案分析"><a href="#6-3-2-解决方案分析" class="headerlink" title="6.3.2. 解决方案分析"></a>6.3.2. 解决方案分析</h3><p>生成还款计划是一个执行时长较长的业务，不建议阻塞主业务流程，此业务对一致性要求较低。</p><p>根据上述需求进行解决方案分析：</p><p><strong>1、采用 Seata 实现 2PC</strong></p><p>Seata 在事务执行过程会进行数据库资源锁定，由于事务执行时长较长会将资源锁定较长时间，所以不适用。</p><p><strong>2、采用 Hmily 实现 TCC</strong></p><p>本需求对业务一致性要求较低，因为生成还款计划的时长较长，所以不要求交易中心修改标的状态为“还款中”就立即生成还款计划 ，所以本方案不适用。</p><p><strong>3、基于 MQ 的可靠消息一致性</strong></p><p>满标审批通过后由交易中心修改标的状态为“还款中”并且向还款服务发送消息，还款服务接收到消息开始生成还款计划，基本于 MQ 的可靠消息一致性方案适用此场景</p><p><strong>4、最大努力通知方案</strong></p><p>满标审批通过后由交易中心向还款服务发送通知要求生成还款计划，还款服务并且对外提供还款计划生成结果校对接口供其它服务查询，最大努力 通知方案也适用本场景 。</p><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><h2 id="7-1-黑马"><a href="#7-1-黑马" class="headerlink" title="7.1. 黑马"></a>7.1. 黑马</h2><p><a href="https://www.bilibili.com/video/BV1FJ411A7mV?p=7&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1FJ411A7mV?p=7&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;005-分布式专题&#x2F;黑马资料-分布式事务&#x2F;day2&#x2F;资料&#x2F;分布式事务专题-v1.1.pdf]]</p><p>事务，事务隔离级别，spring 事务配置，spring 事务的传播特性 - 阿里巴巴面试题，面试题系列（十）</p><p><a href="https://www.bilibili.com/video/BV1EE411p7dD?from=search&seid=11104488055930606136&spm_id_from=333.337.0.0">https://www.bilibili.com/video/BV1EE411p7dD?from=search&seid=11104488055930606136&spm_id_from&#x3D;333.337.0.0</a></p><h2 id="7-2-分布式事务"><a href="#7-2-分布式事务" class="headerlink" title="7.2. 分布式事务"></a>7.2. 分布式事务</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20201006104810317.png" alt="image-20201006104810317"></p><p>【黑马】分布式事务解决方案专题</p><p><a href="https://www.bilibili.com/video/BV1FJ411A7mV/?spm_id_from=autoNext">https://www.bilibili.com/video/BV1FJ411A7mV/?spm_id_from&#x3D;autoNext</a></p><p>Java 事务的类型有三种：JDBC 事务、JTA(Java Transaction API) 事务、容器事务，其中 JDBC 的事务操作用法比较简单，适合于处理同一个数据源的操作。JTA 事务相对复杂，可以用于处理跨多个数据库的事务，是分布式事务的一种解决方案。</p><p>这里还要简单说一下，虽然 JTA 事务是 Java 提供的可用于分布式事务的一套 API，但是不同的 J2EE 平台的实现都不一样，并且都不是很方便使用，所以，一般在项目中不太使用这种较为复杂的 API。现在业内比较常用的分布式事务解决方案主要有异步消息确保型、TCC、最大努力通知等。</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;【十九】Spring Boot之分布式事务(JTA、Atomikos、Druid、Mybatis)_jy02268879的博客-CSDN博客]]</p><h2 id="7-3-尚硅谷-谷粒商城"><a href="#7-3-尚硅谷-谷粒商城" class="headerlink" title="7.3. 尚硅谷 - 谷粒商城"></a>7.3. 尚硅谷 - 谷粒商城</h2><p><a href="https://www.bilibili.com/video/BV1np4y1C7Yf?p=288&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1np4y1C7Yf?p=288&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="7-4-Seata-整合-Oracle"><a href="#7-4-Seata-整合-Oracle" class="headerlink" title="7.4. Seata 整合 Oracle"></a>7.4. Seata 整合 Oracle</h2><p>官网：<a href="http://seata.io/zh-cn/docs/overview/what-is-seata.html">http://seata.io/zh-cn/docs/overview/what-is-seata.html</a></p><p>下载：<a href="https://github.com/seata/seata/tags">https://github.com/seata/seata/tags</a></p><p><a href="https://blog.csdn.net/qq_27699835/article/details/107663452">https://blog.csdn.net/qq_27699835&#x2F;article&#x2F;details&#x2F;107663452</a></p><p><a href="https://www.jianshu.com/p/f5bdec291e63">https://www.jianshu.com/p/f5bdec291e63</a> <a href="https://github.com/majiajue/seata_oracle">https://github.com/majiajue/seata_oracle</a></p>]]></content>
      
      
      <categories>
          
          <category> 分布式事务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式事务 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-基本原理-4、泛型</title>
      <link href="/2022/12/26/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-4%E3%80%81%E6%B3%9B%E5%9E%8B/"/>
      <url>/2022/12/26/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-4%E3%80%81%E6%B3%9B%E5%9E%8B/</url>
      
        <content type="html"><![CDATA[<h1 id="1-什么是泛型"><a href="#1-什么是泛型" class="headerlink" title="1. 什么是泛型"></a>1. 什么是泛型</h1><h2 id="1-1-背景"><a href="#1-1-背景" class="headerlink" title="1.1. 背景"></a>1.1. 背景</h2><p>JAVA 推出泛型以前，程序员可以构建一个元素类型为 Object 的集合，该集合能够存储任意的数据类型对象，而在使用该集合的过程中，需要程序员明确知道存储每个元素的数据类型，否则很容易引发 ClassCastException 异常。</p><h2 id="1-2-概念"><a href="#1-2-概念" class="headerlink" title="1.2. 概念"></a>1.2. 概念</h2><p>Java 泛型（generics）是 JDK5 中引入的一个新特性，泛型提供了编译时类型安全监测机制，该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是参数化类型，也就是所操作的数据类型被指定为一个参数。</p><h2 id="1-3-好处"><a href="#1-3-好处" class="headerlink" title="1.3. 好处"></a>1.3. 好处</h2><p>类型安全</p><p>消除了强制类型的转换</p><h2 id="1-4-类型"><a href="#1-4-类型" class="headerlink" title="1.4. 类型"></a>1.4. 类型</h2><p>E - Element (在集合中使用，因为集合中存放的是元素)<br>T - Type（表示 Java 类，包括基本的类和我们自定义的类）<br>K - Key（表示键，比如 Map 中的 key）<br>V - Value（表示值）<br>N - Number（表示数值类型）<br>？ - （表示不确定的 java 类型）<br>S、U、V - 2nd、3rd、4th types</p><h1 id="2-泛型类和接口"><a href="#2-泛型类和接口" class="headerlink" title="2. 泛型类和接口"></a>2. 泛型类和接口</h1><h2 id="2-1-泛型类"><a href="#2-1-泛型类" class="headerlink" title="2.1. 泛型类"></a>2.1. 泛型类</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227102445.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227102529.png"></p><h2 id="2-2-泛型接口"><a href="#2-2-泛型接口" class="headerlink" title="2.2. 泛型接口"></a>2.2. 泛型接口</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227103449.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227103503.png"></p><h1 id="3-泛型方法"><a href="#3-泛型方法" class="headerlink" title="3. 泛型方法"></a>3. 泛型方法</h1><p><strong>返回值前面加泛型列表</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227115510.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227120152.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227120240.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227120508.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227121617.png"></p><h1 id="4-类型擦除"><a href="#4-类型擦除" class="headerlink" title="4. 类型擦除"></a>4. 类型擦除</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227122726.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227123023.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227123409.png"></p><h2 id="4-1-桥接方法"><a href="#4-1-桥接方法" class="headerlink" title="4.1. 桥接方法"></a>4.1. 桥接方法</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227123845.png"></p><h1 id="5-泛型数组"><a href="#5-泛型数组" class="headerlink" title="5. 泛型数组"></a>5. 泛型数组</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221227124222.png"></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> T[] array;<br><span class="hljs-keyword">public</span> <span class="hljs-title function_">Fruit</span><span class="hljs-params">(Class&lt;T&gt; clazz,<span class="hljs-type">int</span> length)</span> &#123;<br><span class="hljs-comment">//通过Array.newInstance创建泛型数组对象</span><br>array = (T[])Array.newInstance(clazz, length);<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><h2 id="7-1-黑马"><a href="#7-1-黑马" class="headerlink" title="7.1. 黑马"></a>7.1. 黑马</h2><p><a href="https://www.bilibili.com/video/BV1xJ411n77R?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1xJ411n77R?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>[[Java之泛型_BeyondCZN.的博客-CSDN博客]]</p><p>#todo</p><span style="display:none">- [ ] 🚩 - 泛型复习 - 🏡 2023-01-31 12:26</span>https://mp.weixin.qq.com/s/_PA4W6SZEhMLm35OoXq3Ig]]></content>
      
      
      <categories>
          
          <category> 泛型 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 泛型 </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Redis-2、分布式锁与秒杀优化</title>
      <link href="/2022/12/22/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-5%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E4%B8%8E%E7%A7%92%E6%9D%80%E4%BC%98%E5%8C%96/"/>
      <url>/2022/12/22/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-5%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E4%B8%8E%E7%A7%92%E6%9D%80%E4%BC%98%E5%8C%96/</url>
      
        <content type="html"><![CDATA[<h1 id="1-超卖问题"><a href="#1-超卖问题" class="headerlink" title="1. 超卖问题"></a>1. 超卖问题</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221226112842.png"></p><h1 id="2-分布式锁"><a href="#2-分布式锁" class="headerlink" title="2. 分布式锁"></a>2. 分布式锁</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221226114737.png"></p><h2 id="2-1-Zookeeper-实现"><a href="#2-1-Zookeeper-实现" class="headerlink" title="2.1. Zookeeper 实现"></a>2.1. Zookeeper 实现</h2><a href="/2023/06/12/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0-12%E3%80%81Zookeeper/" title="服务注册与发现-12、Zookeeper">服务注册与发现-12、Zookeeper</a><h1 id="3-Redis-分布式锁-set-nx-ex-存在问题⭐️🔴"><a href="#3-Redis-分布式锁-set-nx-ex-存在问题⭐️🔴" class="headerlink" title="3. Redis 分布式锁 -set nx ex- 存在问题⭐️🔴"></a>3. Redis 分布式锁 -set nx ex- 存在问题⭐️🔴</h1><h2 id="3-1-删除其他线程的锁"><a href="#3-1-删除其他线程的锁" class="headerlink" title="3.1. 删除其他线程的锁"></a>3.1. 删除其他线程的锁</h2><h3 id="3-1-1-发生原因"><a href="#3-1-1-发生原因" class="headerlink" title="3.1.1. 发生原因"></a>3.1.1. 发生原因</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224141618.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224141441.png"></p><h3 id="3-1-2-解决方案"><a href="#3-1-2-解决方案" class="headerlink" title="3.1.2. 解决方案"></a>3.1.2. 解决方案</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224141558.png"></p><h2 id="3-2-优化线程-ID"><a href="#3-2-优化线程-ID" class="headerlink" title="3.2. 优化线程 ID"></a>3.2. 优化线程 ID</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224141821.png"></p><h2 id="3-3-系统阻塞导致删锁"><a href="#3-3-系统阻塞导致删锁" class="headerlink" title="3.3. 系统阻塞导致删锁"></a>3.3. 系统阻塞导致删锁</h2><h3 id="3-3-1-发生原因"><a href="#3-3-1-发生原因" class="headerlink" title="3.3.1. 发生原因"></a>3.3.1. 发生原因</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224142443.png"></p><h3 id="3-3-2-解决方案"><a href="#3-3-2-解决方案" class="headerlink" title="3.3.2. 解决方案"></a>3.3.2. 解决方案</h3><p><span style="background-color:#00ff00">保证判断和释放动作的原子性</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224165901.png"></p><h3 id="3-3-3-不可重入"><a href="#3-3-3-不可重入" class="headerlink" title="3.3.3. 不可重入"></a>3.3.3. 不可重入</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607181158.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607181931.png" alt="image.png"></p><h2 id="3-4-问题总结⭐️🔴"><a href="#3-4-问题总结⭐️🔴" class="headerlink" title="3.4. 问题总结⭐️🔴"></a>3.4. 问题总结⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224170853.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230601105641.png" alt="image.png"></p><p>比较容易解决的问题：</p><ol><li>加锁与设置有效期设为原子操作</li><li>锁归属判断，防止误删其他线程的锁</li><li>判断锁归属与 unlock 动作设为原子操作</li></ol><p>但是还是有其他问题：</p><ol><li>不可重入</li><li>不可重试</li><li>超时释放，自动续期</li><li>主从一致性</li></ol><p>所以引入 Redisson 或者 ZK 的分布式锁实现方案</p><h1 id="4-Redisson"><a href="#4-Redisson" class="headerlink" title="4. Redisson"></a>4. Redisson</h1><h2 id="4-1-使用"><a href="#4-1-使用" class="headerlink" title="4.1. 使用"></a>4.1. 使用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224171236.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224171407.png"></p><h2 id="4-2-可重入原理"><a href="#4-2-可重入原理" class="headerlink" title="4.2. 可重入原理"></a>4.2. 可重入原理</h2><h3 id="4-2-1-不可重入原因"><a href="#4-2-1-不可重入原因" class="headerlink" title="4.2.1. 不可重入原因"></a>4.2.1. 不可重入原因</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224171959.png"></p><h3 id="4-2-2-解决办法"><a href="#4-2-2-解决办法" class="headerlink" title="4.2.2. 解决办法"></a>4.2.2. 解决办法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221224172509.png"></p><h2 id="4-3-看门狗原理"><a href="#4-3-看门狗原理" class="headerlink" title="4.3. 看门狗原理"></a>4.3. 看门狗原理</h2><p><a href="https://www.bilibili.com/video/BV1cr4y1671t/?p=67&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1cr4y1671t/?p=67&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221225073438.png"></p><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?t=1305.7&amp;p=67">https://www.bilibili.com/video/BV1cr4y1671t?t=1305.7&amp;p=67</a></p><h3 id="4-3-1-设置定时递归"><a href="#4-3-1-设置定时递归" class="headerlink" title="4.3.1. 设置定时递归"></a>4.3.1. 设置定时递归</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517111725.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517111826.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517111932.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517111844.png" alt="image.png"></p><h3 id="4-3-2-取消定时任务"><a href="#4-3-2-取消定时任务" class="headerlink" title="4.3.2. 取消定时任务"></a>4.3.2. 取消定时任务</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517111558.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230517111631.png" alt="image.png"></p><h2 id="4-4-总结"><a href="#4-4-总结" class="headerlink" title="4.4. 总结"></a>4.4. 总结</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221225073742.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221225074955.png"></p><h1 id="5-秒杀优化"><a href="#5-秒杀优化" class="headerlink" title="5. 秒杀优化"></a>5. 秒杀优化</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221225080334.png"></p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230515065013.png" alt="image.png"></p><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><h2 id="7-1-黑马"><a href="#7-1-黑马" class="headerlink" title="7.1. 黑马"></a>7.1. 黑马</h2><h3 id="7-1-1-视频"><a href="#7-1-1-视频" class="headerlink" title="7.1.1. 视频"></a>7.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?p=51&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1cr4y1671t?p=51&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-1-2-资料"><a href="#7-1-2-资料" class="headerlink" title="7.1.2. 资料"></a>7.1.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">002</span>-DB/<span class="hljs-number">001</span>-Redis/Redis-笔记资料<br></code></pre></td></tr></table></figure><h2 id="7-2-尚硅谷周阳"><a href="#7-2-尚硅谷周阳" class="headerlink" title="7.2. 尚硅谷周阳"></a>7.2. 尚硅谷周阳</h2><h3 id="7-2-1-视频"><a href="#7-2-1-视频" class="headerlink" title="7.2.1. 视频"></a>7.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV13R4y1v7sP?p=138&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13R4y1v7sP?p=138&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-2-2-资料"><a href="#7-2-2-资料" class="headerlink" title="7.2.2. 资料"></a>7.2.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">002</span>-框架源码专题/<span class="hljs-number">002</span>-DB/<span class="hljs-number">001</span>-Redis/尚硅谷Redis7<br></code></pre></td></tr></table></figure><h2 id="7-3-网络笔记"><a href="#7-3-网络笔记" class="headerlink" title="7.3. 网络笔记"></a>7.3. 网络笔记</h2><p>[[用Redis实现分布式锁的血泪史-51CTO.COM]]</p>]]></content>
      
      
      <categories>
          
          <category> 分布式锁 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Redis </tag>
            
            <tag> 分布式锁 </tag>
            
            <tag> 秒杀 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Redis-1、基本原理</title>
      <link href="/2022/12/21/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2022/12/21/005-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%93%E9%A2%98/%E7%BC%93%E5%AD%98-Redis-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-数据类型"><a href="#1-数据类型" class="headerlink" title="1. 数据类型"></a>1. 数据类型</h1><h2 id="1-1-String-点赞"><a href="#1-1-String-点赞" class="headerlink" title="1.1. String- 点赞"></a>1.1. String- 点赞</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317142446.png" alt="image.png"></p><p>setex: 设置带过期时间的 key，动态设置。<br>setex 键秒值真实值</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317142703.png" alt="image.png"></p><p>setnx: 只有在 key 不存在时设置 key 的值<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317142734.png" alt="image.png"></p><p>点赞<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317142549.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317142555.png" alt="image.png"></p><h2 id="1-2-List-关注列表"><a href="#1-2-List-关注列表" class="headerlink" title="1.2. List- 关注列表"></a>1.2. List- 关注列表</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143234.png" alt="image.png"></p><p>一个双端链表的结构，容量是 2 的 32 次方减 1 个元素，大概 40 多亿，主要功能有 push&#x2F;pop 等，一般用在栈、队列、消息队列等场景。</p><p>left、right 都可以插入添加；<br>如果键不存在，创建新的链表；<br>如果键已存在，新增内容；</p><p>如果值全移除，对应的键也就消失了。</p><ul><li>它的底层实际是个 <strong>双向链表，对两端的操作性能很高，通过索引下标的操作中间的节点性能会较差</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143309.png" alt="image.png"></li></ul><p>1 大 V 作者李永乐老师和 CSDN 发布了文章分别是 11 和 22<br>2 阳哥关注了他们两个，只要他们发布了新文章，就会安装进我的 List<br>   <code>lpush likearticle:阳哥id    11 22</code><br>3 查看阳哥自己的号订阅的全部文章，类似分页，下面 0~10 就是一次显示 10 条<br>  <code>lrange likearticle:阳哥id 0 9</code></p><h2 id="1-3-Hash-购物车"><a href="#1-3-Hash-购物车" class="headerlink" title="1.3. Hash- 购物车"></a>1.3. Hash- 购物车</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143430.png" alt="image.png"></p><h2 id="1-4-Set-抽奖"><a href="#1-4-Set-抽奖" class="headerlink" title="1.4. Set- 抽奖"></a>1.4. Set- 抽奖</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143605.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143617.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143646.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143727.png" alt="image.png"></p><h2 id="1-5-Zset-排行榜"><a href="#1-5-Zset-排行榜" class="headerlink" title="1.5. Zset- 排行榜"></a>1.5. Zset- 排行榜</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317143836.png" alt="image.png"></p><h2 id="1-6-Bitmaps"><a href="#1-6-Bitmaps" class="headerlink" title="1.6. Bitmaps"></a>1.6. Bitmaps</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230502-1640%%</span>❕ ^akrc06</p><p>Redis 提供了 Bitmaps 这个“数据类型”可以实现对位的操作：</p><p>（1） <span style="background-color:#ff00ff">Bitmaps 本身不是一种数据类型， 实际上它就是字符串（key-value） ， 但是它可以对字符串的位进行操作。</span></p><p>（2） Bitmaps 单独提供了一套命令， 所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。 可以把 Bitmaps 想象成一个以位为单位的数组， 数组的每个单元只能存储 0 和 1， 数组的下标在 Bitmaps 中叫做偏移量。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222104504.png"></p><h3 id="1-6-1-使用场景"><a href="#1-6-1-使用场景" class="headerlink" title="1.6.1. 使用场景"></a>1.6.1. 使用场景</h3><h4 id="1-6-1-1-setbit"><a href="#1-6-1-1-setbit" class="headerlink" title="1.6.1.1. setbit"></a>1.6.1.1. setbit</h4><p><code>setbit&lt;key&gt;&lt;offset&gt;&lt;value&gt;</code> 设置 Bitmaps 中某个偏移量的值（0 或 1）</p><p><span style="background-color:#00ff00">每个独立用户是否访问过网站</span>存放在 Bitmaps 中， 将访问的用户记做 1， 没有访问的用户记做 0， 用偏移量作为用户的 id。</p><p>设置键的第 offset 个位的值（从 0 算起） ， 假设现在有 20 个用户，userid&#x3D;1， 6， 11， 15， 19 的用户对网站进行了访问， 那么当前 Bitmaps 初始化结果如图</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222104701.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222104712.png"></p><h4 id="1-6-1-2-bitcount"><a href="#1-6-1-2-bitcount" class="headerlink" title="1.6.1.2. bitcount"></a>1.6.1.2. bitcount</h4><p><code>bitcount&lt;key&gt;[start end]</code> 统计字符串从 start 字节到 end 字节比特值为 1 的数量</p><p><span style="background-color:#00ff00">计算 2022-11-06 这天的独立访问用户数量</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222104941.png"></p><p>start 和 end 代表起始和结束字节数， 下面操作计算用户 id 在第 1 个字节到第 3 个字节之间的独立访问用户数， 对应的用户 id 是 11， 15， 19。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222105039.png"></p><h4 id="1-6-1-3-bitop"><a href="#1-6-1-3-bitop" class="headerlink" title="1.6.1.3. bitop"></a>1.6.1.3. bitop</h4><p><code>bitop  and(or/not/xor) &lt;destkey&gt; [key…]</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222105159.png"></p><p>bitop 是一个复合操作， 它可以做多个 Bitmaps 的 and（交集） 、 or（并集） 、 not（非） 、 xor（异或） 操作并将结果保存在 destkey 中。</p><p><span style="background-color:#00ff00">2020-11-04 日访问网站的 userid</span>&#x3D;1,2,5,9。</p><p>setbit unique:users:20201104 1 1<br>setbit unique:users:20201104 2 1<br>setbit unique:users:20201104 5 1<br>setbit unique:users:20201104 9 1</p><h3 id="1-6-2-Bitmaps-与-set-对比"><a href="#1-6-2-Bitmaps-与-set-对比" class="headerlink" title="1.6.2. Bitmaps 与 set 对比"></a>1.6.2. Bitmaps 与 set 对比</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221222105754.png"></p><h3 id="1-6-3-与布隆过滤器的区别"><a href="#1-6-3-与布隆过滤器的区别" class="headerlink" title="1.6.3. 与布隆过滤器的区别"></a>1.6.3. 与布隆过滤器的区别</h3><p>位图存在两个问题<br>• 处理数字很方便，但是处理字符就稍微有点困难了<br>• （关键）位图在数据量过大情况下导致了内存浪费 (<span style="background-color:#ff00ff">太稀疏</span>)</p><p><a href="https://developer.aliyun.com/article/930143#slide-6">https://developer.aliyun.com/article/930143#slide-6</a><br><a href="https://juejin.cn/s/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E5%92%8Cbitmap%E5%8C%BA%E5%88%AB">https://juejin.cn/s/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E5%92%8Cbitmap%E5%8C%BA%E5%88%AB</a></p><h2 id="1-7-HyperLogLog"><a href="#1-7-HyperLogLog" class="headerlink" title="1.7. HyperLogLog"></a>1.7. HyperLogLog</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230430183622.png" alt="image.png"></p><h3 id="1-7-1-什么是基数"><a href="#1-7-1-什么是基数" class="headerlink" title="1.7.1. 什么是基数"></a>1.7.1. 什么是基数</h3><p>比如数据集 {1, 3, 5, 7, 5, 7, 8}， 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数 (不重复元素) 为 5。 基数估计就是在误差可接受的范围内，快速计算基数。</p><p>在工作当中，我们经常会遇到<span style="background-color:#00ff00">与统计相关的功能需求，比如统计网站 PV（PageView 页面访问量）,可以使用 Redis 的 incr、incrby 轻松实现。</span><br><span style="background-color:#00ff00">但像 UV（UniqueVisitor，独立访客）、独立 IP 数、搜索记录数等需要去重和计数的问题</span>如何解决？这种求集合中不重复元素个数的问题称为基数问题。<br>解决基数问题有很多种方案：<br>（1）数据存储在 MySQL 表中，使用 distinct count 计算不重复个数<br>（2）使用 Redis 提供的 hash、set、bitmaps 等数据结构来处理<br>以上的方案结果精确，但随着数据不断增加，导致占用空间越来越大，对于非常大的数据集是不切实际的。<br>能否能够降低一定的精度来平衡存储空间？Redis 推出了 HyperLogLog<br>Redis HyperLogLog 是用来做基数统计的算法，HyperLogLog 的优点是，在输入元素的数量或者体积非常非常大时，计算基数所需的空间总是固定的、并且是很小的。<br>在 Redis 里面，每个 HyperLogLog 键只需要花费 12 KB 内存，就可以计算接近 2^64 个不同元素的基数。这和计算基数时，元素越多耗费内存就越多的集合形成鲜明对比。<br>但是，因为 HyperLogLog 只会根据输入元素来计算基数，而不会储存输入元素本身，所以 HyperLogLog 不能像集合那样，返回输入的各个元素。</p><h3 id="1-7-2-命令"><a href="#1-7-2-命令" class="headerlink" title="1.7.2. 命令"></a>1.7.2. 命令</h3><h4 id="1-7-2-1-pfadd"><a href="#1-7-2-1-pfadd" class="headerlink" title="1.7.2.1. pfadd"></a>1.7.2.1. pfadd</h4><h4 id="1-7-2-2-pfadd"><a href="#1-7-2-2-pfadd" class="headerlink" title="1.7.2.2. pfadd"></a>1.7.2.2. pfadd</h4><h4 id="1-7-2-3-pfmerge"><a href="#1-7-2-3-pfmerge" class="headerlink" title="1.7.2.3. pfmerge"></a>1.7.2.3. pfmerge</h4><h1 id="2-持久化"><a href="#2-持久化" class="headerlink" title="2. 持久化"></a>2. 持久化</h1><h2 id="2-1-RDB"><a href="#2-1-RDB" class="headerlink" title="2.1. RDB"></a>2.1. RDB</h2><h3 id="2-1-1-是什么"><a href="#2-1-1-是什么" class="headerlink" title="2.1.1. 是什么"></a>2.1.1. 是什么</h3><p>在指定的时间间隔内将内存中的数据集快照写入磁盘， 也就是行话讲的 Snapshot 快照，它恢复时是将快照文件直接读到内存里</p><h3 id="2-1-2-RDB-持久化原理"><a href="#2-1-2-RDB-持久化原理" class="headerlink" title="2.1.2. RDB 持久化原理"></a>2.1.2. RDB 持久化原理</h3><p>Redis 会单独创建<span style="background-color:#00ff00">（fork）一个子进程</span>来进行持久化，会<span style="background-color:#00ff00">先将数据写入到一个临时文件中</span>，待持久化过程都结束了，再用这个临时文件替换上次持久化好的文件。整个过程中，<span style="background-color:#ffff00">主进程是不进行任何 IO 操作的，这就确保了极高的性能</span> 。如果需要进行大规模数据的恢复，且对于数据恢复的完整性不是非常敏感，那 RDB 方式要比 AOF 方式<span style="background-color:#00ff00">更加高效</span>。<strong>RDB 的缺点是</strong> 最后一次持久化后的数据可能丢失。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230514071908.png" alt="image.png"></p><h3 id="2-1-3-触发条件"><a href="#2-1-3-触发条件" class="headerlink" title="2.1.3. 触发条件"></a>2.1.3. 触发条件</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314102454.png" alt="image.png"></p><p><span style="background-color:#ff00ff">作用：用作主从同步</span></p><h3 id="2-1-4-自动触发默认配置"><a href="#2-1-4-自动触发默认配置" class="headerlink" title="2.1.4. 自动触发默认配置"></a>2.1.4. 自动触发默认配置</h3><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230317-1548%%</span>❕ ^hp0xn0</p><h4 id="2-1-4-1-Redis6"><a href="#2-1-4-1-Redis6" class="headerlink" title="2.1.4.1. Redis6"></a>2.1.4.1. Redis6</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317145401.png" alt="image.png"></p><h4 id="2-1-4-2-Redis7"><a href="#2-1-4-2-Redis7" class="headerlink" title="2.1.4.2. Redis7"></a>2.1.4.2. Redis7</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317145454.png" alt="image.png"></p><h3 id="2-1-5-手动触发"><a href="#2-1-5-手动触发" class="headerlink" title="2.1.5. 手动触发"></a>2.1.5. 手动触发</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317151243.png" alt="image.png"></p><p> 在 Linux 程序中，fork() 会产生一个和父进程完全相同的子进程，但子进程在此后多会 exec 系统调用，出于效率考虑，尽量避免膨胀。</p><h3 id="2-1-6-如何恢复⭐️🔴"><a href="#2-1-6-如何恢复⭐️🔴" class="headerlink" title="2.1.6. 如何恢复⭐️🔴"></a>2.1.6. 如何恢复⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317145648.png" alt="image.png"></p><p><span style="background-color:#ff0000">备注：不可以把备份文件 dump.rdb 和生产 redis 服务器放在同一台机器，必须分开各自存储，以防生产机物理损坏后备份文件也挂了。</span></p><h3 id="2-1-7-优劣势"><a href="#2-1-7-优劣势" class="headerlink" title="2.1.7. 优劣势"></a>2.1.7. 优劣势</h3><h4 id="2-1-7-1-优势"><a href="#2-1-7-1-优势" class="headerlink" title="2.1.7.1. 优势"></a>2.1.7.1. 优势</h4><p>l 适合大规模的数据恢复<br>l 对数据完整性和一致性要求不高更适合使用<br>l <span style="background-color:#ff00ff">节省磁盘空间</span><br>l <span style="background-color:#ff00ff">恢复速度快</span></p><h4 id="2-1-7-2-劣势"><a href="#2-1-7-2-劣势" class="headerlink" title="2.1.7.2. 劣势"></a>2.1.7.2. 劣势</h4><p>l Fork 的时候，内存中的数据被克隆了一份，大致 2 倍的膨胀性需要考虑<br>l 虽然 Redis 在 fork 时使用了 <strong>写时拷贝技术</strong>，但是如果数据庞大时还是比较消耗性能。<br>l 在备份周期在一定间隔时间做一次备份，所以如果 Redis 意外 down 掉的话，就<span style="background-color:#ff00ff">会丢失最后一次快照后的所有修改</span>。</p><h3 id="2-1-8-优化总结"><a href="#2-1-8-优化总结" class="headerlink" title="2.1.8. 优化总结"></a>2.1.8. 优化总结</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317151654.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317151812.png" alt="image.png"><br>默认 yes<br>如果配置成 no，表示你不在乎数据不一致或者有其他的手段发现和控制这种不一致，那么在快照写入失败时，也能确保 redis 继续接受新的写请求</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317151839.png" alt="image.png"><br>默认 yes<br>对于存储到磁盘中的快照，可以设置是否进行压缩存储。如果是的话，redis 会采用 LZF 算法进行压缩。<br>如果你不想消耗 CPU 来进行压缩的话，可以设置为关闭此功能</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317151910.png" alt="image.png"><br>默认 yes<br>在存储快照后，还可以让 redis 使用 CRC64 算法来进行数据校验，但是这样做会增加大约 10% 的性能消耗，如果希望获取到最大的性能提升，可以关闭此功能</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317151936.png" alt="image.png"><br>rdb-del-sync-files：在没有持久性的情况下删除复制中使用的 RDB 文件启用。默认情况下 no，此选项是禁用的。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317152015.png" alt="image.png"></p><h2 id="2-2-AOF"><a href="#2-2-AOF" class="headerlink" title="2.2. AOF"></a>2.2. AOF</h2><h3 id="2-2-1-是什么"><a href="#2-2-1-是什么" class="headerlink" title="2.2.1. 是什么"></a>2.2.1. 是什么</h3><p>以 <strong>日志</strong> 的形式来记录每个<span style="background-color:#00ff00">写操作</span>（增量保存），将 Redis 执行过的所有写指令记录下来 (<strong>读操作不记录</strong>)， <strong>只许追加文件但不可以改写文件</strong>，redis 启动之初会读取该文件重新构建数据，换言之，redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作<br><span style="background-color:#ff00ff">默认情况下，Redis 没有开启 AOF，开启需要配置</span> <code>appendonly yes</code></p><h3 id="2-2-2-AOF-持久化流程"><a href="#2-2-2-AOF-持久化流程" class="headerlink" title="2.2.2. AOF 持久化流程"></a>2.2.2. AOF 持久化流程</h3><p>（1）客户端发起请求<br>（2）客户端的<span style="background-color:#00ff00">请求写</span>命令会被 append 追加到 AOF 缓冲区内；<br>（3）AOF 缓冲区根据 AOF 持久化策略 <code>[always,everysec,no]</code> 将操作 sync 同步到磁盘的 AOF 文件中；默认为 <code>everysec</code><br>（4）AOF 文件大小超过重写策略或手动重写时，会对 AOF 文件 rewrite 重写，压缩 AOF 文件容量；<br>（5）Redis 服务重启时，会重新 load 加载 AOF 文件中的写操作达到数据恢复的目的；<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314111715.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314111823.png" alt="image.png"></p><h4 id="2-2-2-1-持久化策略"><a href="#2-2-2-1-持久化策略" class="headerlink" title="2.2.2.1. 持久化策略"></a>2.2.2.1. 持久化策略</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314112139.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314112206.png" alt="image.png"></p><h3 id="2-2-3-版本变化"><a href="#2-2-3-版本变化" class="headerlink" title="2.2.3. 版本变化"></a>2.2.3. 版本变化</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314112441.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317155308.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317155101.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317155317.png" alt="image.png"></p><h3 id="2-2-4-优劣势"><a href="#2-2-4-优劣势" class="headerlink" title="2.2.4. 优劣势"></a>2.2.4. 优劣势</h3><h4 id="2-2-4-1-优势"><a href="#2-2-4-1-优势" class="headerlink" title="2.2.4.1. 优势"></a>2.2.4.1. 优势</h4><p><img src="file:////private/var/folders/nk/sshwk5x12rd53h9yvx2t4spc0000gn/T/com.kingsoft.wpsoffice.mac/wps-taylor/ksohtml//wps3.png"> <br>备份机制更稳健，丢失数据概率更低。<br>可读的日志文本，通过操作 AOF 稳健，可以处理误操作。</p><h4 id="2-2-4-2-劣势"><a href="#2-2-4-2-劣势" class="headerlink" title="2.2.4.2. 劣势"></a>2.2.4.2. 劣势</h4><p><span style="background-color:#ff00ff">比起 RDB 占用更多的磁盘空间</span><br><span style="background-color:#ff00ff">恢复备份速度要慢</span><br>每次读写都同步的话，有一定的性能压力<br>存在个别 Bug，造成恢复不能</p><h3 id="2-2-5-AOF-重写机制"><a href="#2-2-5-AOF-重写机制" class="headerlink" title="2.2.5. AOF 重写机制"></a>2.2.5. AOF 重写机制</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314113724.png" alt="image.png"></p><h3 id="2-2-6-总结"><a href="#2-2-6-总结" class="headerlink" title="2.2.6. 总结"></a>2.2.6. 总结</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317155552.png" alt="image.png"></p><h3 id="2-2-7-用哪个好"><a href="#2-2-7-用哪个好" class="headerlink" title="2.2.7. 用哪个好"></a>2.2.7. 用哪个好</h3><p>官方推荐两个都启用<br><span style="background-color:#ff00ff">如果对数据不敏感，可以选单独用 RDB</span><br><span style="background-color:#ff00ff">不建议单独用 AOF，因为可能会出现 Bug</span><br><span style="background-color:#ff00ff">如果只是做纯内存缓存，可以都不用</span></p><h2 id="2-3-RDB-AOF-混合持久化-优先-AOF-如有"><a href="#2-3-RDB-AOF-混合持久化-优先-AOF-如有" class="headerlink" title="2.3. RDB+AOF 混合持久化 - 优先 AOF(如有)"></a>2.3. RDB+AOF 混合持久化 - 优先 AOF(如有)</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-0743%%</span>❕ ^j8eolm</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314113813.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314113900.png" alt="image.png"></p><p><strong>1 开启混合方式设置</strong></p><p>设置 aof-use-rdb-preamble 的值为 yes   yes 表示开启，设置为 no 表示禁用</p><p><strong>2 RDB+AOF 的混合方式</strong></p><p>结论：RDB 镜像做全量持久化，AOF 做增量持久化</p><p>先使用 RDB 进行快照存储，然后使用 AOF 持久化记录所有的写操作，当重写策略满足或手动触发重写的时候，将最新的数据存储为新的 RDB 记录。这样的话，重启服务的时候会从 RDB 和 AOF 两部分恢复数据，既保证了数据完整性，又提高了恢复数据的性能。简单来说：混合持久化方式产生的文件一部分是 RDB 格式，一部分是 AOF 格式。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317160313.png" alt="image.png"></p><h1 id="3-Redis-事务"><a href="#3-Redis-事务" class="headerlink" title="3. Redis 事务"></a>3. Redis 事务</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314122053.png" alt="image.png"></p><p>本质就是一个队列中，一次性、顺序性、排他性的执行一系列 Redis 命令。通过 3 个步骤完成</p><p>1 开启：以 MULTI 开始一个事务<br>2 入队：将多个命令入队到事务中，接到这些命令并不会立即执行，而是放到等待执行的事务队列里面<br>3 执行：由 EXEC 命令触发事务</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314122454.png" alt="image.png"></p><h2 id="3-1-与数据库事务区别"><a href="#3-1-与数据库事务区别" class="headerlink" title="3.1. 与数据库事务区别"></a>3.1. 与数据库事务区别</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230314122344.png" alt="image.png"></p><h1 id="4-Redis-管道"><a href="#4-Redis-管道" class="headerlink" title="4. Redis 管道"></a>4. Redis 管道</h1><h2 id="4-1-背景"><a href="#4-1-背景" class="headerlink" title="4.1. 背景"></a>4.1. 背景</h2><p>Redis 是一种基于客户端 - 服务端模型以及请求&#x2F;响应协议的 TCP 服务。一个请求会遵循以下步骤：</p><p>1 客户端向服务端发送命令分四步 (<span style="background-color:#ff00ff">发送命令→命令排队→命令执行→返回结果</span>)，并监听 Socket 返回，通常以阻塞模式等待服务端响应。</p><p>2 服务端处理命令，并将结果返回给客户端。</p><p><strong>上述两步称为：Round Trip Time(简称 RTT, 数据包往返于两端的时间)，问题笔记最下方</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317160936.png" alt="image.png"><br>如果同时需要执行大量的命令，那么就要等待上一条命令应答后再执行，这中间不仅仅多了 RTT（Round Time Trip），而且还频繁调用系统 IO，发送网络请求，同时需要 redis 调用多次 read() 和 write() 系统方法，系统方法会将数据从用户态转移到内核态，这样就会对进程上下文有比较大的影响了，性能不太好</p><h2 id="4-2-定义"><a href="#4-2-定义" class="headerlink" title="4.2. 定义"></a>4.2. 定义</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317161040.png" alt="image.png"></p><h2 id="4-3-演示"><a href="#4-3-演示" class="headerlink" title="4.3. 演示"></a>4.3. 演示</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317161059.png" alt="image.png"></p><h2 id="4-4-总结"><a href="#4-4-总结" class="headerlink" title="4.4. 总结"></a>4.4. 总结</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230317161208.png" alt="image.png"></p><p>频繁的上下文切换很可能导致 CPU 占比增高。 <a href="/2023/03/04/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-2%E3%80%81%E7%B3%BB%E7%BB%9F%E8%B0%83%E4%BC%98%E6%A1%88%E4%BE%8B%E2%99%A8%EF%B8%8F/" title="性能调优-进阶-2、系统调优案例♨️">性能调优-进阶-2、系统调优案例♨️</a></p><h1 id="5-Redis-发布订阅"><a href="#5-Redis-发布订阅" class="headerlink" title="5. Redis 发布订阅"></a>5. Redis 发布订阅</h1><p>有这种功能</p><h1 id="6-SpringBoot-集成-Redis"><a href="#6-SpringBoot-集成-Redis" class="headerlink" title="6. SpringBoot 集成 Redis"></a>6. SpringBoot 集成 Redis</h1><h1 id="7-原子性、Redis-事务、lua"><a href="#7-原子性、Redis-事务、lua" class="headerlink" title="7. 原子性、Redis 事务、lua"></a>7. 原子性、Redis 事务、lua</h1><h2 id="7-1-Redis-的操作的原子性"><a href="#7-1-Redis-的操作的原子性" class="headerlink" title="7.1. Redis 的操作的原子性"></a>7.1. Redis 的操作的原子性</h2><p>因为 redis 是单线程的！Redis 的 API 是原子性的操作</p><h2 id="7-2-Redis-Lua-原子性"><a href="#7-2-Redis-Lua-原子性" class="headerlink" title="7.2. Redis + Lua 原子性"></a>7.2. Redis + Lua 原子性</h2><p><a href="https://cloud.tencent.com/product/crs?from=10680">Redis</a> 从 2.6.0 版本开始提供了 eval 命令，通过内置的 Lua 解释器，可以让用户执行一段 Lua 脚本并返回数据。因为 Redis 单线程模型的特点，可以保证多个命令的原子性; Redis 的 API 是原子性的操作 eval 是 redis 的一个 Api</p><h2 id="7-3-Redis-集群-Lua-注意"><a href="#7-3-Redis-集群-Lua-注意" class="headerlink" title="7.3. Redis 集群 +Lua 注意"></a>7.3. Redis 集群 +Lua 注意</h2><p>Redis cluster 对多 key 操作有限，要求命令中所有的<span style="background-color:#00ff00">key 都属于一个 slot，才可以被执行</span> 如何将 key 放到同一个 slot 中呢： 你需要将把 key 中的一部分使用 {} 包起来，redis 将通过 {} 中间的内容作为计算 slot 的 key，类似 key1{mykey}、key2{mykey} 这样的都会存放到同一个 slot 中</p><p><a href="https://blog.csdn.net/jing956899449/article/details/53338282">Redis集群+Lua注意事项</a></p><p>[[rediscluster lua使用_家中老九的博客-CSDN博客_redis lua cluster]]</p><h2 id="7-4-Redis-事务、lua、管道使用场景"><a href="#7-4-Redis-事务、lua、管道使用场景" class="headerlink" title="7.4. Redis 事务、lua、管道使用场景"></a>7.4. Redis 事务、lua、管道使用场景</h2><p><a href="https://blog.csdn.net/fangjian1204/article/details/50585080">redis中的事务、lua脚本和管道的使用场景</a></p><p>[[redis中的事务、lua脚本和管道的使用场景_fangjian1204的博客-CSDN博客]]</p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h2 id="8-1-Redis-的-Java-客户端"><a href="#8-1-Redis-的-Java-客户端" class="headerlink" title="8.1. Redis 的 Java 客户端"></a>8.1. Redis 的 Java 客户端</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230502092404.png" alt="image.png"></p><h2 id="8-2-应用场景"><a href="#8-2-应用场景" class="headerlink" title="8.2. 应用场景"></a>8.2. 应用场景</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230502113307.png" alt="image.png"></p><h2 id="8-3-RedisTemplate"><a href="#8-3-RedisTemplate" class="headerlink" title="8.3. RedisTemplate"></a>8.3. RedisTemplate</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223140746.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230502101142.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230502101039.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223141558.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223141804.png"></p><h3 id="8-3-1-自动序列化反序列化"><a href="#8-3-1-自动序列化反序列化" class="headerlink" title="8.3.1. 自动序列化反序列化"></a>8.3.1. 自动序列化反序列化</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223142312.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223142356.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223142442.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221223151308.png"></p><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230415-2227%%</span>❕ ^2sb6jv</p><h2 id="9-1-尚硅谷"><a href="#9-1-尚硅谷" class="headerlink" title="9.1. 尚硅谷"></a>9.1. 尚硅谷</h2><p><a href="https://www.bilibili.com/video/BV13R4y1v7sP?p=38&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13R4y1v7sP?p=38&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>视频下载：&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;011-Java&#x2F;尚硅谷&#x2F;尚硅谷 Redis6 视频课程&#x2F;视频<br>[[Redis6笔记.xmind]]<br>[[Redis6笔记.docx]]</p><p>file:&#x2F;&#x2F;&#x2F;Users&#x2F;taylor&#x2F;Nutstore%20Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98&#x2F;002-DB&#x2F;001-Redis&#x2F;%E5%B0%9A%E7%A1%85%E8%B0%B7Redis7&#x2F;%E8%84%91%E5%9B%BE&#x2F;Redis%E8%84%91%E5%9B%BE%EF%BC%88%E5%9F%BA%E7%A1%80%E7%AF%87+%E9%AB%98%E7%BA%A7%E7%AF%87%EF%BC%89.html</p><h2 id="9-2-黑马"><a href="#9-2-黑马" class="headerlink" title="9.2. 黑马"></a>9.2. 黑马</h2><h3 id="9-2-1-视频"><a href="#9-2-1-视频" class="headerlink" title="9.2.1. 视频"></a>9.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1cr4y1671t?p=108&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1cr4y1671t?p=108&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="9-2-2-资料"><a href="#9-2-2-资料" class="headerlink" title="9.2.2. 资料"></a>9.2.2. 资料</h3><p>已存百度网盘：框架源码专题 -DB-Redis</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/002-框架源码专题/002-DB/001-Redis/Redis-笔记资料<br></code></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Redis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-12、AQS</title>
      <link href="/2022/12/19/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-12%E3%80%81AQS/"/>
      <url>/2022/12/19/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-12%E3%80%81AQS/</url>
      
        <content type="html"><![CDATA[<h1 id="1-是什么"><a href="#1-是什么" class="headerlink" title="1. 是什么"></a>1. 是什么</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107132139.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603111921.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107132213.png"></p><h2 id="1-1-大致结构"><a href="#1-1-大致结构" class="headerlink" title="1.1. 大致结构"></a>1.1. 大致结构</h2><p>在 AbstractQueuedSynchronizer 类中，有几个属性和一个双向队列（CLH 队列）</p><p>AQS 就是并发包下的一个基类</p><p>AQS &#x3D; state + CLH 队列</p><p>Node &#x3D; waitStatud + Thread<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107132402.png"></p><h2 id="1-2-涉及锁"><a href="#1-2-涉及锁" class="headerlink" title="1.2. 涉及锁"></a>1.2. 涉及锁</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107132510.png"></p><h2 id="1-3-模板设计模式"><a href="#1-3-模板设计模式" class="headerlink" title="1.3. 模板设计模式"></a>1.3. 模板设计模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107150343.png"><br>由子类实现具体方法逻辑</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107150420.png"></p><h2 id="1-4-类关系图"><a href="#1-4-类关系图" class="headerlink" title="1.4. 类关系图"></a>1.4. 类关系图</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107133442.png"></p><h2 id="1-5-加锁过程"><a href="#1-5-加锁过程" class="headerlink" title="1.5. 加锁过程"></a>1.5. 加锁过程</h2><ol><li>如果是第一个线程 t1，那么和队列无关，线程直接持有锁。并且也不会初始化队列，如果接下来的线程都是交替执行，那么永远和 AQS 队列无关，都是直接线程持有锁</li><li>如果发生了竞争，比如 t1 持有锁的过程中 t2 来 lock，那么这个时候就会初始化 AQS，初始化 AQS 的时候会在队列的头部虚拟一个 Thread 为 NULL 的 Node，因为队列当中的 head 永远是持有锁的那个 node（除了第一次会虚拟一个，其他时候都是持有锁的那个线程锁封装的 node）</li><li>现在第一次的时候持有锁的是 t1 ，而 t1 不在队列当中所以虚拟了一个 node 节点，队列当中的除了 head 之外的所有的 node 都在 park</li><li>当 t1 释放锁之后 unpark 某个（基本是队列当中的第二个，为什么是第二个呢？前面说过 head 永远是持有锁的那个 node，当有时候也不会是第二个，比如第二个被 cancel 之后，至于为什么会被 cancel，不在我们讨论范围之内，cancel 的条件很苛刻，基本不会发生）node 之后，node 被唤醒，假设 node 是 t2，那么这个时候会首先把 t2 变成 head（sethead）</li><li>在 sethead 方法里面会把 t2 代表的 node 设置为 head，并且把 node 的 Thread 设置为 null，为什么需要设置 null？其实原因很简单，现在 t2 已经拿到锁了，node 就不要排队了，那么 node 对 Thread 的引用就没有意义了。所以队列的 head 里面的 Thread 永远为 null</li><li><a href="https://blog.csdn.net/java_lyvee/article/details/98966684">JUC AQS ReentrantLock源码分析（一）</a></li></ol><h2 id="1-6-解锁过程"><a href="#1-6-解锁过程" class="headerlink" title="1.6. 解锁过程"></a>1.6. 解锁过程</h2><ol><li>调用 unlock()，会调用到 release()，进而调用到 tryRelease(arg)</li><li>tryRelease() 由 Sync 进行实现，其内部对 state 进行减一，完成一次释放锁，如果锁还有其它层未释放，返回 false，然后不做其它操作</li><li>如果锁已经完全释放，即 state&#x3D;0，则将队列中的 head 节点的 waitStatusCAS 地设置为 0， 然后将第二个节点设置为 head 节点，并将其代表的 Thread，unpark 唤醒。该线程获得锁</li><li>如果第二个节点被取消，则从 tail 向前获得一个为被取消的 node 进行 Thread 唤醒。</li></ol><h1 id="2-面试题"><a href="#2-面试题" class="headerlink" title="2. 面试题"></a>2. 面试题</h1><h2 id="2-1-谈谈对-AQS-的理解"><a href="#2-1-谈谈对-AQS-的理解" class="headerlink" title="2.1. 谈谈对 AQS 的理解"></a>2.1. 谈谈对 AQS 的理解</h2><p>AQS 是多线程同步器，它是 J.U.C 包中多个组件的底层实现，如 Lock、 CountDownLatch、Semaphore 等都用到了 AQS. 从本质上来说，AQS 提供了两种锁机制，分别是排它锁，和共享锁。排它锁，就是存在多线程竞争同一共享资源时，同一时刻只允许一个线程访问该共享资源，也就是多个线程中只能有一个线程获得锁资源，比如 Lock 中的 ReentrantLock 重入锁实现就是用到了 AQS 中的排它锁功能。共享锁也称为读锁，就是在同一时刻允许多个线程同时获得锁资源，比如 CountDownLatch 和 Semaphore 都是用到了 AQS 中的共享锁功能。</p><p>设计 AQS 整个体系需要解决的三个核心的问题：<br>①互斥变量的设计以及多线程同时更新互斥变量时的安全性<br>②未竞争到锁资源的线程的等待以及竞争到锁资源的线程释放锁之后的唤醒<br>③锁竞争的公平性和非公平性。</p><p>AQS 采用了一个 <code>volatile int</code> 类型的互斥变量 <code>state</code> 用来记录锁竞争的一个状态，0 表示当前没有任何线程竞争锁资源，而大于等于 1 表示已经有线程正在持有锁资源。<br>一个线程来获取锁资源的时候，首先判断 state 是否等于 0，如果是 (无锁状态)，则把这个 state 通过 CAS 更新成 1，表示占用到锁。此时如果多个线程进行同样的操作，会造成线程安全问题。AQS 采用了 CAS 机制来保证互斥变量 state 的原子性。<br>未获取到锁资源的线程通过 LockSupport 的 park 方法 (底层是 Unsafe 类的 park 方法) 对线程进行阻塞，把阻塞的线程按照先进先出的原则加入到一个双向链表的结构中，当获得锁资源的线程释放锁之后，会从双向链表的头部去唤醒下一个等待的线程再去竞争锁。<br>另外关于公平性和非公平性问题，AQS 的处理方式是，在竞争锁资源的时候，公平锁需要判断双向链表中是否有阻塞的线程，如果有，则需要去排队等待；而非公平锁的处理方式是，不管双向链表中是否存在等待锁的线程，都会直接尝试更改互斥变量 state 去竞争锁。</p><h2 id="2-2-AQS-为什么要使用双向链表"><a href="#2-2-AQS-为什么要使用双向链表" class="headerlink" title="2.2. AQS 为什么要使用双向链表"></a>2.2. AQS 为什么要使用双向链表</h2><p><a href="https://www.bilibili.com/video/BV1br4y1g7Mm?t=208.9">https://www.bilibili.com/video/BV1br4y1g7Mm?t=208.9</a></p><p>加入队列等待时需要判断前驱节点状态<br>竞争锁是需要判断前驱节点是否是头节点<br>需要查找 cancel 的节点</p><h1 id="3-源码解析"><a href="#3-源码解析" class="headerlink" title="3. 源码解析"></a>3. 源码解析</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107132954.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107133056.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107133121.png"></p><h2 id="3-1-重要变量"><a href="#3-1-重要变量" class="headerlink" title="3.1. 重要变量"></a>3.1. 重要变量</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107134050.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107134237.png"></p><h2 id="3-2-lock-gt-A-CAS-成功抢到锁"><a href="#3-2-lock-gt-A-CAS-成功抢到锁" class="headerlink" title="3.2. lock-&gt;A CAS 成功抢到锁"></a>3.2. lock-&gt;A CAS 成功抢到锁</h2><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">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">lock</span><span class="hljs-params">()</span> &#123;<br><span class="hljs-comment">// sync分为了公平和非公平</span><br>    sync.lock();<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-2-1-非公平锁"><a href="#3-2-1-非公平锁" class="headerlink" title="3.2.1. 非公平锁"></a>3.2.1. 非公平锁</h3><p>通过 <code>CAS的方式</code> 尝试将 state 从 0 修改为 1，如果返回 true，代表修改成功，如果修改失败，返回 false。将一个属性设置为当前线程，这个属性是 AQS 的父类提供的<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220075540.png"></p><p><span style="background-color:#ff00ff">A 线程 <code>compareAndSetState(0, 1)</code> 成功</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220075637.png"></p><h3 id="3-2-2-区别"><a href="#3-2-2-区别" class="headerlink" title="3.2.2. 区别"></a>3.2.2. 区别</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220072922.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107151037.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107151146.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220073037.png"></p><h2 id="3-3-acquire"><a href="#3-3-acquire" class="headerlink" title="3.3. acquire"></a>3.3. acquire</h2><p>有 3 个判断方法：tryAcquire、acquireQueued、selfInterrupt</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">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">acquire</span><span class="hljs-params">(<span class="hljs-type">int</span> arg)</span> &#123;<br><span class="hljs-comment">// tryAcquire再次尝试获取锁资源，如果尝试成功，返回true</span><br>    <span class="hljs-keyword">if</span> (!tryAcquire(arg) &amp;&amp;<br><span class="hljs-comment">// 获取锁资源失败后，需要将当前线程封装成一个Node，追加到AQS的队列中</span><br>        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))<br><span class="hljs-comment">// 线程中断</span><br>        selfInterrupt();<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-3-1-tryAcquire"><a href="#3-3-1-tryAcquire" class="headerlink" title="3.3.1. tryAcquire"></a>3.3.1. tryAcquire</h3><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">final</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">nonfairTryAcquire</span><span class="hljs-params">(<span class="hljs-type">int</span> acquires)</span> &#123;<br><span class="hljs-comment">// 获取当前线程</span><br>    <span class="hljs-keyword">final</span> <span class="hljs-type">Thread</span> <span class="hljs-variable">current</span> <span class="hljs-operator">=</span> Thread.currentThread();<br><span class="hljs-comment">// 获取AQS的state的值</span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> getState();<br><span class="hljs-comment">// 如果state为0，代表，尝试再次获取锁资源</span><br>    <span class="hljs-keyword">if</span> (c == <span class="hljs-number">0</span>) &#123;<br><span class="hljs-comment">// CAS尝试修改state，从0-1，如果成功，设置ExclusiveOwnerThread属性为当前线程</span><br>        <span class="hljs-keyword">if</span> (compareAndSetState(<span class="hljs-number">0</span>, acquires)) &#123;<br>            setExclusiveOwnerThread(current);<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        &#125;<br>    &#125;<br><span class="hljs-comment">// 当前占有锁资源的线程是否是当前线程</span><br>    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (current == getExclusiveOwnerThread()) &#123;<br><span class="hljs-comment">// 将state + 1</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">nextc</span> <span class="hljs-operator">=</span> c + acquires;<br><span class="hljs-comment">// 如果加1后，小于0，超所锁可重入的最大值，抛出Error</span><br>        <span class="hljs-keyword">if</span> (nextc &lt; <span class="hljs-number">0</span>) <span class="hljs-comment">// overflow</span><br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">&quot;Maximum lock count exceeded&quot;</span>);<br><span class="hljs-comment">// 没问题，就重新对state进行复制</span><br>        setState(nextc);<br><span class="hljs-comment">// 锁重入成功</span><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></code></pre></td></tr></table></figure><h4 id="3-3-1-1-nonfairTryAcquire-gt-B-抢占失败"><a href="#3-3-1-1-nonfairTryAcquire-gt-B-抢占失败" class="headerlink" title="3.3.1.1. nonfairTryAcquire-&gt;B 抢占失败"></a>3.3.1.1. nonfairTryAcquire-&gt;B 抢占失败</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220080937.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220101550.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107160811.png"></p><p><span style="background-color:#ff00ff">B 线程进入 nonfairTryAcquire() 方法，返回 false</span>，取反后为 true，判断&amp;&amp;后面的条件，从而进入 <code>addWaiter</code> 方法</p><h3 id="3-3-2-addWaiter-gt-B-节点参与竞争抢占失败"><a href="#3-3-2-addWaiter-gt-B-节点参与竞争抢占失败" class="headerlink" title="3.3.2. addWaiter-&gt;B 节点参与竞争抢占失败"></a>3.3.2. addWaiter-&gt;B 节点参与竞争抢占失败</h3><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 说明前面获取锁资源失败，放到队列中等待。。。</span><br><span class="hljs-keyword">private</span> Node <span class="hljs-title function_">addWaiter</span><span class="hljs-params">(Node mode)</span> &#123;<br><span class="hljs-comment">// 创建Node类，并且设置thread为当前线程，设置为排它锁</span><br>    <span class="hljs-type">Node</span> <span class="hljs-variable">node</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>(Thread.currentThread(), mode);<br>  <span class="hljs-comment">// 获取AQS中队列的尾部节点</span><br>    <span class="hljs-type">Node</span> <span class="hljs-variable">pred</span> <span class="hljs-operator">=</span> tail;<br><span class="hljs-comment">// 如果tail != null，现在队列有人排队</span><br>    <span class="hljs-keyword">if</span> (pred != <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-comment">// 将当前节点的prev，设置为刚才的尾部节点</span><br>        node.prev = pred;<br><span class="hljs-comment">// 基于CAS的方式，将tail节点设置为当前节点</span><br>        <span class="hljs-keyword">if</span> (compareAndSetTail(pred, node)) &#123;<br><span class="hljs-comment">// 将之前的为节点的next，设置为当前节点</span><br>            pred.next = node;<br>            <span class="hljs-keyword">return</span> node;<br>        &#125;<br>    &#125;<br><span class="hljs-comment">// 查看下面~</span><br>    enq(node);<br>    <span class="hljs-keyword">return</span> node;<br>&#125;<br><br><span class="hljs-comment">//-------------------------------</span><br><span class="hljs-comment">// 现在没人排队，我是第一个~~，  如果前面CAS失败，也会进到这个位置重新往队列尾巴塞。</span><br><span class="hljs-keyword">private</span> Node <span class="hljs-title function_">enq</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Node node)</span> &#123;<br><span class="hljs-comment">// 死循环~~</span><br>    <span class="hljs-keyword">for</span> (;;) &#123;<br><span class="hljs-comment">// 重新获取当前的tail节点为t</span><br>        <span class="hljs-type">Node</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> tail;<br>        <span class="hljs-keyword">if</span> (t == <span class="hljs-literal">null</span>) &#123; <br><span class="hljs-comment">// 现在没人排队, 我是第一个，没头没尾，都是空</span><br>            <span class="hljs-keyword">if</span> (compareAndSetHead(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>()))<span class="hljs-comment">// 初始化一个Node作为head，而这个head没有意义。</span><br><span class="hljs-comment">// 将头尾都指向了这个初始化的Node</span><br>                tail = head;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-comment">// 有人排队，往队列尾巴塞</span><br><span class="hljs-comment">// 当前节点的上一个指向tail。</span><br>            node.prev = t;<br><span class="hljs-comment">// 基于CAS的方式，将tail节点设置为当前节点</span><br>            <span class="hljs-keyword">if</span> (compareAndSetTail(t, node)) &#123;   <br><span class="hljs-comment">// 将之前的为节点的next，设置为当前节点</span><br>                t.next = node;<br>                <span class="hljs-keyword">return</span> t;<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="3-3-2-1-B-第一个等待-enq"><a href="#3-3-2-1-B-第一个等待-enq" class="headerlink" title="3.3.2.1. B 第一个等待 enq"></a>3.3.2.1. B 第一个等待 enq</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220081933.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220085632.png"></p><h4 id="3-3-2-2-死循环先生成-1-个占位节点"><a href="#3-3-2-2-死循环先生成-1-个占位节点" class="headerlink" title="3.3.2.2. 死循环先生成 1 个占位节点"></a>3.3.2.2. 死循环先生成 1 个占位节点</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220082550.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220082621.png"></p><h4 id="3-3-2-3-第二次循环-B-节点入队"><a href="#3-3-2-3-第二次循环-B-节点入队" class="headerlink" title="3.3.2.3. 第二次循环 B 节点入队"></a>3.3.2.3. 第二次循环 B 节点入队</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220082852.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220083010.png"></p><h4 id="3-3-2-4-其他节点类似-C-节点入队"><a href="#3-3-2-4-其他节点类似-C-节点入队" class="headerlink" title="3.3.2.4. 其他节点类似 C 节点入队"></a>3.3.2.4. 其他节点类似 C 节点入队</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220085948.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220090333.png"></p><h3 id="3-3-3-acquireQueued-gt-处理队列中的节点"><a href="#3-3-3-acquireQueued-gt-处理队列中的节点" class="headerlink" title="3.3.3. acquireQueued-&gt;处理队列中的节点"></a>3.3.3. acquireQueued-&gt;处理队列中的节点</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><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-comment">// 已经将node加入到了双向队列中，然后执行当前方法</span><br><span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">acquireQueued</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Node node, <span class="hljs-type">int</span> arg)</span> &#123;<br><span class="hljs-comment">// 标识！！！！</span><br>    <span class="hljs-type">boolean</span> <span class="hljs-variable">failed</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br>    <span class="hljs-keyword">try</span> &#123;<br><span class="hljs-comment">// 标识！！！！</span><br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">interrupted</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>        <span class="hljs-keyword">for</span> (;;) &#123;<br><span class="hljs-comment">// 获取当前节点的上一个节点p</span><br>            <span class="hljs-keyword">final</span> <span class="hljs-type">Node</span> <span class="hljs-variable">p</span> <span class="hljs-operator">=</span> node.predecessor();<br><span class="hljs-comment">// 如果p是头，再次尝试获取锁资源（state从0-1，锁重入操作），成功返回true，失败返回false</span><br>            <span class="hljs-keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;<br><span class="hljs-comment">// 拿到锁资源设置head节点为当前节点，将thread，prev设置为null，因为拿到锁资源了，排队跟我没关系</span><br>                setHead(node);<br>                p.next = <span class="hljs-literal">null</span>;   <span class="hljs-comment">// 帮助GC回收</span><br>                failed = <span class="hljs-literal">false</span>;  <span class="hljs-comment">// 将标识修改为false</span><br>                <span class="hljs-keyword">return</span> interrupted;  <span class="hljs-comment">// 返回interrupted </span><br>            &#125;<br><span class="hljs-comment">// 保证上一个节点是-1，才会返回true，才会将线程阻塞，等待唤醒获取锁资源</span><br>            <span class="hljs-keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;<br><span class="hljs-comment">// 基于Unsafe类的park方法，挂起线程~~~</span><br>                parkAndCheckInterrupt())    <span class="hljs-comment">// 针对fail属性，这里是唯一可能出现异常的地方，JVM内部出现问题时，可以这么理解，fianlly代码块中的内容，执行的几率约等于0~</span><br>                interrupted = <span class="hljs-literal">true</span>;<br>        &#125;<br>    &#125; <span class="hljs-keyword">finally</span> &#123;<br>        <span class="hljs-keyword">if</span> (failed)<br>            cancelAcquire(node);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="3-3-3-1-tryAcquire"><a href="#3-3-3-1-tryAcquire" class="headerlink" title="3.3.3.1. tryAcquire"></a>3.3.3.1. tryAcquire</h4><p><code>acquireQueued</code> 方法处理队列，因为 B 在队头，所以先处理 B。如果 p 是占位节点，继续&amp;&amp;后面的判断，即<span style="background-color:#ff00ff">B 线程会执行 tryAcquire，再次进行尝试</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220103100.png"></p><p>predecessor 返回前驱节点，如果前驱节点为 null，抛出 NPE</p><p><span style="background-color:#ff00ff">B 节点 tryAcquire，再次尝试仍然失败，进入下面 if 判断中的 shouldParkAfterFailedAcquire 方法</span></p><h4 id="3-3-3-2-shouldParkAfterFailedAcquire"><a href="#3-3-3-2-shouldParkAfterFailedAcquire" class="headerlink" title="3.3.3.2. shouldParkAfterFailedAcquire"></a>3.3.3.2. shouldParkAfterFailedAcquire</h4><h5 id="3-3-3-2-1-第-1-次循环-gt-将前面节点-waitStatus-置为-1"><a href="#3-3-3-2-1-第-1-次循环-gt-将前面节点-waitStatus-置为-1" class="headerlink" title="3.3.3.2.1. 第 1 次循环 -&gt;将前面节点 waitStatus 置为 -1"></a>3.3.3.2.1. 第 1 次循环 -&gt;将前面节点 waitStatus 置为 -1</h5><p>队头第一个线程节点 B 再次尝试失败时进入 <code>shouldParkAfterFailedAcquire</code> 方法<br>自旋进入第 1 次循环时，会走最下面的分支，将<span style="background-color:#00ff00">占位节点</span>的 <code>waitStatus</code> 改为 -1<br>compareAndSetWaitStatus</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">// node是当前节点，pred是上一个节点</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">shouldParkAfterFailedAcquire</span><span class="hljs-params">(Node pred, Node node)</span> &#123;<br><span class="hljs-comment">// 获取上一个节点的状态</span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">ws</span> <span class="hljs-operator">=</span> pred.waitStatus;<br><span class="hljs-comment">// 如果上一个节点状态为SIGNAL，一切正常！</span><br>    <span class="hljs-keyword">if</span> (ws == Node.SIGNAL)<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br><span class="hljs-comment">// 如果上一个节点已经失效了</span><br>    <span class="hljs-keyword">if</span> (ws &gt; <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-keyword">do</span> &#123;<br><span class="hljs-comment">// 将当前节点的prev指针指向了上一个的上一个！</span><br>            node.prev = pred = pred.prev;<br>        &#125; <span class="hljs-keyword">while</span> (pred.waitStatus &gt; <span class="hljs-number">0</span>);  <span class="hljs-comment">// 一致找到小于等于0的</span><br><span class="hljs-comment">// 将重新标识好的最近的有效节点的next</span><br>        pred.next = node;<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-comment">// 小于等于0，不等于-1，将上一个有效节点状态修改为-1</span><br>        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);<br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220105351.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220105429.png"></p><h5 id="3-3-3-2-2-第-2-次循环-gt-B-park-住"><a href="#3-3-3-2-2-第-2-次循环-gt-B-park-住" class="headerlink" title="3.3.3.2.2. 第 2 次循环 -&gt;B park 住"></a>3.3.3.2.2. 第 2 次循环 -&gt;B park 住</h5><p>自旋进入第 2 次循环时，<code>shouldParkAfterFailedAcquire</code> 中的 if 判断 <code>ws == Node.SIGNAL</code> 返回 ture，继续进行 acquireQueued 方法中第 2 个 if 判断中的 <code>parkAndCheckInterrupt()</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220105715.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220105614.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220105851.png"></p><h4 id="3-3-3-3-parkAndCheckInterrupt-gt-park-B-节点"><a href="#3-3-3-3-parkAndCheckInterrupt-gt-park-B-节点" class="headerlink" title="3.3.3.3. parkAndCheckInterrupt-&gt;park B 节点"></a>3.3.3.3. parkAndCheckInterrupt-&gt;park B 节点</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220110118.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220101905.png"></p><h4 id="3-3-3-4-C-节点重复-shouldParkAfterFailedAcquire-中的循环"><a href="#3-3-3-4-C-节点重复-shouldParkAfterFailedAcquire-中的循环" class="headerlink" title="3.3.3.4. C 节点重复 shouldParkAfterFailedAcquire 中的循环"></a>3.3.3.4. C 节点重复 shouldParkAfterFailedAcquire 中的循环</h4><p><span style="background-color:#00ff00">第 1 次循环，将其前面的 B 节点的 waitStatus 置为 -1</span> <span style="background-color:#ff00ff">（规律就是后面节点将前面节点的 waitStatus 置为 -1，表示已就绪）</span><br><span style="background-color:#00ff00">第 2 次循环，执行 parkAndCheckInterrupt，将自己 park 住</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107174849.png"></p><p>被 park 后，线程 B、C 等都进入 <code>WATING</code> 状态，等待被唤醒</p><p>线程状态复习：<a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-7%E3%80%81Thread/" title="并发基础-7、Thread">并发基础-7、Thread</a></p><h2 id="3-4-unlock"><a href="#3-4-unlock" class="headerlink" title="3.4. unlock"></a>3.4. unlock</h2><p>release(1)</p><h2 id="3-5-release"><a href="#3-5-release" class="headerlink" title="3.5. release"></a>3.5. release</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107182307.png"></p><h3 id="3-5-1-tryRelease-gt-返回-true"><a href="#3-5-1-tryRelease-gt-返回-true" class="headerlink" title="3.5.1. tryRelease-&gt;返回 true"></a>3.5.1. tryRelease-&gt;返回 true</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220112725.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220112845.png"></p><h3 id="3-5-2-unparkSuccessor"><a href="#3-5-2-unparkSuccessor" class="headerlink" title="3.5.2. unparkSuccessor"></a>3.5.2. unparkSuccessor</h3><h4 id="3-5-2-1-compareAndSetWaitStatus"><a href="#3-5-2-1-compareAndSetWaitStatus" class="headerlink" title="3.5.2.1. compareAndSetWaitStatus"></a>3.5.2.1. compareAndSetWaitStatus</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107182938.png"></p><h4 id="3-5-2-2-unpark"><a href="#3-5-2-2-unpark" class="headerlink" title="3.5.2.2. unpark"></a>3.5.2.2. unpark</h4><p>unpark 就会唤醒上面 parkAndCheckInterrupt 中 park 的线程，线程 B 被唤醒后，<code>从parkAndCheckInterrupt</code> 方法中的 837 行继续执行，出来后在自旋中继续循环</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107183229.png"></p><p>假设没有线程中断的特殊情况，B 节点出来 parkAndCheckInterrupt 方法之后，还在 acquireQueued 方法的死循环中继续循环。此时 node 是 B 节点，那么 p 就是占位节点，进入 if 判断中，<code>p==head</code> 为 true，继续&amp;&amp;后面的 tryAcquire 方法</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107184607.png"></p><h5 id="3-5-2-2-1-B-在-tryAcquire-方法中-CAS-成功抢到锁"><a href="#3-5-2-2-1-B-在-tryAcquire-方法中-CAS-成功抢到锁" class="headerlink" title="3.5.2.2.1. B 在 tryAcquire 方法中 CAS 成功抢到锁"></a>3.5.2.2.1. B 在 tryAcquire 方法中 CAS 成功抢到锁</h5><p>state 变为 1，占有窗口的 Thread 变为 Thread B<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220132848.png"></p><p>将节点 B 设置为 head，占位节点的 next 置为 null<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220132951.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107185011.png"></p><p><span style="background-color:#ff00ff">也就是将原来的占位节点出队，B 变为新的占位节点</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220133021.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220133102.png"></p><h2 id="3-6-selfInterrupt"><a href="#3-6-selfInterrupt" class="headerlink" title="3.6. selfInterrupt"></a>3.6. selfInterrupt</h2><p>该方法其实是为了中断线程。但为什么获取了锁以后还要中断线程呢？这里简单分析一下：</p><p>因为 LockSupport.park 阻塞线程后，有两种可能被唤醒。</p><p><strong>第一种情况</strong>，前节点是头节点，释放锁后，会调用 LockSupport.unpark 唤醒当前线程。整个过程没有涉及到中断，最终 acquireQueued 返回 false 时，不需要调用 selfInterrupt。</p><p><strong>第二种情况</strong>，LockSupport.park 支持响应中断请求，能够被其他线程通过 interrupt() 唤醒。但这种唤醒并没有用，因为线程前面可能还有等待线程，在 acquireQueued 的循环里，线程会再次被阻塞。parkAndCheckInterrupt 返回的是 Thread.interrupted()，不仅返回中断状态，还会清除中断状态，保证阻塞线程忽略中断。最终 acquireQueued 返回 true 时，真正的中断状态已经被清除，需要调用 selfInterrupt 维持中断状态，即要进行“中断补偿”操作。</p><p>优雅退出线程中，也有设计中断补偿：<a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-7%E3%80%81Thread/" title="并发基础-7、Thread">并发基础-7、Thread</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220170534.png"></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><h2 id="5-1-尚硅谷周阳"><a href="#5-1-尚硅谷周阳" class="headerlink" title="5.1. 尚硅谷周阳"></a>5.1. 尚硅谷周阳</h2><p><a href="https://www.bilibili.com/video/BV1uX4y1u7ht">https://www.bilibili.com/video/BV1uX4y1u7ht</a></p><h2 id="5-2-测试程序"><a href="#5-2-测试程序" class="headerlink" title="5.2. 测试程序"></a>5.2. 测试程序</h2><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;juc-day02<br>[[ReentrantLock3.java]]<br>[[AQSDemo.java]]<br>selfInterrupt：[[Java 并发之 AQS 详解（下）  HeapDump性能社区]]</p><h2 id="5-3-整体流程图"><a href="#5-3-整体流程图" class="headerlink" title="5.3. 整体流程图"></a>5.3. 整体流程图</h2><p><a href="https://www.processon.com/view/5e29b0e8e4b04579e40c15a7?fromnew=1">https://www.processon.com/view/5e29b0e8e4b04579e40c15a7?fromnew=1</a></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
            <tag> AQS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-0、PostProcessor</title>
      <link href="/2022/12/12/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-0%E3%80%81PostProcessor/"/>
      <url>/2022/12/12/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-0%E3%80%81PostProcessor/</url>
      
        <content type="html"><![CDATA[<h1 id="1-增强器分类"><a href="#1-增强器分类" class="headerlink" title="1. 增强器分类"></a>1. 增强器分类</h1><p>BeanFactoryPostProcessor</p><p>BeanPostProcessor</p><p>InitializingBean</p><h1 id="2-BeanFactoryPostProcessor"><a href="#2-BeanFactoryPostProcessor" class="headerlink" title="2. BeanFactoryPostProcessor"></a>2. BeanFactoryPostProcessor</h1><p>[[MyBeanFactoryPostProcessor.java]]</p><h2 id="2-1-作用"><a href="#2-1-作用" class="headerlink" title="2.1. 作用"></a>2.1. 作用</h2><p>beanFactory 的后置处理器，在 BeanFactory 标准初始化之后调用，用来定制和修改 BeanFactory 的内容；</p><h2 id="2-2-时机"><a href="#2-2-时机" class="headerlink" title="2.2. 时机"></a>2.2. 时机</h2><p>所有的 bean 定义已经保存加载到 beanFactory，但是 bean 的实例还未创建</p><h2 id="2-3-原理"><a href="#2-3-原理" class="headerlink" title="2.3. 原理"></a>2.3. 原理</h2><p> 1)、ioc 容器创建对象的过程中<br> 2)、执行 <code>invokeBeanFactoryPostProcessors(beanFactory)</code> 方法，遍历所有 BeanFactoryPostProcessors 执行他们的 <code>postProcessBeanFactory</code> 方法</p><pre><code>如何找到所有的BeanFactoryPostProcessor并执行他们的方法？  1）、直接在BeanFactory中找到所有类型是BeanFactoryPostProcessor的组件，并执行他们的方法  2）、在初始化创建其他组件方法finishBeanFactoryInitialization前面执行</code></pre><h1 id="3-BeanDefinitionRegistryPostProcessor"><a href="#3-BeanDefinitionRegistryPostProcessor" class="headerlink" title="3. BeanDefinitionRegistryPostProcessor"></a>3. BeanDefinitionRegistryPostProcessor</h1><p>[[MyBeanDefinitionRegistryPostProcessor.java]]</p><h2 id="3-1-作用"><a href="#3-1-作用" class="headerlink" title="3.1. 作用"></a>3.1. 作用</h2><p>利用 BeanDefinitionRegistryPostProcessor 给容器中再额外添加一些组件；</p><h2 id="3-2-时机"><a href="#3-2-时机" class="headerlink" title="3.2. 时机"></a>3.2. 时机</h2><p>优先于 BeanFactoryPostProcessor 执行；<br>在所有 bean 定义信息<span style="background-color:#00ff00">将要被加载</span>，bean 实例还未创建的；</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221211124812.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221211131421.png"></p><h2 id="3-3-原理"><a href="#3-3-原理" class="headerlink" title="3.3. 原理"></a>3.3. 原理</h2><p>1）、ioc 创建对象<br>2）、refresh()-&gt;invokeBeanFactoryPostProcessors(beanFactory);<br>3）、从容器中获取到所有的 BeanDefinitionRegistryPostProcessor 组件。<br>  1、依次触发所有的 postProcessBeanDefinitionRegistry() 方法<br>  2、再来触发 postProcessBeanFactory() 方法 BeanFactoryPostProcessor；</p><p>4）、再来从容器中找到 BeanFactoryPostProcessor 组件；然后依次触发 postProcessBeanFactory() 方法</p><h2 id="3-4-子类示例"><a href="#3-4-子类示例" class="headerlink" title="3.4. 子类示例"></a>3.4. 子类示例</h2><p>ConfigurationClassPostProcessor</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213192509.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213193117.png"></p><h1 id="4-BeanPostProcessor"><a href="#4-BeanPostProcessor" class="headerlink" title="4. BeanPostProcessor"></a>4. BeanPostProcessor</h1><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><p>bean 后置处理器，bean 创建对象初始化前后进行拦截工作的</p><h2 id="4-1-原理"><a href="#4-1-原理" class="headerlink" title="4.1. 原理"></a>4.1. 原理</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206194938.png"></p><p>遍历得到容器中所有的 BeanPostProcessor；挨个执行 beforeInitialization，<br>一但返回 null，跳出 for 循环，不会执行后面的 BeanPostProcessor.postProcessorsBeforeInitialization</p><p>给 bean 进行属性赋值之后<br><code>populateBean</code>(beanName, mbd, instanceWrapper);</p><p>在初始化方法 invokeInitMethods 执行前后分别执行 applyBeanPostProcessorsBefore(After)Initialization 方法<br><code>initializeBean</code>{<br>    <span style="background-color:#00ff00">applyBeanPostProcessorsBeforeInitialization</span>(wrappedBean, beanName);<br>    <code>invokeInitMethods</code>(beanName, wrappedBean, mbd); 执行自定义初始化<br>    <span style="background-color:#00ff00">applyBeanPostProcessorsAfterInitialization</span>(wrappedBean, beanName);<br> }</p><h2 id="4-2-Spring-底层使用"><a href="#4-2-Spring-底层使用" class="headerlink" title="4.2. Spring 底层使用"></a>4.2. Spring 底层使用</h2><p>bean 赋值，注入其他组件，@Autowired，生命周期注解功能，@Async,xxx BeanPostProcessor;</p><h3 id="4-2-1-ApplicationContextAwareProcessor"><a href="#4-2-1-ApplicationContextAwareProcessor" class="headerlink" title="4.2.1. ApplicationContextAwareProcessor"></a>4.2.1. ApplicationContextAwareProcessor</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206211018.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206211206.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206211352.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206211512.png"></p><h3 id="4-2-2-InitDestroyAnnotationBeanPostProcessor"><a href="#4-2-2-InitDestroyAnnotationBeanPostProcessor" class="headerlink" title="4.2.2. InitDestroyAnnotationBeanPostProcessor"></a>4.2.2. InitDestroyAnnotationBeanPostProcessor</h3><p>处理 <code>@PostConstruct</code>、<code>@PreDestroy</code> 的 BeanPostProcessor</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221209193632.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206211818.png"></p><h3 id="4-2-3-AutowiredAnnotationBeanPostProcessor"><a href="#4-2-3-AutowiredAnnotationBeanPostProcessor" class="headerlink" title="4.2.3. AutowiredAnnotationBeanPostProcessor"></a>4.2.3. AutowiredAnnotationBeanPostProcessor</h3><h1 id="5-InitializingBean"><a href="#5-InitializingBean" class="headerlink" title="5. InitializingBean"></a>5. InitializingBean</h1><h2 id="5-1-作用"><a href="#5-1-作用" class="headerlink" title="5.1. 作用"></a>5.1. 作用</h2><p>Bean 组件初始化以后对组件进行后续设置；在于额外处理</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">void afterPropertiesSet() throws Exception;<br></code></pre></td></tr></table></figure><h2 id="5-2-时机"><a href="#5-2-时机" class="headerlink" title="5.2. 时机"></a>5.2. 时机</h2><h2 id="5-3-原理"><a href="#5-3-原理" class="headerlink" title="5.3. 原理"></a>5.3. 原理</h2><h2 id="5-4-使用"><a href="#5-4-使用" class="headerlink" title="5.4. 使用"></a>5.4. 使用</h2><h1 id="6-SmartInstantiationAwareBeanPostProcessor"><a href="#6-SmartInstantiationAwareBeanPostProcessor" class="headerlink" title="6. SmartInstantiationAwareBeanPostProcessor"></a>6. SmartInstantiationAwareBeanPostProcessor</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221214084949.png"></p><h2 id="6-1-注册监听器时调用"><a href="#6-1-注册监听器时调用" class="headerlink" title="6.1. 注册监听器时调用"></a>6.1. 注册监听器时调用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221214084909.png"></p><h2 id="6-2-创建实例时调用"><a href="#6-2-创建实例时调用" class="headerlink" title="6.2. 创建实例时调用"></a>6.2. 创建实例时调用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221214084807.png"></p><h1 id="7-AutowiredAnnotationBeanPostProcessor"><a href="#7-AutowiredAnnotationBeanPostProcessor" class="headerlink" title="7. AutowiredAnnotationBeanPostProcessor"></a>7. AutowiredAnnotationBeanPostProcessor</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221214105941.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221214110046.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221214105854.png"></p><h1 id="8-ApplicationListener"><a href="#8-ApplicationListener" class="headerlink" title="8. ApplicationListener"></a>8. ApplicationListener</h1><p>[[ExtConfig.java]]</p><h2 id="8-1-使用"><a href="#8-1-使用" class="headerlink" title="8.1. 使用"></a>8.1. 使用</h2><ol><li>写一个监听器（ApplicationListener 实现类）来监听某个事件（ApplicationEvent 及其子类）</li><li>在被 Spring 管理起来的 service 中使用注解 <code> @EventListener</code></li></ol><h2 id="8-2-原理"><a href="#8-2-原理" class="headerlink" title="8.2. 原理"></a>8.2. 原理</h2><h3 id="8-2-1-事件来源"><a href="#8-2-1-事件来源" class="headerlink" title="8.2.1. 事件来源"></a>8.2.1. 事件来源</h3><p> 1）、ContextRefreshedEvent 事件：<br>     1）、容器创建对象：refresh()；<br>     2）、finishRefresh(); 容器刷新完成会发布 ContextRefreshedEvent 事件<br> 2）、自己发布事件；<br> 3）、容器关闭会发布 ContextClosedEvent；</p><h3 id="8-2-2-初始化事件多播器（派发器）"><a href="#8-2-2-初始化事件多播器（派发器）" class="headerlink" title="8.2.2. 初始化事件多播器（派发器）"></a>8.2.2. 初始化事件多播器（派发器）</h3><p>1）、容器创建对象：refresh();<br>2）、initApplicationEventMulticaster(); 初始化 ApplicationEventMulticaster；<br>    1）、先去容器中找有没有 id&#x3D;“applicationEventMulticaster”的组件；<br>    2）、如果没有 this.applicationEventMulticaster &#x3D; new SimpleApplicationEventMulticaster(beanFactory);<br>        并且加入到容器中，我们就可以在其他组件要派发事件，自动注入这个 applicationEventMulticaster；</p><h3 id="8-2-3-容器注册监听器"><a href="#8-2-3-容器注册监听器" class="headerlink" title="8.2.3. 容器注册监听器"></a>8.2.3. 容器注册监听器</h3><p>1）、容器创建对象：refresh();<br>2）、registerListeners();<br>    从容器中拿到所有的监听器，把他们注册到 applicationEventMulticaster 中；<br>    <code>String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);</code><br>    &#x2F;&#x2F;将 listener 注册到 ApplicationEventMulticaster 中<br>    getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);</p><h3 id="8-2-4-发布流程"><a href="#8-2-4-发布流程" class="headerlink" title="8.2.4. 发布流程"></a>8.2.4. 发布流程</h3><p>publishEvent(new ContextRefreshedEvent(this));</p><p>1）、获取事件的多播器（派发器）：getApplicationEventMulticaster()<br>2）、multicastEvent 派发事件：<br>3）、获取到所有的 ApplicationListener；<br>    <code>for (final ApplicationListener&lt;?&gt; listener : getApplicationListeners(event, type))</code> {<br>    1）、如果有 Executor，可以支持使用 Executor 进行异步派发；<br>        Executor executor &#x3D; getTaskExecutor();<br>    2）、否则，同步的方式直接执行 listener 方法；invokeListener(listener, event);<br>     拿到 listener 回调 onApplicationEvent 方法；</p><h3 id="8-2-5-EventListener-注解原理"><a href="#8-2-5-EventListener-注解原理" class="headerlink" title="8.2.5. @EventListener 注解原理"></a>8.2.5. @EventListener 注解原理</h3><p>使用 <code>EventListenerMethodProcessor</code> 处理器来解析方法上的@EventListener；<br><code>EventListenerMethodProcessor</code> 实现了 <code>SmartInitializingSingleton</code> 接口<br> SmartInitializingSingleton 原理：-&gt;afterSingletonsInstantiated();<br>1）、ioc 容器创建对象并 refresh()；<br>2）、AbstractApplicationContext.finishBeanFactoryInitialization(beanFactory); 初始化剩下的单实例 bean；<br>   在 beanFactory.preInstantiateSingletons() 中<br>    1）、先创建所有的单实例 bean；getBean();<br>    2）、获取所有创建好的单实例 bean，判断是否是 SmartInitializingSingleton 类型的；<br>        如果是就调用 afterSingletonsInstantiated();</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221211145708.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221211145957.png"></p><p>在 this.processBean(factories, beanName, type) 中会 this.applicationContext.addApplicationListener(applicationListener);<br>将@EventListener 标注的 Listener 加入到容器中</p><h3 id="8-2-6-BeanFactory-和-ApplicationContext"><a href="#8-2-6-BeanFactory-和-ApplicationContext" class="headerlink" title="8.2.6. BeanFactory 和 ApplicationContext"></a>8.2.6. BeanFactory 和 ApplicationContext</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221212185609.png"></p><h1 id="9-实战经验"><a href="#9-实战经验" class="headerlink" title="9. 实战经验"></a>9. 实战经验</h1><h1 id="10-参考与感谢"><a href="#10-参考与感谢" class="headerlink" title="10. 参考与感谢"></a>10. 参考与感谢</h1><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-基本原理-1、反射</title>
      <link href="/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-1%E3%80%81%E5%8F%8D%E5%B0%84/"/>
      <url>/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-1%E3%80%81%E5%8F%8D%E5%B0%84/</url>
      
        <content type="html"><![CDATA[<h1 id="1-定义"><a href="#1-定义" class="headerlink" title="1. 定义"></a>1. 定义</h1><p> 在《Thinking in Java》一书中曾说到：</p><blockquote><p>反射机制并没有什么神奇之处，当通过反射与一个未知类型的对象打交道时，JVM 只是简单地检查这个对象，看它属于哪个特定的类，再用它做其他事情之前必须先加载那个类的 Class 对象，那个类的 .class 文件对于 JVM 来说必须是可获取的：要么在本地，要么在网络。普通方式和反射调用区别在于，编译器在编译时打开和检查 .class 文件，而对于反射机制来说，.class 文件在编译时是不可获取的，所以是<span style="background-color:#00ff00">在运行时打开和检查 .class 文件</span>。</p></blockquote><blockquote><p>多态也在普通方式的范畴内。RTTI，即 Run-Time Type Identification 运行时类型认定，通过运行时类型信息程序能够使用父类的指针或引用来检查这些指针或引用所指的对象的实际派生类型，是多态实现的技术基础。RTTI 的功能主要是通过 Class 类文件实现的，更精确一点是通过 Class 类文件的方法表实现的。虽然是运行时判断所调用方法具体属于哪个类型，但是父类、子类的所有信息，在编译期是已经知道的了，即.class 文件在编译时已经获取到了。而对于反射机制来说，.class 文件在编译时是不可获取的，在运行时才获取到。</p></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519071628.jpg" alt="image-20200403082314270"><br><a href="https://juejin.im/post/5a44c0ad518825455f2f96e5">https://juejin.im/post/5a44c0ad518825455f2f96e5</a></p><p>java 的反射机制是指在程序运行状态中，给定任意一个类，都可以获取到这个类的属性和方法；给定任意一个对象都可以调用这个对象的属性和方法，这种动态的获取类的信息和调用对象的方法的功能称之为 java 的反射机制。<br>一言以蔽之：反射机制可以让你在程序运行时，拿到任意一个类的属性和方法并调用它。</p><h1 id="2-功能"><a href="#2-功能" class="headerlink" title="2. 功能"></a>2. 功能</h1><ul><li>运行时构造一个类的对象；</li><li>运行时获取一个类所具有的的成员变量和方法；</li><li>运行时调用任意一个对象的方法；</li><li>生成动态代理；</li></ul><h1 id="3-使用方法"><a href="#3-使用方法" class="headerlink" title="3. 使用方法"></a>3. 使用方法</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519071647.jpg" alt="image-20200403142556303"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519071641.jpg" alt="image-20200403142342973"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230519071636.jpg" alt="image-20200403142443132"></p><h1 id="4-优缺点"><a href="#4-优缺点" class="headerlink" title="4. 优缺点"></a>4. 优缺点</h1><h2 id="4-1-优点"><a href="#4-1-优点" class="headerlink" title="4.1. 优点"></a>4.1. 优点</h2><ol><li>增加程序的灵活性，可以在运行的过程中动态对类进行修改和操作</li><li>提高代码的复用率，比如动态代理，就是用到了反射来实现</li><li>可以在运行时轻松获取任意一个类的方法、属性，并且还能通过反射进行动态调用</li></ol><h2 id="4-2-缺点"><a href="#4-2-缺点" class="headerlink" title="4.2. 缺点"></a>4.2. 缺点</h2><ol><li>反射会涉及到动态类型的解析，所以 JVM 无法对这些代码进行优化，导致性能要比非反射调用更低。</li><li>使用反射以后，代码的可读性会下降</li><li>反射可以绕过一些限制访问的属性或者方法，可能会导致破坏了代码本身的抽象性</li></ol><h1 id="5-方法调用"><a href="#5-方法调用" class="headerlink" title="5. 方法调用"></a>5. 方法调用</h1><h2 id="5-1-反射"><a href="#5-1-反射" class="headerlink" title="5.1. 反射"></a>5.1. 反射</h2><h2 id="5-2-MethodHandles"><a href="#5-2-MethodHandles" class="headerlink" title="5.2. MethodHandles"></a>5.2. MethodHandles</h2><p>从目前接触到的 API 来看，似乎和以前用的反射区别并不大，其实不然，上面的特性包含在 <a href="https://jcp.org/en/jsr/detail?id=292">JSR 292</a> 中，提供了比反射 API 更加强大的动态方法调用能力，并且新增了一个 java 虚拟机指令 <code>invokedynamic</code>，<code>invokedynamic</code> 指令通过引导方法（bootstrap method，BSM）机制来使用方法句柄。有关该指令更详细的信息可以参考 Java 虚拟机规范。</p><p>另外，在 JDK9 中新增了 <code>Variable Handles</code>（变量句柄）相关功能，主要是用来取代 <code>java.util.concurrent.atomic</code> 包以及 <code>sun.misc.Unsafe</code> 类的功能，在 Lookup 类中，新增了 <code>findVarHandle</code> 方法来获取变量句柄，提供了各种细粒度的原子或有序性操作，更加安全和性能更高，毕竟 <code>sun.misc.Unsafe</code> 以后不推荐使用了，不安全。</p><p>参考：</p><ul><li><a href="https://jcp.org/en/jsr/detail?id=292">JSR 292</a></li><li><a href="https://zhuanlan.zhihu.com/p/28124632">Invokedynamic：Java的秘密武器</a></li><li><a href="https://www.infoq.com/articles/Invokedynamic-Javas-secret-weapon">Invokedynamic - Java’s Secret Weapon</a></li></ul><p>这样看下来，MethodHandle 方法句柄的调用方式明显比 reflect 反射调用的方式至少在代码层面要繁琐很多。</p><p>并且对于 MethodHandle 而言，没有 <code>Method.setAccessible</code> 之类的操作，导致 private 和 protected 方法只有在类的内部代码中才能使用。甚至使用方法上甚至还要自己指定对于的 JVM 调用方式（invokevirutal &#x2F; invokespecial &#x2F; invokestatic）。</p><p>那 MethodHandle 方法在 Java7 中引入的意义何在呢？<br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230519-1320%%</span>❕ ^loszgb</p><p><span style="background-color:#ff00ff">最大的一个原因是出于性能考虑的，MethodHandle 的访问检查是在创建时进行校验的，而不是在实际调用时。这也就意味着生成了一个 MethodHandle 方法句柄之后，多次调用仅有一次权限检查，而 reflect 反射会在每次 invoke 时进行校验。</span></p><p>并且对于 JVM 而言，可以完全透视 <em>MethodHandle</em> 并将尝试对其进行优化，从而获得更好的性能。</p><h3 id="5-2-1-性能对比"><a href="#5-2-1-性能对比" class="headerlink" title="5.2.1. 性能对比"></a>5.2.1. 性能对比</h3><p><a href="https://www.cnblogs.com/danzZ/p/14190415.html">https://www.cnblogs.com/danzZ/p/14190415.html</a></p><p><a href="https://blog.51cto.com/u_15127639/2873107">https://blog.51cto.com/u_15127639/2873107</a></p><p><a href="https://ljd1996.github.io/2019/10/21/Java%E5%8F%8D%E5%B0%84/">https://ljd1996.github.io/2019/10/21/Java%E5%8F%8D%E5%B0%84/</a></p><h1 id="6-实战经验"><a href="#6-实战经验" class="headerlink" title="6. 实战经验"></a>6. 实战经验</h1><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p><a href="https://zhuanlan.zhihu.com/p/519992996">https://zhuanlan.zhihu.com/p/519992996</a><br><a href="https://blog.csdn.net/wenyuan65/article/details/81145900">https://blog.csdn.net/wenyuan65/article/details/81145900</a></p><p><a href="https://blog.51cto.com/u_15127639/2873107">https://blog.51cto.com/u_15127639/2873107</a><br><a href="https://www.cnblogs.com/danzZ/p/14190415.html">https://www.cnblogs.com/danzZ/p/14190415.html</a></p>]]></content>
      
      
      <categories>
          
          <category> 基本原理 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 反射 </tag>
            
            <tag> 方法调用 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-基本原理-2、多态与代理</title>
      <link href="/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-2%E3%80%81%E5%A4%9A%E6%80%81%E4%B8%8E%E4%BB%A3%E7%90%86/"/>
      <url>/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-2%E3%80%81%E5%A4%9A%E6%80%81%E4%B8%8E%E4%BB%A3%E7%90%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-代理"><a href="#1-代理" class="headerlink" title="1. 代理"></a>1. 代理</h1><h2 id="1-1-代理模式"><a href="#1-1-代理模式" class="headerlink" title="1.1. 代理模式"></a>1.1. 代理模式</h2><a href="/2023/01/12/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7%E3%80%81%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/" title="设计模式-7、代理模式">设计模式-7、代理模式</a><h2 id="1-2-反射"><a href="#1-2-反射" class="headerlink" title="1.2. 反射"></a>1.2. 反射</h2><a href="/2022/12/09/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-1%E3%80%81%E5%8F%8D%E5%B0%84/" title="基本原理-1、反射">基本原理-1、反射</a><h2 id="1-3-多态"><a href="#1-3-多态" class="headerlink" title="1.3. 多态"></a>1.3. 多态</h2><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/" title="性能调优专题-基础-5、JVM-虚拟机栈">性能调优专题-基础-5、JVM-虚拟机栈</a><p><a href="https://blog.csdn.net/wenyuan65/article/details/81145900">https://blog.csdn.net/wenyuan65/article/details/81145900</a></p><h3 id="1-3-1-动态代理与多态的关系"><a href="#1-3-1-动态代理与多态的关系" class="headerlink" title="1.3.1. 动态代理与多态的关系"></a>1.3.1. 动态代理与多态的关系</h3><h2 id="1-4-多态与反射的区别"><a href="#1-4-多态与反射的区别" class="headerlink" title="1.4. 多态与反射的区别"></a>1.4. 多态与反射的区别</h2><p><a href="https://www.51cto.com/article/668984.html">https://www.51cto.com/article/668984.html</a></p><h2 id="1-5-框架中的应用"><a href="#1-5-框架中的应用" class="headerlink" title="1.5. 框架中的应用"></a>1.5. 框架中的应用</h2><p><a href="https://www.cnblogs.com/Xianhuii/p/16918191.html">https://www.cnblogs.com/Xianhuii/p/16918191.html</a></p><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><h2 id="3-1-黑马程序员"><a href="#3-1-黑马程序员" class="headerlink" title="3.1. 黑马程序员"></a>3.1. 黑马程序员</h2><p><a href="https://www.bilibili.com/video/BV1Np4y1z7BU/?p=59&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Np4y1z7BU/?p=59&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>课件已下载：&#x2F;Volumes&#x2F;Seagate Bas&#x2F;001-ArchitectureRoad&#x2F;资料-java设计模式（图解+框架源码分析+实战）<br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;design_patterns</p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;009-内功心法专题&#x2F;设计模式&#x2F;黑马&#x2F;设计模式]]</p><p><span style="background-color:#ff00ff">动态代理的例子不是很好</span></p><h2 id="3-2-尚硅谷-JAVA大数据集合"><a href="#3-2-尚硅谷-JAVA大数据集合" class="headerlink" title="3.2. 尚硅谷-JAVA大数据集合"></a>3.2. 尚硅谷-JAVA大数据集合</h2><p><a href="https://www.bilibili.com/video/BV1Dx411f7ib/?p=266&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Dx411f7ib/?p=266&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>课件已下载：&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;011-Java&#x2F;尚硅谷大数据 共50阶段-2018&#x2F;尚硅谷-第12阶段《spring》&#x2F;day02-spring&#x2F;node<br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring02</p><p>[[02_尚硅谷大数据技术之Spring(老师原版).docx]]</p><h2 id="3-3-其他资料"><a href="#3-3-其他资料" class="headerlink" title="3.3. 其他资料"></a>3.3. 其他资料</h2><p><a href="https://www.bilibili.com/video/BV184411x7XA/?p=22&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV184411x7XA/?p=22&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      <categories>
          
          <category> 代理 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 代理 </tag>
            
            <tag> 动态代理 </tag>
            
            <tag> 多态 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-5、声明式事务</title>
      <link href="/2022/12/08/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-5%E3%80%81%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1-@EnableTransactionManagement/"/>
      <url>/2022/12/08/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-5%E3%80%81%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1-@EnableTransactionManagement/</url>
      
        <content type="html"><![CDATA[<h1 id="1-源码原理"><a href="#1-源码原理" class="headerlink" title="1. 源码原理"></a>1. 源码原理</h1><p>@EnableTransactionManagement 的作用原理</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221209200229.png"></p><h2 id="1-1-Import-使用方法"><a href="#1-1-Import-使用方法" class="headerlink" title="1.1. @Import 使用方法"></a>1.1. @Import 使用方法</h2><blockquote><p>@Import 使用方法</p></blockquote><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><h2 id="1-2-EnableTransactionManagement"><a href="#1-2-EnableTransactionManagement" class="headerlink" title="1.2. @ EnableTransactionManagement"></a>1.2. @ EnableTransactionManagement</h2><h3 id="1-2-1-注册时机"><a href="#1-2-1-注册时机" class="headerlink" title="1.2.1. 注册时机"></a>1.2.1. 注册时机</h3><p>与 AOP 的注册时机是相同的:<br>BD 注册：在 refresh() 的第 5 步，<code>invokeBeanFactoryPostProcessors</code>，如下图所示，方法执行完毕后，事务相关 BD 都已注册完毕</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221135326.png" alt="image.png"></p><h3 id="1-2-2-TransactionManagementConfigurationSelector⭐️🔴"><a href="#1-2-2-TransactionManagementConfigurationSelector⭐️🔴" class="headerlink" title="1.2.2. TransactionManagementConfigurationSelector⭐️🔴"></a>1.2.2. TransactionManagementConfigurationSelector⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230212112328.png" alt="image.png"></p><p>利用 <code>TransactionManagementConfigurationSelector</code> 给容器中会导入 2 个组件</p><ol><li>AutoProxyRegistrar 注册器 (loadBeanDefinitionsFromRegistrars) ❕<span style="display:none">%%<br>1916-🏡⭐️◼️处理 ImportBeanDefinitionRegistrar 类型的 import 注解 ?🔜MSTM📝 invokeBeanFactoryPP 中，postProcessBeanDefinitionRegistry 中 loadBeanDefinitionFromRegistrars，处理 ImportBeanDefinitionRegistrar 类型的 import 注解，核心逻辑就是去遍历被 import 进来的 ImportBeanDefinitionRegistrar 的自定义方法 registerBeanDefinitions◼️⭐️-point-20230211-1916%%</span></li><li>ProxyTransactionManagementConfiguration 配置类</li></ol><h3 id="1-2-3-AutoProxyRegistrar-①"><a href="#1-2-3-AutoProxyRegistrar-①" class="headerlink" title="1.2.3. AutoProxyRegistrar ①"></a>1.2.3. AutoProxyRegistrar ①</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230217072541.png" alt="image.png"></p><h4 id="1-2-3-1-注册-InfrastructureAdvisorAutoProxyCreator"><a href="#1-2-3-1-注册-InfrastructureAdvisorAutoProxyCreator" class="headerlink" title="1.2.3.1. 注册 InfrastructureAdvisorAutoProxyCreator"></a>1.2.3.1. 注册 InfrastructureAdvisorAutoProxyCreator</h4><p> AutoProxyRegistrar 实现了 <code>ImportBeanDefinitionRegistrar</code> 接口，会调用实现的 <code>registerBeanDefinitions</code> 方法，给容器中注册一个 <code>InfrastructureAdvisorAutoProxyCreator</code> 组件；</p><h4 id="1-2-3-2-作用"><a href="#1-2-3-2-作用" class="headerlink" title="1.2.3.2. 作用"></a>1.2.3.2. 作用</h4><p> InfrastructureAdvisorAutoProxyCreator 的作用？<br>    <span style="background-color:#00ff00">利用后置处理器机制在对象初始化以后，包装对象</span>，返回一个 <code>代理对象（增强器）</code>，代理对象执行方法利用拦截器链进行调用；</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218180541.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210083209.png"></p><h3 id="1-2-4-ProxyTransactionManagementConfiguration-②"><a href="#1-2-4-ProxyTransactionManagementConfiguration-②" class="headerlink" title="1.2.4. ProxyTransactionManagementConfiguration ②"></a>1.2.4. ProxyTransactionManagementConfiguration ②</h3><p>ProxyTransactionManagementConfiguration 做了什么？</p><h4 id="1-2-4-1-注册事务解析器"><a href="#1-2-4-1-注册事务解析器" class="headerlink" title="1.2.4.1. 注册事务解析器"></a>1.2.4.1. 注册事务解析器</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215110710.png" alt="image.png"></p><p>在初始化完成之后，BeanPostProcessor 的子接口 InstantiationAwareBeanPostProcessor 的子类 AbstractAutoPorxyCreator 会执行 postProcessAfterInitialization() 方法，在里面会创建动态代理<br>在创建过程中会解析@Transactional 注解，确定是否需要生成动态代理👇🏻<br>❕<span style="display:none">%%<br>1136-🏡⭐️◼️事务解析器的作用◼️⭐️-point-20230215-1136%%</span></p><a href="/2022/12/08/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-5%E3%80%81%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1-@EnableTransactionManagement/" title="Spring-5、声明式事务-@EnableTransactionManagement">Spring-5、声明式事务-@EnableTransactionManagement</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230205082801.png" alt="image.png"><br>❕</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215182611.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215182919.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215183535.png" alt="image.png"></p><h5 id="1-2-4-1-1-事务解析器的作用⭐️🔴"><a href="#1-2-4-1-1-事务解析器的作用⭐️🔴" class="headerlink" title="1.2.4.1.1. 事务解析器的作用⭐️🔴"></a>1.2.4.1.1. 事务解析器的作用⭐️🔴</h5><ol><li><span style="background-color:#ff00ff">this.attributeCache.put(cacheKey, txAttr)</span></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215184320.png" alt="image.png"></p><ol start="3"><li>给出是否需要增强代理的判断</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215183934.png" alt="image.png"></p><h4 id="1-2-4-2-注册事务拦截器-配置事务管理器"><a href="#1-2-4-2-注册事务拦截器-配置事务管理器" class="headerlink" title="1.2.4.2. 注册事务拦截器 (配置事务管理器)"></a>1.2.4.2. 注册事务拦截器 (配置事务管理器)</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215110732.png" alt="image.png"><br>❕<span style="display:none">%%<br>»1.🏡⭐️◼️给事务拦截器配置了事务管理器◼️⭐️-point-20230220-1349%%</span></p><h4 id="1-2-4-3-注册事务增强器-advisor-⭐️🔴"><a href="#1-2-4-3-注册事务增强器-advisor-⭐️🔴" class="headerlink" title="1.2.4.3. 注册事务增强器 (advisor)⭐️🔴"></a>1.2.4.3. 注册事务增强器 (advisor)⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210083822.png"><br>给容器中注册事务增强器（BeanFactoryTransactionAttributeSourceAdvisor），然后 set<strong>事务注解解析器</strong> 和 <strong>事务拦截器</strong><br>事务增强器要用事务注解的信息的时候，用 <code>AnnotationTransactionAttributeSource</code> 来解析事务注解</p><h5 id="1-2-4-3-1-set-事务解析器-AnnotationTransactionAttributeSource"><a href="#1-2-4-3-1-set-事务解析器-AnnotationTransactionAttributeSource" class="headerlink" title="1.2.4.3.1. set 事务解析器 (AnnotationTransactionAttributeSource)"></a>1.2.4.3.1. set 事务解析器 (AnnotationTransactionAttributeSource)</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230216134243.png" alt="image.png"></p><h5 id="1-2-4-3-2-set-事务拦截器-TransactionInterceptor"><a href="#1-2-4-3-2-set-事务拦截器-TransactionInterceptor" class="headerlink" title="1.2.4.3.2. set 事务拦截器 (TransactionInterceptor)"></a>1.2.4.3.2. set 事务拦截器 (TransactionInterceptor)</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210092111.png"></p><h3 id="1-2-5-增强器的解析和缓存⭐️🔴"><a href="#1-2-5-增强器的解析和缓存⭐️🔴" class="headerlink" title="1.2.5. 增强器的解析和缓存⭐️🔴"></a>1.2.5. 增强器的解析和缓存⭐️🔴</h3><p>不同于 AOP，声明式事务没有切面逻辑，所以没有解析切面以及封装成 Advisor 的过程。它是直接给 new 了一个出来并通过@Bean 注册到容器中<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220171741.png" alt="image.png"></p><p>在 AOP 的解析过程中，直接解析出来并缓存起来，然后在 <code>findAdvisorsThatCanApply</code> 方法中判断是否有 <code>@Transactional</code> 注解来确定是否为当前 Bean 生成动态代理 ❕<span style="display:none">%%<br>»7.🏡⭐️◼️声明式事务与 AOP 的不同之处 ?🔜MSTM📝 AOP 的增强器需要解析 advice 和封装 advisor，其中还包括顺序问题之类。而声明式事务的 advisor 是直接给到容器的，判断是否需要增强就判断该 Bean 是否有@Transactional 注解即可。◼️⭐️-point-20230220-1723%%</span></p><h4 id="1-2-5-1-继承关系"><a href="#1-2-5-1-继承关系" class="headerlink" title="1.2.5.1. 继承关系"></a>1.2.5.1. 继承关系</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220171150.png" alt="image.png"></p><h4 id="1-2-5-2-缓存增强器"><a href="#1-2-5-2-缓存增强器" class="headerlink" title="1.2.5.2. 缓存增强器"></a>1.2.5.2. 缓存增强器</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220171024.png" alt="image.png"></p><h3 id="1-2-6-判断是否需要增强⭐️🔴"><a href="#1-2-6-判断是否需要增强⭐️🔴" class="headerlink" title="1.2.6. 判断是否需要增强⭐️🔴"></a>1.2.6. 判断是否需要增强⭐️🔴</h3><p>^he1cd1</p><h4 id="1-2-6-1-判断入口"><a href="#1-2-6-1-判断入口" class="headerlink" title="1.2.6.1. 判断入口"></a>1.2.6.1. 判断入口</h4><p><a href="https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5">https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5</a><br>❕<span style="display:none">%%<br>1101-🏡⭐️◼️findAdvisorsThatCanApply：<a href="https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5%E2%97%BC%EF%B8%8F%E2%AD%90%EF%B8%8F-point-20230215-1101%%">https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5◼️⭐️-point-20230215-1101%%</a></span><br><a href="https://blog.csdn.net/paralysed/article/details/120398874">https://blog.csdn.net/paralysed/article/details/120398874</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215095704.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230215110221.png" alt="image.png"></p><h4 id="1-2-6-2-判断方法⭐️🔴"><a href="#1-2-6-2-判断方法⭐️🔴" class="headerlink" title="1.2.6.2. 判断方法⭐️🔴"></a>1.2.6.2. 判断方法⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220171352.png" alt="image.png"></p><p>如何判断当前类具备事务，具有创建动态代理的资格？</p><p>答：从&#x3D;&#x3D;当前正在初始化的 bean 的&#x3D;&#x3D;所有的方法中，找到带有@Transactional 的方法，根据<span style="background-color:#00ff00">方法优先原则</span>，由本类方法 &#x3D;&#x3D;&gt; 接口方法 &#x3D;&#x3D;&gt; 父类方法的顺序去找，如果找到，就表示有创建动态代理的资格。如果方法上都没有，则去类上面找，由本类上 &#x3D;&#x3D;&gt; 接口上 &#x3D;&#x3D;&gt; 父类上的顺序去找，如果找到，就表示有创建动态代理的资格。</p><h2 id="1-3-TransactionInterceptor⭐️🔴"><a href="#1-3-TransactionInterceptor⭐️🔴" class="headerlink" title="1.3. TransactionInterceptor⭐️🔴"></a>1.3. TransactionInterceptor⭐️🔴</h2><h3 id="1-3-1-结构"><a href="#1-3-1-结构" class="headerlink" title="1.3.1. 结构"></a>1.3.1. 结构</h3><ol><li>TransactionInterceptor：保存了 <code>事务属性信息</code> 和 <code>事务管理器</code>；</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210101952.png"></p><ol start="2"><li>extends TransactionAspectSupport implements MethodInterceptor<br><span style="background-color:#00ff00">他是一个 MethodInterceptor；在目标方法执行的时候； 会执行拦截器链，就会进入拦截器中的方法</span></li></ol><h3 id="1-3-2-执行"><a href="#1-3-2-执行" class="headerlink" title="1.3.2. 执行"></a>1.3.2. 执行</h3><h4 id="1-3-2-1-获取-PlatformTransactionManager"><a href="#1-3-2-1-获取-PlatformTransactionManager" class="headerlink" title="1.3.2.1. 获取 PlatformTransactionManager"></a>1.3.2.1. 获取 PlatformTransactionManager</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210084239.png"></p><h4 id="1-3-2-2-执行目标方法"><a href="#1-3-2-2-执行目标方法" class="headerlink" title="1.3.2.2. 执行目标方法"></a>1.3.2.2. 执行目标方法</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210091041.png"></p><p><span style="background-color:#ff00ff">如果异常，获取到事务管理器，利用事务管理回滚操作；</span><br><span style="background-color:#ff00ff">如果正常，利用事务管理器，提交事务</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210102700.png"></p><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><p>[[Spring注解驱动开发-尚硅谷-雷丰阳]]</p><h2 id="2-1-环境搭建"><a href="#2-1-环境搭建" class="headerlink" title="2.1. 环境搭建"></a>2.1. 环境搭建</h2><ul><li>1、导入相关依赖<br>        数据源、数据库驱动、Spring-jdbc 模块</li><li>2、配置数据源、JdbcTemplate（Spring 提供的简化数据库操作的工具）操作数据</li></ul><p><span style="background-color:#00ff00">spring-jdbc 会导入 2 个依赖</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213080333.png" alt="image.png"></p><h2 id="2-2-重点配置"><a href="#2-2-重点配置" class="headerlink" title="2.2. 重点配置"></a>2.2. 重点配置</h2><ul><li>3、给方法上标注 @Transactional 表示当前方法是一个事务方法；</li><li>4、 <span style="background-color:#00ff00">EnableTransactionManagement</span> 开启基于注解的事务管理功能；</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221210080220.png"></p><ul><li>5、<span style="background-color:#00ff00">配置事务管理器来控制事务</span>;<br>         @Bean<br>          public PlatformTransactionManager transactionManager()</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221209195806.png"></p><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><h2 id="3-1-尚硅谷-雷丰阳"><a href="#3-1-尚硅谷-雷丰阳" class="headerlink" title="3.1. 尚硅谷 - 雷丰阳"></a>3.1. 尚硅谷 - 雷丰阳</h2><p><a href="https://www.bilibili.com/video/BV1gW411W7wy?p=37&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1gW411W7wy?p=37&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="3-1-1-示例代码"><a href="#3-1-1-示例代码" class="headerlink" title="3.1.1. 示例代码"></a>3.1.1. 示例代码</h3><p>spring-annotation:  [[IOCTest_Tx.java]]</p>]]></content>
      
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> 声明式事务 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-6、整合Mybatis</title>
      <link href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-6%E3%80%81%E6%95%B4%E5%90%88Mybatis-@MapperScan/"/>
      <url>/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-6%E3%80%81%E6%95%B4%E5%90%88Mybatis-@MapperScan/</url>
      
        <content type="html"><![CDATA[<h1 id="1-环境准备-ConfigurationClassPostProcessor"><a href="#1-环境准备-ConfigurationClassPostProcessor" class="headerlink" title="1. 环境准备 ConfigurationClassPostProcessor"></a>1. 环境准备 ConfigurationClassPostProcessor</h1><p><a href="https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5">https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5</a><br>❕<span style="display:none">%%<br>▶2.🏡⭐️◼️CCPP 是容器环境最重要的准备工作 ?🔜MSTM📝 只有 ConfigurationClassPostProcessor 在 this() 中注册完 BD，在 refresh() 中的第 5 步 invokeBeanFactoryPostProcessors 中创建完 Bean 并执行 postProcessBeanDefinitionRegistry 方法，其他注解才能陆续的被注册 BD 被创建 Bean。◼️⭐️-point-20230220-1406%%</span></p><h2 id="1-1-注册-BPP-的-BD"><a href="#1-1-注册-BPP-的-BD" class="headerlink" title="1.1. 注册 BPP 的 BD"></a>1.1. 注册 BPP 的 BD</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230218071902.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219120859.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219121007.png" alt="image.png"></p><h2 id="1-2-创建-BPP-的-Bean"><a href="#1-2-创建-BPP-的-Bean" class="headerlink" title="1.2. 创建 BPP 的 Bean"></a>1.2. 创建 BPP 的 Bean</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219132747.png" alt="image.png"></p><h1 id="2-注册-MapperScannerConfigurer"><a href="#2-注册-MapperScannerConfigurer" class="headerlink" title="2. 注册 MapperScannerConfigurer"></a>2. 注册 MapperScannerConfigurer</h1><h2 id="2-1-注册-MapperScannerConfigurer-BD"><a href="#2-1-注册-MapperScannerConfigurer-BD" class="headerlink" title="2.1. 注册 MapperScannerConfigurer BD"></a>2.1. 注册 MapperScannerConfigurer BD</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103112033.png"></p><h3 id="2-1-1-Import-MapperScannerRegistrar"><a href="#2-1-1-Import-MapperScannerRegistrar" class="headerlink" title="2.1.1. @Import(MapperScannerRegistrar)"></a>2.1.1. @Import(MapperScannerRegistrar)</h3><blockquote><p>@Import 使用方法</p></blockquote><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><p>❕<span style="display:none">%%<br>▶3.🏡⭐️◼️@Import 的使用方式有 3 种，1. 直接导入单个 class，2. 导入一个全限定名数组字符串，3. 导入一个实现了 ImportBeanDefinitionRegistrar 的 registrar 类，在该类里手动注入一个 BD，这种方式会在 ConfigurationClassBeanDefinitionReader 中的 loadBeanDefinitionFromRegistrar 方法中尽心解析和注册◼️⭐️-point-20230220-1428%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220142445.png" alt="image.png"></p><p>可以发现@MapperScan 注解中使用的是@Import 的第 2 种用法，<span style="background-color:#00ff00">导入一个实现了 ImportBeanDefinitionRegistrar 的类</span>  <code>MapperScannerRegistrar.class</code><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919193638832.png" alt="image-20210919193638832"></p><p>@MapperScan 注解的属性不为空，说明配置了这个注解，就会调用分支里的重写的方法 <code>registerBeanDefinitions</code><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919193743332.png" alt="image-20210919193743332"></p><h3 id="2-1-2-建造者构造-MapperScannerConfigurer-BD"><a href="#2-1-2-建造者构造-MapperScannerConfigurer-BD" class="headerlink" title="2.1.2. 建造者构造 MapperScannerConfigurer BD"></a>2.1.2. 建造者构造 MapperScannerConfigurer BD</h3><p>在 <code>MapperScannerRegistrar.class</code> 的方法 <code>registerBeanDefinitions</code> 中用创建 bean 定义构造器 <code>BeanDefinitionBuilder</code>，并通过构造器构建出 <code>MapperScannerConfigurer</code> 的 bean 定义<br>应用到了设计模式：<span style="background-color:#00ff00">建造者设计模式</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919193901449.png" alt="image-20210919193901449"></p><p>然后<span style="background-color:#00ff00">将构建的 MapperScannerConfigurer 的 bean 定义注册到容器中</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919194157619.png" alt="image-20210919194157619"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919194255778.png" alt="image-20210919194255778"></p><h3 id="2-1-3-注册流程-loadBeanDefinitionsFromRegistrars"><a href="#2-1-3-注册流程-loadBeanDefinitionsFromRegistrars" class="headerlink" title="2.1.3. 注册流程 loadBeanDefinitionsFromRegistrars"></a>2.1.3. 注册流程 loadBeanDefinitionsFromRegistrars</h3><p><code>ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry</code><br><code>this.reader.loadBeanDefinitions</code><br><code>loadBeanDefinitionsForConfigurationClass</code><br><code>loadBeanDefinitionsFromRegistrars</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219162048.png" alt="image.png"></p><p><code>ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry</code> 走完除了 Mapper 接口对应的 BD，其他的包括 <code>MapperScannerConfigurer</code> 在内都已注册 BD。接下来走 <code>MapperScannerConfigurer</code> 的 postProcessBeanDefinitionRegistry 才开始注册 Mapper 的 BD。❕<span style="display:none">%%<br>▶4.🏡⭐️◼️ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry 方法走完后的现状 ?🔜MSTM📝 MapperScannerConfigurer BD 已经注册好◼️⭐️-point-20230220-1452%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219170305.png" alt="image.png"></p><h2 id="2-2-创建-MapperScannerConfigurer-Bean"><a href="#2-2-创建-MapperScannerConfigurer-Bean" class="headerlink" title="2.2. 创建 MapperScannerConfigurer Bean"></a>2.2. 创建 MapperScannerConfigurer Bean</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219162804.png" alt="image.png"></p><p>因为 MapperScannerConfigurer 是 <code>BeanDefinitionRegistryPostProcessor</code> 的实现类</p><h2 id="2-3-MapperScannerConfigurer-继承关系⭐️🔴"><a href="#2-3-MapperScannerConfigurer-继承关系⭐️🔴" class="headerlink" title="2.3. MapperScannerConfigurer 继承关系⭐️🔴"></a>2.3. MapperScannerConfigurer 继承关系⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230218070835.png" alt="image.png"></p><h1 id="3-扫描-Mapper-接口成为-BD"><a href="#3-扫描-Mapper-接口成为-BD" class="headerlink" title="3. 扫描 Mapper 接口成为 BD"></a>3. 扫描 Mapper 接口成为 BD</h1><p>上面一步注册了<span style="background-color:#00ff00">MapperScannerConfigurer 的 BD 信息</span>，接下来看 <code>MapperScannerConfigurer</code> 的作用。<br>类 MapperScannerConfigurer 实现了 <code>BeanDefinitionRegistryPostProcessor</code> 接口中的 <code>postProcessBeanDefinitionRegistry</code> 方法，这个方法也可以为容器中注册 bean 定义</p><h2 id="3-1-触发时机⭐️🔴"><a href="#3-1-触发时机⭐️🔴" class="headerlink" title="3.1. 触发时机⭐️🔴"></a>3.1. 触发时机⭐️🔴</h2><p><code>MapperScannerConfigurer.postProcessBeanDefinitionRegistry</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103192812.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219145449.png" alt="image.png"></p><h2 id="3-2-扫描原理-ClassPathMapperScanner-⭐️🔴"><a href="#3-2-扫描原理-ClassPathMapperScanner-⭐️🔴" class="headerlink" title="3.2. 扫描原理 (ClassPathMapperScanner)⭐️🔴"></a>3.2. 扫描原理 (ClassPathMapperScanner)⭐️🔴</h2><p><a href="https://www.processon.com/diagraming/63f1d3dea600c6676369dd8f">https://www.processon.com/diagraming/63f1d3dea600c6676369dd8f</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220151117.png" alt="image.png"></p><p>❕<span style="display:none">%%<br>»5.🏡⭐️◼️重写了 2 个方法：isCandidateComponent 和 processBeanDefinitions 方法◼️⭐️-point-20230220-1507%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221065911.png" alt="image.png"></p><p>实现 BeanDefinitionRegistryPostProcessor 是<span style="background-color:#00ff00">IOC 的一个扩展点</span>（<a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-2%E3%80%81IOC/" title="Spring-2、IOC">Spring-2、IOC</a>）可以自主的在 bean 的概念态到定义态的过程中，往容器里注入 bean 定义<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920092547247.png" alt="image-20210920092547247"></p><p>BeanDefinitionRegistryPostProcessor 中的 <code>postProcessBeanDefinitionRegistry()</code> 方法会在 IOC 容器中<span style="background-color:#00ff00">注册@MapperScan 扫描到的 mapper 包下的 bean 定义</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920094515529.png" alt="image-20210920094515529"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205193715.png"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920095742460.png" alt="image-20210920095742460"></p><h3 id="3-2-1-设置过滤规则"><a href="#3-2-1-设置过滤规则" class="headerlink" title="3.2.1. 设置过滤规则"></a>3.2.1. 设置过滤规则</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230218231029.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219064452.png" alt="image.png"></p><p><a href="https://www.bilibili.com/video/BV1uF411L73Q?p=102&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1uF411L73Q?p=102&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="3-2-2-设置-BD-判断规则"><a href="#3-2-2-设置-BD-判断规则" class="headerlink" title="3.2.2. 设置 BD 判断规则"></a>3.2.2. 设置 BD 判断规则</h3><h4 id="3-2-2-1-Spring-默认的判断规则"><a href="#3-2-2-1-Spring-默认的判断规则" class="headerlink" title="3.2.2.1. Spring 默认的判断规则"></a>3.2.2.1. Spring 默认的判断规则</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219091501.png" alt="image.png"></p><p> <strong>isIndependent</strong> 判断当前 BeanDefinition 对应的类是否是独立类（顶级类，静态内部类）**顶层的类 (独立类)**（没有父类或静态内部类) ❕<span style="display:none">%%<br>▶1.🏡⭐️◼️顶级类的概念 ?🔜MSTM📝 再没有父类或者是静态内部类◼️⭐️-point-20230222-0701%%</span></p><p> <strong>isConcrete</strong> 是否非接口非抽象类<br> <strong>hasAnnotatedMethods(Lookup.class.getName())</strong> 是否有包含@Lookup 注解的方法<br> <br> 成立条件：<strong>首先必须是独立类，其次要么是非抽象类非接口，要么是抽象类但是有@Lookup 注解的方法</strong></p><h4 id="3-2-2-2-Mybatis-重写的判断规则"><a href="#3-2-2-2-Mybatis-重写的判断规则" class="headerlink" title="3.2.2.2. Mybatis 重写的判断规则"></a>3.2.2.2. Mybatis 重写的判断规则</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219091359.png" alt="image.png"><br><span style="background-color:#ff00ff">扫描独立的接口</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219091046.png" alt="image.png"></p><h3 id="3-2-3-扫描过程"><a href="#3-2-3-扫描过程" class="headerlink" title="3.2.3. 扫描过程"></a>3.2.3. 扫描过程</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219074600.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230219074624.png" alt="image.png"></p><h1 id="4-mapper-实例化-mapper-是接口原本无法实例化"><a href="#4-mapper-实例化-mapper-是接口原本无法实例化" class="headerlink" title="4. mapper 实例化 (mapper 是接口原本无法实例化)"></a>4. mapper 实例化 (mapper 是接口原本无法实例化)</h1><h3 id="4-1-processBeanDefinitions-偷梁换柱-⭐️🔴"><a href="#4-1-processBeanDefinitions-偷梁换柱-⭐️🔴" class="headerlink" title="4.1. processBeanDefinitions(偷梁换柱)⭐️🔴"></a>4.1. processBeanDefinitions(偷梁换柱)⭐️🔴</h3><p><span style="background-color:#ff0000">ClassPathMapperScanner</span><span style="background-color:#ff00ff">将接口类型的 mapper 的 definition 类型改为 MapperFactoryBean</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230218073114.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103141309.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103141332.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103171153.png"></p><h3 id="4-2-MapperFactoryBean⭐️🔴"><a href="#4-2-MapperFactoryBean⭐️🔴" class="headerlink" title="4.2. MapperFactoryBean⭐️🔴"></a>4.2. MapperFactoryBean⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103174216.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103174347.png"><br>❕<span style="display:none">%%<br>▶6.🏡⭐️◼️为什么要用 factoryBean ?🔜MSTM📝 因为我们要在使用 Mapper 就必须获取 Bean，但是 Mapper 是接口无法实例化，所以通过使用动态代理类来使用 Mapper 接口中的方法。此时容器无法自动管理和装配我们需要的动态代理，所以需要告诉容器要返回给我们什么，那么 factoryBean 可以完成这种功能。在 getObject 方法中返回创建的动态代理◼️⭐️-point-20230220-1524%%</span></p><h3 id="4-3-JDK-动态代理接口设置"><a href="#4-3-JDK-动态代理接口设置" class="headerlink" title="4.3. JDK 动态代理接口设置"></a>4.3. JDK 动态代理接口设置</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920112818543.png" alt="image-20210920112818543"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230217225257.png" alt="image.png"></p><p>指定生成代理类时使用的是有参构造器，并且入参是我们的 mapper 接口</p><h3 id="4-4-修改注入模式-byType⭐️🔴"><a href="#4-4-修改注入模式-byType⭐️🔴" class="headerlink" title="4.4. 修改注入模式 -byType⭐️🔴"></a>4.4. 修改注入模式 -byType⭐️🔴</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103172137.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220220237.png" alt="image.png"></p><h4 id="4-4-1-原因-1-非空判断"><a href="#4-4-1-原因-1-非空判断" class="headerlink" title="4.4.1. 原因 1 非空判断"></a>4.4.1. 原因 1 非空判断</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230218075321.png" alt="image.png"></p><p>是因为 MapperFactoryBean 继承于 SqlSessionDaoSupport，SqlSessionDaoSupport 里有个属性 sqlSessionTemplate 有非空断言。因为属性上没有@Autowired，默认情况下是无法自动装配的。所以要改成按类型，在 IOC 里查找到之后，调用 set 方法，进行赋值就可以完成自动装配</p><p>Spring 中 <code>AbstractBeanDefinition.AUTOWIRE_BY_TYPE</code> 默认设置为 0，需要加@Autowired 才能完成自动装配。如果没有显示加@Autowired，那么就必须修改自动注入模式。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220221106.png" alt="image.png"><br>❕<span style="display:none">%%<br>▶9.🏡⭐️◼️为什么要修改自动注入模式 ?🔜MSTM📝 因为 Spring 默认值是 0，要完成自动注入必须显示的加@Autowired。而我们的 MapperFactoryBean 是通过 FactoryBean 获取到的，没法加@Autowired，同时又继承自 SqlSessionSupport，里面有个 notnull 断言的 SqlSessionTemplate。我们在使用声明式事务时配置的 sqlSessionFactory，在 setter 方法中，设置了 SQLSessionTemplate，巧妙的解决了非空断言的问题。◼️⭐️-point-20230220-2221%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230103172231.png"></p><h4 id="4-4-2-原因-2-不易报错"><a href="#4-4-2-原因-2-不易报错" class="headerlink" title="4.4.2. 原因 2 不易报错"></a>4.4.2. 原因 2 不易报错</h4><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919190639402-20210920113130852.png" alt="image-20210919190639402"></p><p><span style="background-color:#00ff00">sqlSessionTemplate 必须赋值</span>，改为 by_type 赋值不容易出错</p><h1 id="5-mapper-使用逻辑"><a href="#5-mapper-使用逻辑" class="headerlink" title="5. mapper 使用逻辑"></a>5. mapper 使用逻辑</h1><h2 id="5-1-生成动态代理并缓存-SqlSessionFactoryBean"><a href="#5-1-生成动态代理并缓存-SqlSessionFactoryBean" class="headerlink" title="5.1. 生成动态代理并缓存 (SqlSessionFactoryBean)"></a>5.1. 生成动态代理并缓存 (SqlSessionFactoryBean)</h2><h3 id="5-1-1-流程图"><a href="#5-1-1-流程图" class="headerlink" title="5.1.1. 流程图"></a>5.1.1. 流程图</h3><p><a href="https://www.processon.com/diagraming/63f04e03e39b2d4955a8c9cc">https://www.processon.com/diagraming/63f04e03e39b2d4955a8c9cc</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230220183628.png" alt="image.png"></p><h3 id="5-1-2-缓存-mapper-接口信息"><a href="#5-1-2-缓存-mapper-接口信息" class="headerlink" title="5.1.2. 缓存 mapper 接口信息"></a>5.1.2. 缓存 mapper 接口信息</h3><h4 id="5-1-2-1-SqlSessionFactoryBean-继承关系"><a href="#5-1-2-1-SqlSessionFactoryBean-继承关系" class="headerlink" title="5.1.2.1. SqlSessionFactoryBean 继承关系"></a>5.1.2.1. SqlSessionFactoryBean 继承关系</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230226181732.png" alt="image.png"></p><p>实现了 <code>InitializingBean</code> 的 <code>afterPropertiesSet()</code> 方法</p><h4 id="5-1-2-2-缓存时机"><a href="#5-1-2-2-缓存时机" class="headerlink" title="5.1.2.2. 缓存时机"></a>5.1.2.2. 缓存时机</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230226182200.png" alt="image.png"></p><h3 id="5-1-3-生成动态代理⭐️🔴⭐️🔴"><a href="#5-1-3-生成动态代理⭐️🔴⭐️🔴" class="headerlink" title="5.1.3. 生成动态代理⭐️🔴⭐️🔴"></a>5.1.3. 生成动态代理⭐️🔴⭐️🔴</h3><ol><li>由 <code>MapperScannerConfigurer</code> 来扫描对应的 Mapper 接口的 BD 信息，并将类型替换为 <code>MapperFactoryBean</code>。</li><li>由 <code>SqlSessionFactoryBean</code> 来扫描 mapper 接口并配置与 <code>MapperProxyFactory</code> 的对应关系，保存到 mapperRegistry 中的 <code>knownMappers</code> Map 中。</li><li><span style="background-color:#ff00ff">自动装配 Mapper 时，从容器获取 mapper 但由于实现了 FactoryBean 接口，所以由 getObject 方法生成。在 getObjects 方法中，最终调用到 mapperRegistry 中的 getMapper 方法，调用 <code>MapperProxyFactory</code> 的 <code>newInstance</code> 生成对应的 <code>MapperProxy</code> 即 Mapper 接口的动态代理。</span> ❕<span style="display:none">%%<br>▶8.🏡⭐️◼️最终得到的是 MapperFactoryBean 吗 ?🔜MSTM📝 当然不是，而是 MapperProxy◼️⭐️-point-20230220-1846%%</span></li></ol><h2 id="5-2-使用动态代理"><a href="#5-2-使用动态代理" class="headerlink" title="5.2. 使用动态代理"></a>5.2. 使用动态代理</h2><p>当从 Spring 中获取 Mapper 接口时，将会调用对应的 MapperFactoryBean 的 getObjects 方法，该方法返回值即为对应的 MapperProxyFactory 创建的 MapperProxy 动态代理<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920115553380.png" alt="image-20210920115553380"><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920120211908.png" alt="image-20210920120211908"><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920120338745.png" alt="image-20210920120338745"><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920120418425.png" alt="image-20210920120418425"></p><h1 id="6-MapperScan-与-Mapper-区别"><a href="#6-MapperScan-与-Mapper-区别" class="headerlink" title="6. @MapperScan 与@Mapper 区别"></a>6. @MapperScan 与@Mapper 区别</h1><p>@MapperScan 是 Spring-Mybatis 的注解<br>@Mapper 是 Mybatis 的注解</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230218163834.png" alt="image.png"></p><p>SpringBoot 中如果没有包路径配置，单单只加@Mapper 也可以生效。但 Spring 中必须要有包扫描路径，@Mapper 只最为标记使用，配合 <code>@MapperScan(value = &quot;com.bjpowernode.mapper&quot;, annotationClass = Mapper.class)</code> 进行过滤<br>❕<span style="display:none">%%<br>▶5.🏡⭐️◼️Spring 与 SpringBoot 配置上的不同 ?🔜MSTM📝 ◼️⭐️-point-20230226-1832%%</span></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-图灵司马"><a href="#8-1-图灵司马" class="headerlink" title="8.1. 图灵司马"></a>8.1. 图灵司马</h2><p>Spring 整合 MyBatis 源码真谛、springIOC 源码剖析、@Import 注解作用详解、FactoryBean 与的区别<br><a href="https://www.bilibili.com/video/BV1uJ41117A7?p=4&amp;spm_id_from=pageDriver">https://www.bilibili.com/video/BV1uJ41117A7?p=4&amp;spm_id_from=pageDriver</a></p><h2 id="8-2-动力节点"><a href="#8-2-动力节点" class="headerlink" title="8.2. 动力节点"></a>8.2. 动力节点</h2><p><a href="https://www.bilibili.com/video/BV1uF411L73Q?p=98&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1uF411L73Q?p=98&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>[[Spring整合MyBatis源码深度剖析.docx]]<br>其他已存百度网盘 - 动力节点</p><h3 id="8-2-1-代码示例"><a href="#8-2-1-代码示例" class="headerlink" title="8.2.1. 代码示例"></a>8.2.1. 代码示例</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;spring-framework&#x2F;spring-z-mybatis-2&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;bjpowernode&#x2F;config&#x2F;MyConfig.java]]</p><h2 id="8-3-网络笔记"><a href="#8-3-网络笔记" class="headerlink" title="8.3. 网络笔记"></a>8.3. 网络笔记</h2><p><a href="https://www.cnblogs.com/hei12138/p/mybatis-spring.html">https://www.cnblogs.com/hei12138/p/mybatis-spring.html</a></p><h2 id="8-4-内部类"><a href="#8-4-内部类" class="headerlink" title="8.4. 内部类"></a>8.4. 内部类</h2><p><a href="https://blog.csdn.net/f641385712/article/details/106451554">https://blog.csdn.net/f641385712/article/details/106451554</a></p>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Mybatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-2、IOC</title>
      <link href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-2%E3%80%81IOC/"/>
      <url>/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-2%E3%80%81IOC/</url>
      
        <content type="html"><![CDATA[<h1 id="1-容器-API"><a href="#1-容器-API" class="headerlink" title="1. 容器 API"></a>1. 容器 API</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230303200238.png" alt="image.png"></p><ul><li>BeanDefinitionRegistry: 定义对 BeanDefinition 的各种增删改查操作</li><li><span style="background-color:#ff00ff">DefaultSingletonBeanRegistry: Bean 仓库</span></li><li>AutowireCapableBeanFactory: 提供创建 Bean, 自动注入, 初始化已经应用 Bean 的后处理器</li><li>ConfigurableListableBeanFactory: BeanFactory 配置清单, 指定忽略类型及接口等</li><li><span style="background-color:#ff00ff">DefaultListableBeanFactory: Bean 工厂</span></li></ul><h1 id="2-工作原理"><a href="#2-工作原理" class="headerlink" title="2. 工作原理"></a>2. 工作原理</h1><h2 id="2-1-工作流程"><a href="#2-1-工作流程" class="headerlink" title="2.1. 工作流程"></a>2.1. 工作流程</h2><ol><li>IOC 是什么</li><li>Bean 的声明方式</li><li>IOC 的工作流程</li></ol><p>IOC 的全称是 Inversion Of Control, 也就是控制反转，它的核心思想是把对象的管理权限交给容器。应用程序如果需要使用到某个对象实例，直接从 IOC 容器中去获取就行，这样设计的好处是降低了程序里面对象与对象之间的耦合性。使得程序的整个体系结构变得更加灵活。</p><h3 id="2-1-1-Bean-定义注册"><a href="#2-1-1-Bean-定义注册" class="headerlink" title="2.1.1. Bean 定义注册"></a>2.1.1. Bean 定义注册</h3><p>Spring 里面很多方式去定义 Bean，（如图）比如 XML 里面的 <code>&lt;bean&gt;</code> 标签、@Service、 @Component、@Repository、@Configuration 配置类中的@Bean 注解等等。 Spring 在启动的时候，会去解析这些 Bean 然后保存到 IOC 容器里面。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603223202.png" alt="image.png"></p><h3 id="2-1-2-IOC-容器的初始化"><a href="#2-1-2-IOC-容器的初始化" class="headerlink" title="2.1.2. IOC 容器的初始化"></a>2.1.2. IOC 容器的初始化</h3><p>这个阶段主要是根据程序中定义的 XML 或者注解等 Bean 的声明方式 (如图）通过解析和加载后生成 BeanDefinition，然后把 BeanDefinition 注册到 IOC 容器<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603223452.png" alt="image.png"><br>通过注解或者 xml 声明的 bean 都会解析得到一个 BeanDefinition 实体，实体中包含 这个 bean 中定义的基本属性。 最后把这个 BeanDefinition 保存到一个 Map 集合里面，从而完成了 IOC 的初始化。 IoC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护，它 IoC 容器控制 反转的核心。</p><h3 id="2-1-3-Bean-初始化及依赖注入"><a href="#2-1-3-Bean-初始化及依赖注入" class="headerlink" title="2.1.3. Bean 初始化及依赖注入"></a>2.1.3. Bean 初始化及依赖注入</h3><p>然后进入到第二个阶段，这个阶段会做两个事情（如图） 1. 通过反射针对没有设置 lazy-init 属性的单例 bean 进行初始化。 2. 完成 Bean 的依赖注入。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603223858.png" alt="image.png"></p><h3 id="2-1-4-Bean-的使用"><a href="#2-1-4-Bean-的使用" class="headerlink" title="2.1.4. Bean 的使用"></a>2.1.4. Bean 的使用</h3><p>（如图）通常我们会通过@Autowired 或者 BeanFactory.getBean() 从 IOC 容器中获取指定的 bean 实例。另外，针对设置 lazy-init 属性以及非单例 bean 的实例化，是在每次获取 bean 对象的时候，调用 bean 的初始化方法来完成实例化的，并且 Spring IOC 容器不会去管理这些 Bean<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603223932.png" alt="image.png"></p><h2 id="2-2-生命周期"><a href="#2-2-生命周期" class="headerlink" title="2.2. 生命周期"></a>2.2. 生命周期</h2><p>Spring 加载流程图 (徐庶)<br><a href="https://www.processon.com/view/link/5f15341b07912906d9ae8642">https://www.processon.com/view/link/5f15341b07912906d9ae8642</a></p><h1 id="3-容器刷新"><a href="#3-容器刷新" class="headerlink" title="3. 容器刷新"></a>3. 容器刷新</h1><p>Spring 容器刷新 12 步详细<br><a href="https://www.processon.com/diagraming/639548487d9c084a6a3d4021">https://www.processon.com/diagraming/639548487d9c084a6a3d4021</a></p><h1 id="4-循环依赖"><a href="#4-循环依赖" class="headerlink" title="4. 循环依赖"></a>4. 循环依赖</h1><h2 id="4-1-循环依赖的种类⭐️🔴"><a href="#4-1-循环依赖的种类⭐️🔴" class="headerlink" title="4.1. 循环依赖的种类⭐️🔴"></a>4.1. 循环依赖的种类⭐️🔴</h2><h3 id="4-1-1-属性注入依赖"><a href="#4-1-1-属性注入依赖" class="headerlink" title="4.1.1. 属性注入依赖"></a>4.1.1. 属性注入依赖</h3><p>通过三级缓存解决 ❕<span style="display:none">%%<br>2153-🏡⭐️◼️Spring 是如何解决循环依赖的？🔜MSTM📝◼️⭐️-point-202301282153%%</span></p><h3 id="4-1-2-构造方法依赖"><a href="#4-1-2-构造方法依赖" class="headerlink" title="4.1.2. 构造方法依赖"></a>4.1.2. 构造方法依赖</h3><p>解决方案：<a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-2%E3%80%81IOC/" title="Spring-2、IOC">Spring-2、IOC</a></p><p>懒加载原理：<a href="/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/" title="Spring-9、@Lazy">Spring-9、@Lazy</a></p><h3 id="4-1-3-多例模式"><a href="#4-1-3-多例模式" class="headerlink" title="4.1.3. 多例模式"></a>4.1.3. 多例模式</h3><p>无解</p><h2 id="4-2-变更历史"><a href="#4-2-变更历史" class="headerlink" title="4.2. 变更历史"></a>4.2. 变更历史</h2><p>大概是在<span style="background-color:#ff00ff">Spring2.0.3</span>加入了循环依赖的解决方案，在 springBean 的生命周期管理之后加入的新代码。其中涉及到了代理类生成时机的冲突问题<br>Spring 正常的代理应该是发生在 bean 初始化后，由 AbstractAutoProxyCreator.postProcessAfterInitialization 处理。而循环依赖要求 bean 在填充属性前就提前生成代理，否则就会出现最终版本不一致错误。所以 Spring 在代码中开了个口子，循环依赖发生时，提前代理，没有循环依赖，代理方式不变，依然是初始化以后代理。<br>其实也可以不分提前代理和正常代理，全部的 Bean 都直接提前生成代理，但那样的话 AbstractAutoProxyCreator.postProcessAfterInitialization 直接废了，相当于把原本的逻辑推翻重写，如此只是为了解决循环依赖的话就会变得得不偿失，没有完全必要的情况下对核心代码大改甚至推翻重写是一种大忌。</p><p>而三级缓存的实现提供了提前生成代理的口子，而不是直接生成代理，只有发生循环依赖<span style="background-color:#00ff00">执行 getObject 才会生成代理</span>，达到上述循环依赖发生时，提前代理，没有循环依赖，代理方式不变，依然是初始化以后代理的目的。</p><p>原文链接：<a href="https://blog.csdn.net/u012098021/article/details/107352463/">https://blog.csdn.net/u012098021/article/details/107352463/</a></p><h3 id="4-2-1-关闭默认支持循环依赖"><a href="#4-2-1-关闭默认支持循环依赖" class="headerlink" title="4.2.1. 关闭默认支持循环依赖"></a>4.2.1. 关闭默认支持循环依赖</h3><p>SpringBoot2.6 之后关闭了默认支持循环依赖的开关<br>开启方法： <code>spring.main.allow-circular-references=true</code></p><p><a href="https://juejin.cn/post/7096798740593246222">https://juejin.cn/post/7096798740593246222</a></p><h2 id="4-3-循环依赖流程⭐️🔴"><a href="#4-3-循环依赖流程⭐️🔴" class="headerlink" title="4.3. 循环依赖流程⭐️🔴"></a>4.3. 循环依赖流程⭐️🔴</h2><p><a href="https://www.processon.com/diagraming/639c04ed1efad465c9cd90da">https://www.processon.com/diagraming/639c04ed1efad465c9cd90da</a><br><a href="https://www.processon.com/diagraming/639e8c7f1efad465c9cfdd1a">https://www.processon.com/diagraming/639e8c7f1efad465c9cfdd1a</a></p><p><a href="https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c">https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c</a></p><h3 id="4-3-1-各级缓存的加入时机"><a href="#4-3-1-各级缓存的加入时机" class="headerlink" title="4.3.1. 各级缓存的加入时机"></a>4.3.1. 各级缓存的加入时机</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322180813.png" alt="image.png"></p><h4 id="4-3-1-1-一级缓存"><a href="#4-3-1-1-一级缓存" class="headerlink" title="4.3.1.1. 一级缓存"></a>4.3.1.1. 一级缓存</h4><p><code>Map&lt;String, Object&gt; singletonObjects</code><br>new ConcurrentHashMap&lt;&gt;(256)<br><span style="background-color:#ff00ff">解决完整 Bean 与不完整 Bean 混乱问题</span></p><h4 id="4-3-1-2-二级缓存"><a href="#4-3-1-2-二级缓存" class="headerlink" title="4.3.1.2. 二级缓存"></a>4.3.1.2. 二级缓存</h4><p><span style="display:none">%%<br>▶39.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1708%%</span>❕ ^kyatt6</p><p><code>Map&lt;String, Object&gt; earlySingletonObjects</code><br>new ConcurrentHashMap&lt;&gt;(16)<br><span style="background-color:#ff00ff">提供缓存功能，以便从中获取原始或者代理对象，同时能够解决动态代理多次创建问题</span><br><span style="background-color:#ff0000">只要有循环依赖，不管有没有 AOP，<code>earlySingletonObjects</code> 里都会有数据，比如 ABA，AB 在 3 级缓存中都有数据，B 在后面填充属性 A 时将 A 的三级缓存用掉，生成半成品 A 放入 2 级缓存中，3 级缓存的 A 被删除。而 B 直接从 3 级来到 1 级。</span><br><span style="background-color:#ff00ff">只是二级缓存中可能是原始 Bean，也可能是代理 Bean</span></p><h4 id="4-3-1-3-三级缓存"><a href="#4-3-1-3-三级缓存" class="headerlink" title="4.3.1.3. 三级缓存"></a>4.3.1.3. 三级缓存</h4><p><code>Map&lt;String, ObjectFactory&lt;?&gt;&gt; singletonFactories</code><br>new HashMap&lt;&gt;(16)</p><p><span style="background-color:#ff0000">实例对象创建之后，属性填充之前</span><br>检测循环依赖条件：<span style="background-color:#ff00ff">单例&amp;允许循环依赖&amp;当前 bean 正在创建中</span><br><span style="background-color:#ff00ff">解决死循环问题</span></p><p><span style="background-color:#ff00ff">不管有没有循环依赖，三级缓存中都会有数据</span></p><h2 id="4-4-循环依赖问题汇总"><a href="#4-4-循环依赖问题汇总" class="headerlink" title="4.4. 循环依赖问题汇总"></a>4.4. 循环依赖问题汇总</h2><h3 id="4-4-1-为什么一级二级是-ConcurrentHashMap"><a href="#4-4-1-为什么一级二级是-ConcurrentHashMap" class="headerlink" title="4.4.1. 为什么一级二级是 ConcurrentHashMap"></a>4.4.1. 为什么一级二级是 ConcurrentHashMap</h3><p>因为先手线程加锁加在查询一级、二级缓存之后，所以一级、二级缓存的查询需要支持并发而不影响性能</p><p><a href="https://www.bilibili.com/video/BV1pe4y1h7wK?p=19&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1pe4y1h7wK?p=19&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217082853.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217082919.png"></p><p><a href="https://www.bilibili.com/video/BV1ET4y1N7Sp/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ET4y1N7Sp/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>debug 了 AOP+ 循环依赖</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216174022.png" alt=" zzzz"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216111636.png"></p><p>最新 Spring 全家桶 -2021 面试 - 重灾区：Spring&#x2F;SpringCloud&#x2F;SpringBoot&#x2F;SpringMVC，肝完就跳槽</p><p><a href="https://www.bilibili.com/video/BV1x64y1x71b?p=56">https://www.bilibili.com/video/BV1x64y1x71b?p=56</a></p><p><a href="https://www.bilibili.com/video/BV1pY4y1A7Pv/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204!%5B%5D(https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216101945.png)">https://www.bilibili.com/video/BV1pY4y1A7Pv/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204![](https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216101945.png)</a></p><h2 id="4-5-AOP-提前代理逻辑"><a href="#4-5-AOP-提前代理逻辑" class="headerlink" title="4.5. AOP 提前代理逻辑"></a>4.5. AOP 提前代理逻辑</h2><h3 id="4-5-1-正常代理"><a href="#4-5-1-正常代理" class="headerlink" title="4.5.1. 正常代理"></a>4.5.1. 正常代理</h3><p>对象初始化之后创建动态代理，这是 SpringBean 的正常生命周期<br>BeanPostProcessor.<span style="background-color:#ff00ff">postProcessAfterInitialization()</span>，具体实现类是<span style="background-color:#ff00ff">AbstractAutoProxyCreator</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230129150239.png" alt="image.png"></p><h3 id="4-5-2-提前代理"><a href="#4-5-2-提前代理" class="headerlink" title="4.5.2. 提前代理"></a>4.5.2. 提前代理</h3><h4 id="4-5-2-1-getEarlyBeanReference"><a href="#4-5-2-1-getEarlyBeanReference" class="headerlink" title="4.5.2.1. getEarlyBeanReference"></a>4.5.2.1. getEarlyBeanReference</h4><p><span style="display:none">%%<br>▶32.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-1933%%</span>❕ ^qideqv</p><p><span style="background-color:#ff00ff">getEarlyBeanReference 方法就是为了生成提前代理对象而设计的。</span><br>如果有代理❕<span style="display:none">%%<br>▶9.🏡⭐️◼️项目中开启 AOP 的情况 ?🔜MSTM📝  项目中使用了@EnableAspectJAutoProxyCreator 或者@EnableTransactionManagement 注解，或者是 SpringBoot 项目 (自动开启 AOP)，并且代码中使用了切面及通知方法◼️⭐️-point-20230303-1940%%</span>，那么在 6. registerBeanPostProcessors() 中会将提供 AOP 功能的 Bean(AnnotationAwareAspectJAutoProxyCreator) 注册完成，然后在后面的 getEarlyBeanReference(beanName, mbd, bean) 方法中就会走 AbstractAutoProxyCreator 的实现，返回 proxy。否则，如果没有 AOP 代理，那么会走 SmartInstantiationAwareBeanPostProcessor 的默认实现，返回正常 bean</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230129150452.png" alt="image.png"></p><h4 id="4-5-2-2-细节"><a href="#4-5-2-2-细节" class="headerlink" title="4.5.2.2. 细节"></a>4.5.2.2. 细节</h4><p>防止提前代理后，正常代理再生成一次代理<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216185956.png"></p><p><a href="https://blog.csdn.net/u012098021/article/details/107352463/">https://blog.csdn.net/u012098021/article/details/107352463/</a></p><p>代理对象示例</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217144110.png"></p><h4 id="4-5-2-3-产生的影响⭐️🔴"><a href="#4-5-2-3-产生的影响⭐️🔴" class="headerlink" title="4.5.2.3. 产生的影响⭐️🔴"></a>4.5.2.3. 产生的影响⭐️🔴</h4><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【如果开启了代理，并且当前 Bean 使用了切面或者事务就会注入动态代理】◼️⭐️-point-20230303-2023%%</span><br><span style="background-color:#ff00ff">@Autowired 自动注入的对象有可能会是代理对象，取决于 Bean 是否使用了 AOP 注解</span><br>spring 很多功能都是通过 aop 来实现，比如事务，缓存注解，异步、还有一些自定义的 aop 等等，而 aop 是通过动态代理来实现的，spring 主要用到的动态代理有 jdk 的动态代理和 cglib。</p><ul><li>Spring 在没有使用 aop 的时候自动注入的时候是原始类型对象</li><li>在发生 aop 的时候，若代理对象有实现接口，则默认会使用 jdk 动态代理</li><li>在发生 aop 的时候，若代理对象没有实现接口，则默认会使用 cglib 动态代理</li><li>jdk 动态代理必须有实现接口</li><li>可以强制使用 cglib 来做 spring 动态代理</li></ul><p>示例代码： <a href="https://xie.infoq.cn/article/29ce37bd3f6f4c5b405549c06">https://xie.infoq.cn/article/29ce37bd3f6f4c5b405549c06</a></p><h3 id="4-5-3-二级缓存的作用"><a href="#4-5-3-二级缓存的作用" class="headerlink" title="4.5.3. 二级缓存的作用"></a>4.5.3. 二级缓存的作用</h3><ol><li><p>如果调用 getEarlyBeanReference 生成动态代理对象立即返回，不使用二级缓存来缓存起来，就会出现多次生成同一个对象的动态代理的情况，比如 B、C 都与 A 循环依赖，那么不用缓存判断，直接返回的话，就会生成 2 次 A 的代理对象 ❕<span style="display:none">%%<br>1519-🏡⭐️◼️二级缓存的作用是什么？🔜MSTM📝 除了缓存正常对象的半成品对象，最主要的功能是防止重复生成动态代理对象◼️⭐️-point-202301291519%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230129151801.png" alt="image.png"></p></li><li><p>如果提前代理，后面需要从二级缓存中取出返回出去。</p></li></ol><h3 id="4-5-4-测试程序"><a href="#4-5-4-测试程序" class="headerlink" title="4.5.4. 测试程序"></a>4.5.4. 测试程序</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;spring-framework&#x2F;springsource-test&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;config&#x2F;MainConfig.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322165716.png" alt="image.png"><br><a href="https://www.bilibili.com/video/BV15b4y117RJ?p=188&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15b4y117RJ?p=188&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="4-6-Async-导致循环依赖报错⭐️🔴"><a href="#4-6-Async-导致循环依赖报错⭐️🔴" class="headerlink" title="4.6. @Async 导致循环依赖报错⭐️🔴"></a>4.6. @Async 导致循环依赖报错⭐️🔴</h2><h3 id="4-6-1-原因分析"><a href="#4-6-1-原因分析" class="headerlink" title="4.6.1. 原因分析"></a>4.6.1. 原因分析</h3><p>❕<span style="display:none">%%<br>0836-🏡⭐️◼️一句话概括@Async 导致循环依赖报错的原因🔜MSTM📝 一句话：发生循环依赖时，没有像 AOP 一样做到提前代理：在实例化之后放入三级缓存，在属性填充时真正用到的时候调用 <code>singletonFactory.getObject()</code> 时根据是否用到 AOP 来决定填充一个代理对象。@EnableAsync 在属性填充时给了一个半成品对象，而在初始化完成之后又变成了动态代理对象，导致前后不一致。◼️⭐️-point-202302030836%%</span><br>@EnableAsync 开启时它会向容器内注入 <code>AsyncAnnotationBeanPostProcessor</code>，它是一个 BeanPostProcessor，实现了<span style="background-color:#ff00ff">postProcessAfterInitialization</span>方法。此处我们看代码，创建代理的动作在抽象父类 AbstractAdvisingBeanPostProcessor 上：</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// @since 3.2   注意：@EnableAsync在Spring3.1后出现</span><br><span class="hljs-comment">// 继承自ProxyProcessorSupport，所以具有动态代理相关属性~ 方便创建代理对象</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractAdvisingBeanPostProcessor</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ProxyProcessorSupport</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">BeanPostProcessor</span> &#123;<br><br><span class="hljs-comment">// 这里会缓存所有被处理的Bean~~~  eligible：合适的</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;Class&lt;?&gt;, Boolean&gt; eligibleBeans = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>&lt;&gt;(<span class="hljs-number">256</span>);<br><br><span class="hljs-comment">//postProcessBeforeInitialization方法什么不做~</span><br><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> Object <span class="hljs-title function_">postProcessBeforeInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> &#123;<br><span class="hljs-keyword">return</span> bean;<br>&#125;<br><br><span class="hljs-comment">// 关键是这里。当Bean初始化完成后这里会执行，这里会决策看看要不要对此Bean创建代理对象再返回~~~</span><br><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> Object <span class="hljs-title function_">postProcessAfterInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> &#123;<br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.advisor == <span class="hljs-literal">null</span> || bean <span class="hljs-keyword">instanceof</span> AopInfrastructureBean) &#123;<br><span class="hljs-comment">// Ignore AOP infrastructure such as scoped proxies.</span><br><span class="hljs-keyword">return</span> bean;<br>&#125;<br><br><span class="hljs-comment">// 如果此Bean已经被代理了（比如已经被事务那边给代理了~~）</span><br><span class="hljs-keyword">if</span> (bean <span class="hljs-keyword">instanceof</span> Advised) &#123;<br><span class="hljs-type">Advised</span> <span class="hljs-variable">advised</span> <span class="hljs-operator">=</span> (Advised) bean;<br><br><span class="hljs-comment">// 此处拿的是AopUtils.getTargetClass(bean)目标对象，做最终的判断</span><br><span class="hljs-comment">// isEligible()是否合适的判断方法  是本文最重要的一个方法，下文解释~</span><br><span class="hljs-comment">// 此处还有个小细节：isFrozen为false也就是还没被冻结的时候，就只向里面添加一个切面接口   并不要自己再创建代理对象了  省事</span><br><span class="hljs-keyword">if</span> (!advised.isFrozen() &amp;&amp; isEligible(AopUtils.getTargetClass(bean))) &#123;<br><span class="hljs-comment">// Add our local Advisor to the existing proxy&#x27;s Advisor chain...</span><br><span class="hljs-comment">// beforeExistingAdvisors决定这该advisor最先执行还是最后执行</span><br><span class="hljs-comment">// 此处的advisor为：AsyncAnnotationAdvisor  它切入Class和Method标注有@Aysnc注解的地方~~~</span><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.beforeExistingAdvisors) &#123;<br>advised.addAdvisor(<span class="hljs-number">0</span>, <span class="hljs-built_in">this</span>.advisor);<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>advised.addAdvisor(<span class="hljs-built_in">this</span>.advisor);<br>&#125;<br><span class="hljs-keyword">return</span> bean;<br>&#125;<br>&#125;<br><br><span class="hljs-comment">// 若不是代理对象，此处就要下手了~~~~isEligible() 这个方法特别重要</span><br><span class="hljs-keyword">if</span> (isEligible(bean, beanName)) &#123;<br><span class="hljs-comment">// copy属性  proxyFactory.copyFrom(this); 生成一个新的ProxyFactory </span><br><span class="hljs-type">ProxyFactory</span> <span class="hljs-variable">proxyFactory</span> <span class="hljs-operator">=</span> prepareProxyFactory(bean, beanName);<br><span class="hljs-comment">// 如果没有强制采用CGLIB 去探测它的接口~</span><br><span class="hljs-keyword">if</span> (!proxyFactory.isProxyTargetClass()) &#123;<br>evaluateProxyInterfaces(bean.getClass(), proxyFactory);<br>&#125;<br><span class="hljs-comment">// 添加进此切面~~ 最终为它创建一个getProxy 代理对象</span><br>proxyFactory.addAdvisor(<span class="hljs-built_in">this</span>.advisor);<br><span class="hljs-comment">//customize交给子类复写（实际子类目前都没有复写~）</span><br>customizeProxyFactory(proxyFactory);<br><span class="hljs-keyword">return</span> proxyFactory.getProxy(getProxyClassLoader());<br>&#125;<br><br><span class="hljs-comment">// No proxy needed.</span><br><span class="hljs-keyword">return</span> bean;<br>&#125;<br><br><span class="hljs-comment">// 我们发现BeanName最终其实是没有用到的~~~</span><br><span class="hljs-comment">// 但是子类AbstractBeanFactoryAwareAdvisingPostProcessor是用到了的  没有做什么 可以忽略~~~</span><br><span class="hljs-keyword">protected</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEligible</span><span class="hljs-params">(Object bean, String beanName)</span> &#123;<br><span class="hljs-keyword">return</span> isEligible(bean.getClass());<br>&#125;<br><span class="hljs-keyword">protected</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEligible</span><span class="hljs-params">(Class&lt;?&gt; targetClass)</span> &#123;<br><span class="hljs-comment">// 首次进来eligible的值肯定为null~~~</span><br><span class="hljs-type">Boolean</span> <span class="hljs-variable">eligible</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.eligibleBeans.get(targetClass);<br><span class="hljs-keyword">if</span> (eligible != <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-keyword">return</span> eligible;<br>&#125;<br><span class="hljs-comment">// 如果根本就没有配置advisor  也就不用看了~</span><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.advisor == <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>&#125;<br><br><span class="hljs-comment">// 最关键的就是canApply这个方法，如果AsyncAnnotationAdvisor  能切进它  那这里就是true</span><br><span class="hljs-comment">// 本例中方法标注有@Aysnc注解，所以铁定是能被切入的  返回true继续上面方法体的内容</span><br>eligible = AopUtils.canApply(<span class="hljs-built_in">this</span>.advisor, targetClass);<br><span class="hljs-built_in">this</span>.eligibleBeans.put(targetClass, eligible);<br><span class="hljs-keyword">return</span> eligible;<br>&#125;<br>...<br>&#125;<br><br></code></pre></td></tr></table></figure><p>根本原理是只要能被切面 <code>AsyncAnnotationAdvisor</code> 切入（即只需要类&#x2F;方法有标注 <code>@Async</code> 注解即可）的 Bean，在初始化完成之后，经过 <code>BeanPostProcessor.postProcessAfterInitialization()</code> 最终都会生成一个代理对象（若已经是代理对象里，只需要加入该切面即可了）赋值给上面的 <code>exposedObject</code> 作为返回最终 add 进 Spring 容器内。</p><h3 id="4-6-2-流程分析"><a href="#4-6-2-流程分析" class="headerlink" title="4.6.2. 流程分析"></a>4.6.2. 流程分析</h3><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-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">A</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">AInterface</span> &#123;<br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> BInterface b;<br>    <span class="hljs-meta">@Async</span><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_">funA</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br>&#125;<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_">B</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">BInterface</span> &#123;<br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> AInterface a;<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_">funB</span><span class="hljs-params">()</span> &#123;<br>        a.funA();<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><ol><li>A-&gt;B: context.getBean(A) 开始创建 A，A 实例化完成后给 A 的依赖属性 b 开始赋值</li><li>B-&gt;A: context.getBean(B) 开始创建 B，B 实例化完成后给 B 的依赖属性 a 开始赋值</li></ol><blockquote><p>重点：此时因为 A 支持循环依赖，而且没有动态代理所以会执行 A 的 getEarlyBeanReference 方法得到它的早期引用。而执行 getEarlyBeanReference() 的时候因为@Async 根本还没执行，所以最终返回的仍旧是原始对象的地址</p></blockquote><ol start="3"><li>B 完成初始化、完成属性的赋值，此时属性 field 持有的是 Bean A<span style="background-color:#00ff00">原始类型的引用</span></li><li>继续 A 的创建流程：完成了 A 的属性的赋值（此时已持有 B 的实例的引用），继续执行初始化方法 initializeBean(…)，在此处会解析@Aysnc 注解，从而生成一个代理对象，所以最终 exposedObject 是一个代理对象（而非原始对象）最终加入到容器里</li></ol><p><span style="display:none">%%<br>▶11.🏡⭐️◼️【原因是 ABA 时，A 实例化完成将原始类型引用放入三级缓存，属性填充 B 时触发 B 的创建流程，B 实例化完成后填充属性时从三级缓存中拿到的是原始类型的 A 的早期引用，B 创建完成后，继续 A 的创建流程，但最后初始化完成后 A 上的@Async 注解生效生成了动态代理，但是 B 中的引用还是原始类型的引用，造成前后不一致】◼️⭐️-point-20230303-2035%%</span> ^vlhwms</p><blockquote><p>上演尴尬的场面：<span style="background-color:#00ff00">B 引用的属性 A 是个原始对象，而此处准备 return 的实例 A 竟然是个代理对象，也就是说 B 引用的并非是最终对象（不是最终放进容器里的对象）</span><br>执行自检程序：由于 allowRawInjectionDespiteWrapping 默认值是 false，表示不允许上面不一致的情况发生，so 最终就抛错了</p></blockquote><h3 id="4-6-3-解决方案"><a href="#4-6-3-解决方案" class="headerlink" title="4.6.3. 解决方案"></a>4.6.3. 解决方案</h3><p>通过上面分析，知道了问题的根本原因，现总结出解决上述新问题的解决方案，可分为下面三种方案：</p><ol><li>把 allowRawInjectionDespiteWrapping 设置为 true</li><li>使用@Lazy 或者@ComponentScan(lazyInit &#x3D; true) 解决</li><li>不要让@Async 的 Bean 参与循环依赖</li></ol><p>@Lazy 原理： <a href="/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/" title="Spring-9、@Lazy">Spring-9、@Lazy</a></p><h3 id="4-6-4-其他"><a href="#4-6-4-其他" class="headerlink" title="4.6.4. 其他"></a>4.6.4. 其他</h3><p>本质上：@Async 的后置处理器，没有在 getEarlyRefrence 时，创建被@Async 修饰代理对象，而是在 initlizeBean 方法中创建了代理对象。作为对比，被@Aspect 修饰的 Aop 对象会在 getEarlyRefrence 阶段，提前创建代理对象，顾 aop 时不存在该问题</p><p><a href="https://blog.csdn.net/f641385712/article/details/92797058">https://blog.csdn.net/f641385712/article/details/92797058</a></p><p>[[(210条消息) 使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析，以及提供解决方案【享学Spring】_YourBatman的博客-CSDN博客]]</p><h2 id="4-7-BFPP-导致自动配置失败"><a href="#4-7-BFPP-导致自动配置失败" class="headerlink" title="4.7. BFPP 导致自动配置失败"></a>4.7. BFPP 导致自动配置失败</h2><p><span style="background-color:#ff00ff">Bean 工厂后置处理器在容器刷新的第 5 步，远在第 11 步中的 9 大 Bean 后置处理器之上</span><br><a href="https://www.bilibili.com/video/BV15b4y117RJ?p=169&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15b4y117RJ?p=169&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322185831.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322190004.png" alt="image.png"></p><ol><li>BFPP 尽量使用静态工厂方法</li><li>依赖注入使用局部变量</li></ol><h2 id="4-8-多例的循环依赖"><a href="#4-8-多例的循环依赖" class="headerlink" title="4.8. 多例的循环依赖"></a>4.8. 多例的循环依赖</h2><p>Spring 有没有解决多例 Bean 的循环依赖？<br>a. 多例不会使用缓存进行存储（多例 Bean 每次使用都需要重新创建）<br>b. 不缓存早期对象就无法解决循环</p><p><span style="background-color:#ff00ff">  关键： 一定要有一个缓存保存它的早期对象作为死循环的出口</span></p><h2 id="4-9-构造方法的循环依赖"><a href="#4-9-构造方法的循环依赖" class="headerlink" title="4.9. 构造方法的循环依赖"></a>4.9. 构造方法的循环依赖</h2><p><span style="display:none">%%<br>▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230322-1813%%</span>❕ ^7cjsyr</p><h3 id="4-9-1-Lazy"><a href="#4-9-1-Lazy" class="headerlink" title="4.9.1. @Lazy"></a>4.9.1. @Lazy</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322174213.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230129154529.png" alt="image.png"></p><p>使用@Lazy 注解，会创建 cglib 动态代理<br><span style="display:none"></p><ul><li><input checked="" disabled="" type="checkbox"> 🚩 - todo：<a href="https://zhuanlan.zhihu.com/p/562691467">https://zhuanlan.zhihu.com/p/562691467</a> - 🏡 2023-01-29 15:53</span><a href="/2023/02/11/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-9%E3%80%81@Lazy/" title="Spring-9、@Lazy">Spring-9、@Lazy</a></li></ul><p><a href="https://www.yuanjava.cn/posts/spring-constructor-circular-dependencies/">https://www.yuanjava.cn/posts/spring-constructor-circular-dependencies/</a><br><a href="https://blog.csdn.net/WX10301075WX/article/details/123904543">https://blog.csdn.net/WX10301075WX/article/details/123904543</a></p><p>循环依赖</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220190226.png"></p><h3 id="4-9-2-ObjectFactory"><a href="#4-9-2-ObjectFactory" class="headerlink" title="4.9.2. ObjectFactory"></a>4.9.2. ObjectFactory</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322174709.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322174447.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322174621.png" alt="image.png"></p><h3 id="4-9-3-Provider"><a href="#4-9-3-Provider" class="headerlink" title="4.9.3. Provider"></a>4.9.3. Provider</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322174904.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322175411.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322175431.png" alt="image.png"></p><h3 id="4-9-4-Scope"><a href="#4-9-4-Scope" class="headerlink" title="4.9.4. @Scope"></a>4.9.4. @Scope</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322175605.png" alt="image.png"></p><h2 id="4-10-防止获取到不完整的-Bean⭐️🔴"><a href="#4-10-防止获取到不完整的-Bean⭐️🔴" class="headerlink" title="4.10. 防止获取到不完整的 Bean⭐️🔴"></a>4.10. 防止获取到不完整的 Bean⭐️🔴</h2><p><span style="display:none">%%<br>▶75.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-1455%%</span>❕ ^d4s7ab</p><p><span style="display:none">%%<br>▶12.🏡⭐️◼️【创建对象时用同一把锁同时锁住 2 个地方，分别是 2 个重载的 getsingleton 方法。第一个锁住创建 Bean 的整个流程，包括实例化、属性填充和初始化，在此过程中，第二把锁在三级缓存前面，其他线程按次序到一级、二级缓存中拿都是空的，被阻塞在三级缓存前面。第一个线程创建完对象，放入 1 级缓存，并清空 23 级缓存，第二个线程通过双重检查锁机制，再次查看 1 级缓存就拿到了需要的 Bean】◼️⭐️-point-20230303-2218%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230102105440.png"><br><a href="https://www.bilibili.com/video/BV1t44y1C73F?p=29&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1t44y1C73F?p=29&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><span style="background-color:#00ff00"><font color=#ff0000>上面这个图对应的版本为 5.0.X，后面版本 5.2.X 之后，Spring 只锁在三级缓存前面，也正因此一级、二级缓存用的是 ConcurrentHashMap</font></span> ❕<span style="display:none">%%<br>1741-🏡⭐️◼️Spring 防止不完整的 Bean 的源码中版本变化🔜MSTM📝 5.2.X 之后，只锁三级缓存，把二级缓存拿到了锁外面，没看出来什么特殊作用，也没有影响，因为线程 1 最终将完整的 bean 加入了一级缓存，并清空了二三级缓存，线程 2 获得锁之后，按一二三级缓存进行查询，先查到一级缓存中有就返回了◼️⭐️-point-202301291741%%</span></p><ol><li>两个地方加了同一把锁，即锁住了一级缓存</li><li>先在创建 bean 的过程中加了锁，具体位置在 <code>doGetBean</code> 中的第二个 <code>getSingleton</code>，即入参为 <code>beanName</code> 和 <code>ObjectFactory&lt;?&gt;</code></li><li><span style="background-color:#ff0000">于此同时，相当于也在 getSingleton(beanName, boolean) 中加了锁，因为是同一把对象锁</span> ❕<span style="display:none">%%<br>1538-🏡⭐️◼️Spring 如何防止获取到不完整的 Bean 的🔜MSTM📝 分别在创建 bean(getSingleton(beanName,beanFactory)) 和获取 bean 的 (getSingleton(beanName)) 方法这 2 个地方加了同一个对象锁，使用了一级缓存对象。在创建过程中先进入 getSingleton(beanName)，但是因为判断逻辑没有进入到加锁的地方，第一次加锁是在 getSingleton(beanName,beanFactory)，然后对象锁就生效了，相当于同时在 getSingleton(beanName) 里也加了锁◼️⭐️-point-202301301538%%</span></li></ol><p><span style="background-color:#ff00ff">只能说太精妙了，2 个锁都是在单例仓库 <code>DefaultSingletonBeanRegistry</code> 中，<code>getSingleton</code> 的 2 个重载的方法里：</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230130144138.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230130144101.png" alt="image.png"></p><h1 id="5-扩展点"><a href="#5-扩展点" class="headerlink" title="5. 扩展点"></a>5. 扩展点</h1><p>^ywsrjj</p><a href="/2023/01/28/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-7%E3%80%81%E6%89%A9%E5%B1%95%E7%82%B9/" title="Spring-7、扩展点">Spring-7、扩展点</a><h1 id="6-设计模式"><a href="#6-设计模式" class="headerlink" title="6. 设计模式"></a>6. 设计模式</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213090019.png"></p><h1 id="7-实战经验"><a href="#7-实战经验" class="headerlink" title="7. 实战经验"></a>7. 实战经验</h1><p>手动集成 Bean 到容器中<br><a href="https://blog.csdn.net/yuan882696yan/article/details/99082252">https://blog.csdn.net/yuan882696yan/article/details/99082252</a></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-comment">// 装配外部bean（不在spring容器中的bean）</span><br><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">ApplicationContext</span> <span class="hljs-variable">context</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileSystemXmlApplicationContext</span>(<span class="hljs-string">&quot;C:\\Users\\IdeaProjects\\springDemo\\src\\main\\java\\com\\beanfactory\\autowireCapableBeanFactory\\test.xml&quot;</span>);<br>        <span class="hljs-type">AutowireCapableBeanFactory</span> <span class="hljs-variable">autowireCapableBeanFactory</span> <span class="hljs-operator">=</span> context.getAutowireCapableBeanFactory();<br>        <span class="hljs-type">Object</span> <span class="hljs-variable">bean</span> <span class="hljs-operator">=</span> autowireCapableBeanFactory.autowire(Foo.class, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, <span class="hljs-literal">true</span>);<br><span class="hljs-comment">// Foo没有在test.xml中注册，不受spring容器管制</span><br><span class="hljs-comment">// 通过autowireCapableBeanFactory的方式，依然可以获取到foo，并执行foo里的test方法</span><br>        <span class="hljs-type">Foo</span> <span class="hljs-variable">foo</span> <span class="hljs-operator">=</span> (Foo) bean;<br>        foo.test();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><h2 id="8-1-图灵徐庶"><a href="#8-1-图灵徐庶" class="headerlink" title="8.1. 图灵徐庶"></a>8.1. 图灵徐庶</h2><p><a href="https://www.bilibili.com/video/BV1t44y1C73F/?p=24&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1t44y1C73F/?p=24&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a>   75p</p><p>[[Spring全家桶面试题—图灵徐庶.pdf]]</p><p><a href="https://www.processon.com/view/link/5f5075c763768959e2d109df#map">https://www.processon.com/view/link/5f5075c763768959e2d109df#map</a><br><a href="https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c">https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c</a><br>Bean 创建过程： <a href="https://www.processon.com/view/link/5f15341b07912906d9ae8642">https://www.processon.com/view/link/5f15341b07912906d9ae8642</a></p><h2 id="8-2-尚硅谷雷丰阳"><a href="#8-2-尚硅谷雷丰阳" class="headerlink" title="8.2. 尚硅谷雷丰阳"></a>8.2. 尚硅谷雷丰阳</h2><h3 id="8-2-1-配套视频"><a href="#8-2-1-配套视频" class="headerlink" title="8.2.1. 配套视频"></a>8.2.1. 配套视频</h3><p><a href="https://www.bilibili.com/video/BV1gW411W7wy/?spm_id_from=333.337.search-card.all.click">https://www.bilibili.com/video/BV1gW411W7wy/?spm_id_from=333.337.search-card.all.click</a></p><h3 id="8-2-2-配套代码"><a href="#8-2-2-配套代码" class="headerlink" title="8.2.2. 配套代码"></a>8.2.2. 配套代码</h3><p>spring 注解驱动开发 _spring 源码版 - 雷丰阳 - 尚硅谷<br>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring注解驱动开发-尚硅谷-雷丰阳&#x2F;spring-annotation&#x2F;src&#x2F;main&#x2F;resources&#x2F;SpringSource.txt]]</p><h3 id="8-2-3-配套笔记"><a href="#8-2-3-配套笔记" class="headerlink" title="8.2.3. 配套笔记"></a>8.2.3. 配套笔记</h3><p><a href="https://liayun.blog.csdn.net/article/details/111413608">https://liayun.blog.csdn.net/article/details/111413608</a></p><h2 id="8-3-马士兵-lianpengju"><a href="#8-3-马士兵-lianpengju" class="headerlink" title="8.3. 马士兵 -lianpengju"></a>8.3. 马士兵 -lianpengju</h2><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;lianpengju&#x2F;spring-beans&#x2F;src&#x2F;main&#x2F;java&#x2F;org&#x2F;springframework&#x2F;beans&#x2F;factory&#x2F;support&#x2F;DefaultSingletonBeanRegistry.java]] ❕<span style="display:none">%%<br>0915-🏡⭐️◼️gradle 配置有问题，测试例子使用的是尚硅谷的 spring-framework：springsource-test◼️⭐️-point-202301290915%%</span></p><p><a href="https://www.bilibili.com/video/BV11B4y1175J?p=6&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV11B4y1175J?p=6&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="8-4-黑马"><a href="#8-4-黑马" class="headerlink" title="8.4. 黑马"></a>8.4. 黑马</h2><h3 id="8-4-1-视频"><a href="#8-4-1-视频" class="headerlink" title="8.4.1. 视频"></a>8.4.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV15b4y117RJ?p=196&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15b4y117RJ?p=196&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="8-4-2-资料"><a href="#8-4-2-资料" class="headerlink" title="8.4.2. 资料"></a>8.4.2. 资料</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/011-面试专题/黑马面试题<br></code></pre></td></tr></table></figure><h2 id="8-5-测试-Demo"><a href="#8-5-测试-Demo" class="headerlink" title="8.5. 测试 Demo"></a>8.5. 测试 Demo</h2><p>013-DemoCode&#x2F;spring-framework&#x2F;spring<br>[[AnnotationMainTest.java]]</p>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> IOC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-3、AOP</title>
      <link href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/"/>
      <url>/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/</url>
      
        <content type="html"><![CDATA[<h1 id="1-AOP"><a href="#1-AOP" class="headerlink" title="1. AOP"></a>1. AOP</h1><h2 id="1-1-分类比较"><a href="#1-1-分类比较" class="headerlink" title="1.1. 分类比较"></a>1.1. 分类比较</h2><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230507-1556%%</span>❕ ^1m5ekc</p><p>Spring AOP 和 AspectJ AOP(静态 AOP)</p><p>简而言之，Spring AOP 和 AspectJ 有不同的目标。<br>Spring AOP 旨在通过 Spring IoC 提供一个简单的 AOP 实现，以解决编码人员面临的最常出现的问题。这并不是完整的 AOP 解决方案，<span style="background-color:#ffff00">它只能用于 Spring 容器管理的 beans</span>。</p><p>另一方面，AspectJ 是最原始的 AOP 实现技术，提供了完整的的 AOP 解决方案。AspectJ 更为健壮，相对于 Spring AOP 也显得更为复杂。值得注意的是，AspectJ 能够被应用于所有的领域对象<br><a href="https://juejin.im/post/5a695b3cf265da3e47449471">https://juejin.im/post/5a695b3cf265da3e47449471</a></p><p><span style="background-color:#ffff00">Spring AOP 属于运行时增强，而 AspectJ 是编译时增强。 Spring AOP 基于代理 (Proxying)，而 AspectJ 基于字节码操作 (Bytecode Manipulation)。</span></p><p>Spring AOP 已经集成了 AspectJ ，AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大，但是 Spring AOP 相对来说更简单</p><p>如果我们的切面比较少，那么两者性能差异不大。但是，当切面太多的话，最好选择 AspectJ ，它比 Spring AOP 快很多。<br><a href="https://blog.csdn.net/weixin_44259720/article/details/95996541">https://blog.csdn.net/weixin_44259720/article/details/95996541</a></p><h2 id="1-2-方案选择"><a href="#1-2-方案选择" class="headerlink" title="1.2. 方案选择"></a>1.2. 方案选择</h2><ul><li>框架：如果应用程序不使用 Spring 框架，那么我们别无选择，只能放弃使用 Spring AOP 的想法，因为它无法管理任何超出 spring 容器范围的东西。 但是，如果我们的应用程序完全是使用 Spring 框架创建的，那么我们可以使用 Spring AOP，因为它很直接便于学习和应用。</li><li>灵活性：鉴于有限的连接点支持，Spring AOP 并不是一个完整的 AOP 解决方案，但它解决了程序员面临的最常见的问题。 如果我们想要深入挖掘并利用 AOP 达到其最大能力，并希望获得来自各种可用连接点的支持，那么 AspectJ 是最佳选择。</li><li>性能：如果我们使用有限的切面，那么性能差异很小。 但是，有时候应用程序有数万个切面的情况。 在这种情况下，我们不希望使用运行时织入，所以最好选择 AspectJ。 已知 AspectJ 比 Spring AOP 快 8 到 35 倍。</li><li>共同优点：这两个框架是完全兼容的。 我们可以随时利用 Spring AOP，并且仍然使用 AspectJ 来获得前者不支持的连接点。</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218143952.png"></p><h2 id="1-3-术语解析"><a href="#1-3-术语解析" class="headerlink" title="1.3. 术语解析"></a>1.3. 术语解析</h2><p><a href="https://www.bilibili.com/video/av58225341?p=270">https://www.bilibili.com/video/av58225341?p=270</a></p><p>[[02_尚硅谷大数据技术之Spring(老师原版).docx]]</p><h3 id="1-3-1-配套代码"><a href="#1-3-1-配套代码" class="headerlink" title="1.3.1. 配套代码"></a>1.3.1. 配套代码</h3><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring03&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;aspectJ&#x2F;annotation&#x2F;LoggingAspect.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230114085041.jpg" alt="image-20200328181706288"></p><h3 id="1-3-2-目标-Target"><a href="#1-3-2-目标-Target" class="headerlink" title="1.3.2. 目标 (Target)"></a>1.3.2. 目标 (Target)</h3><p>业务逻辑：比如加、减、乘、除</p><h3 id="1-3-3-代理-Proxy"><a href="#1-3-3-代理-Proxy" class="headerlink" title="1.3.3. 代理 (Proxy)"></a>1.3.3. 代理 (Proxy)</h3><h3 id="1-3-4-横切关注点"><a href="#1-3-4-横切关注点" class="headerlink" title="1.3.4. 横切关注点"></a>1.3.4. 横切关注点</h3><p>在程序代码中的具体体现，对应程序执行的<span style="background-color:#00ff00">某个特定位置</span>。例如：<span style="background-color:#00ff00">某个方法调用前、调用后、方法捕获到异常后等</span>。</p><h3 id="1-3-5-通知-Advice"><a href="#1-3-5-通知-Advice" class="headerlink" title="1.3.5. 通知 (Advice)"></a>1.3.5. 通知 (Advice)</h3><p>在横切关注点所设定的地方，所要完成的特定动作</p><h3 id="1-3-6-切面-Aspect"><a href="#1-3-6-切面-Aspect" class="headerlink" title="1.3.6. 切面 (Aspect)"></a>1.3.6. 切面 (Aspect)</h3><p>将相关通知汇总到一个类中，管理切点和通知</p><h3 id="1-3-7-连接点-Joinpoint"><a href="#1-3-7-连接点-Joinpoint" class="headerlink" title="1.3.7. 连接点 (Joinpoint)"></a>1.3.7. 连接点 (Joinpoint)</h3><p>横切关注点与目标对象的目标方法的交叉点。比如<span style="background-color:#00ff00">方法执行前</span>与<span style="background-color:#ffff00">add()</span> ❕<span style="display:none">%%<br>1522-🏡⭐️◼️连接点怎么理解 ?🔜MSTM📝 可以理解为时空交叉点，方法执行为时间，增强要还在哪个位置，比如方法执行之前还是之后，这是空间，时空交叉点就是连接点；切点：是一种表达式，类似于查询语言一样，确定在什么地方什么时候插入增强逻辑；通知：就是公共的增强逻辑，包括前置、后置、环绕、返回、异常 5 种，而切面就是管理通知和切点的类◼️⭐️-point-202302091522%%</span> ^6c4nvo</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230102115010.png"></p><h2 id="1-4-实现方式"><a href="#1-4-实现方式" class="headerlink" title="1.4. 实现方式"></a>1.4. 实现方式</h2><p>[[(209条消息) AOP的实现的几种方式_Ydoing的博客-CSDN博客_aop实现]]</p><h1 id="2-SpringAOP-实现原理"><a href="#2-SpringAOP-实现原理" class="headerlink" title="2. SpringAOP 实现原理"></a>2. SpringAOP 实现原理</h1><h2 id="2-1-容器环境准备"><a href="#2-1-容器环境准备" class="headerlink" title="2.1. 容器环境准备"></a>2.1. 容器环境准备</h2><p>因为任何 Bean 创建都需要 <code>BeanDefinitionRegistryPostProcessor</code> 先将 BD 注册到容器中，而在 Spring 中唯一的实现类就是 <code>ConfigurationClassPostProcessor</code>，所以第一步就是它。</p><a href="/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/" title="Spring-8、BeanDefinition">Spring-8、BeanDefinition</a><h2 id="2-2-注册-Creator-BD"><a href="#2-2-注册-Creator-BD" class="headerlink" title="2.2. 注册 Creator BD"></a>2.2. 注册 Creator BD</h2><p>在 refresh() 中的第 5 步 <strong>执行</strong>BeanFactoryPostProcessor 的方法中，<strong>BeanDefinitionRegistryPostProcessor</strong>.postProcessBeanDefinitionRegistry</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230210085318.png" alt="image.png"></p><h3 id="2-2-1-AspectJAutoProxyRegistrar-注册-CreatorBD"><a href="#2-2-1-AspectJAutoProxyRegistrar-注册-CreatorBD" class="headerlink" title="2.2.1. AspectJAutoProxyRegistrar(注册 CreatorBD)"></a>2.2.1. AspectJAutoProxyRegistrar(注册 CreatorBD)</h3><h4 id="2-2-1-1-EnableAspectJAutoProxy"><a href="#2-2-1-1-EnableAspectJAutoProxy" class="headerlink" title="2.2.1.1. @EnableAspectJAutoProxy"></a>2.2.1.1. @EnableAspectJAutoProxy</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217155821.png"></p><p>implements ImportBeanDefinitionRegistrar</p><p>利用重写的 registerBeanDefinitions 方法，给容器中注册一个自动代理创建器 <code>AnnotationAwareAspectJAutoProxyCreator</code>，放到 <code>beanDefinitionMap</code> 中</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217184124.png"></p><h4 id="2-2-1-2-AnnotationAwareAspectJAutoProxyCreator"><a href="#2-2-1-2-AnnotationAwareAspectJAutoProxyCreator" class="headerlink" title="2.2.1.2. AnnotationAwareAspectJAutoProxyCreator"></a>2.2.1.2. AnnotationAwareAspectJAutoProxyCreator</h4><h5 id="2-2-1-2-1-继承关系"><a href="#2-2-1-2-1-继承关系" class="headerlink" title="2.2.1.2.1. 继承关系"></a>2.2.1.2.1. 继承关系</h5><pre><code>     AnnotationAwareAspectJAutoProxyCreator         -&gt;AspectJAwareAdvisorAutoProxyCreator             -&gt;AbstractAdvisorAutoProxyCreator                 -&gt;AbstractAutoProxyCreator                         implements                                SmartInstantiationAwareBeanPostProcessor,BeanFactoryAware                     关注后置处理器（在bean初始化完成前后做事情）、自动装配BeanFactory</code></pre><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221112438.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230303124338.png" alt="image.png"></p><h5 id="2-2-1-2-2-关键方法"><a href="#2-2-1-2-2-关键方法" class="headerlink" title="2.2.1.2.2. 关键方法"></a>2.2.1.2.2. 关键方法</h5><p> AbstractAutoProxyCreator.setBeanFactory()</p><p> AbstractAutoProxyCreator.有后置处理器的逻辑；</p><p> AbstractAdvisorAutoProxyCreator.setBeanFactory() -&gt; initBeanFactory()</p><p> AnnotationAwareAspectJAutoProxyCreator.initBeanFactory()-&gt;super.initBeanFactory</p><h3 id="2-2-2-注册-Creator-BPP-Bean"><a href="#2-2-2-注册-Creator-BPP-Bean" class="headerlink" title="2.2.2. 注册 Creator BPP Bean"></a>2.2.2. 注册 Creator BPP Bean</h3><h4 id="2-2-2-1-创建-BPP-Bean"><a href="#2-2-2-1-创建-BPP-Bean" class="headerlink" title="2.2.2.1. 创建 BPP Bean"></a>2.2.2.1. 创建 BPP Bean</h4><p>来到刷新的第 6 步，registerBeanPostProcessors() 中，用 <code>PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this)</code> 注册 postprocessor。<code>beanFactory.getBean(ppName, BeanPostProcessor.class)</code> 会创建所有 postprocessor 对象。BPP 的 bean 也是 bean，在 getBean 的过程中，12 大步都会走。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217183744.png"></p><h4 id="2-2-2-2-设置-BeanFactory"><a href="#2-2-2-2-设置-BeanFactory" class="headerlink" title="2.2.2.2. 设置 BeanFactory"></a>2.2.2.2. 设置 BeanFactory</h4><p>会在 initializeBean 中真正调用初始化方法之前，<code>invokeAwareMethods(beanName, bean)</code> 中调用 <code>setBeanFactory()</code>;<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218080356.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218080716.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218080940.png"></p><blockquote><ol start="2"><li>将 BPP(AnnotationAwareAspectJAutoProxyCreator) 保存到 <code>List&lt;BeanPostProcessor&gt; </code>beanPostProcessors<br><code>beanFactory.addBeanPostProcessor(postProcessor)</code></li></ol></blockquote><h2 id="2-3-增强逻辑"><a href="#2-3-增强逻辑" class="headerlink" title="2.3. 增强逻辑"></a>2.3. 增强逻辑</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211073953.png" alt="image.png"><br><a href="https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5">https://www.processon.com/diagraming/63e4bfa27c423a1934f127a5</a></p><p><a href="https://www.processon.com/view/link/5f1958a35653bb7fd24d0aad">https://www.processon.com/view/link/5f1958a35653bb7fd24d0aad</a></p><h3 id="2-3-1-postProcessBeforeInstantiation-解析切面并缓存通知"><a href="#2-3-1-postProcessBeforeInstantiation-解析切面并缓存通知" class="headerlink" title="2.3.1. postProcessBeforeInstantiation(解析切面并缓存通知)"></a>2.3.1. postProcessBeforeInstantiation(解析切面并缓存通知)</h3><p><span style="display:none">%%<br>▶28.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-0929%%</span>❕ ^o0kxx7</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230102185902.png"></p><p>在 11. finishBeanFactoryInitialization() 中，<span style="background-color:#ff00ff">任何一个 bean 创建实例之前</span>，会走到 <code>resolveBeforeInstantiation</code> 方法中，会执行类型为 <code>InstantiationAwareBeanPostProcessor</code> 的 postprocessor 的<span style="background-color:#00ff00">applyBeanPostProcessorsBeforeInstantiation</span>方法。而我们的 AnnotationAwareAspectJAutoProxyCreator 正是此种类型。所以会执行其父类<span style="background-color:#00ff00">AbstractAutoProxyCreator</span>重写的 <code>postProcessBeforeInstantiation</code> 方法<br><code>AnnotationAwareAspectJAutoProxyCreator</code> 是 <code>InstantiationAwareBeanPostProcessor</code> 类型的后置处理器，重写的方法为</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217195752.png"></p><h4 id="2-3-1-1-getCustomTargetSource"><a href="#2-3-1-1-getCustomTargetSource" class="headerlink" title="2.3.1.1. getCustomTargetSource"></a>2.3.1.1. getCustomTargetSource</h4><p>这个方法主要解析用户配置的切面类，getCustomTargetSource 用来处理用户自定义 TargetSource 的场景，一般没人自定义，除非我们的容器中有 TargetSourceCreator 并且我们的 bean 需要实现</p><h4 id="2-3-1-2-findCandidateAdvisors"><a href="#2-3-1-2-findCandidateAdvisors" class="headerlink" title="2.3.1.2. findCandidateAdvisors"></a>2.3.1.2. findCandidateAdvisors</h4><p>通过 Spring IoC 容器找到系统中实现了 Advisor 接口的 bean，<span style="background-color:#ff00ff">声明式事务的 advisor 就是在这里被查询到的</span>。❕<span style="display:none">%%<br>▶2.🏡⭐️◼️声明式事务的 advisor 是在哪里被查询到的 ?🔜MSTM📝 在第 11 步中的 9 大后置处理器中的第一个，即 InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation 方法中，在解析切面中的 findCandidateAdvisors 方法中。这个方法是查询所有实现了 Advisor 接口的类，而在我们的声明式事务中，@EnableTransactionManagement 注解中注入了 TransactionManagementConfigurationSelector，在里面注入了 AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration，在里面显示注入了 Advisor(BeanFactoryTransactionAttributeSourceAdvisor)◼️⭐️-point-20230222-0731%%</span> ^q0d8rn</p><h4 id="2-3-1-3-buildAspectJAdvisors⭐️🔴"><a href="#2-3-1-3-buildAspectJAdvisors⭐️🔴" class="headerlink" title="2.3.1.3. buildAspectJAdvisors⭐️🔴"></a>2.3.1.3. buildAspectJAdvisors⭐️🔴</h4><p> 通过 Spring IoC 容器获取<span style="background-color:#ff00ff">所有</span>标注 @AspectJ 注解的 Bean，再获取标注 AspectJ 注解（除了@Pointcut 其他的注解 @After、@Before、@AfterReturning 等）的方法，将这些方法封装为一个个 Advisor，<strong>缓存在 <code>this.advisorsCache</code> 中，以 <code>beanName</code> 为 key，<code>List&lt;Advisor&gt;</code> 为 value。</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 通过封装的bean获取其Advice，如@Before，@After等等，并且将这些  </span><br><span class="hljs-comment">// Advice都解析并且封装为一个个的Advisor  </span><br>List&lt;Advisor&gt; classAdvisors = <span class="hljs-built_in">this</span>.advisorFactory.getAdvisors(factory);  <br><span class="hljs-comment">// 如果切面类是singleton类型，则将解析得到的Advisor进行缓存，  </span><br><span class="hljs-comment">// 否则将当前的factory进行缓存，以便再次获取时可以通过factory直接获取  </span><br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.beanFactory.isSingleton(beanName)) &#123;  <br>   <span class="hljs-built_in">this</span>.advisorsCache.put(beanName, classAdvisors);  <br>&#125;<br></code></pre></td></tr></table></figure><h5 id="2-3-1-3-1-AspectJAdvisorFactory-getAdvisors"><a href="#2-3-1-3-1-AspectJAdvisorFactory-getAdvisors" class="headerlink" title="2.3.1.3.1. AspectJAdvisorFactory.getAdvisors"></a>2.3.1.3.1. AspectJAdvisorFactory.getAdvisors</h5><p>首先获取切面类型，然后获取除 Pointcut 注解的所有方法，根据方法注解来创建 Advisor 通知器。</p><p>判断当前方法是否标注有@Before，@After 或@Around 等注解，如果标注了，则将其封装为一个 Advisor，其中会调用 getPointcut 包含了切点信息<br><strong>AspectJAdvisorFactory.getAdvisor</strong></p><p>获取当前方法中@Before，@After 或者@Around 等标注的注解，并且获取该注解的值，将其封装为一个 AspectJExpressionPointcut 对象<br><strong>getPointcut</strong></p><p>将获取到的切点，切点方法等信息封装为一个 Advisor 对象，也就是说当前 Advisor 包含有所有当前切面进行环绕所需要的信息<br><strong>new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,this, aspectInstanceFactory, declarationOrderInAspect, aspectName)</strong></p><p><span style="background-color:#ff0000">InstantiationModelAwarePointcutAdvisorImpl 中的 <code>getAdvice()</code> 是个同步方法</span></p><h5 id="2-3-1-3-2-事务相关内容⭐️🔴⭐️🔴"><a href="#2-3-1-3-2-事务相关内容⭐️🔴⭐️🔴" class="headerlink" title="2.3.1.3.2. 事务相关内容⭐️🔴⭐️🔴"></a>2.3.1.3.2. 事务相关内容⭐️🔴⭐️🔴</h5><h4 id="2-3-1-4-结论"><a href="#2-3-1-4-结论" class="headerlink" title="2.3.1.4. 结论"></a>2.3.1.4. 结论</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230210140623.png" alt="image.png"><br><a href="https://segmentfault.com/a/1190000041228253">https://segmentfault.com/a/1190000041228253</a> 我们的 aop 解析切面以及事务解析事务注解都是在这里完成的</p><p>AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation 方法的主要核心在于将容器中 <strong>所有的切面对应的通知都扫描出来并包装成 InstantiationModelAwarePointcutAdvisorImpl 类型的对象并添加到缓存中</strong>（<strong>这里要注意：不管是自定义的切面、还是实现了 Advisor 接口 (比如声明式事务注册进来的) 的切面都会被扫描出来</strong>）。这是一种预热机制，先把数据准备好，后续需要时直接再从缓存中拿。</p><p><a href="https://juejin.cn/post/6978742789165187079">https://juejin.cn/post/6978742789165187079</a></p><p><a href="https://www.jianshu.com/p/b45bf3befa25">https://www.jianshu.com/p/b45bf3befa25</a></p><p><a href="https://blog.csdn.net/qq_42419406/article/details/116640641">https://blog.csdn.net/qq_42419406/article/details/116640641</a></p><h3 id="2-3-2-postProcessAfterInitialization-使用缓存创建代理"><a href="#2-3-2-postProcessAfterInitialization-使用缓存创建代理" class="headerlink" title="2.3.2. postProcessAfterInitialization(使用缓存创建代理)"></a>2.3.2. postProcessAfterInitialization(使用缓存创建代理)</h3><p><a href="https://www.processon.com/view/link/5f1e93f25653bb7fd2549b7c">https://www.processon.com/view/link/5f1e93f25653bb7fd2549b7c</a></p><p> <strong>时机：对象初始化完成之后</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230102185744.png"></p><p>  return &#96;wrapIfNecessary(bean, beanName, cacheKey)</p><p>  1）、获取当前 bean 的所有增强器（通知方法）  <code>Object[]  specificInterceptors</code><br>      1、找到候选的所有的增强器（找哪些通知方法是需要切入当前 bean 方法的）<br>      2、<span style="background-color:#00ff00">获取到能在 bean 使用的增强器</span>。<br>      3、给增强器排序<br>  2）、保存当前 bean 在 advisedBeans 中；<br>   <span style="background-color:#00ff00">  3）、如果当前 bean 需要增强，创建当前 bean 的代理对象；</span><br><span style="background-color:#00ff00">  1）、获取所有增强器（通知方法）</span><br><span style="background-color:#00ff00">  2）、保存到 proxyFactory</span><br><span style="background-color:#00ff00">  3）、创建代理对象：Spring 自动决定</span><br><span style="background-color:#00ff00">    JdkDynamicAopProxy(config);jdk 动态代理；</span><br>    ObjenesisCglibAopProxy(config);cglib 的动态代理；<br>  4）、给容器中返回当前组件使用 cglib 增强了的代理对象；<br>  5）、<span style="background-color:#00ff00">以后容器中获取到的就是这个组件的代理对象，执行目标方法的时候，代理对象就会执行通知方法的流程</span>；</p><h4 id="2-3-2-1-wrapIfNecessary"><a href="#2-3-2-1-wrapIfNecessary" class="headerlink" title="2.3.2.1. wrapIfNecessary"></a>2.3.2.1. wrapIfNecessary</h4><h4 id="2-3-2-2-getAdvicesAndAdvisorsForBean"><a href="#2-3-2-2-getAdvicesAndAdvisorsForBean" class="headerlink" title="2.3.2.2. getAdvicesAndAdvisorsForBean"></a>2.3.2.2. getAdvicesAndAdvisorsForBean</h4><p><a href="https://blog.csdn.net/wang489687009/article/details/121099165">https://blog.csdn.net/wang489687009/article/details/121099165</a><br><a href="https://blog.csdn.net/yy_diego/article/details/118213155">https://blog.csdn.net/yy_diego/article/details/118213155</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230210215209.png" alt="image.png"></p><h4 id="2-3-2-3-findEligibleAdvisors"><a href="#2-3-2-3-findEligibleAdvisors" class="headerlink" title="2.3.2.3. findEligibleAdvisors"></a>2.3.2.3. findEligibleAdvisors</h4><p>AbstractAdvisorAutoProxyCreator：findCandidateAdvisors→<br>AnnotationAwareAspectJAutoProxyCreator：advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());</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-comment">// 通过所有的aspectNames在缓存中获取切面对应的Advisor，这里如果是单例的，则直接从advisorsCache  </span><br><span class="hljs-comment">// 获取，如果是多例类型的，则通过MetadataAwareAspectInstanceFactory立即生成一个</span><br>List&lt;Advisor&gt; cachedAdvisors = <span class="hljs-built_in">this</span>.advisorsCache.get(aspectName);<br></code></pre></td></tr></table></figure><p>&#x2F;&#x2F; 提供的 hook 方法，用于对目标 Advisor 进行扩展<br>extendAdvisors(eligibleAdvisors);<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323143217.png" alt="image.png"></p><h4 id="2-3-2-4-createProxy"><a href="#2-3-2-4-createProxy" class="headerlink" title="2.3.2.4. createProxy"></a>2.3.2.4. createProxy</h4><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-comment">// 获取当前bean的Advices和Advisors  </span><br>Object[] specificInterceptors = getAdvicesAndAdvisorsForBean<br><span class="hljs-title function_">createProxy</span><span class="hljs-params">(bean.getClass()</span>, beanName, specificInterceptors, <span class="hljs-keyword">new</span> <span class="hljs-title class_">SingletonTargetSource</span>(bean))<br></code></pre></td></tr></table></figure><h2 id="2-4-执行逻辑"><a href="#2-4-执行逻辑" class="headerlink" title="2.4. 执行逻辑"></a>2.4. 执行逻辑</h2><p><a href="https://www.processon.com/view/link/5f4dd513e0b34d1abc735998">https://www.processon.com/view/link/5f4dd513e0b34d1abc735998</a></p><p>容器中保存了组件的代理对象（cglib 增强后的对象），这个对象里面保存了详细信息（比如增强器，目标对象，xxx）<br><span style="background-color:#00ff00">拦截器链：每一个通知方法又被包装为方法拦截器，利用 MethodInterceptor 机制执行方法逻辑</span>。如果没有拦截器链，直接执行目标方法</p><h3 id="2-4-1-获取并封装拦截器链⭐️🔴"><a href="#2-4-1-获取并封装拦截器链⭐️🔴" class="headerlink" title="2.4.1. 获取并封装拦截器链⭐️🔴"></a>2.4.1. 获取并封装拦截器链⭐️🔴</h3><p>执行目标方法执行时，<span style="background-color:#ff00ff">CglibAopProxy.intercept()</span> 会拦截目标方法的执行，根据 ProxyFactory 对象 (<code>this.advised</code>) 获取<span style="background-color:#00ff00">将要执行的目标方法的</span>拦截器链</p><ol><li><font color=#ff0000><span style="background-color:#ffff00">遍历所有的增强器，将其转换为 MethodInterceptor</span></font><br>如果是 MethodInterceptor 类型，直接加入到集合中。如果不是，使用 AdvisorAdapter 将增强器转为 MethodInterceptor 类型；<font color=#ff0000><span style="background-color:#ffff00">转换完成返回 MethodInterceptor 数组</span></font>；</li><li>如果是 PointcutAdvisor 需要 match 切点，匹配成功则放入 <code>List&lt;Object&gt;</code> 返回</li><li>获得拦截器链之后，<span style="background-color:#ff00ff">把需要执行的目标对象，目标方法，</span><br><span style="background-color:#ff00ff">拦截器链等信息封装起来，创建一个 <code>CglibMethodInvocation 对象</code></span>，并调用 Object retVal &#x3D;  mi.proceed();</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217230956.png"></p><h3 id="2-4-2-触发执行拦截器链"><a href="#2-4-2-触发执行拦截器链" class="headerlink" title="2.4.2. 触发执行拦截器链"></a>2.4.2. 触发执行拦截器链</h3><h4 id="2-4-2-1-执行入口"><a href="#2-4-2-1-执行入口" class="headerlink" title="2.4.2.1. 执行入口"></a>2.4.2.1. 执行入口</h4><ol><li>创建 CglibMethodInvocation 对象后调用 <code>proceed()</code> 方法<br><code>new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()</code></li><li>执行进入 <code>proceed()</code> 方法中，获取第 <code>++this.currentInterceptorIndex</code> 个拦截器。<code>this.currentInterceptorIndex</code> 默认值是 <code>-1</code></li><li>调用拦截器的 <code>invoke(this)</code> 方法，<code>this</code> 就是当前 <code>CglibMethodInvocation</code> 对象</li><li>在不同拦截器中的 invoke 方法中，会再次调用 <code>mi.proceed()</code>，进入到第 2 步，依次递归，执行每个拦截器中的方法，直到递归调用的执行出口为止<br>❕<span style="display:none">%%<br>▶1.🏡⭐️◼️cglibMethodInvocation 对象是链式调用的控制对象◼️⭐️-point-20230223-1444%%</span></li><li>值得注意的是在第一个默认的拦截器 <code>ExposeInvocationInterceptor</code> 中将 CglibMethodInvocation 对象放到了 ThreadLocal 中以便共享使用<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323154744.png" alt="image.png"></li></ol><h4 id="2-4-2-2-执行出口"><a href="#2-4-2-2-执行出口" class="headerlink" title="2.4.2.2. 执行出口"></a>2.4.2.2. 执行出口</h4><p><code>this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1</code><br>如果没有拦截器，或者拦截器的索引等于拦截器数组 -1 （即执行到了最后一个拦截器）就会执行目标方法；</p><h4 id="2-4-2-3-执行过程"><a href="#2-4-2-3-执行过程" class="headerlink" title="2.4.2.3. 执行过程"></a>2.4.2.3. 执行过程</h4><p><span style="background-color:#00ff00">链式获取每一个拦截器，拦截器执行 invoke 方法，每一个拦截器等待下一个拦截器执行完成返回以后再来执行；</span><br>拦截器链的触发过程，即 proceed 方法的执行过程。拦截器链的机制，保证了通知方法与目标方法的执行顺序</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217233439.png"></p><p>每种拦截器的执行逻辑不同，比如 <code>AspectJAfterThrowingAdvice</code>，advice 方法在 catch 块里执行。其他拦截器请看 <a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217233822.png"></p><p><span style="background-color:#ff00ff">而 this.advice.afterReturing 没有设置 catch 块，所以出错之后也就不会被执行到了</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221217233758.png"></p><h4 id="2-4-2-4-执行顺序分析"><a href="#2-4-2-4-执行顺序分析" class="headerlink" title="2.4.2.4. 执行顺序分析"></a>2.4.2.4. 执行顺序分析</h4><p>流程图<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218103312.png"><br>❕<span style="display:none">%%<br>1904-🏡⭐️◼️拦截器链的调用逻辑 ?🔜MSTM📝 在创建 advisors 时，由于 ReflectiveAspectJAdvisorFactory 静态代码块中设定了 Around、Before、After、AfterReturning、AfterThrowing 的顺序，所以加入 advisorsCache 中的 declarationOrder 也是这个顺序。在创建代理时，除了用于辅助拦截器链执行的 ExposeInvocationInterceptor 之外，其他 advisors 按 declarationOrder 做了倒序排列，所以在拦截器链中的顺序为 Expose、AfterThrowing、AfterReturning、After、Before、Around◼️⭐️-point-20230211-1904%%</span></p><h2 id="2-5-简单概述"><a href="#2-5-简单概述" class="headerlink" title="2.5. 简单概述"></a>2.5. 简单概述</h2><p>总结：<br>    1）、  @ EnableAspectJAutoProxy 开启 AOP 功能<br>    2）、 @ EnableAspectJAutoProxy 会给容器中注册一个组件 <code>AnnotationAwareAspectJAutoProxyCreator</code><br>    3）、AnnotationAwareAspectJAutoProxyCreator 是一个后置处理器；<br>    4）、容器的创建流程：<br>          1）、registerBeanPostProcessors（）注册后置处理器；创建 AnnotationAwareAspectJAutoProxyCreator 对象<br>          2）、finishBeanFactoryInitialization（）初始化剩下的单实例 bean<br>          1）、创建业务逻辑组件和切面组件<br>          2）、AnnotationAwareAspectJAutoProxyCreator 拦截组件的创建过程<br>          3）、组件创建完之后，判断组件是否需要增强<br>            是：切面的通知方法，包装成增强器（Advisor）; 给业务逻辑组件创建一个代理对象（cglib）；<br>    5）、执行目标方法：<br>          1）、代理对象执行目标方法<br>          2）、CglibAopProxy.intercept()；<br>          1）、得到目标方法的拦截器链（增强器包装成拦截器 MethodInterceptor）<br>          2）、利用拦截器的链式机制，依次进入每一个拦截器进行执行；<br>          3）、效果：<br>                正常执行：前置通知 -》目标方法 -》后置通知 -》返回通知<br>                出现异常：前置通知 -》目标方法 -》后置通知 -》异常通知</p><h2 id="2-6-顺序问题-【两种顺序】⭐️🔴"><a href="#2-6-顺序问题-【两种顺序】⭐️🔴" class="headerlink" title="2.6. 顺序问题 -【两种顺序】⭐️🔴"></a>2.6. 顺序问题 -【两种顺序】⭐️🔴</h2><p><span style="display:none">%%<br>▶29.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-1550%%</span>❕ ^8u7u46</p><h4 id="2-6-1-通知执行与目标方法执行之间的顺序在哪确定的⭐️🔴"><a href="#2-6-1-通知执行与目标方法执行之间的顺序在哪确定的⭐️🔴" class="headerlink" title="2.6.1. 通知执行与目标方法执行之间的顺序在哪确定的⭐️🔴"></a>2.6.1. 通知执行与目标方法执行之间的顺序在哪确定的⭐️🔴</h4><p>在各种通知的源码中写死的，这种顺序是不会变的，而拦截器链中通知的顺序可以通过 <code>@Order</code> 注解改变 ❕<span style="display:none">%%<br>1608-🏡⭐️◼️通知方法和目标方法之间的执行顺序 ?🔜MSTM📝 各种不同的通知写死在源码里的，这个顺序是不能变化的◼️⭐️-point-20230211-1608%%</span></p><h5 id="2-6-1-1-通知种类"><a href="#2-6-1-1-通知种类" class="headerlink" title="2.6.1.1. 通知种类"></a>2.6.1.1. 通知种类</h5><p><a href="https://blog.csdn.net/Ethan_199402/article/details/109491789">https://blog.csdn.net/Ethan_199402/article/details/109491789</a><br>是否是 MethodInterceptor 类型的<br>ExposeInvocationInterceptor：是<br><span style="background-color:#ff00ff">AspectJAfterThrowingAdvice: 是</span><br><span style="background-color:#ffff00">AspectJAfterReturningAdvice: 不是，转换为 AfterReturningAdviceInterceptor</span><br>AspectJAfterAdvice: 是<br>AspectJAroundAdvice 是<br><span style="background-color:#ffff00">AspectJMethodBeforeAdvice 不是，转换为 MethodBeforeAdviceInterceptor</span></p><p>PS: 虽然有适配器，但 AspectJAfterThrowingAdvice 实现了 MethodInterceptor<br><a href="https://www.vnjohn.com/aop-process-source.html">https://www.vnjohn.com/aop-process-source.html</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211110153.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211110253.png" alt="image.png"></p><h5 id="2-6-1-2-ExposeInvocationInterceptor"><a href="#2-6-1-2-ExposeInvocationInterceptor" class="headerlink" title="2.6.1.2. ExposeInvocationInterceptor"></a>2.6.1.2. ExposeInvocationInterceptor</h5><p>当生成代理对象之后，该进行逻辑方法的调用了；但此时，有 6 个 advisor，除了五种增强通知之外，还有一个是 ExposeInvocationInterceptor（将当前 methodInvocation 存入线程上下文）它起着一个桥梁的作用，它们在执行时是按照某个顺序来执行的，而且由一个通知跳转到另外一个通知，所以此时，我们需要构建一个拦截器链（责任链模式）只有创建好链式结构，才能顺序的往下执行.</p><p>在 <code>findEligibleAdvisors</code> 方法中调用 <code>makeAdvisorChainAspectJCapableIfNecessary</code> 创建增强器链时被加入到增强器链中<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323161037.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323161550.png" alt="png"></p><h5 id="2-6-1-3-AspectJAfterThrowingAdvice"><a href="#2-6-1-3-AspectJAfterThrowingAdvice" class="headerlink" title="2.6.1.3. AspectJAfterThrowingAdvice"></a>2.6.1.3. AspectJAfterThrowingAdvice</h5><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_">AspectJAfterThrowingAdvice</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractAspectJAdvice</span>  <br>      <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MethodInterceptor</span>, AfterAdvice, Serializable &#123;<br> ...     <br><span class="hljs-meta">@Override</span>  <br><span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(MethodInvocation mi)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>   <span class="hljs-keyword">try</span> &#123;  <br>      <span class="hljs-keyword">return</span> mi.proceed();  <br>   &#125;  <br>   <span class="hljs-keyword">catch</span> (Throwable ex) &#123;  <br>      <span class="hljs-keyword">if</span> (shouldInvokeOnThrowing(ex)) &#123;  <br>         invokeAdviceMethod(getJoinPointMatch(), <span class="hljs-literal">null</span>, ex);  <br>      &#125;  <br>      <span class="hljs-keyword">throw</span> ex;  <br>   &#125;  <br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="2-6-1-4-AfterReturningAdviceInterceptor⭐️"><a href="#2-6-1-4-AfterReturningAdviceInterceptor⭐️" class="headerlink" title="2.6.1.4. AfterReturningAdviceInterceptor⭐️"></a>2.6.1.4. AfterReturningAdviceInterceptor⭐️</h5><p>由 AfterReturningAdvice 适配而来</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">class</span> <span class="hljs-title class_">AfterReturningAdviceInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MethodInterceptor</span>, AfterAdvice, Serializable &#123;  <br>  <br>   <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AfterReturningAdvice advice;  <br>  <br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(MethodInvocation mi)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>      <span class="hljs-type">Object</span> <span class="hljs-variable">retVal</span> <span class="hljs-operator">=</span> mi.proceed();  <br>      <span class="hljs-built_in">this</span>.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());  <br>      <span class="hljs-keyword">return</span> retVal;  <br>   &#125;  <br>  <br>&#125;<br></code></pre></td></tr></table></figure><h5 id="2-6-1-5-AspectJAfterAdvice"><a href="#2-6-1-5-AspectJAfterAdvice" class="headerlink" title="2.6.1.5. AspectJAfterAdvice"></a>2.6.1.5. AspectJAfterAdvice</h5><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_">AspectJAfterAdvice</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractAspectJAdvice</span>  <br>      <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MethodInterceptor</span>, AfterAdvice, Serializable &#123;  <br>  ...<br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(MethodInvocation mi)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>      <span class="hljs-keyword">try</span> &#123;  <br>         <span class="hljs-comment">// 执行下一个通知/拦截器  </span><br>         <span class="hljs-keyword">return</span> mi.proceed();  <br>      &#125;  <br>      <span class="hljs-keyword">finally</span> &#123;  <br>         <span class="hljs-comment">// 后置通知的方法总是会被执行,原因就在这finally  </span><br>         invokeAdviceMethod(getJoinPointMatch(), <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);  <br>      &#125;  <br>   &#125;  <br>...<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="2-6-1-6-AspectJAroundAdvice"><a href="#2-6-1-6-AspectJAroundAdvice" class="headerlink" title="2.6.1.6. AspectJAroundAdvice"></a>2.6.1.6. AspectJAroundAdvice</h5><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_">AspectJAroundAdvice</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractAspectJAdvice</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MethodInterceptor</span>, Serializable &#123;  <br>...<br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(MethodInvocation mi)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>      <span class="hljs-keyword">if</span> (!(mi <span class="hljs-keyword">instanceof</span> ProxyMethodInvocation)) &#123;  <br>         <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalStateException</span>(<span class="hljs-string">&quot;MethodInvocation is not a Spring ProxyMethodInvocation: &quot;</span> + mi);  <br>      &#125;  <br>      <span class="hljs-type">ProxyMethodInvocation</span> <span class="hljs-variable">pmi</span> <span class="hljs-operator">=</span> (ProxyMethodInvocation) mi;  <br>      <span class="hljs-type">ProceedingJoinPoint</span> <span class="hljs-variable">pjp</span> <span class="hljs-operator">=</span> lazyGetProceedingJoinPoint(pmi);  <br>      <span class="hljs-type">JoinPointMatch</span> <span class="hljs-variable">jpm</span> <span class="hljs-operator">=</span> getJoinPointMatch(pmi);  <br>      <span class="hljs-keyword">return</span> invokeAdviceMethod(pjp, jpm, <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);  <br>   &#125;  <br>...<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="2-6-1-7-MethodBeforeAdviceInterceptor⭐️"><a href="#2-6-1-7-MethodBeforeAdviceInterceptor⭐️" class="headerlink" title="2.6.1.7. MethodBeforeAdviceInterceptor⭐️"></a>2.6.1.7. MethodBeforeAdviceInterceptor⭐️</h5><p>由 AspectJMethodBeforeAdvice 适配而来</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-comment">// MethodBefore前置通知Interceptor</span><br><span class="hljs-comment">// 实现了MethodInterceptor接口,持有AspectJMethodBeforeAdvice对象</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MethodBeforeAdviceInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MethodInterceptor</span>, BeforeAdvice, Serializable &#123;  <br>  <br>   <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> MethodBeforeAdvice advice;  <br>   <span class="hljs-comment">/**  </span><br><span class="hljs-comment">    * 为指定的advice创建对应的MethodBeforeAdviceInterceptor对象  </span><br><span class="hljs-comment">    *  </span><br><span class="hljs-comment">    * Create a new MethodBeforeAdviceInterceptor for the given advice.    * <span class="hljs-doctag">@param</span> advice the MethodBeforeAdvice to wrap  </span><br><span class="hljs-comment">    */</span>   <span class="hljs-keyword">public</span> <span class="hljs-title function_">MethodBeforeAdviceInterceptor</span><span class="hljs-params">(MethodBeforeAdvice advice)</span> &#123;  <br>      Assert.notNull(advice, <span class="hljs-string">&quot;Advice must not be null&quot;</span>);  <br>      <span class="hljs-built_in">this</span>.advice = advice;  <br>   &#125;  <br>    <br>   <span class="hljs-comment">// 这个invoke方法是拦截器的回调方法，会在代理对应的方法被调用时触发回调  </span><br>   <span class="hljs-meta">@Override</span>  <br>   <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(MethodInvocation mi)</span> <span class="hljs-keyword">throws</span> Throwable &#123;  <br>      <span class="hljs-comment">// 执行前置通知的方法  </span><br>      <span class="hljs-built_in">this</span>.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());  <br>      <span class="hljs-comment">// 执行下一个通知/拦截器，但是该拦截器是最后一个了，所以会调用目标方法  </span><br>      <span class="hljs-keyword">return</span> mi.proceed();  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h4 id="2-6-2-拦截器链中通知的顺序是在哪里确定的⭐️🔴"><a href="#2-6-2-拦截器链中通知的顺序是在哪里确定的⭐️🔴" class="headerlink" title="2.6.2. 拦截器链中通知的顺序是在哪里确定的⭐️🔴"></a>2.6.2. 拦截器链中通知的顺序是在哪里确定的⭐️🔴</h4><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-0837%%</span>❕ ^q5brru</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213062027.png" alt="image.png"></p><p><span style="background-color:#ffff00">shouldskip 流程</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230213062138.png" alt="image.png"></p><p><code>METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);</code><br>是在 <span style="background-color:#ff00ff">ReflectiveAspectJAdvisorFactory</span> 中的静态代码块中定义的 ❕<span style="display:none">%%<br>1615-🏡⭐️◼️拦截器链中通知的顺序是如何确定的 ?🔜MSTM📝 拦截器链中的顺序最初是从创建通知的时候确定的，是在 ReflectiveAspectJAdvisorFactory 工厂的静态代码块中定义了：Around、Before、After、AfterReturning、AfterThrowing。创建 advisor 时 declarationOrder 就是 size 大小的数值，所以分别是 0 到 4。然后在创建代理时，获取 advisorsCache 中的 advisors 之后进行了排序，除了用于辅助创建拦截器链的 ExposeInvocationInterceptor 之外，都按 declarationOrder 做了倒序排列，所以最后拦截器链中的顺序就是 ExposeInvocationInterceptor、AfterThrowing、AfterRetruning、After、Before、Around。◼️⭐️-point-20230211-1615%%</span></p><p><code>findCandidateAdvisors</code> → <code>buildAspectJAdvisors</code> → <code>getAdvisors</code> → <code>getAdvisorMethods</code> → <code>getAdvisor</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211141542.png" alt="image.png"></p><p>获取所有不包含 Pointcut 注解的方法，然后进行排序，根据对应的顺序 Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211145421.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211160555.png" alt="image.png"></p><h5 id="2-6-2-1-findEligibleAdvisors-sortAdvisors⭐️🔴"><a href="#2-6-2-1-findEligibleAdvisors-sortAdvisors⭐️🔴" class="headerlink" title="2.6.2.1. findEligibleAdvisors.sortAdvisors⭐️🔴"></a>2.6.2.1. findEligibleAdvisors.sortAdvisors⭐️🔴</h5><p>在使用时，除了用于创建链条的 <code>ExposeInvocationInterceptor</code> ，<span style="background-color:#ff00ff">其他通知都做了倒序排列</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211120820.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211135310.png" alt="image.png"></p><p><strong>不同优先级</strong><br>compareTo → compare → doCompare</p><p><strong>相同优先级且同属一个切面</strong><br>AspectJPrecedenceComparator.comparePrecedenceWithinAspect →<br>getAspectDeclarationOrder</p><h5 id="2-6-2-2-getAdvicesAndAdvisorsForBean"><a href="#2-6-2-2-getAdvicesAndAdvisorsForBean" class="headerlink" title="2.6.2.2. getAdvicesAndAdvisorsForBean"></a>2.6.2.2. getAdvicesAndAdvisorsForBean</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211121352.png" alt="image.png"></p><h5 id="2-6-2-3-getInterceptorsAndDynamicInterceptionAdvice"><a href="#2-6-2-3-getInterceptorsAndDynamicInterceptionAdvice" class="headerlink" title="2.6.2.3. getInterceptorsAndDynamicInterceptionAdvice"></a>2.6.2.3. getInterceptorsAndDynamicInterceptionAdvice</h5><p>得到的顺序与 <code>getAdvicesAndAdvisorsForBean</code> 是一致的，也就是说排序的地方就在 <code>findEligibleAdvisors.sortAdvisors</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211121913.png" alt="image.png"></p><h5 id="2-6-2-4-自定义顺序"><a href="#2-6-2-4-自定义顺序" class="headerlink" title="2.6.2.4. 自定义顺序"></a>2.6.2.4. 自定义顺序</h5><p><span style="display:none">%%<br>▶8.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230307-0928%%</span>❕ ^1mjgqz</p><p><a href="https://blog.csdn.net/CSDN_KONGlX/article/details/125486683">https://blog.csdn.net/CSDN_KONGlX/article/details/125486683</a><br><a href="https://cloud.tencent.com/developer/inventory/657/article/1526899">https://cloud.tencent.com/developer/inventory/657/article/1526899</a></p><p>多个@Aspect、Advisor 排序规则</p><p><strong>1、在 spring 容器中获取@Aspect、Advisor 类型的所有 bean，得到一个列表 list1</strong><br><strong>2、对 list1 按照 order 的值升序排序，得到结果 list2</strong><br><strong>3、然后再对 list2 中@Aspect 类型的 bean 内部的通知进行排序，规则</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230211162401.png" alt="image.png"><br>如上图所示，这 2 个 Aspect 就像 2 个圆圈在外面拦截，中间是目标方法。当一个请求进来要执行目标方法：</p><ul><li>首先会被外圈的 <code>@Order(1)</code> 拦截器拦截</li><li>然后被内圈 <code>@Order(2)</code> 拦截器拦截</li><li>执行完目标方法后，先经过 <code>@Order(2)</code> 的后置拦截器</li><li>最后再通过 <code>@Order(1)</code> 的后置拦截器</li></ul><h1 id="3-注解式开发"><a href="#3-注解式开发" class="headerlink" title="3. 注解式开发"></a>3. 注解式开发</h1><p><a href="https://www.bilibili.com/video/BV1ME411o7Uu?p=8">https://www.bilibili.com/video/BV1ME411o7Uu?p=8</a></p><h2 id="3-1-使用示例"><a href="#3-1-使用示例" class="headerlink" title="3.1. 使用示例"></a>3.1. 使用示例</h2><p>Spring 注解驱动开发 - 尚硅谷 - 雷丰阳&#x2F;spring-annotation&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;atguigu&#x2F;config&#x2F;MainConfigOfAOP.java<br>[[MainConfigOfAOP.java]]</p><h2 id="3-2-主要三步"><a href="#3-2-主要三步" class="headerlink" title="3.2. 主要三步"></a>3.2. 主要三步</h2><ul><li>1）、将业务逻辑组件和切面类都加入到容器中；告诉 Spring 哪个是切面类（@Aspect）</li><li>2）、在切面类上的每一个通知方法上标注通知注解，告诉 Spring 何时何地运行（切入点表达式）</li><li>3）、开启基于注解的 aop 模式；@EnableAspectJAutoProxy</li></ul><h2 id="3-3-详细步骤"><a href="#3-3-详细步骤" class="headerlink" title="3.3. 详细步骤"></a>3.3. 详细步骤</h2><ul><li>1、导入 aop 模块；Spring AOP：(spring-aspects)</li><li>2、定义一个业务逻辑类（MathCalculator）；在业务逻辑运行的时候将日志进行打印（方法之前、方法运行结束、方法出现异常，xxx）</li><li>3、定义一个日志切面类（LogAspects）：切面类里面的方法需要动态感知 MathCalculator.div 运行到哪里然后执行；</li><li><pre><code>通知方法：  </code></pre></li><li><pre><code>   前置通知(@Before)：logStart：在目标方法(div)运行之前运行  </code></pre></li><li><pre><code>   后置通知(@After)：logEnd：在目标方法(div)运行结束之后运行（无论方法正常结束还是异常结束）  </code></pre></li><li><pre><code>   返回通知(@AfterReturning)：logReturn：在目标方法(div)正常返回之后运行  </code></pre></li><li><pre><code>   异常通知(@AfterThrowing)：logException：在目标方法(div)出现异常以后运行  </code></pre></li><li><pre><code>   环绕通知(@Around)：动态代理，手动推进目标方法运行（joinPoint.procced()）  </code></pre></li><li>4、给切面类的目标方法标注何时何地运行（通知注解）；</li><li>5、将切面类和业务逻辑类（目标方法所在类）都加入到容器中;</li><li>6、必须告诉 Spring 哪个类是切面类 (给切面类上加一个注解：@Aspect)</li><li>7、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的 aop 模式】</li><li><pre><code>在Spring中很多的 @EnableXXX;</code></pre></li></ul><h1 id="4-设计模式"><a href="#4-设计模式" class="headerlink" title="4. 设计模式"></a>4. 设计模式</h1><h2 id="4-1-代理模式"><a href="#4-1-代理模式" class="headerlink" title="4.1. 代理模式"></a>4.1. 代理模式</h2><h2 id="4-2-适配器模式"><a href="#4-2-适配器模式" class="headerlink" title="4.2. 适配器模式"></a>4.2. 适配器模式</h2><p>Spring AOP 的增强或通知 (Advice) 使用到了适配器模式，与之相关的接口是 <code>AdvisorAdapter</code></p><p>Advice 常用的类型有：BeforeAdvice（目标方法调用前,前置通知）、AfterAdvice（目标方法调用后,后置通知）、AfterReturningAdvice(目标方法执行结束后，return 之前) 等等。</p><p>每个类型 Advice（通知）都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor。</p><p>Spring 预定义的通知要通过对应的适配器，适配成 MethodInterceptor 接口 (方法拦截器) 类型的对象（如：MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice）。</p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h2 id="5-1-通知的版本变化"><a href="#5-1-通知的版本变化" class="headerlink" title="5.1. 通知的版本变化"></a>5.1. 通知的版本变化</h2><p><span style="display:none">%%<br>▶34.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1035%%</span>📙❕ ^nj3sol</p><p><a href="https://www.bilibili.com/video/BV1Hy4y1B78T?p=32&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Hy4y1B78T?p=32&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221218113347.png"></p><p>确切的说是<span style="background-color:#00ff00">5.2.7 版本</span>就发生了变化，将 After 放在 AfterThrowing 或者 AfterReturning 后面，更符合人们的正常思维习惯。</p><p>[[001-Spring面试题笔记#8 2 Spring通知有哪些类型？]]</p><p><span style="background-color:#ff0000">顺序的源头</span>：<a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a></p><h2 id="5-2-默认动态代理变化⭐️🔴"><a href="#5-2-默认动态代理变化⭐️🔴" class="headerlink" title="5.2. 默认动态代理变化⭐️🔴"></a>5.2. 默认动态代理变化⭐️🔴</h2><p><span style="display:none">%%<br>▶27.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-0949%%</span>❕❕ ^5vgexk</p><p><a href="https://cloud.tencent.com/developer/article/1532547">https://cloud.tencent.com/developer/article/1532547</a></p><ol><li>Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。</li><li>SpringBoot 2.x 开始，为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。<br>   <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230302143216.png" alt="image.png"></li><li>在 SpringBoot 2.x 中，如果需要默认使用 JDK 动态代理可以通过配置项 <code>spring.aop.proxy-target-class=false</code> 来进行修改，<code>proxyTargetClass</code> 配置已无效。</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment">#在application.properties文件中通过spring.aop.proxy-target-class来配置 spring.aop.proxy-target-class=false</span><br></code></pre></td></tr></table></figure><p>自定义注解<br><a href="http://www.imooc.com/article/22556">http://www.imooc.com/article/22556</a></p><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-尚硅谷-雷丰阳-Spring-注解驱动开发"><a href="#6-1-尚硅谷-雷丰阳-Spring-注解驱动开发" class="headerlink" title="6.1. 尚硅谷 - 雷丰阳 -Spring 注解驱动开发"></a>6.1. 尚硅谷 - 雷丰阳 -Spring 注解驱动开发</h2><h3 id="6-1-1-视频"><a href="#6-1-1-视频" class="headerlink" title="6.1.1. 视频"></a>6.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV114411c7hV/?p=36&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV114411c7hV/?p=36&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="6-1-2-配套代码"><a href="#6-1-2-配套代码" class="headerlink" title="6.1.2. 配套代码"></a>6.1.2. 配套代码</h3><p>[[MainConfigOfAOP.java]]</p><h3 id="6-1-3-配套笔记"><a href="#6-1-3-配套笔记" class="headerlink" title="6.1.3. 配套笔记"></a>6.1.3. 配套笔记</h3><p><a href="https://liayun.blog.csdn.net/article/details/111413608">https://liayun.blog.csdn.net/article/details/111413608</a></p><h2 id="6-2-尚硅谷-周阳-大厂面试第三季"><a href="#6-2-尚硅谷-周阳-大厂面试第三季" class="headerlink" title="6.2. 尚硅谷 - 周阳 - 大厂面试第三季"></a>6.2. 尚硅谷 - 周阳 - 大厂面试第三季</h2><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;brain-mapping&#x2F;docs&#x2F;大厂面试题第3季.mmap]]</p><h2 id="6-3-图灵徐庶"><a href="#6-3-图灵徐庶" class="headerlink" title="6.3. 图灵徐庶"></a>6.3. 图灵徐庶</h2><p><a href="https://www.bilibili.com/video/BV1mf4y1c7cV?p=88&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1mf4y1c7cV?p=88&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>[[Spring全家桶面试题—图灵徐庶.pdf]]<br><a href="https://www.processon.com/view/link/5f5075c763768959e2d109df#map">https://www.processon.com/view/link/5f5075c763768959e2d109df#map</a></p><h2 id="6-4-尚硅谷大数据技术之-Spring"><a href="#6-4-尚硅谷大数据技术之-Spring" class="headerlink" title="6.4. 尚硅谷大数据技术之 Spring"></a>6.4. 尚硅谷大数据技术之 Spring</h2><h3 id="6-4-1-视频"><a href="#6-4-1-视频" class="headerlink" title="6.4.1. 视频"></a>6.4.1. 视频</h3><p><a href="https://www.bilibili.com/video/av58225341?p=270">https://www.bilibili.com/video/av58225341?p=270</a></p><h3 id="6-4-2-配套代码"><a href="#6-4-2-配套代码" class="headerlink" title="6.4.2. 配套代码"></a>6.4.2. 配套代码</h3><p>[[02_尚硅谷大数据技术之Spring(老师原版).docx]]</p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring03&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;aspectJ&#x2F;xml&#x2F;LoggingAspect.java]]</p><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring03&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;aspectJ&#x2F;annotation&#x2F;LoggingAspect.java]]</p><h2 id="6-5-网络笔记"><a href="#6-5-网络笔记" class="headerlink" title="6.5. 网络笔记"></a>6.5. 网络笔记</h2><p><a href="https://blog.csdn.net/andy_zhang2007/article/details/86479685">https://blog.csdn.net/andy_zhang2007/article/details/86479685</a> <sup><span style="font-size:10px;color:green"> »20230220-0911</span></sup><br><a href="https://blog.csdn.net/qq_42419406/article/details/116640641">https://blog.csdn.net/qq_42419406/article/details/116640641</a> <sup><span style="font-size:10px;color:green"> »20230220-0928</span></sup></p><p><a href="https://blog.csdn.net/yuan882696yan/article/details/115359291">https://blog.csdn.net/yuan882696yan/article/details/115359291</a><br><a href="https://blog.csdn.net/yuan882696yan/article/details/116137802">https://blog.csdn.net/yuan882696yan/article/details/116137802</a><br><a href="https://juejin.cn/post/7037839773267918879#heading-3">https://juejin.cn/post/7037839773267918879#heading-3</a><br><a href="https://juejin.cn/post/6978742789165187079#heading-6">https://juejin.cn/post/6978742789165187079#heading-6</a><br><a href="https://segmentfault.com/a/1190000041228253">https://segmentfault.com/a/1190000041228253</a><br><a href="https://www.jianshu.com/p/b45bf3befa25">https://www.jianshu.com/p/b45bf3befa25</a></p><p><a href="https://www.luckfirefly.com/archives/spring%E6%8F%AD%E7%A7%98-%E6%B7%B1%E5%85%A5advisedsupport">https://www.luckfirefly.com/archives/spring%E6%8F%AD%E7%A7%98-%E6%B7%B1%E5%85%A5advisedsupport</a></p><p><a href="https://blog.csdn.net/lom9357bye/article/details/124659927">https://blog.csdn.net/lom9357bye/article/details/124659927</a> <sup><span style="font-size:10px;color:green"> »20230220-0841</span></sup></p>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
            <tag> AOP </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-10、JVM-启动</title>
      <link href="/2022/12/03/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81JVM-%E5%90%AF%E5%8A%A8/"/>
      <url>/2022/12/03/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81JVM-%E5%90%AF%E5%8A%A8/</url>
      
        <content type="html"><![CDATA[<h1 id="1-启动入口"><a href="#1-启动入口" class="headerlink" title="1. 启动入口"></a>1. 启动入口</h1><p><code>sun.misc.Launcher</code>类是java的入口，在启动java应用的时候会首先创建<code>Launcher</code>类，创建<code>Launcher</code>类的时候会准备应用程序运行中需要的类加载器，位于<code>jre/lib/rt.jar</code>中。</p><p>Launcher作为JAVA应用的入口，根据双亲委派模型，Laucher是由JVM创建的，它的类加载器是BootStrapClassLoader， 这是一个C++编写的类加载器，是java应用体系中最顶层的类加载器，负责加载JVM需要的一些类库(<JAVA_HOME>&#x2F;lib)，而Launcher类在rt.jar中，正好处于该加载器的加载路径下。</p><h1 id="2-启动流程"><a href="#2-启动流程" class="headerlink" title="2. 启动流程"></a>2. 启动流程</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109132729.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108215133.png"></p><h1 id="3-ClassLoader初始化源码解读"><a href="#3-ClassLoader初始化源码解读" class="headerlink" title="3. ClassLoader初始化源码解读"></a>3. ClassLoader初始化源码解读</h1><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//JDK 1.8 </span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Launcher</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Launcher</span> <span class="hljs-variable">launcher</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Launcher</span>(); <span class="hljs-comment">//类被加载时，触发创建静态实例对象，进而触发构造函数</span><br>    <br><span class="hljs-keyword">public</span> <span class="hljs-title function_">Launcher</span><span class="hljs-params">()</span> &#123;    <span class="hljs-comment">//构造函数</span><br>  ExtClassLoader extClassLoader;<br>  <span class="hljs-keyword">try</span> &#123;<br>    extClassLoader = ExtClassLoader.getExtClassLoader();   <span class="hljs-comment">// 获取ExtClassLoader</span><br>  &#125; <span class="hljs-keyword">catch</span> (IOException iOException) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InternalError</span>(<span class="hljs-string">&quot;Could not create extension class loader&quot;</span>);<br>  &#125; <br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-built_in">this</span>.loader = AppClassLoader.getAppClassLoader(extClassLoader);  <span class="hljs-comment">//以ExtClassLoader 作为父类加载器创建一个AppClassLoader</span><br>  &#125; <span class="hljs-keyword">catch</span> (IOException iOException) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InternalError</span>(<span class="hljs-string">&quot;Could not create application class loader&quot;</span>);<br>  &#125; <br>  Thread.currentThread().setContextClassLoader(<span class="hljs-built_in">this</span>.loader);   <span class="hljs-comment">//设置默认加载器</span><br>  ....<br>   &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>首先我们知道Launcher类是应用的入口，位于rt.jar包中，默认会被<code>BootstrapClassLoader</code>加载，<span style="background-color:#00ff00">被加载时，触发创建静态实例对象，进而触发构造函数，在构造函数内，完成ExtClassLoader及AppClassLoader的创建</span>。<span style="background-color:#ff00ff">并设置线程上下文类加载器。</span></p><h2 id="3-1-ExtClassLoader"><a href="#3-1-ExtClassLoader" class="headerlink" title="3.1. ExtClassLoader"></a>3.1. ExtClassLoader</h2><p>Launcher在创建的时候，第一件事情就是获取ExtClassLoader, ExtClassLoader<span style="background-color:#00ff00">在JVM中是一个单例</span>， 创建过程也是通过获取环境变量来获取ext加载的目录，生成一个ExtClassLoader，ExtClassLoader是URLClassLoader的子类。</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">static</span> Launcher.ExtClassLoader <span class="hljs-title function_">getExtClassLoader</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>      <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>) &#123;<br>          <span class="hljs-type">Class</span> <span class="hljs-variable">clazz</span> <span class="hljs-operator">=</span> Launcher.ExtClassLoader.class;<br>          <span class="hljs-keyword">synchronized</span>(Launcher.ExtClassLoader.class) &#123;<br>              <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>) &#123;<br>                  instance = createExtClassLoader();   <span class="hljs-comment">//单例模式</span><br>              &#125;<br>          &#125;<br>      &#125;<br>      <span class="hljs-keyword">return</span> instance;<br>  &#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108225410.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108225459.png"></p><p>ExtClassLoader的parent在此处设置为了null</p><h2 id="3-2-AppClassLoader"><a href="#3-2-AppClassLoader" class="headerlink" title="3.2. AppClassLoader"></a>3.2. AppClassLoader</h2><p>在获取ExtClassLoader之后，以此作为父类加载器创建一个sun.misc.Launcher.AppClassLoader, AppClassLoader的加载路径是java.class.path标记的路径，相同的，AppClassLoader也是URLClassLoader的子类。</p><h2 id="3-3-ContextClassLoader"><a href="#3-3-ContextClassLoader" class="headerlink" title="3.3. ContextClassLoader"></a>3.3. ContextClassLoader</h2><p>第18行，最终会将当前线程的<code>上下文类加载器</code>设置为<code>AppClassLoader</code>。</p><h1 id="4-JVM启动流程"><a href="#4-JVM启动流程" class="headerlink" title="4. JVM启动流程"></a>4. JVM启动流程</h1><h2 id="4-1-总体流程"><a href="#4-1-总体流程" class="headerlink" title="4.1. 总体流程"></a>4.1. 总体流程</h2><p>虚拟机的启动入口位于 share&#x2F;tools&#x2F;launcher&#x2F;java.c 的 main 方法，整个流程分为如下几个步骤：<br>1、配置JVM装载环境<br>2、解析JVM参数<br>3、设置线程栈大小<br>4、执行Java main方法： [[JVM源码分析之JVM启动流程_猿灯塔_InfoQ写作社区#2、加载主类的 class]]</p><p>[[JVM源码分析之JVM启动流程_猿灯塔_InfoQ写作社区]]</p><h2 id="4-2-创建引导类加载器流程"><a href="#4-2-创建引导类加载器流程" class="headerlink" title="4.2. 创建引导类加载器流程"></a>4.2. 创建引导类加载器流程</h2><p>#todo</p><span style="display:none">- [ ] 🚩 - 学习创建引导类加载器详细流程 - 🏡 2023-01-24 14:48</span>https://blog.csdn.net/luanlouis/article/details/50529868<h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><p><a href="https://blog.csdn.net/m0_45406092/article/details/108984101">https://blog.csdn.net/m0_45406092/article/details/108984101</a><br><a href="https://cloud.tencent.com/developer/article/2132669">https://cloud.tencent.com/developer/article/2132669</a></p><p>JDK9类加载器变化：[[探秘Java9之类加载 - 掘金]]</p><p>[[HotSpot JVM 的启动过程做了什么？ - 知乎]]</p><p><a href="https://xie.infoq.cn/article/509ebe9333a72b65b2fe85534">https://xie.infoq.cn/article/509ebe9333a72b65b2fe85534</a></p><p><a href="https://cloud.tencent.com/developer/article/1038435">https://cloud.tencent.com/developer/article/1038435</a></p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-4、SpringMVC</title>
      <link href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/"/>
      <url>/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/</url>
      
        <content type="html"><![CDATA[<h1 id="1-SpringMVC-容器启动"><a href="#1-SpringMVC-容器启动" class="headerlink" title="1. SpringMVC 容器启动"></a>1. SpringMVC 容器启动</h1><p>双十二之 Spring 整合 springmvc</p><p><a href="https://www.bilibili.com/video/BV1AJ411y72k?p=4&amp;spm_id_from=pageDriver">https://www.bilibili.com/video/BV1AJ411y72k?p=4&amp;spm_id_from=pageDriver</a></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919170344220.png" alt="image-20210919170344220"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919171033804.png" alt="image-20210919171033804"></p><h2 id="1-1-逻辑综述"><a href="#1-1-逻辑综述" class="headerlink" title="1.1. 逻辑综述"></a>1.1. 逻辑综述</h2><p>我们知道根据 <code>Servlet3.0规范</code>，web 容器（比如 Tomcat 服务器 (7 及以上版本)）在启动应用的时候，会扫描当前应用 <code>每一个jar包</code> 里面的 META-INF&#x2F;services&#x2F;<span style="background-color:#ff00ff">javax.servlet.ServletContainerInitializer 文件</span>中指定的实现类，然后再<span style="background-color:#00ff00">运行该实现类中的方法</span></p><p>而在我们整合 SpringMVC 用到的 spring-web-xxx.RELEASE.jar 中的 META-INF&#x2F;services&#x2F;目录里面有一个 javax.servlet.ServletContainerInitializer 文件，并且在该文件中指定的实现类就是 org.springframework.web.<code>SpringServletContainerInitializer</code>，打开该实现类，发现它上面标注了 <code>@HandlesTypes(WebApplicationInitializer.class)</code> 这样一个注解。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221223918.png" alt="image.png"></p><p>因此，&#x3D;&#x3D;web 容器在启动应用的时候&#x3D;&#x3D;，<span style="background-color:#00ff00">便会来扫描并加载 org.springframework.web.SpringServletContainerInitializer 实现类</span>，而且会传入我们感兴趣的类型（即 <code>WebApplicationInitializer接口</code>）的所有后代类型，最终再运行其 <code>onStartup</code> 方法。</p><p>具体来说，我们可以看到它会<span style="background-color:#00ff00">遍历感兴趣的类型（即 WebApplicationInitializer 接口）的所有后代类型，然后<font color=#ff0000><strong>利用反射技术创建</strong></font> WebApplicationInitializer 类型的对象</span>，<span style="background-color:#00ff00">而我们自定义的 (整合需要)MyWebAppInitializer 就是 WebApplicationInitializer 这种类型的</span>。而且创建完之后，都会存储到名为 initializers 的 <code>LinkedList&lt;WebApplicationInitializer&gt;</code> 集合中。接着，又会遍历该集合，并调用每一个 WebApplicationInitializer 接口实现对象的 onStartup 方法。</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919165159997.png" alt="image-20210919165159997"></p><p>遍历到每一个 WebApplicationInitializer 接口实现对象，调用其 onStartup 方法，实际上就是先调用其（例如我们自定义的 MyWebAppInitializer）最高父类 <code>Abstract ContextLoaderInitializer</code> 的 onStartup 方法，创建根容器；然后再调用其次高父类 <code>AbstractDispatcherServletInitializer</code> 的 onStartup 方法，创建 web 容器以及 DispatcherServlet；接着，根据其重写的 getServletMappings 方法来为 DispatcherServlet 配置映射信息</p><p><a href="https://liayun.blog.csdn.net/article/details/114968293">https://liayun.blog.csdn.net/article/details/114968293</a></p><h2 id="1-2-扫描感兴趣类"><a href="#1-2-扫描感兴趣类" class="headerlink" title="1.2. 扫描感兴趣类"></a>1.2. 扫描感兴趣类</h2><h3 id="1-2-1-Servlet3-0-规范"><a href="#1-2-1-Servlet3-0-规范" class="headerlink" title="1.2.1. Servlet3.0 规范"></a>1.2.1. Servlet3.0 规范</h3><p><strong>Shared libraries（共享库）／ runtimes pluggability（运行时插件能力）</strong><br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230330-1347%%</span>❕ ^yqskms</p><p>1、Servlet 容器启动会扫描，当前应用里面每一个 jar 包的 ServletContainerInitializer 的实现<br>2、提供 ServletContainerInitializer 的实现类；<br>必须绑定在，<span style="background-color:#ffff00">META-INF／services／javax.servlet.ServletContainerInitizer</span><br>文件的内容就是 ServletContainerInitializer 实现类的全类名</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221225700.png" alt="image.png"></p><p><strong>总结：</strong><br>servlet 容器 (比如 tomcat) 启动时，会扫描 jar 包下 MATE-INF&#x2F;service 路径下的 <code>javax.servlet.ServletContainerInitializer 文件中的类</code>，比如整合 SpringMVC 的是：<code>SpringServletContainerInitializer</code>，在 jar 包 spring-web-xxx.RELEASE.jar 中可以看到里面的全限定名是 <code>org.springframework.web.SpringServletContainerInitializer</code>。<span style="background-color:#00ff00">容器启动时会调用</span>SpringServletContainerInitializer 的 <code>onStartup()</code> 方法</p><h3 id="1-2-2-HandlesTypes"><a href="#1-2-2-HandlesTypes" class="headerlink" title="1.2.2. @HandlesTypes"></a>1.2.2. @HandlesTypes</h3><p>在 SpringServletContainerInitializer 的 <code>onStartup()</code> 方法<br>执行过程中会把 <span style="background-color:#00ff00">WebApplicationInitializer</span> 的 (由 <code>@HandlesTypes</code> 注解标注的) 所有实现类，通过反射创建实例并放到 List 集合中（<span style="background-color:#ff00ff">接口和抽象类会在反射实例化的时候排除掉</span>），然后循环遍历这些实现类并调用其 <code>onStartup()</code> 方法。子类中有就调用子类中的，否则调用父类中的 <code>onStartup()</code> 方法。</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919130511692.png" alt="image-20210919130511692"><br><span style="background-color:#ff00ff">接口和抽象类会在反射实例化的时候排除掉</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919130826992.png" alt="image-20210919130826992"></p><p>将感兴趣的类放入初始化集合中，然后遍历执行 onstarup() 方法</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920062831249.png" alt="image-20210920062831249"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205125635.png"></p><h3 id="1-2-3-MyWebAppIntializer-AbstractAnnotationConfigDispatcherServletInitializer"><a href="#1-2-3-MyWebAppIntializer-AbstractAnnotationConfigDispatcherServletInitializer" class="headerlink" title="1.2.3. MyWebAppIntializer-AbstractAnnotationConfigDispatcherServletInitializer"></a>1.2.3. MyWebAppIntializer-AbstractAnnotationConfigDispatcherServletInitializer</h3><p>我们在整合 SpringMVC 时，需要编写继承 <code>AbstractAnnotationConfigDispatcherServletInitializer</code> 的实现类，比如 MyWebAppIntializer，也会被收集到 <code>List&lt;WebApplicationInitializer&gt;</code> 中，随后触发这个实现类的 onStartup() 方法 ❕<span style="display:none">%%<br>▶15.🏡⭐️◼️与 SpringBoot 外置 Tomcat 启动原理不同的是 ?🔜MSTM📝 外置 Tomcat 启动需要一个 SpringBootServletInitializer 的子类，重写 Configurer 方法，将 SpringBoot 的主启动类传入；而 Spring 整合 SpringMVC 需要一个 AbstractAnnotationDispatcherServletInitializer 的子类，并重写 3 个方法，分别配置父容器、子容器的配置类，以及配置 mapping◼️⭐️-point-20230302-1941%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324074100.png" alt="image.png"></p><p>实现了 <code>WebApplicationInitializer</code> 的类，执行 onstartup() 方法，使用<span style="background-color:#ff00ff">模板方法</span>，调用父类 <code>AbstractDispatcherServletInitializer</code>–&gt;<code>AbstractContextLoaderInitializer</code> 中的 onstartup() 方法</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324115342.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324115510.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324123800.png" alt="image.png"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919172252681.png" alt="image-20210919172252681"></p><h2 id="1-3-创建父容器"><a href="#1-3-创建父容器" class="headerlink" title="1.3. 创建父容器"></a>1.3. 创建父容器</h2><h3 id="1-3-1-注册上下文监听器对象"><a href="#1-3-1-注册上下文监听器对象" class="headerlink" title="1.3.1. 注册上下文监听器对象"></a>1.3.1. 注册上下文监听器对象</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920060115516.png" alt="image-20210920060115516"></p><p>相当于 xml 配置中的 ContextLoaderLIstener 节点的作用<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920135818734.png" alt="image-20210920135818734"></p><h3 id="1-3-2-AbstractContextLoaderInitializer⭐️🔴"><a href="#1-3-2-AbstractContextLoaderInitializer⭐️🔴" class="headerlink" title="1.3.2. AbstractContextLoaderInitializer⭐️🔴"></a>1.3.2. AbstractContextLoaderInitializer⭐️🔴</h3><p>创建空的父容器，此时没有 service 和 dao 组件<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919151224644.png" alt="image-20210919151224644"></p><h3 id="1-3-3-配置父容器"><a href="#1-3-3-配置父容器" class="headerlink" title="1.3.3. 配置父容器"></a>1.3.3. 配置父容器</h3><p>根据 Servlet3.0 规范，父容器中只包含<span style="background-color:#ff00ff"> Services 和 Repositories </span>的 beans</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221204203723.png"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920141622667.png" alt="image-20210920141622667"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919152108852.png" alt="image-20210919152108852"></p><p>getRootConfigClasses()，是获取根容器配置类信息，相当于 xml 配置中的</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920060243843.png" alt="image-20210920060243843"></p><p>而在我们自己整合 SpringMVC 时，需要指定 2 个容器的配置类 RootConfig.class 和 WebAppConfig.class</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920061256353.png" alt="image-20210920061256353"></p><p>根据官网图示，父容器配置类配置的时候排除 Controller 的注解</p><h4 id="1-3-3-1-排除-controller"><a href="#1-3-3-1-排除-controller" class="headerlink" title="1.3.3.1. 排除 controller"></a>1.3.3.1. 排除 controller</h4><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920064859118.png" alt="image-20210920064859118"></p><h3 id="1-3-4-把空的父容器放到监听器中"><a href="#1-3-4-把空的父容器放到监听器中" class="headerlink" title="1.3.4. 把空的父容器放到监听器中"></a>1.3.4. 把空的父容器放到监听器中</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221204204126.png"></p><h3 id="1-3-5-把初始化器放到监听器中"><a href="#1-3-5-把初始化器放到监听器中" class="headerlink" title="1.3.5. 把初始化器放到监听器中"></a>1.3.5. 把初始化器放到监听器中</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221204205050.png"></p><h3 id="1-3-6-把监听器放到应用上下文中"><a href="#1-3-6-把监听器放到应用上下文中" class="headerlink" title="1.3.6. 把监听器放到应用上下文中"></a>1.3.6. 把监听器放到应用上下文中</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221204204949.png"></p><h3 id="1-3-7-刷新填充-Bean⭐️🔴"><a href="#1-3-7-刷新填充-Bean⭐️🔴" class="headerlink" title="1.3.7. 刷新填充 Bean⭐️🔴"></a>1.3.7. 刷新填充 Bean⭐️🔴</h3><p>ContextLoaderListener.contextInitialized()–&gt;ContextLoader.initWebApplicationContext()</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920065412110.png" alt="image-20210920065412110"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920143223675.png" alt="image-20210920143223675"></p><h3 id="1-3-8-把父容器放到-servletContext-中"><a href="#1-3-8-把父容器放到-servletContext-中" class="headerlink" title="1.3.8. 把父容器放到 servletContext 中"></a>1.3.8. 把父容器放到 servletContext 中</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920070531699.png" alt="image-20210920070531699"></p><h2 id="1-4-创建子容器"><a href="#1-4-创建子容器" class="headerlink" title="1.4. 创建子容器"></a>1.4. 创建子容器</h2><p><a href="https://www.processon.com/diagraming/641d94b7b13bd654f028beeb">https://www.processon.com/diagraming/641d94b7b13bd654f028beeb</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324224029.png" alt="image.png"></p><h3 id="1-4-1-AbstractDispatcherServletInitializer⭐️🔴"><a href="#1-4-1-AbstractDispatcherServletInitializer⭐️🔴" class="headerlink" title="1.4.1. AbstractDispatcherServletInitializer⭐️🔴"></a>1.4.1. AbstractDispatcherServletInitializer⭐️🔴</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920062635793.png" alt="image-20210920062635793"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920064437655.png" alt="image-20210920064437655"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920064538223.png" alt="image-20210920064538223"></p><h3 id="1-4-2-子容器配置类"><a href="#1-4-2-子容器配置类" class="headerlink" title="1.4.2. 子容器配置类"></a>1.4.2. 子容器配置类</h3><h4 id="1-4-2-1-禁用-default⭐️🔴⭐️🔴"><a href="#1-4-2-1-禁用-default⭐️🔴⭐️🔴" class="headerlink" title="1.4.2.1. 禁用 default⭐️🔴⭐️🔴"></a>1.4.2.1. 禁用 default⭐️🔴⭐️🔴</h4><p><span style="display:none">%%<br>▶28.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1012%%</span>❕❕ ^7a3bp3</p><p>PS: 父容器的排除规则不需要特别处理，而子容器扫描规则，<span style="background-color:#00ff00">需要加禁用默认过滤规则的注解</span><font color=#ff0000>useDefaultFilters&#x3D;false</font>，否则不生效，<span style="background-color:#ffff00">导致把所有的都扫进去</span>！！<br>如果此时父容器中有 AOP 的话，就会导致 AOP 失效。案例：<a href="/2023/03/03/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98-%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8-1%E3%80%81SpringMVC/" title="经验专题-框架使用-1、SpringMVC">经验专题-框架使用-1、SpringMVC</a></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920065136644.png" alt="image-20210920065136644"></p><h3 id="1-4-3-set-父容器"><a href="#1-4-3-set-父容器" class="headerlink" title="1.4.3. set 父容器"></a>1.4.3. set 父容器</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919172729747.png" alt="image-20210919172729747"></p><h3 id="1-4-4-刷新填充-Bean⭐️🔴"><a href="#1-4-4-刷新填充-Bean⭐️🔴" class="headerlink" title="1.4.4. 刷新填充 Bean⭐️🔴"></a>1.4.4. 刷新填充 Bean⭐️🔴</h3><p>DispatcherServlet–&gt;FrameworkServlet–&gt;HttpServletBean 中的 init() 方法</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920071024334.png" alt="image-20210920071024334"></p><p>获取上面父容器步骤中放入 servletContext 中的父容器<br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920071143137.png" alt="image-20210920071143137"></p><h3 id="1-4-5-设置容器父子关系⭐️🔴"><a href="#1-4-5-设置容器父子关系⭐️🔴" class="headerlink" title="1.4.5. 设置容器父子关系⭐️🔴"></a>1.4.5. 设置容器父子关系⭐️🔴</h3><p><span style="background-color:#ff00ff">将父容器引用赋值给子容器的 parent 属性</span><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920071223743.png" alt="image-20210920071223743"></p><p><span style="background-color:#ff00ff">父子容器的类型都是一样的，都是 AnnotationConfigWebApplicationContext，只是子容器的 parent 属性设置的是父容器的引用。</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221204214738.png"><br>❕<span style="display:none">%%<br>▶16.🏡⭐️◼️父子容器的关系，Bean 之间的关系，获取 Bean 的顺序◼️⭐️-point-20230302-1956%%</span><br><span style="background-color:#ff00ff">所以子容器可以访问父容器的对象，而反之不行</span></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920071713257.png" alt="image-20210920071713257"></p><h2 id="1-5-父子容器-bean-关系"><a href="#1-5-父子容器-bean-关系" class="headerlink" title="1.5. 父子容器 bean 关系"></a>1.5. 父子容器 bean 关系</h2><h2 id="1-6-获取-bean-的顺序-先子后父⭐️🔴"><a href="#1-6-获取-bean-的顺序-先子后父⭐️🔴" class="headerlink" title="1.6. 获取 bean 的顺序-先子后父⭐️🔴"></a>1.6. 获取 bean 的顺序-先子后父⭐️🔴</h2><p><span style="background-color:#ff0000">优先从子容器中获取</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230304081658.png" alt="image.png"></p><h1 id="2-Servlet3-0-整合逻辑"><a href="#2-Servlet3-0-整合逻辑" class="headerlink" title="2. Servlet3.0 整合逻辑"></a>2. Servlet3.0 整合逻辑</h1><p><a href="https://www.bilibili.com/video/BV1gW411W7wy?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1gW411W7wy?p=56&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://liayun.blog.csdn.net/article/details/114915111">https://liayun.blog.csdn.net/article/details/114915111</a><br><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web">https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web</a></p><h2 id="2-1-步骤"><a href="#2-1-步骤" class="headerlink" title="2.1. 步骤"></a>2.1. 步骤</h2><h3 id="2-1-1-初始化配置"><a href="#2-1-1-初始化配置" class="headerlink" title="2.1.1. 初始化配置"></a>2.1.1. 初始化配置</h3><ol><li>编写一个 WebApplicationInitializer 接口的实现类，比如 AbstractAnnotationConfigDispatcherServletInitializer 的实现类，叫 MyWebAppIntializer，在里面配置好 getServletMappings</li><li>分别编写父子容器组件扫描配置类<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_">MyWebAppInitializer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractAnnotationConfigDispatcherServletInitializer</span> &#123;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Class&lt;?&gt;[] getRootConfigClasses() &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Class</span>&lt;?&gt;[] &#123; RootConfig.class &#125;;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> Class&lt;?&gt;[] getServletConfigClasses() &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Class</span>&lt;?&gt;[] &#123; App1Config.class &#125;;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> String[] getServletMappings() &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[] &#123; <span class="hljs-string">&quot;/app1/*&quot;</span> &#125;;<br>    &#125;<br>&#125;<br><br><br></code></pre></td></tr></table></figure></li></ol><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920083910940.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205094019.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205094119.png"></p><p>需要我们指定 2 个容器的配置类 RootConfig.class 和 WebAppConfig.class<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205094246.png"></p><h3 id="2-1-2-开启-MVC-config"><a href="#2-1-2-开启-MVC-config" class="headerlink" title="2.1.2. 开启 MVC config"></a>2.1.2. 开启 MVC config</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205094721.png"></p><p>1）、@EnableWebMvc: 开启 SpringMVC 定制配置功能；<br>    <span style="background-color:#ff00ff">相当于原来 XML 配置的 <a href="mvc:annotation-driven/">mvc:annotation-driven/</a>；</span></p><h3 id="2-1-3-设置-MVC-config"><a href="#2-1-3-设置-MVC-config" class="headerlink" title="2.1.3. 设置 MVC config"></a>2.1.3. 设置 MVC config</h3><p><a href="https://liayun.blog.csdn.net/article/details/114991617">https://liayun.blog.csdn.net/article/details/114991617</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205094953.png"></p><p>2）、配置组件（视图解析器、视图映射、静态资源映射、拦截器。。。）<br>配置类如果实现 WebMvcConfigurer 接口，那么就得一个一个来实现其中的方法了，所以要使用模板的思想，继承一个实现了所有或者部分接口的类来避免这个问题</p><pre><code>extends WebMvcConfigurerAdapter</code></pre><h2 id="2-2-原理概述"><a href="#2-2-原理概述" class="headerlink" title="2.2. 原理概述"></a>2.2. 原理概述</h2><h3 id="2-2-1-Servlet3-0-支持"><a href="#2-2-1-Servlet3-0-支持" class="headerlink" title="2.2.1. Servlet3.0 支持"></a>2.2.1. Servlet3.0 支持</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920133303352.png" alt="image-20210920133303352"></p><h3 id="2-2-2-层次结构"><a href="#2-2-2-层次结构" class="headerlink" title="2.2.2. 层次结构"></a>2.2.2. 层次结构</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920133649757.png" alt="image-20210920133649757"></p><h3 id="2-2-3-逻辑概述"><a href="#2-2-3-逻辑概述" class="headerlink" title="2.2.3. 逻辑概述"></a>2.2.3. 逻辑概述</h3><figure class="highlight plaintext"><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 plaintext">1、web容器在启动的时候，会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer<br>2、加载这个文件指定的类SpringServletContainerInitializer<br>3、spring的应用一启动会加载感兴趣的【实现了WebApplicationInitializer接口】的所有组件；<br>4、并且为WebApplicationInitializer组件创建对象（组件不是接口，不是抽象类）而WebApplicationInitializer有3个子抽象类<br>1）、AbstractContextLoaderInitializer：创建根容器；createRootApplicationContext()；<br>2）、AbstractDispatcherServletInitializer：<br>创建一个web的ioc容器；createServletApplicationContext();<br>创建了DispatcherServlet；createDispatcherServlet()；<br>将创建的DispatcherServlet添加到ServletContext中；<br>getServletMappings();<br>3）、AbstractAnnotationConfigDispatcherServletInitializer：注解方式配置的DispatcherServlet初始化器<br>创建根容器：createRootApplicationContext()<br>getRootConfigClasses();传入一个配置类<br>创建web的ioc容器： createServletApplicationContext();<br>获取配置类；getServletConfigClasses();<br><br>总结：<br>以注解方式来启动SpringMVC；继承AbstractAnnotationConfigDispatcherServletInitializer；<br>实现抽象方法指定DispatcherServlet的配置信息；<br></code></pre></td></tr></table></figure><h2 id="2-3-经验总结"><a href="#2-3-经验总结" class="headerlink" title="2.3. 经验总结"></a>2.3. 经验总结</h2><h3 id="2-3-1-禁用默认过滤规则⭐️🔴"><a href="#2-3-1-禁用默认过滤规则⭐️🔴" class="headerlink" title="2.3.1. 禁用默认过滤规则⭐️🔴"></a>2.3.1. 禁用默认过滤规则⭐️🔴</h3><p>PS: 父容器的<span style="background-color:#ff00ff">排除规则 (excludeFilters)</span>不需要特别处理，但是子容器的<span style="background-color:#ff00ff">只扫描规则 (includeFilters)</span>，<span style="background-color:#ff0000">需要加禁用默认过滤规则的注解，否则不生效，导致把所有的都扫进去！！</span></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210920061716788.png" alt="image-20210920061716788"></p><p>则 SpringMVC 容器不仅仅扫描并注册带有 <code>@Controller</code> 注解的 Bean，而且还扫描并注册了带有 <code>@Component</code> 的子注解 <code>@Service</code>、<code>@Reposity</code> 的 Bean。因为 <code>useDefaultFilters</code> 默认为 true。所以如果不需要默认的，则 <code>useDefaultFilters=false</code> 禁用掉。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324110148.png" alt="image.png"></p><p><strong>当我们进行上面的配置时，SpringMVC 容器会把 service、dao 层的 bean 重新加载，从而造成新加载的 bean 覆盖了老的 bean，但事务的 AOP 代理没有配置在 spring-mvc.xml 配置文件中，造成事务失效。解决办法是：在 spring-mvc.xml 配置文件中的 context:component-scan 标签中使用 use-default-filters&#x3D;“false”禁用掉默认的行为。</strong></p><p>链接： <a href="https://www.imooc.com/article/21845">https://www.imooc.com/article/21845</a></p><h1 id="3-SpringMVC-工作原理"><a href="#3-SpringMVC-工作原理" class="headerlink" title="3. SpringMVC 工作原理"></a>3. SpringMVC 工作原理</h1><p><a href="https://www.bilibili.com/video/BV1uF411L73Q?p=78&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1uF411L73Q?p=78&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://blog.csdn.net/weixin_48922154/article/details/113867510">https://blog.csdn.net/weixin_48922154/article/details/113867510</a></p><p>可以参考 SpringBoot 中的 SpringMVC 流程：<br><a href="https://www.bilibili.com/video/BV1Et411Y7tQ?p=139&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Et411Y7tQ?p=139&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>[[3、SpringBoot-基础#^ijjhr0]]</p><h2 id="3-1-总体概述"><a href="#3-1-总体概述" class="headerlink" title="3.1. 总体概述"></a>3.1. 总体概述</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221175948.png" alt="image.png"></p><p>（1）用户发送请求至前端控制器 <code>DispatcherServlet</code>；<br>（2） DispatcherServlet 收到请求后，调用 HandlerMapping 处理器映射器，请求获取 Handle；<br>（3）处理器映射器&#x3D;&#x3D;根据请求 url 找到具体的处理器&#x3D;&#x3D;，生成处理器对象及处理器拦截器 (如果有则生成) 一并返回给 DispatcherServlet；<br>（4）DispatcherServlet 调用 HandlerAdapter 处理器适配器；<br>（5）HandlerAdapter 经过适配调用具体处理器 (Handler，也叫后端控制器)；<br>（6）Handler 执行完成返回 ModelAndView；<br>（7）HandlerAdapter 将 Handler 执行结果 ModelAndView 返回给 DispatcherServlet；<br>（8）DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器进行解析；<br>（9）ViewResolver 解析后返回具体 View；<br>（10）DispatcherServlet 对 View 进行渲染视图（即将模型数据填充至视图中）<br>（11）DispatcherServlet 响应用户。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221181604.png" alt="image.png"></p><h2 id="3-2-初始化阶段"><a href="#3-2-初始化阶段" class="headerlink" title="3.2. 初始化阶段"></a>3.2. 初始化阶段</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230321195320.png" alt="image.png"></p><h3 id="3-2-1-初始化流程"><a href="#3-2-1-初始化流程" class="headerlink" title="3.2.1. 初始化流程"></a>3.2.1. 初始化流程</h3><p><a href="https://www.bilibili.com/video/BV1c3411s7aB/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1c3411s7aB/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>Servlet.init → GenericServlet.init → HttpServletBean.init →<br>HttpServletBean.initServletBean → FreemarkerServlet.initServletBean<br>FreemarkerServlet.initWebApplicationContext → configureAndRefreshWebApplicationContext(cwac)</p><h2 id="3-3-匹配阶段"><a href="#3-3-匹配阶段" class="headerlink" title="3.3. 匹配阶段"></a>3.3. 匹配阶段</h2><a href="/2023/01/13/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8%E3%80%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/" title="设计模式-8、适配器模式">设计模式-8、适配器模式</a><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211011090131565.png" alt="image-20211011090131565"><br>能识别 <code>@RequestMapping</code> 的 <code>RequestMappingHandlerMapping</code> 优先级最高<br>请求进来，挨个尝试所有的 HandlerMapping 看是否有请求信息。<br>          ○ 如果有就找到这个请求对应的 handler<br>          ○ 如果没有就是下一个 HandlerMapping</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230321200230.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324230808.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324230825.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324230858.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324230932.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324231015.png" alt="image.png"></p><h2 id="3-4-执行阶段"><a href="#3-4-执行阶段" class="headerlink" title="3.4. 执行阶段"></a>3.4. 执行阶段</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230321195206.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324151637.png" alt="image.png"></p><h1 id="4-SpringMVC-怎么和-AJAX-相互调用的"><a href="#4-SpringMVC-怎么和-AJAX-相互调用的" class="headerlink" title="4. SpringMVC 怎么和 AJAX 相互调用的"></a>4. SpringMVC 怎么和 AJAX 相互调用的</h1><p>（1）加入 Jackson.jar<br>（2）在配置文件中配置 json 的消息转换器 (jackson 不需要该配置 HttpMessageConverter），如果是其他 JSON 处理器，需要手动@Bean 注入相应的转换器</p><figure class="highlight xml"><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 xml">&lt;!‐‐它就会帮我们配置了默认json映射‐‐&gt;<br>&lt;mvc:annotation‐driven conversion‐service=&quot;conversionService&quot; &gt;&lt;/mvc:annotation‐driven&gt;<br></code></pre></td></tr></table></figure><p>（3）在接受 Ajax 方法里面可以直接返回 Object,List 等,但方法前面要加上@ResponseBody 注解</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230221212940.png" alt="image.png"></p><h1 id="5-Spring-和-SpringMVC-为什么需要父子容器"><a href="#5-Spring-和-SpringMVC-为什么需要父子容器" class="headerlink" title="5. Spring 和 SpringMVC 为什么需要父子容器"></a>5. Spring 和 SpringMVC 为什么需要父子容器</h1><p>就功能性来说不用子父容器也可以完成（参考：SpringBoot 就没用子父容器）<br>1. 所以父子容器的主要作用应该是划分框架边界。有点单一职责的味道。service、dao 层我们一般使用 spring 框架来管理、controller 层交给 springmvc 管理<br>2. 规范整体架构使父容器 service 无法访问子容器 controller、子容器 controller 可以访问父容器 service<br>3. 方便子容器的切换。如果现在我们想把 web 层从 spring mvc 替换成 struts，那么只需要将 spring­mvc.xml 替换成 Struts 的配置文件 struts.xml 即可，而 spring­core.xml 不需要改变。<br>4. 为了节省重复 bean 创建</p><h1 id="6-与-Servlet-关系"><a href="#6-与-Servlet-关系" class="headerlink" title="6. 与 Servlet 关系"></a>6. 与 Servlet 关系</h1><p>[[Servlet 到 Spring MVC 的简化之路 - 掘金]]</p><h1 id="7-面试题相关"><a href="#7-面试题相关" class="headerlink" title="7. 面试题相关"></a>7. 面试题相关</h1><h2 id="7-1-是否可以把所有-Bean-都通过-Spring-容器来管理？"><a href="#7-1-是否可以把所有-Bean-都通过-Spring-容器来管理？" class="headerlink" title="7.1. 是否可以把所有 Bean 都通过 Spring 容器来管理？"></a>7.1. 是否可以把所有 Bean 都通过 Spring 容器来管理？</h2><p>（Spring 的 applicationContext.xml 中配置全局扫描)<br>不可以，这样会导致我们请求接口的时候产生 404。如果所有的 Bean 都交给父容器，<span style="background-color:#ff00ff">SpringMVC 在初始化 HandlerMethods 的时候（initHandlerMethods）并没有 getBean 的方式获取到所需要的 Bean，而是通过 getBeanNameByType 获取到 BD 的名字，并没有去查找父容器的 bean</span>，所以无法根据 Controller 的 handler 方法注册 HandlerMethod，也就无法根据请求 URI 获取到 HandlerMethod 来进行匹配</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224074657.png" alt="image.png"></p><h2 id="7-2-是否可以把我们所需的-Bean-都放入-Spring­mvc-子容器里面来管理"><a href="#7-2-是否可以把我们所需的-Bean-都放入-Spring­mvc-子容器里面来管理" class="headerlink" title="7.2. 是否可以把我们所需的 Bean 都放入 Spring­mvc 子容器里面来管理"></a>7.2. 是否可以把我们所需的 Bean 都放入 Spring­mvc 子容器里面来管理</h2><p>（springmvc 的 springservlet.xml 中配置全局扫描）?<br>可以，因为父容器的体现无非是为了获取子容器不包含的 bean,  如果全部包含在子容器完全用不到父容器了，所以是可以全部放在 springmvc 子容器来管理的。<br>虽然可以这么做不过一般应该是不推荐这么去做的，一般人也不会这么干的。如果你的项目里有用到事物、或者 aop 记得也需要把这部分配置需要放到 Spring-mvc 子容器的配置文件来，不然一部分内容在子容器和一部分内容在父容器, 可能就会导致你的事物或者 AOP 不生效。     <br>所以如果 aop 或事物如果不生效也有可能是通过父容器 (spring) 去增强子容器 (Springmvc)，也就无法增强。</p><h2 id="7-3-如何实现无-XML-零配置的-SpringMVC"><a href="#7-3-如何实现无-XML-零配置的-SpringMVC" class="headerlink" title="7.3. 如何实现无 XML 零配置的 SpringMVC"></a>7.3. 如何实现无 XML 零配置的 SpringMVC</h2><p>1. 省略 web.xml<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224095051.png" alt="image.png"></p><p>a. servlet3.0 之后规范中提供了 <span style="background-color:#ff00ff">SPI 扩展机制</span> :<br>META-INF&#x2F;services&#x2F;javax.servlet.ServletContainerInitializer<br>b. SpringMVC 通过实现 ServletContainerInitializer 接口<br>c. 动态注册 ContextLoaderListener 和 DispatcherServlet 并创建子父容器 (ApplicationContext)<br>2. 省略 spring.xml 和 spring-mvc.xml(只是 sprinmvc 方式 ，springboot 在自动配置类完成)   配置类 –xml<br>a. 实现一个继承 AbstractAnnotationConfigDispatcherServletInitializer 的类<br>b. 该类就实现了 ServletContainerInitializer，它会创建 ContextLoaderListener 和 DispatcherServlet<br>c. 还会创建父子容器， 创建容器时传入父子容器配置类则可以替代 spring.xml 和 spring-mvc.xml</p><h2 id="7-4-SpringMVC-的拦截器和过滤器有什么区别？执行顺序？"><a href="#7-4-SpringMVC-的拦截器和过滤器有什么区别？执行顺序？" class="headerlink" title="7.4. SpringMVC 的拦截器和过滤器有什么区别？执行顺序？"></a>7.4. SpringMVC 的拦截器和过滤器有什么区别？执行顺序？</h2><p><span style="background-color:#ff00ff">拦截器不依赖于 servlet 容器，过滤器依赖于 servlet 容器。</span><br>拦截器只能对 action 请求 (DispatcherServlet 映射的请求) 起作用，而过滤器则可以对几乎所有的请求起作用。<br>拦截器可以访问容器中的 Bean(DI)，而过滤器不能访问（基于 spring 注册的过滤器也可以访问容器中的 bean）。<br>执行顺序：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230224104345.png" alt="image.png"><br>多个过滤器的执行顺序跟 xml 文件中定义的先后关系有关<br>当然，对于多个拦截器它们之间的执行顺序跟在 SpringMVC 的配置文件中定义的先后顺序有关。</p><h2 id="7-5-各种容器关系"><a href="#7-5-各种容器关系" class="headerlink" title="7.5. 各种容器关系"></a>7.5. 各种容器关系</h2><p><a href="https://blog.csdn.net/u012060033/article/details/104953011">https://blog.csdn.net/u012060033/article/details/104953011</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323223738.png" alt="image.png"></p><h3 id="7-5-1-各种服务器"><a href="#7-5-1-各种服务器" class="headerlink" title="7.5.1. 各种服务器"></a>7.5.1. 各种服务器</h3><p><a href="https://www.cnblogs.com/guanghe/p/15174643.html">https://www.cnblogs.com/guanghe/p/15174643.html</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230323225255.png" alt="image.png"></p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><h2 id="9-1-图灵徐庶"><a href="#9-1-图灵徐庶" class="headerlink" title="9.1. 图灵徐庶"></a>9.1. 图灵徐庶</h2><p><a href="https://www.bilibili.com/video/BV1mf4y1c7cV/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1mf4y1c7cV/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="9-2-网络笔记"><a href="#9-2-网络笔记" class="headerlink" title="9.2. 网络笔记"></a>9.2. 网络笔记</h2><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;SpringMVC 启动流程及相关源码分析 - 简书]]<br><a href="https://www.jianshu.com/p/dc64d02e49ac">https://www.jianshu.com/p/dc64d02e49ac</a></p>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> SpringMVC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>框架源码专题-Spring-1、基本原理</title>
      <link href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
      <url>/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-学习来源"><a href="#1-学习来源" class="headerlink" title="1. 学习来源"></a>1. 学习来源</h1><p><a href="https://monkback.github.io/post/spring/spring%E5%AE%B9%E5%99%A8%E4%B8%8Eweb%E5%AE%B9%E5%99%A8/">https://monkback.github.io/post/spring/spring%E5%AE%B9%E5%99%A8%E4%B8%8Eweb%E5%AE%B9%E5%99%A8/</a></p><p><a href="https://www.freesion.com/article/96551187760/">https://www.freesion.com/article/96551187760/</a></p><p>Spring 面试题知识点合集，B 站高质量面试教程！</p><p><a href="https://www.bilibili.com/video/BV1t44y1C73F?p=3">https://www.bilibili.com/video/BV1t44y1C73F?p=3</a></p><p>尚硅谷 spring 源码解析</p><p><a href="https://www.bilibili.com/video/BV114411c7hV?p=31">https://www.bilibili.com/video/BV114411c7hV?p=31</a></p><p>最新 Spring 全家桶 -2021 面试 - 重灾区：Spring&#x2F;SpringCloud&#x2F;SpringBoot&#x2F;SpringMVC，肝完就跳槽</p><p><a href="https://www.bilibili.com/video/BV1x64y1x71b?p=55">https://www.bilibili.com/video/BV1x64y1x71b?p=55</a></p><p><a href="https://www.bilibili.com/video/BV19v411K7ss?spm_id_from=333.999.0.0">https://www.bilibili.com/video/BV19v411K7ss?spm_id_from=333.999.0.0</a></p><h1 id="2-源码构建"><a href="#2-源码构建" class="headerlink" title="2. 源码构建"></a>2. 源码构建</h1><p>gitee&#x2F;leifengyang&#x2F;spring-framework</p><figure class="highlight plaintext"><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 plaintext">repositories &#123;  <br>    maven &#123; url &quot;http://maven.aliyun.com/nexus/content/groups/public/&quot; &#125;  <br>    maven &#123; url &quot;https://maven.aliyun.com/repository/spring&quot; &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;spring-framework</p><h1 id="3-框架结构"><a href="#3-框架结构" class="headerlink" title="3. 框架结构"></a>3. 框架结构</h1><p><a href="https://www.bilibili.com/video/BV1664y1s7dF/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1664y1s7dF/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>[[AbstractApplicationContext.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213094702.png"></p><h2 id="3-1-Resource"><a href="#3-1-Resource" class="headerlink" title="3.1. Resource"></a>3.1. Resource</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213084703.png"></p><h2 id="3-2-DefaultListableBeanFactory"><a href="#3-2-DefaultListableBeanFactory" class="headerlink" title="3.2. DefaultListableBeanFactory"></a>3.2. DefaultListableBeanFactory</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213081736.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213081552.png"></p><h2 id="3-3-Beanfactory"><a href="#3-3-Beanfactory" class="headerlink" title="3.3. Beanfactory"></a>3.3. Beanfactory</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213082228.png"></p><h2 id="3-4-BeanDefinition"><a href="#3-4-BeanDefinition" class="headerlink" title="3.4. BeanDefinition"></a>3.4. BeanDefinition</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213083643.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213090144.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213090301.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213090551.png"></p><h2 id="3-5-Aware"><a href="#3-5-Aware" class="headerlink" title="3.5. Aware"></a>3.5. Aware</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213094642.png"></p><h2 id="3-6-bean-注解创建"><a href="#3-6-bean-注解创建" class="headerlink" title="3.6. bean 注解创建"></a>3.6. bean 注解创建</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213100021.png"></p><h1 id="4-Bean-的生命周期"><a href="#4-Bean-的生命周期" class="headerlink" title="4. Bean 的生命周期"></a>4. Bean 的生命周期</h1><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210914223221546.png" alt="image-20210914223221546"></p><h2 id="4-1-生命周期阶段"><a href="#4-1-生命周期阶段" class="headerlink" title="4.1. 生命周期阶段"></a>4.1. 生命周期阶段</h2><p><strong>构造（对象创建）</strong></p><ul><li><pre><code>单实例：在容器启动的时候创建对象  </code></pre></li><li><pre><code>多实例：在每次获取的时候创建对象</code></pre></li></ul><p>BeanPostProcessor.postProcessBeforeInitialization<br><strong>初始化</strong></p><ul><li><pre><code>对象创建完成，并赋值好，调用初始化方法。。。  </code></pre>BeanPostProcessor.postProcessAfterInitialization</li></ul><p><strong>销毁</strong></p><ul><li><pre><code>单实例：容器关闭的时候  </code></pre></li><li><pre><code>多实例：容器不会管理这个bean；容器不会调用销毁方法；</code></pre></li></ul><h2 id="4-2-生命周期管理"><a href="#4-2-生命周期管理" class="headerlink" title="4.2. 生命周期管理"></a>4.2. 生命周期管理</h2><p>我们可以自定义初始化和销毁方法；容器在 bean 进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法</p><ul><li>1）、指定初始化和销毁方法；<br>           通过@Bean 指定 init-method 和 destroy-method；</li><li>2）、通过让 Bean 实现 InitializingBean（定义初始化逻辑），<br>                                        DisposableBean（定义销毁逻辑）;</li><li>3）、可以使用 JSR250；<br>            @PostConstruct：在 bean 创建完成并且属性赋值完成；来执行初始化方法<br>            @PreDestroy：在容器销毁 bean 之前通知我们进行清理工作</li><li>4）、BeanPostProcessor【interface】：bean 的后置处理器；<br>            在 bean 初始化前后进行一些处理工作；<br>            postProcessBeforeInitialization: 在初始化之前工作<br>            postProcessAfterInitialization: 在初始化之后工作</li></ul><h2 id="4-3-实例化"><a href="#4-3-实例化" class="headerlink" title="4.3. 实例化"></a>4.3. 实例化</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211012070750684.png" alt="image-20211012070750684"></p><h3 id="4-3-1-注册-Bean-的方式⭐️🔴"><a href="#4-3-1-注册-Bean-的方式⭐️🔴" class="headerlink" title="4.3.1. 注册 Bean 的方式⭐️🔴"></a>4.3.1. 注册 Bean 的方式⭐️🔴</h3><p><span style="display:none">%%<br>▶8.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230322-2258%%</span>❕ ^1on5ia</p><p><span style="background-color:#00ff00">第 1~3 种是从 BD 开始，注册 BD 进去就相当于注册了 Bean</span><br><span style="background-color:#00ff00">第 4 种，直接注入 Class，跳过了 BD 到 Class 的反射 (创建实例之前的 resolveBeanClass 方法)</span></p><p>#todo</p><span style="display:none">- [ ] 🚩 - @Import 原理 - 🏡 2023-02-02 09:27</span>[[../../../../cubox/006-ChromeCapture/Spring源码 笔记 雷丰阳 BV1gW411W7wy - 飞飞很要强 - 博客园]]<p><strong>给容器中注册组件</strong></p><h4 id="4-3-1-1-古老的-xml-方式"><a href="#4-3-1-1-古老的-xml-方式" class="headerlink" title="4.3.1.1. 古老的 xml 方式"></a>4.3.1.1. 古老的 xml 方式</h4><p>使用 xml 的方式来声明 Bean 的定义，Spring 容器在启动的时候会加载并解析这 个 xml，把 bean 装载到 IOC 容器中。</p><h4 id="4-3-1-2-ComponentScan-组件标注注解"><a href="#4-3-1-2-ComponentScan-组件标注注解" class="headerlink" title="4.3.1.2. @ComponentScan+ 组件标注注解"></a>4.3.1.2. @ComponentScan+ 组件标注注解</h4><p>（@Controller&#x2F;@Service&#x2F;@Repository&#x2F;@Component）<br><span style="background-color:#ff00ff">局限于我们自己写的类，可以自己加上注解，如果是第三方则需要用@Bean 的方式</span></p><h4 id="4-3-1-3-Configuration-Bean"><a href="#4-3-1-3-Configuration-Bean" class="headerlink" title="4.3.1.3. @Configuration+@Bean"></a>4.3.1.3. @Configuration+@Bean</h4><p><span style="background-color:#ff00ff">[导入的第三方包里面的组件]</span></p><h4 id="4-3-1-4-Import"><a href="#4-3-1-4-Import" class="headerlink" title="4.3.1.4. @Import"></a>4.3.1.4. @Import</h4><p><span style="display:none">%%<br>▶35.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-2029%%</span>❕ ^j2peg1</p><h5 id="4-3-1-4-1-作用"><a href="#4-3-1-4-1-作用" class="headerlink" title="4.3.1.4.1. 作用"></a>4.3.1.4.1. 作用</h5><p>快速给容器中导入一个组件</p><h5 id="4-3-1-4-2-使用方法-3-种"><a href="#4-3-1-4-2-使用方法-3-种" class="headerlink" title="4.3.1.4.2. 使用方法 -3 种"></a>4.3.1.4.2. 使用方法 -3 种</h5><h6 id="4-3-1-4-2-1-指定一个类"><a href="#4-3-1-4-2-1-指定一个类" class="headerlink" title="4.3.1.4.2.1. 指定一个类"></a>4.3.1.4.2.1. 指定一个类</h6><ol><li>@Import(要导入到容器中的组件 id)；容器中就会自动注册这个组件，id 默认是全类名</li></ol><h6 id="4-3-1-4-2-2-ImportSelector"><a href="#4-3-1-4-2-2-ImportSelector" class="headerlink" title="4.3.1.4.2.2. ImportSelector"></a>4.3.1.4.2.2. ImportSelector</h6><ol start="2"><li>implements <code>ImportSelector</code> : 返回需要导入的组件的全类名数组；<br>implements ImportSelector，重写 selectImports 方法，返回 <code>全类名String[]</code></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202090922.png" alt="image.png"></p><p>例子：[[MyImportSelector.java]]</p><h6 id="4-3-1-4-2-3-ImportBeanDefinitionRegistrar"><a href="#4-3-1-4-2-3-ImportBeanDefinitionRegistrar" class="headerlink" title="4.3.1.4.2.3. ImportBeanDefinitionRegistrar"></a>4.3.1.4.2.3. ImportBeanDefinitionRegistrar</h6><ol start="3"><li>implements <code>ImportBeanDefinitionRegistrar</code> : 手动注册 bean 到容器中<br>implements ImportBeanDefinitionRegistrar，重写 registerBeanDefinitions 方法，通过 registry.registerBeanDefinition 注册 RootBeanDefinition 类型的自定义 beanDefinition</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230202091123.png" alt="image.png"></p><p>例子：[[MyImportBeanDefinitionRegistrar.java]]</p><h5 id="4-3-1-4-3-源码示例⭐️🔴"><a href="#4-3-1-4-3-源码示例⭐️🔴" class="headerlink" title="4.3.1.4.3. 源码示例⭐️🔴"></a>4.3.1.4.3. 源码示例⭐️🔴</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230602064349.png" alt="image.png"></p><h4 id="4-3-1-5-implements-FactoryBean"><a href="#4-3-1-5-implements-FactoryBean" class="headerlink" title="4.3.1.5. implements FactoryBean"></a>4.3.1.5. implements FactoryBean</h4><p>(1）、默认获取到的是工厂 bean 调用 getObject 创建的对象<br>(2）、要获取工厂 Bean 本身，我们需要给 id 前面加一个&amp;，即<br><code>&amp;colorFactoryBean</code></p><p><code>implements FactoryBean&lt;Color&gt;</code> 重写 getObject() 方法</p><p>例子 ：spring-annotation：[[ColorFactoryBean.java]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230201084234.png" alt="image.png"></p><h3 id="4-3-2-Bean-与-Configuration"><a href="#4-3-2-Bean-与-Configuration" class="headerlink" title="4.3.2. @Bean 与@Configuration"></a>4.3.2. @Bean 与@Configuration</h3><p><a href="https://blog.csdn.net/z69183787/article/details/121979457">https://blog.csdn.net/z69183787/article/details/121979457</a></p><h4 id="4-3-2-1-结论"><a href="#4-3-2-1-结论" class="headerlink" title="4.3.2.1. 结论"></a>4.3.2.1. 结论</h4><p><font color=#ff0000><span style="background-color:#ffff00">如果不加@Configuration，或者加了其他注解，比如@Component，结果是一样的，都没有使用 Spring 管理的 bean</span></font></p><p><code>ConfigurationClassPostProcessor</code> 中的 <code>postProcessBeanFactory</code> 方法中 <code>enhanceConfigurationClasses</code>，使用 cglib 对配置类进行代理，因为@Bean 方法到时候要进行创建 Bean 的实例。即如果加了@Configuration 则会在这里创建 cglib 代理，当调用@Bean 方法时会先检测容器中是否存在。而且所有带有 <code>@Configuration</code> 注解的 bean 都会变成增强的类。</p><p><a href="https://blog.csdn.net/qq_37419449/article/details/121156300">https://blog.csdn.net/qq_37419449/article/details/121156300</a><br><a href="https://www.cnblogs.com/lvbinbin2yujie/p/10279416.html#titleThree">https://www.cnblogs.com/lvbinbin2yujie/p/10279416.html#titleThree</a></p><h4 id="4-3-2-2-测试"><a href="#4-3-2-2-测试" class="headerlink" title="4.3.2.2. 测试"></a>4.3.2.2. 测试</h4><p>[[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;Spring02&#x2F;src&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;test&#x2F;App.java]]</p><h4 id="4-3-2-3-full-模式和-lite-模式"><a href="#4-3-2-3-full-模式和-lite-模式" class="headerlink" title="4.3.2.3. full 模式和 lite 模式"></a>4.3.2.3. full 模式和 lite 模式</h4><a href="/2023/02/01/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-8%E3%80%81BeanDefinition/" title="Spring-8、BeanDefinition">Spring-8、BeanDefinition</a><h3 id="4-3-3-Import-和-ImportSelector⭐️🔴"><a href="#4-3-3-Import-和-ImportSelector⭐️🔴" class="headerlink" title="4.3.3. @Import 和 @ImportSelector⭐️🔴"></a>4.3.3. @Import 和 @ImportSelector⭐️🔴</h3><p><span style="display:none">%%<br>▶37.🏡⭐️◼️【SpringBoot 自动装配就是使用的 DeferedImportSelector】◼️⭐️-point-20230304-1259%%</span>❕ ^514kuw</p><h4 id="4-3-3-1-ImportSelector"><a href="#4-3-3-1-ImportSelector" class="headerlink" title="4.3.3.1. ImportSelector"></a>4.3.3.1. ImportSelector</h4><ul><li>此接口是 spring 中导入外部配置的核心接口，根据给定的条件（通常是一个或多个注解属性即@Conditional 条件注解）判断要导入哪个配置类</li><li>如果该接口的实现类同时实现了一些 Aware 接口，那么在调用 selectImports 方法之前先调用上述接口中的回调方法</li><li>如果<span style="background-color:#ff00ff">需要在所有的@Configuration 处理完再导入</span>，可以实现 DeferredImportSelector 接口</li></ul><h4 id="4-3-3-2-DeferredImportSelector"><a href="#4-3-3-2-DeferredImportSelector" class="headerlink" title="4.3.3.2. DeferredImportSelector"></a>4.3.3.2. DeferredImportSelector</h4><p>是 ImportSelector 的一个子类<br>DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才进行处理</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230324173014.png"><br>优先级最低，就可以在最后面坐兜底的默认配置。</p><p>[[MySpringBoot#^bypdob]]</p><p><a href="https://www.bilibili.com/video/BV15b4y117RJ?p=173&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15b4y117RJ?p=173&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322134249.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322134859.png" alt="image.png"></p><h2 id="4-4-初始化和销毁"><a href="#4-4-初始化和销毁" class="headerlink" title="4.4. 初始化和销毁"></a>4.4. 初始化和销毁</h2><h3 id="4-4-1-Bean"><a href="#4-4-1-Bean" class="headerlink" title="4.4.1. @Bean"></a>4.4.1. @Bean</h3><h3 id="4-4-2-实现-InitializingBean-和-DisposableBean-接口"><a href="#4-4-2-实现-InitializingBean-和-DisposableBean-接口" class="headerlink" title="4.4.2. 实现 InitializingBean 和 DisposableBean 接口"></a>4.4.2. 实现 InitializingBean 和 DisposableBean 接口</h3><h3 id="4-4-3-使用-JSR250"><a href="#4-4-3-使用-JSR250" class="headerlink" title="4.4.3. 使用 JSR250"></a>4.4.3. 使用 JSR250</h3><p>@PostConstruct：在 bean 创建完成并且属性赋值完成；来执行初始化方法，<span style="background-color:#00ff00">在真正初始化方法之前执行</span><br>@PreDestroy：在容器销毁 bean 之前通知我们进行清理工作</p><h3 id="4-4-4-PostConstruct-原理⭐️🔴⭐️🔴"><a href="#4-4-4-PostConstruct-原理⭐️🔴⭐️🔴" class="headerlink" title="4.4.4. @PostConstruct 原理⭐️🔴⭐️🔴"></a>4.4.4. @PostConstruct 原理⭐️🔴⭐️🔴</h3><a href="/2023/02/19/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-11%E3%80%81@PostConstruct/" title="Spring-11、@PostConstruct">Spring-11、@PostConstruct</a><h3 id="4-4-5-BeanPostProcessor【interface】"><a href="#4-4-5-BeanPostProcessor【interface】" class="headerlink" title="4.4.5. BeanPostProcessor【interface】"></a>4.4.5. BeanPostProcessor【interface】</h3><p>bean 的后置处理器，在 bean 初始化前后进行一些处理工作；<br>postProcessBeforeInitialization: 在初始化之前工作<br>postProcessAfterInitialization: 在初始化之后工作</p><h1 id="5-启动流程"><a href="#5-启动流程" class="headerlink" title="5. 启动流程"></a>5. 启动流程</h1><h2 id="5-1-SpringMVC-启动"><a href="#5-1-SpringMVC-启动" class="headerlink" title="5.1. SpringMVC 启动"></a>5.1. SpringMVC 启动</h2><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/" title="Spring-4、SpringMVC">Spring-4、SpringMVC</a><h2 id="5-2-SpringBoot-启动"><a href="#5-2-SpringBoot-启动" class="headerlink" title="5.2. SpringBoot 启动"></a>5.2. SpringBoot 启动</h2><p>[[3、SpringBoot-基础#^214fh8]]</p><h2 id="5-3-SpringBoot-Tomcat-启动"><a href="#5-3-SpringBoot-Tomcat-启动" class="headerlink" title="5.3. SpringBoot+Tomcat 启动"></a>5.3. SpringBoot+Tomcat 启动</h2><p>[[3、SpringBoot-基础#^kb445n]]</p><h2 id="5-4-Tomcat-SpringBoot-启动"><a href="#5-4-Tomcat-SpringBoot-启动" class="headerlink" title="5.4. Tomcat+SpringBoot 启动"></a>5.4. Tomcat+SpringBoot 启动</h2><p>[[3、SpringBoot-基础#^er3z4j]]</p><h1 id="6-事务管理"><a href="#6-事务管理" class="headerlink" title="6. 事务管理"></a>6. 事务管理</h1><a href="/2022/12/08/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-5%E3%80%81%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1-@EnableTransactionManagement/" title="Spring-5、声明式事务-@EnableTransactionManagement">Spring-5、声明式事务-@EnableTransactionManagement</a><h1 id="7-包扫描"><a href="#7-包扫描" class="headerlink" title="7. 包扫描"></a>7. 包扫描</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215154753.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215152909.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215152951.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215153021.png"></p><h1 id="8-自动装配"><a href="#8-自动装配" class="headerlink" title="8. 自动装配"></a>8. 自动装配</h1><p>[[3、SpringBoot-基础#^dxovlh]]</p><h1 id="9-PostProcessor"><a href="#9-PostProcessor" class="headerlink" title="9. PostProcessor"></a>9. PostProcessor</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215100405.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215201708.png"></p><h1 id="10-FactoryBean-和-BeanFactory"><a href="#10-FactoryBean-和-BeanFactory" class="headerlink" title="10. FactoryBean 和 BeanFactory"></a>10. FactoryBean 和 BeanFactory</h1><h2 id="10-1-FactoryBean"><a href="#10-1-FactoryBean" class="headerlink" title="10.1. FactoryBean"></a>10.1. FactoryBean</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221215204720.png"><br><span style="background-color:#00ff00">singletonObject 中也有 helloFactory，类型就是 FactoryBean</span><br><span style="background-color:#00ff00">而 factoryBeanObjectCache 中也有 helloFactory，类型却是 Hello</span></p><h2 id="10-2-BeanFactory"><a href="#10-2-BeanFactory" class="headerlink" title="10.2. BeanFactory"></a>10.2. BeanFactory</h2><h1 id="11-Spring-SpringMVC-SpringBoot"><a href="#11-Spring-SpringMVC-SpringBoot" class="headerlink" title="11. Spring SpringMVC SpringBoot"></a>11. Spring SpringMVC SpringBoot</h1><p>2019 最新 | 深思阿里 p7 面试题 -spring 整合 springMVC Servlet3.x 最新规范解读</p><p><a href="https://www.bilibili.com/video/BV1VE411d7hg?from=search&amp;seid=16332531297836154700&amp;spm_id_from=333.337.0.0">https://www.bilibili.com/video/BV1VE411d7hg?from=search&amp;seid=16332531297836154700&amp;spm_id_from=333.337.0.0</a></p><h2 id="11-1-区别"><a href="#11-1-区别" class="headerlink" title="11.1. 区别"></a>11.1. 区别</h2><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919091305500.png" alt="image-20210919091305500"></p><h1 id="12-异步处理"><a href="#12-异步处理" class="headerlink" title="12. 异步处理"></a>12. 异步处理</h1><p>&#x2F;Users&#x2F;weileluo&#x2F;001-study&#x2F;尚硅谷&#x2F;尚硅谷 Spring 注解驱动开发&#x2F;Spring 注解驱动开发&#x2F;视频&#x2F;60、springmvc- 异步请求 - 返回 Callable.avi</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210919094549509.png" alt="image-20210919094549509"></p><h1 id="13-2-种定义-Bean-的方式"><a href="#13-2-种定义-Bean-的方式" class="headerlink" title="13. 2 种定义 Bean 的方式"></a>13. 2 种定义 Bean 的方式</h1><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211007111122968.png" alt="image-20211007111122968"></p><h1 id="14-IOC"><a href="#14-IOC" class="headerlink" title="14. IOC"></a>14. IOC</h1><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-2%E3%80%81IOC/" title="Spring-2、IOC">Spring-2、IOC</a><h1 id="15-AOP"><a href="#15-AOP" class="headerlink" title="15. AOP"></a>15. AOP</h1><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a><h1 id="16-SpringMVC"><a href="#16-SpringMVC" class="headerlink" title="16. SpringMVC"></a>16. SpringMVC</h1><a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-4%E3%80%81SpringMVC/" title="Spring-4、SpringMVC">Spring-4、SpringMVC</a><h1 id="17-整合-Mybatis"><a href="#17-整合-Mybatis" class="headerlink" title="17. 整合 Mybatis"></a>17. 整合 Mybatis</h1><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-6%E3%80%81%E6%95%B4%E5%90%88Mybatis-@MapperScan/" title="Spring-6、整合Mybatis-@MapperScan">Spring-6、整合Mybatis-@MapperScan</a><h1 id="18-Spring-中用到的设计模式"><a href="#18-Spring-中用到的设计模式" class="headerlink" title="18. Spring 中用到的设计模式"></a>18. Spring 中用到的设计模式</h1><p><a href="https://www.processon.com/mindmap/639819cb1efad451e01ed494">https://www.processon.com/mindmap/639819cb1efad451e01ed494</a></p><p><a href="https://blog.csdn.net/weixin_44259720/article/details/95996541">https://blog.csdn.net/weixin_44259720/article/details/95996541</a></p><p>[[【雷丰阳 设计模式与框架源码】【01】总览设计模式、设计模式7大原则 - W3cJava]]</p><h2 id="18-1-单例、原型模式"><a href="#18-1-单例、原型模式" class="headerlink" title="18.1. 单例、原型模式"></a>18.1. 单例、原型模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322152450.png" alt="image.png"></p><h2 id="18-2-简单工厂模式"><a href="#18-2-简单工厂模式" class="headerlink" title="18.2. 简单工厂模式"></a>18.2. 简单工厂模式</h2><p>BeanFactory.getBean<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322152613.png" alt="image.png"></p><h2 id="18-3-建造者模式"><a href="#18-3-建造者模式" class="headerlink" title="18.3. 建造者模式"></a>18.3. 建造者模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322152558.png" alt="image.png"></p><p>Mybatis 的 mapper 就是使用了 BeanDefinitionBuilder</p><h2 id="18-4-代理模式"><a href="#18-4-代理模式" class="headerlink" title="18.4. 代理模式"></a>18.4. 代理模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322153242.png" alt="image.png"></p><p>ObjenesisCglibAopPorxy 是对 Cglib 的一个补充，因为 Cglib 要求必须有无参构造，而 ObjenesisCglibAopProxy 可以不要求必须有无参构造。 <span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230322-1533%%</span>❕ ^8r3nrd</p><h2 id="18-5-适配器模式"><a href="#18-5-适配器模式" class="headerlink" title="18.5. 适配器模式"></a>18.5. 适配器模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230322152723.png" alt="image.png"></p><h2 id="18-6-装饰者模式"><a href="#18-6-装饰者模式" class="headerlink" title="18.6. 装饰者模式"></a>18.6. 装饰者模式</h2><p>SpringBoot 的 rest 原理</p><h2 id="18-7-门面模式-外观模式"><a href="#18-7-门面模式-外观模式" class="headerlink" title="18.7. 门面模式 (外观模式)"></a>18.7. 门面模式 (外观模式)</h2><p>AbstractApplicationContext.getBean</p><h2 id="18-8-模板方法模式"><a href="#18-8-模板方法模式" class="headerlink" title="18.8. 模板方法模式"></a>18.8. 模板方法模式</h2><p>父子容器启动</p><h2 id="18-9-策略模式"><a href="#18-9-策略模式" class="headerlink" title="18.9. 策略模式"></a>18.9. 策略模式</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221213081043.png"></p><h2 id="18-10-观察者模式"><a href="#18-10-观察者模式" class="headerlink" title="18.10. 观察者模式"></a>18.10. 观察者模式</h2><p>主要用于当一个对象的状态发生改变时，所有依赖于它的对象都会得到通知，在 Spring 中一般以 Listener 结尾， 比如 ApplicationListener 等等。</p><h1 id="19-自定义-Spring-框架"><a href="#19-自定义-Spring-框架" class="headerlink" title="19. 自定义 Spring 框架"></a>19. 自定义 Spring 框架</h1><h2 id="19-1-7-2-spring-核心功能结构"><a href="#19-1-7-2-spring-核心功能结构" class="headerlink" title="19.1. 7.2 spring 核心功能结构"></a>19.1. 7.2 spring 核心功能结构</h2><p>Spring 大约有 20 个模块，由 1300 多个不同的文件构成。这些模块可以分为:</p><p>核心容器、AOP 和设备支持、数据访问与集成、Web 组件、通信报文和集成测试等，下面是 Spring 框架的总体架构图：</p><p><img src="file:///Users/taylor/Nutstore%20Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/Java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%B5%84%E6%96%99day06/%E7%AC%94%E8%AE%B0/img/image-20200429111324770.png?lastModify=1670250440"></p><p>核心容器由 beans、core、context 和 expression（Spring Expression Language，SpEL）4 个模块组成。</p><ul><li>spring-beans 和 spring-core 模块是 Spring 框架的核心模块，包含了控制反转（Inversion of Control，IOC）和依赖注入（Dependency Injection，DI）。BeanFactory 使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory 属于延时加载，也就是说在实例化容器对象后并不会自动实例化 Bean，只有当 Bean 被使用时，BeanFactory 才会对该 Bean 进行实例化与依赖关系的装配。</li><li>spring-context 模块构架于核心模块之上，扩展了 BeanFactory，为它添加了 Bean 生命周期控制、框架事件体系及资源加载透明化等功能。此外，该模块还提供了许多企业级支持，如邮件访问、远程访问、任务调度等，ApplicationContext 是该模块的核心接口，它的超类是 BeanFactory。与 BeanFactory 不同，ApplicationContext 实例化后会自动对所有的单实例 Bean 进行实例化与依赖关系的装配，使之处于待用状态。</li><li>spring-context-support 模块是对 Spring IoC 容器及 IoC 子容器的扩展支持。</li><li>spring-context-indexer 模块是 Spring 的类管理组件和 Classpath 扫描组件。</li><li>spring-expression 模块是统一表达式语言（EL）的扩展模块，可以查询、管理运行中的对象，同时也可以方便地调用对象方法，以及操作数组、集合等。它的语法类似于传统 EL，但提供了额外的功能，最出色的要数函数调用和简单字符串的模板函数。EL 的特性是基于 Spring 产品的需求而设计的，可以非常方便地同 Spring IoC 进行交互。</li></ul><h3 id="19-1-1-7-1-1-bean-概述"><a href="#19-1-1-7-1-1-bean-概述" class="headerlink" title="19.1.1. 7.1.1 bean 概述"></a>19.1.1. 7.1.1 bean 概述</h3><p>Spring 就是面向 <code>Bean</code> 的编程（BOP,Bean Oriented Programming），Bean 在 Spring 中处于核心地位。Bean 对于 Spring 的意义就像 Object 对于 OOP 的意义一样，Spring 中没有 Bean 也就没有 Spring 存在的意义。Spring IoC 容器通过配置文件或者注解的方式来管理 bean 对象之间的依赖关系。</p><p>spring 中 bean 用于对一个类进行封装。如下面的配置：</p><figure class="highlight plaintext"><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><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><br>&lt;bean id=&quot;userService&quot; class=&quot;com.itheima.service.impl.UserServiceImpl&quot;&gt;  <br>    &lt;property name=&quot;userDao&quot; ref=&quot;userDao&quot;&gt;&lt;/property&gt;  <br>&lt;/bean&gt;  <br>&lt;bean id=&quot;userDao&quot; class=&quot;com.itheima.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt;<br><br>public interface BeanFactory &#123;  <br>​  <br>  String FACTORY_BEAN_PREFIX = &quot;&amp;&quot;;  <br>​  <br>  //根据bean的名称获取IOC容器中的的bean对象  <br>  Object getBean(String name) throws BeansException;  <br>  //根据bean的名称获取IOC容器中的的bean对象，并指定获取到的bean对象的类型，这样我们使用时就不需要进行类型强转了  <br>  &lt;T&gt; T getBean(String name, Class&lt;T&gt; requiredType) throws BeansException;  <br>  Object getBean(String name, Object... args) throws BeansException;  <br>  &lt;T&gt; T getBean(Class&lt;T&gt; requiredType) throws BeansException;  <br>  &lt;T&gt; T getBean(Class&lt;T&gt; requiredType, Object... args) throws BeansException;  <br>    <br>  &lt;T&gt; ObjectProvider&lt;T&gt; getBeanProvider(Class&lt;T&gt; requiredType);  <br>  &lt;T&gt; ObjectProvider&lt;T&gt; getBeanProvider(ResolvableType requiredType);  <br>​  <br>  //判断容器中是否包含指定名称的bean对象  <br>  boolean containsBean(String name);  <br>  //根据bean的名称判断是否是单例  <br>  boolean isSingleton(String name) throws NoSuchBeanDefinitionException;  <br>  boolean isPrototype(String name) throws NoSuchBeanDefinitionException;  <br>  boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;  <br>  boolean isTypeMatch(String name, Class&lt;?&gt; typeToMatch) throws NoSuchBeanDefinitionException;  <br>  @Nullable  <br>  Class&lt;?&gt; getType(String name) throws NoSuchBeanDefinitionException;  <br>  String[] getAliases(String name);  <br>&#125;<br><br>&lt;bean id=&quot;userDao&quot; class=&quot;com.itheima.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt;  <br>​  <br>bean标签还有很多属性：  <br>  scope、init-method、destory-method等。<br><br>public interface BeanDefinitionReader &#123;  <br>​  <br>  //获取BeanDefinitionRegistry注册器对象  <br>  BeanDefinitionRegistry getRegistry();  <br>​  <br>  @Nullable  <br>  ResourceLoader getResourceLoader();  <br>​  <br>  @Nullable  <br>  ClassLoader getBeanClassLoader();  <br>​  <br>  BeanNameGenerator getBeanNameGenerator();  <br>​  <br>  /*  <br>    下面的loadBeanDefinitions都是加载bean定义，从指定的资源中  <br>  */  <br>  int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;  <br>  int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;  <br>  int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;  <br>  int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;  <br>&#125;<br><br>public interface BeanDefinitionRegistry extends AliasRegistry &#123;  <br>​  <br>  //往注册表中注册bean  <br>  void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)  <br>      throws BeanDefinitionStoreException;  <br>​  <br>  //从注册表中删除指定名称的bean  <br>  void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;  <br>​  <br>  //获取注册表中指定名称的bean  <br>  BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;  <br>      <br>  //判断注册表中是否已经注册了指定名称的bean  <br>  boolean containsBeanDefinition(String beanName);  <br>      <br>  //获取注册表中所有的bean的名称  <br>  String[] getBeanDefinitionNames();  <br>      <br>  int getBeanDefinitionCount();  <br>  boolean isBeanNameInUse(String beanName);  <br>&#125;<br><br>-   DefaultListableBeanFactory<br>    <br>    在该类中定义了如下代码，就是用来注册bean<br>    <br>    private final Map&lt;String, BeanDefinition&gt; beanDefinitionMap = new ConcurrentHashMap&lt;&gt;(256);<br>    <br>-   SimpleBeanDefinitionRegistry<br>    <br>    在该类中定义了如下代码，就是用来注册bean<br>    <br>    private final Map&lt;String, BeanDefinition&gt; beanDefinitionMap = new ConcurrentHashMap&lt;&gt;(64);<br>    <br>    &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;  <br>    &lt;beans&gt;  <br>        &lt;bean id=&quot;userService&quot; class=&quot;com.itheima.service.impl.UserServiceImpl&quot;&gt;  <br>            &lt;property name=&quot;userDao&quot; ref=&quot;userDao&quot;&gt;&lt;/property&gt;  <br>        &lt;/bean&gt;  <br>        &lt;bean id=&quot;userDao&quot; class=&quot;com.itheima.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt;  <br>    &lt;/beans&gt;<br>    <br>    public class PropertyValue &#123;  <br>    ​  <br>      private String name;  <br>      private String ref;  <br>      private String value;  <br>    ​  <br>      public PropertyValue() &#123;  <br>      &#125;  <br>    ​  <br>      public PropertyValue(String name, String ref,String value) &#123;  <br>        this.name = name;  <br>        this.ref = ref;  <br>        this.value = value;  <br>      &#125;  <br>    ​  <br>      public String getName() &#123;  <br>        return name;  <br>      &#125;  <br>    ​  <br>      public void setName(String name) &#123;  <br>        this.name = name;  <br>      &#125;  <br>    ​  <br>      public String getRef() &#123;  <br>        return ref;  <br>      &#125;  <br>    ​  <br>      public void setRef(String ref) &#123;  <br>        this.ref = ref;  <br>      &#125;  <br>    ​  <br>      public String getValue() &#123;  <br>        return value;  <br>      &#125;  <br>    ​  <br>      public void setValue(String value) &#123;  <br>        this.value = value;  <br>      &#125;  <br>    &#125;<br>    <br>    public class MutablePropertyValues implements Iterable&lt;PropertyValue&gt; &#123;  <br>    ​  <br>        private final List&lt;PropertyValue&gt; propertyValueList;  <br>    ​  <br>        public MutablePropertyValues() &#123;  <br>            this.propertyValueList = new ArrayList&lt;PropertyValue&gt;();  <br>        &#125;  <br>    ​  <br>        public MutablePropertyValues(List&lt;PropertyValue&gt; propertyValueList) &#123;  <br>            this.propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList&lt;PropertyValue&gt;());  <br>        &#125;  <br>    ​  <br>        public PropertyValue[] getPropertyValues() &#123;  <br>            return this.propertyValueList.toArray(new PropertyValue[0]);  <br>        &#125;  <br>    ​  <br>        public PropertyValue getPropertyValue(String propertyName) &#123;  <br>            for (PropertyValue pv : this.propertyValueList) &#123;  <br>                if (pv.getName().equals(propertyName)) &#123;  <br>                    return pv;  <br>                &#125;  <br>            &#125;  <br>            return null;  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public Iterator&lt;PropertyValue&gt; iterator() &#123;  <br>            return propertyValueList.iterator();  <br>        &#125;  <br>    ​  <br>        public boolean isEmpty() &#123;  <br>            return this.propertyValueList.isEmpty();  <br>        &#125;  <br>    ​  <br>        public MutablePropertyValues addPropertyValue(PropertyValue pv) &#123;  <br>            for (int i = 0; i &lt; this.propertyValueList.size(); i++) &#123;  <br>                PropertyValue currentPv = this.propertyValueList.get(i);  <br>                if (currentPv.getName().equals(pv.getName())) &#123;  <br>                    this.propertyValueList.set(i, new PropertyValue(pv.getName(),pv.getRef(), pv.getValue()));  <br>                    return this;  <br>                &#125;  <br>            &#125;  <br>            this.propertyValueList.add(pv);  <br>            return this;  <br>        &#125;  <br>    ​  <br>        public boolean contains(String propertyName) &#123;  <br>            return getPropertyValue(propertyName) != null;  <br>        &#125;  <br>    &#125;<br>    <br>    public class BeanDefinition &#123;  <br>        private String id;  <br>        private String className;  <br>    ​  <br>        private MutablePropertyValues propertyValues;  <br>    ​  <br>        public BeanDefinition() &#123;  <br>            propertyValues = new MutablePropertyValues();  <br>        &#125;  <br>    ​  <br>        public String getId() &#123;  <br>            return id;  <br>        &#125;  <br>    ​  <br>        public void setId(String id) &#123;  <br>            this.id = id;  <br>        &#125;  <br>    ​  <br>        public String getClassName() &#123;  <br>            return className;  <br>        &#125;  <br>    ​  <br>        public void setClassName(String className) &#123;  <br>            this.className = className;  <br>        &#125;  <br>    ​  <br>        public void setPropertyValues(MutablePropertyValues propertyValues) &#123;  <br>            this.propertyValues = propertyValues;  <br>        &#125;  <br>    ​  <br>        public MutablePropertyValues getPropertyValues() &#123;  <br>            return propertyValues;  <br>        &#125;  <br>    &#125;<br>    <br>    public interface BeanDefinitionRegistry &#123;  <br>    ​  <br>        //注册BeanDefinition对象到注册表中  <br>        void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);  <br>    ​  <br>        //从注册表中删除指定名称的BeanDefinition对象  <br>        void removeBeanDefinition(String beanName) throws Exception;  <br>    ​  <br>        //根据名称从注册表中获取BeanDefinition对象  <br>        BeanDefinition getBeanDefinition(String beanName) throws Exception;  <br>    ​  <br>        boolean containsBeanDefinition(String beanName);  <br>    ​  <br>        int getBeanDefinitionCount();  <br>    ​  <br>        String[] getBeanDefinitionNames();  <br>    &#125;<br>    <br>    public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry &#123;  <br>    ​  <br>        private Map&lt;String, BeanDefinition&gt; beanDefinitionMap = new HashMap&lt;String, BeanDefinition&gt;();  <br>    ​  <br>        @Override  <br>        public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) &#123;  <br>            beanDefinitionMap.put(beanName,beanDefinition);  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public void removeBeanDefinition(String beanName) throws Exception &#123;  <br>            beanDefinitionMap.remove(beanName);  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public BeanDefinition getBeanDefinition(String beanName) throws Exception &#123;  <br>            return beanDefinitionMap.get(beanName);  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public boolean containsBeanDefinition(String beanName) &#123;  <br>            return beanDefinitionMap.containsKey(beanName);  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public int getBeanDefinitionCount() &#123;  <br>            return beanDefinitionMap.size();  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public String[] getBeanDefinitionNames() &#123;  <br>            return beanDefinitionMap.keySet().toArray(new String[1]);  <br>        &#125;  <br>    &#125;<br>    <br>    public interface BeanDefinitionReader &#123;  <br>    ​  <br>      //获取注册表对象  <br>        BeanDefinitionRegistry getRegistry();  <br>      //加载配置文件并在注册表中进行注册  <br>        void loadBeanDefinitions(String configLocation) throws Exception;  <br>    &#125;<br>    <br>    public class XmlBeanDefinitionReader implements BeanDefinitionReader &#123;  <br>    ​  <br>        private BeanDefinitionRegistry registry;  <br>    ​  <br>        public XmlBeanDefinitionReader() &#123;  <br>            this.registry = new SimpleBeanDefinitionRegistry();  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public BeanDefinitionRegistry getRegistry() &#123;  <br>            return registry;  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public void loadBeanDefinitions(String configLocation) throws Exception &#123;  <br>    ​  <br>            InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);  <br>            SAXReader reader = new SAXReader();  <br>            Document document = reader.read(is);  <br>            Element rootElement = document.getRootElement();  <br>            //解析bean标签  <br>            parseBean(rootElement);  <br>        &#125;  <br>    ​  <br>        private void parseBean(Element rootElement) &#123;  <br>    ​  <br>            List&lt;Element&gt; elements = rootElement.elements();  <br>            for (Element element : elements) &#123;  <br>                String id = element.attributeValue(&quot;id&quot;);  <br>                String className = element.attributeValue(&quot;class&quot;);  <br>                BeanDefinition beanDefinition = new BeanDefinition();  <br>                beanDefinition.setId(id);  <br>                beanDefinition.setClassName(className);  <br>                List&lt;Element&gt; list = element.elements(&quot;property&quot;);  <br>                MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();  <br>                for (Element element1 : list) &#123;  <br>                    String name = element1.attributeValue(&quot;name&quot;);  <br>                    String ref = element1.attributeValue(&quot;ref&quot;);  <br>                    String value = element1.attributeValue(&quot;value&quot;);  <br>                    PropertyValue propertyValue = new PropertyValue(name,ref,value);  <br>                    mutablePropertyValues.addPropertyValue(propertyValue);  <br>                &#125;  <br>                beanDefinition.setPropertyValues(mutablePropertyValues);  <br>    ​  <br>                registry.registerBeanDefinition(id,beanDefinition);  <br>            &#125;  <br>        &#125;  <br>    &#125;<br>    <br>    public interface BeanFactory &#123;  <br>      //根据bean对象的名称获取bean对象  <br>        Object getBean(String name) throws Exception;  <br>      //根据bean对象的名称获取bean对象，并进行类型转换  <br>        &lt;T&gt; T getBean(String name, Class&lt;? extends T&gt; clazz) throws Exception;  <br>    &#125;<br>    <br>    public interface ApplicationContext extends BeanFactory &#123;  <br>      //进行配置文件加载并进行对象创建  <br>        void refresh() throws IllegalStateException, Exception;  <br>    &#125;<br>    <br>    public abstract class AbstractApplicationContext implements ApplicationContext &#123;  <br>    ​  <br>        protected BeanDefinitionReader beanDefinitionReader;  <br>        //用来存储bean对象的容器   key存储的是bean的id值，value存储的是bean对象  <br>        protected Map&lt;String, Object&gt; singletonObjects = new HashMap&lt;String, Object&gt;();  <br>    ​  <br>        //存储配置文件的路径  <br>        protected String configLocation;  <br>    ​  <br>        public void refresh() throws IllegalStateException, Exception &#123;  <br>    ​  <br>            //加载BeanDefinition  <br>            beanDefinitionReader.loadBeanDefinitions(configLocation);  <br>    ​  <br>            //初始化bean  <br>            finishBeanInitialization();  <br>        &#125;  <br>    ​  <br>        //bean的初始化  <br>        private void finishBeanInitialization() throws Exception &#123;  <br>            BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();  <br>            String[] beanNames = registry.getBeanDefinitionNames();  <br>    ​  <br>            for (String beanName : beanNames) &#123;  <br>                BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);  <br>                getBean(beanName);  <br>            &#125;  <br>        &#125;  <br>    &#125;<br>    <br>    public class ClassPathXmlApplicationContext extends AbstractApplicationContext&#123;  <br>    ​  <br>        public ClassPathXmlApplicationContext(String configLocation) &#123;  <br>            this.configLocation = configLocation;  <br>            //构建XmlBeanDefinitionReader对象  <br>            beanDefinitionReader = new XmlBeanDefinitionReader();  <br>            try &#123;  <br>                this.refresh();  <br>            &#125; catch (Exception e) &#123;  <br>            &#125;  <br>        &#125;  <br>    ​  <br>        //根据bean的id属性值获取bean对象  <br>        @Override  <br>        public Object getBean(String name) throws Exception &#123;  <br>    ​  <br>            //return singletonObjects.get(name);  <br>            Object obj = singletonObjects.get(name);  <br>            if(obj != null) &#123;  <br>                return obj;  <br>            &#125;  <br>    ​  <br>            BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();  <br>            BeanDefinition beanDefinition = registry.getBeanDefinition(name);  <br>            if(beanDefinition == null) &#123;  <br>                return null;  <br>            &#125;  <br>            String className = beanDefinition.getClassName();  <br>            Class&lt;?&gt; clazz = Class.forName(className);  <br>            Object beanObj = clazz.newInstance();  <br>            MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();  <br>            for (PropertyValue propertyValue : propertyValues) &#123;  <br>                String propertyName = propertyValue.getName();  <br>                String value = propertyValue.getValue();  <br>                String ref = propertyValue.getRef();  <br>                if(ref != null &amp;&amp; !&quot;&quot;.equals(ref)) &#123;  <br>    ​  <br>                    Object bean = getBean(ref);  <br>                    String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName);  <br>                    Method[] methods = clazz.getMethods();  <br>                    for (Method method : methods) &#123;  <br>                        if(method.getName().equals(methodName)) &#123;  <br>                            method.invoke(beanObj,bean);  <br>                        &#125;  <br>                    &#125;  <br>                &#125;  <br>    ​  <br>                if(value != null &amp;&amp; !&quot;&quot;.equals(value)) &#123;  <br>                    String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName);  <br>                    Method method = clazz.getMethod(methodName, String.class);  <br>                    method.invoke(beanObj,value);  <br>                &#125;  <br>            &#125;  <br>            singletonObjects.put(name,beanObj);  <br>            return beanObj;  <br>        &#125;  <br>    ​  <br>        @Override  <br>        public &lt;T&gt; T getBean(String name, Class&lt;? extends T&gt; clazz) throws Exception &#123;  <br>    ​  <br>            Object bean = getBean(name);  <br>            if(bean != null) &#123;  <br>                return clazz.cast(bean);  <br>            &#125;  <br>            return null;  <br>        &#125;  <br>    &#125;<br></code></pre></td></tr></table></figure><p>了解 Spring 底层对对象的大体管理机制。</p><p>了解设计模式在具体的开发中的使用。</p><p> 以后学习 spring 源码，通过该案例的实现，可以降低 spring 学习的入门成本。</p><p> spring 框架底层是很复杂的，进行了很深入的封装，并对外提供了很好的扩展性。而我们自定义 SpringIOC 有以下几个目的：</p><h4 id="7-4-5-3-整个设计和-Spring-的设计还是有一定的出入"><a href="#7-4-5-3-整个设计和-Spring-的设计还是有一定的出入" class="headerlink" title="7.4.5.3 整个设计和 Spring 的设计还是有一定的出入"></a>7.4.5.3 整个设计和 Spring 的设计还是有一定的出入</h4><h4 id="7-4-5-2-符合大部分设计原则"><a href="#7-4-5-2-符合大部分设计原则" class="headerlink" title="7.4.5.2 符合大部分设计原则"></a>7.4.5.2 符合大部分设计原则</h4><p>  spring 框架其实使用到了很多设计模式，如 AOP 使用到了代理模式，选择 JDK 代理或者 CGLIB 代理使用到了策略模式，还有适配器模式，装饰者模式，观察者模式等。</p><ul><li><p>工厂模式。这个使用工厂模式 + 配置文件的方式。</p><ul><li>单例模式。Spring IOC 管理的 bean 对象都是单例的，此处的单例不是通过构造器进行单例的控制的，而是 spring 框架对每一个 bean 只创建了一个对象。</li><li>模板方法模式。AbstractApplicationContext 类中的 finishBeanInitialization() 方法调用了子类的 getBean() 方法，因为 getBean() 的实现和环境息息相关。</li><li>迭代器模式。对于 MutablePropertyValues 类定义使用到了迭代器模式，因为此类存储并管理 PropertyValue 对象，也属于一个容器，所以给该容器提供一个遍历方式。</li></ul><h4 id="7-4-5-1-使用到的设计模式"><a href="#7-4-5-1-使用到的设计模式" class="headerlink" title="7.4.5.1 使用到的设计模式"></a>7.4.5.1 使用到的设计模式</h4><h3 id="7-4-5-自定义-Spring-IOC-总结"><a href="#7-4-5-自定义-Spring-IOC-总结" class="headerlink" title="7.4.5 自定义 Spring IOC 总结"></a>7.4.5 自定义 Spring IOC 总结</h3><ul><li>在构造方法中，创建 BeanDefinitionReader 对象。</li><li>在构造方法中，调用 refresh() 方法，用于进行配置文件加载、创建 bean 对象并存储到容器中。</li><li>重写父接口中的 getBean() 方法，并实现依赖注入操作。</li></ul><p>该类主要是加载类路径下的配置文件，并进行 bean 对象的创建，主要完成以下功能：</p><h4 id="7-4-4-4-ClassPathXmlApplicationContext-类"><a href="#7-4-4-4-ClassPathXmlApplicationContext-类" class="headerlink" title="7.4.4.4 ClassPathXmlApplicationContext 类"></a>7.4.4.4 ClassPathXmlApplicationContext 类</h4><blockquote><p>注意：该类 finishBeanInitialization() 方法中调用 getBean() 方法使用到了模板方法模式。</p></blockquote><ul><li><p>作为 ApplicationContext 接口的子类，所以该类也是非延时加载，所以需要在该类中定义一个 Map 集合，作为 bean 对象存储的容器。</p></li><li><p>声明 BeanDefinitionReader 类型的变量，用来进行 xml 配置文件的解析，符合单一职责原则。</p><p>  BeanDefinitionReader 类型的对象创建交由子类实现，因为只有子类明确到底创建 BeanDefinitionReader 哪儿个子实现类对象。</p></li></ul><h4 id="7-4-4-3-AbstractApplicationContext-类"><a href="#7-4-4-3-AbstractApplicationContext-类" class="headerlink" title="7.4.4.3 AbstractApplicationContext 类"></a>7.4.4.3 AbstractApplicationContext 类</h4><ul><li>加载配置文件。</li><li>根据注册表中的 BeanDefinition 对象封装的数据进行 bean 对象的创建。</li></ul><p>该接口的所以的子实现类对 bean 对象的创建都是非延时的，所以在该接口中定义 <code>refresh()</code> 方法，该方法主要完成以下两个功能：</p><h4 id="7-4-4-2-ApplicationContext-接口"><a href="#7-4-4-2-ApplicationContext-接口" class="headerlink" title="7.4.4.2 ApplicationContext 接口"></a>7.4.4.2 ApplicationContext 接口</h4><p>在该接口中定义 IOC 容器的统一规范即获取 bean 对象。</p><h4 id="7-4-4-1-BeanFactory-接口"><a href="#7-4-4-1-BeanFactory-接口" class="headerlink" title="7.4.4.1 BeanFactory 接口"></a>7.4.4.1 BeanFactory 接口</h4><h3 id="7-4-4-IOC-容器相关类"><a href="#7-4-4-IOC-容器相关类" class="headerlink" title="7.4.4 IOC 容器相关类"></a>7.4.4 IOC 容器相关类</h3><p>XmlBeanDefinitionReader 类是专门用来解析 xml 配置文件的。该类实现 BeanDefinitionReader 接口并实现接口中的两个功能。</p><h4 id="7-4-3-2-XmlBeanDefinitionReader-类"><a href="#7-4-3-2-XmlBeanDefinitionReader-类" class="headerlink" title="7.4.3.2 XmlBeanDefinitionReader 类"></a>7.4.3.2 XmlBeanDefinitionReader 类</h4><ul><li>获取注册表的功能，让外界可以通过该对象获取注册表对象。</li><li>加载配置文件，并注册 bean 数据。</li></ul><p>BeanDefinitionReader 是用来解析配置文件并在注册表中注册 bean 的信息。定义了两个规范：</p><h4 id="7-4-3-1-BeanDefinitionReader-接口"><a href="#7-4-3-1-BeanDefinitionReader-接口" class="headerlink" title="7.4.3.1 BeanDefinitionReader 接口"></a>7.4.3.1 BeanDefinitionReader 接口</h4><h3 id="7-4-3-定义解析器相关类"><a href="#7-4-3-定义解析器相关类" class="headerlink" title="7.4.3 定义解析器相关类"></a>7.4.3 定义解析器相关类</h3><p>该类实现了 BeanDefinitionRegistry 接口，定义了 Map 集合作为注册表容器。</p><h4 id="7-4-2-2-SimpleBeanDefinitionRegistry-类"><a href="#7-4-2-2-SimpleBeanDefinitionRegistry-类" class="headerlink" title="7.4.2.2 SimpleBeanDefinitionRegistry 类"></a>7.4.2.2 SimpleBeanDefinitionRegistry 类</h4><ul><li>注册 BeanDefinition 对象到注册表中</li><li>从注册表中删除指定名称的 BeanDefinition 对象</li><li>根据名称从注册表中获取 BeanDefinition 对象</li><li>判断注册表中是否包含指定名称的 BeanDefinition 对象</li><li>获取注册表中 BeanDefinition 对象的个数</li><li>获取注册表中所有的 BeanDefinition 的名称</li></ul><p>BeanDefinitionRegistry 接口定义了注册表的相关操作，定义如下功能：</p><h4 id="7-4-2-1-BeanDefinitionRegistry-接口"><a href="#7-4-2-1-BeanDefinitionRegistry-接口" class="headerlink" title="7.4.2.1 BeanDefinitionRegistry 接口"></a>7.4.2.1 BeanDefinitionRegistry 接口</h4><h3 id="7-4-2-定义注册表相关类"><a href="#7-4-2-定义注册表相关类" class="headerlink" title="7.4.2 定义注册表相关类"></a>7.4.2 定义注册表相关类</h3><p>BeanDefinition 类用来封装 bean 信息的，主要包含 id（即 bean 对象的名称）、class（需要交由 spring 管理的类的全类名）及子标签 property 数据。</p><h4 id="7-4-1-3-BeanDefinition-类"><a href="#7-4-1-3-BeanDefinition-类" class="headerlink" title="7.4.1.3 BeanDefinition 类"></a>7.4.1.3 BeanDefinition 类</h4><p>一个 bean 标签可以有多个 property 子标签，所以再定义一个 MutablePropertyValues 类，用来存储并管理多个 PropertyValue 对象。</p><h4 id="7-4-1-2-MutablePropertyValues-类"><a href="#7-4-1-2-MutablePropertyValues-类" class="headerlink" title="7.4.1.2 MutablePropertyValues 类"></a>7.4.1.2 MutablePropertyValues 类</h4><p>用于封装 bean 的属性，体现到上面的配置文件就是封装 bean 标签的子标签 property 标签数据。</p><h4 id="7-4-1-1-PropertyValue-类"><a href="#7-4-1-1-PropertyValue-类" class="headerlink" title="7.4.1.1 PropertyValue 类"></a>7.4.1.1 PropertyValue 类</h4><h3 id="7-4-1-定义-bean-相关的-pojo-类"><a href="#7-4-1-定义-bean-相关的-pojo-类" class="headerlink" title="7.4.1 定义 bean 相关的 pojo 类"></a>7.4.1 定义 bean 相关的 pojo 类</h3><p>现要对下面的配置文件进行解析，并自定义 Spring 框架的 IOC 对涉及到的对象进行管理。</p><h2 id="7-4-自定义-SpringIOC"><a href="#7-4-自定义-SpringIOC" class="headerlink" title="7.4 自定义 SpringIOC"></a>7.4 自定义 SpringIOC</h2><p>ClassPathXmlApplicationContext 对 Bean 配置资源的载入是从 refresh（）方法开始的。refresh（）方法是一个模板方法，规定了 IoC 容器的启动流程，有些逻辑要交给其子类实现。它对 Bean 配置资源进行载入，ClassPathXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh（）方法启动整个 IoC 容器对 Bean 定义的载入过程。</p><h3 id="7-3-5-创建容器"><a href="#7-3-5-创建容器" class="headerlink" title="7.3.5 创建容器"></a>7.3.5 创建容器</h3></li></ul><p>从上面类图可以看到 BeanDefinitionRegistry 接口的子实现类主要有以下几个：</p><p>继承结构图如下：</p><p>BeanDefinitionReader 用来解析 bean 定义，并封装 BeanDefinition 对象，而我们定义的配置文件中定义了很多 bean 标签，所以就有一个问题，解析的 BeanDefinition 对象存储到哪儿？答案就是 BeanDefinition 的注册中心，而该注册中心顶层接口就是 BeanDefinitionRegistry。</p><h3 id="19-1-2-7-3-4-BeanDefinitionRegistry-解析"><a href="#19-1-2-7-3-4-BeanDefinitionRegistry-解析" class="headerlink" title="19.1.2. 7.3.4 BeanDefinitionRegistry 解析"></a>19.1.2. 7.3.4 BeanDefinitionRegistry 解析</h3><p>看看 BeanDefinitionReader 接口定义的功能来理解它具体的作用：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205223252.png"></p><p>Bean 的解析过程非常复杂，功能被分得很细，因为这里需要被扩展的地方很多，必须保证足够的灵活性，以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过 BeanDefinitionReader 来完成，看看 Spring 中 BeanDefinitionReader 的类结构图，如下图所示。</p><h3 id="19-1-3-7-3-3-BeanDefinitionReader-解析"><a href="#19-1-3-7-3-3-BeanDefinitionReader-解析" class="headerlink" title="19.1.3. 7.3.3 BeanDefinitionReader 解析"></a>19.1.3. 7.3.3 BeanDefinitionReader 解析</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205223154.png"></p><p>其继承体系如下图所示。</p><p>Spring IoC 容器管理我们定义的各种 Bean 对象及其相互关系，而 Bean 对象在 Spring 实现中是以 BeanDefinition 来描述的，如下面配置文件</p><h3 id="19-1-4-7-3-2-BeanDefinition-解析"><a href="#19-1-4-7-3-2-BeanDefinition-解析" class="headerlink" title="19.1.4. 7.3.2 BeanDefinition 解析"></a>19.1.4. 7.3.2 BeanDefinition 解析</h3><ul><li>ClasspathXmlApplicationContext : 根据类路径加载 xml 配置文件，并创建 IOC 容器对象。</li><li>FileSystemXmlApplicationContext ：根据系统路径加载 xml 配置文件，并创建 IOC 容器对象。</li><li>AnnotationConfigApplicationContext ：加载注解类配置，并创建 IOC 容器。</li></ul><p>要知道工厂是如何产生对象的，我们需要看具体的 IoC 容器实现，Spring 提供了许多 IoC 容器实现，比如：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205223032.png"></p><p>BeanFactory 有一个很重要的子接口，就是 ApplicationContext 接口，该接口主要来规范容器中的 bean 对象是非延时加载，即在创建容器对象的时候就对象 bean 进行初始化，并存储到一个容器中。</p><p>在 BeanFactory 里只对 IoC 容器的基本行为做了定义，根本不关心你的 Bean 是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品，不关心工厂是怎么生产这些产品的。</p><p>这三个接口共同定义了 Bean 的集合、Bean 之间的关系及 Bean 行为。最基本的 IoC 容器接口是 BeanFactory，来看一下它的源码：</p><ul><li>ListableBeanFactory 接口表示这些 Bean 可列表化。</li><li>HierarchicalBeanFactory 表示这些 Bean 是有继承关系的，也就是每个 Bean 可能有父 Bean</li><li>AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。</li></ul><p>每个接口都有它的使用场合，主要是为了区分在 Spring 内部操作过程中对象的传递和转化，对对象的数据访问所做的限制。例如，</p><p>那么为何要定义这么多层次的接口呢？</p><p>其中，BeanFactory 作为最顶层的一个接口，定义了 IoC 容器的基本功能规范，BeanFactory 有三个重要的子接口：ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory，它实现了所有的接口。</p><p>Spring 中 Bean 的创建是典型的工厂模式，这一系列的 Bean 工厂，即 IoC 容器，为开发者管理对象之间的依赖关系提供了很多便利和基础服务，在 Spring 中有许多 IoC 容器的实现供用户选择，其相互关系如下图所示。 <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221205222818.png"></p><h3 id="19-1-5-7-3-1-BeanFactory-解析"><a href="#19-1-5-7-3-1-BeanFactory-解析" class="headerlink" title="19.1.5. 7.3.1 BeanFactory 解析"></a>19.1.5. 7.3.1 BeanFactory 解析</h3><h2 id="19-2-7-3-Spring-IOC-相关接口分析"><a href="#19-2-7-3-Spring-IOC-相关接口分析" class="headerlink" title="19.2. 7.3 Spring IOC 相关接口分析"></a>19.2. 7.3 Spring IOC 相关接口分析</h2><ul><li>spring 将 bean 对象交由一个叫 IOC 容器进行管理。</li><li>bean 对象之间的依赖关系在配置文件中体现，并由 spring 完成。</li></ul><p>为什么 Bean 如此重要呢？<br>基本原理</p><h1 id="20-其他"><a href="#20-其他" class="headerlink" title="20. 其他"></a>20. 其他</h1><h2 id="20-1-属性赋值用的是深拷贝"><a href="#20-1-属性赋值用的是深拷贝" class="headerlink" title="20.1. 属性赋值用的是深拷贝"></a>20.1. 属性赋值用的是深拷贝</h2><h2 id="20-2-装配用了多态特性"><a href="#20-2-装配用了多态特性" class="headerlink" title="20.2. 装配用了多态特性"></a>20.2. 装配用了多态特性</h2><h2 id="20-3-InstantiationStrategy"><a href="#20-3-InstantiationStrategy" class="headerlink" title="20.3. InstantiationStrategy"></a>20.3. InstantiationStrategy</h2><p>[[【死磕 Spring】—– IOC 之 bean 的实例化策略：InstantiationStrategy - 腾讯云开发者社区-腾讯云]]</p><h1 id="21-实战经验"><a href="#21-实战经验" class="headerlink" title="21. 实战经验"></a>21. 实战经验</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206220003.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221206223022.png"></p><h1 id="22-参考与感谢"><a href="#22-参考与感谢" class="headerlink" title="22. 参考与感谢"></a>22. 参考与感谢</h1><h2 id="22-1-雷丰阳-SpringSource"><a href="#22-1-雷丰阳-SpringSource" class="headerlink" title="22.1. 雷丰阳 -SpringSource"></a>22.1. 雷丰阳 -SpringSource</h2><p><a href="https://www.bilibili.com/video/BV1Uq4y1n7mS/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Uq4y1n7mS/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>有注释可运行： [[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;spring-framework&#x2F;springsource-test&#x2F;src&#x2F;main&#x2F;java&#x2F;com&#x2F;atguigu&#x2F;spring&#x2F;MainTest.java]]</p><h2 id="22-2-spring-注解驱动开发-spring-源码版-雷丰阳-尚硅谷"><a href="#22-2-spring-注解驱动开发-spring-源码版-雷丰阳-尚硅谷" class="headerlink" title="22.2. spring 注解驱动开发 _spring 源码版 - 雷丰阳 - 尚硅谷"></a>22.2. spring 注解驱动开发 _spring 源码版 - 雷丰阳 - 尚硅谷</h2><a href="/2022/12/04/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-3%E3%80%81AOP%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-@EnableAspectJAutoProxy/" title="Spring-3、AOP实现原理-@EnableAspectJAutoProxy">Spring-3、AOP实现原理-@EnableAspectJAutoProxy</a><h2 id="22-3-动力节点"><a href="#22-3-动力节点" class="headerlink" title="22.3. 动力节点"></a>22.3. 动力节点</h2><p><a href="https://www.bilibili.com/video/BV1uF411L73Q?p=36&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1uF411L73Q?p=36&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="22-4-图灵"><a href="#22-4-图灵" class="headerlink" title="22.4. 图灵"></a>22.4. 图灵</h2><p>2021 年上半年诸葛老师最新课程更新，已存百度网盘</p><h2 id="22-5-马士兵"><a href="#22-5-马士兵" class="headerlink" title="22.5. 马士兵"></a>22.5. 马士兵</h2><p>有注释未运行： [[pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;lianpengju&#x2F;spring-context&#x2F;src&#x2F;main&#x2F;java&#x2F;org&#x2F;springframework&#x2F;context&#x2F;support&#x2F;AbstractApplicationContext.java]]</p>]]></content>
      
      
      <categories>
          
          <category> 框架源码专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 框架源码专题 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-11、乱序问题</title>
      <link href="/2022/11/26/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-11%E3%80%81%E4%B9%B1%E5%BA%8F%E9%97%AE%E9%A2%98/"/>
      <url>/2022/11/26/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-11%E3%80%81%E4%B9%B1%E5%BA%8F%E9%97%AE%E9%A2%98/</url>
      
        <content type="html"><![CDATA[<h1 id="1-乱序问题"><a href="#1-乱序问题" class="headerlink" title="1. 乱序问题"></a>1. 乱序问题</h1><h2 id="1-1-乱序来源"><a href="#1-1-乱序来源" class="headerlink" title="1.1. 乱序来源"></a>1.1. 乱序来源</h2><ol><li>编译器优化的重排序。编译器在 <strong>不改变单线程程序语义</strong> 的前提下，可以重新安排语句的执行顺序。</li><li>指令级并行的重排序。现代处理器采用了指令级并行技术（Instruction-Level Parallelism，ILP）来将多条指令重叠执行。如果不存在 <strong>数据依赖性</strong>，处理器可以改变语句对应机器指令的执行顺序。</li><li>内存系统的重排序。由于处理器使用缓存和读&#x2F;写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。</li></ol><p>1 属于 <strong>编译器重排序</strong>，2 和 3 属于 <strong>处理器重排序</strong>。从 Java 源代码到最终实际执行的指令序列，会分别经历下面 3 种重排序：</p><p><img src="https://upload-images.jianshu.io/upload_images/1996162-af5541f8143da4b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/896/format/webp"></p><p><span style="background-color:#ff00ff">1. 即时编译器优化</span><br><span style="background-color:#ff00ff">2. 指令执行乱序</span><br><span style="background-color:#ff00ff">3. 内存写入乱序</span></p><h3 id="1-1-1-即时编译优化"><a href="#1-1-1-即时编译优化" class="headerlink" title="1.1.1. 即时编译优化"></a>1.1.1. 即时编译优化</h3><h4 id="1-1-1-1-C-语言"><a href="#1-1-1-1-C-语言" class="headerlink" title="1.1.1.1. C 语言"></a>1.1.1.1. C 语言</h4><p>volatile 和编译器屏障都是通过禁止 <code>即时编译优化</code> 来保证可见性的</p><p>编译器屏障<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114171841.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114171934.png"></p><h4 id="1-1-1-2-Java"><a href="#1-1-1-2-Java" class="headerlink" title="1.1.1.2. Java"></a>1.1.1.2. Java</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221127112446.jpg"></p><p>编译期：像 c&#x2F;c++ 只有一个编译期，就是调用 gcc 命令将 c&#x2F;c++ 代码编译成汇编代码。<span style="background-color:#ff00ff">但是 Java 中有两个编译期：</span><br><span style="background-color:#ff00ff">1、调用 javac 命令将 Java 代码编译成 Java 字节码，即静态编译期；</span><br><span style="background-color:#ff00ff">2、JIT 编译器将字节码编译为机器码，即动态编译期</span><br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230409-0830%%</span>❕ ^yt5sch</p><p>如果加不加 volatile 生成的字节码文件都一个样，那在运行的时候 JVM 是怎么知道的呢？类属性在 JVM 中存储的时候会有一个属性：<span style="background-color:#ff0000">Access flags</span>。JVM 在运行的时候就是通过该属性来判断操作的类属性有没有加 volatile 修饰，上图。</p><p><img src="https://pic4.zhimg.com/v2-84b90b04eb569cc6fb20e31505f8a173_b.jpg"></p><p><img src="https://pic2.zhimg.com/v2-07c2eeef5e789f7b3567b4cc42ad2005_b.jpg"></p><h3 id="1-1-2-指令执行乱序"><a href="#1-1-2-指令执行乱序" class="headerlink" title="1.1.2. 指令执行乱序"></a>1.1.2. 指令执行乱序</h3><a href="/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-1%E3%80%81JMM%E4%B8%8EMESI/" title="并发基础-1、JMM与MESI">并发基础-1、JMM与MESI</a><h3 id="1-1-3-内存写入乱序"><a href="#1-1-3-内存写入乱序" class="headerlink" title="1.1.3. 内存写入乱序"></a>1.1.3. 内存写入乱序</h3><p>CPU 架构中，因为 MESI 协议需要使用保存缓冲区，有可能产生不可见问题</p><h2 id="1-2-Java-volatile-乱序解决方案⭐️🔴"><a href="#1-2-Java-volatile-乱序解决方案⭐️🔴" class="headerlink" title="1.2. Java volatile 乱序解决方案⭐️🔴"></a>1.2. Java volatile 乱序解决方案⭐️🔴</h2><p>Java 中的 volatile 是通过禁止 <code>即时编译优化</code> 和 <code>内存写入乱序</code> 来保证可见性的，其中</p><ol><li>禁止 <code>即时编译优化</code> 是通过<span style="background-color:#00ff00">编译器屏障</span></li><li>禁止 <code>内存写入乱序</code> 是通过 lock 前缀 (<span style="background-color:#00ff00">CPU 屏障</span>)</li></ol><p>x86 是可以保证不会出现 CPU 的指令执行乱序的</p><h2 id="1-3-as-if-serial"><a href="#1-3-as-if-serial" class="headerlink" title="1.3. as-if-serial"></a>1.3. as-if-serial</h2><p>[[为什么需用指令重排序 - 简书]]<br>as-if-serial 的意思是：<span style="background-color:#00ff00">不管指令怎么重排序，在单线程下执行结果不能被改变</span><br><span style="background-color:#ff00ff">不管是编译器级别还是处理器级别的重排序都必须遵循 as-if-serial 语义</span></p><p>为了遵守 as-if-serial 语义，编译器和处理器不会对存在 <strong>数据依赖关系</strong> 的操作做重排序。但是 as-if-serial 规则允许对 <strong>有控制依赖关系</strong> 的指令做重排序，因为在单线程程序中，对存在控制依赖的操作重排序，不会改变执行结果，但是多线程下确有可能会改变结果。</p><h2 id="1-4-happens-before"><a href="#1-4-happens-before" class="headerlink" title="1.4. happens-before"></a>1.4. happens-before</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106112504.png"></p><p><span style="background-color:#ff00ff">A Happens-Before B：无论 A 事件和 B 事件是否发生在同一个线程里，A 发生过的事情对 B 来说总是可见的。</span></p><h3 id="1-4-1-程序顺序规则"><a href="#1-4-1-程序顺序规则" class="headerlink" title="1.4.1. 程序顺序规则"></a>1.4.1. 程序顺序规则</h3><p>一个线程中的每个操作，happens-before 于该线程中的任意后续操作。<br>    主要含义是：在一个线程内不管指令怎么重排序，程序运行的结果都不会发生改变。和 as-if-serial 比较像。</p><h3 id="1-4-2-监视器锁规则-先加锁再解锁"><a href="#1-4-2-监视器锁规则-先加锁再解锁" class="headerlink" title="1.4.2. 监视器锁规则-先加锁再解锁"></a>1.4.2. 监视器锁规则-先加锁再解锁</h3><p>对一个锁的解锁，happens-before 于随后对这个锁的加锁。<br>    主要含义是：同一个锁的解锁一定发生在加锁之后</p><h3 id="1-4-3-管程锁定规则-后获锁可以感知前解锁线程的操作"><a href="#1-4-3-管程锁定规则-后获锁可以感知前解锁线程的操作" class="headerlink" title="1.4.3. 管程锁定规则-后获锁可以感知前解锁线程的操作"></a>1.4.3. 管程锁定规则-后获锁可以感知前解锁线程的操作</h3><p>一个线程获取到锁后，它能看到前一个获取到锁的线程所有的操作结果。<br>    主要含义是：无论是在单线程环境还是多线程环境，对于同一个锁来说，一个线程对这个锁解锁之后，另一个线程获取了这个锁都能看到前一个线程的操作结果！(管程是一种通用的同步原语，synchronized 就是管程的实现）</p><h3 id="1-4-4-volatile-变量规则"><a href="#1-4-4-volatile-变量规则" class="headerlink" title="1.4.4. volatile 变量规则"></a>1.4.4. volatile 变量规则</h3><p>对一个 volatile 域的写，happens-before 于任意后续对这个 volatile 域的读。<br>    主要含义是：<span style="background-color:#ff00ff">如果一个线程先去写一个 volatile 变量，然后另一个线程又去读这个变量，那么这个写操作的结果一定对读的这个线程可见</span>。</p><h3 id="1-4-5-传递性"><a href="#1-4-5-传递性" class="headerlink" title="1.4.5. 传递性"></a>1.4.5. 传递性</h3><p>如果 A happens-before B，且 B happens-before C，那么 A happens-before C。</p><h3 id="1-4-6-start-规则-子线程可见"><a href="#1-4-6-start-规则-子线程可见" class="headerlink" title="1.4.6. start() 规则-子线程可见"></a>1.4.6. start() 规则-子线程可见</h3><p>如果线程 A 执行操作 ThreadB.start()（启动线程 B），那么 A 线程的 ThreadB.start() 操作 happens-before 于线程 B 中的任意操作。<br>    主要含义是：<span style="background-color:#ff00ff">线程 A 在启动子线程 B 之前对共享变量的修改结果对线程 B 可见</span>。</p><h3 id="1-4-7-join-规则"><a href="#1-4-7-join-规则" class="headerlink" title="1.4.7. join() 规则"></a>1.4.7. join() 规则</h3><p>如果线程 A 执行操作 ThreadB.join() 并成功返回，那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回。<br>    主要含义是：<span style="background-color:#ff00ff">如果在线程 A 执行过程中调用了线程 B 的 join 方法，那么当 B 执行完成后，在线程 B 中所有操作结果对线程 A 可见</span>。</p><h3 id="1-4-8-线程中断规则"><a href="#1-4-8-线程中断规则" class="headerlink" title="1.4.8. 线程中断规则"></a>1.4.8. 线程中断规则</h3><p>对线程 interrupt 方法的调用 happens-before 于被中断线程的代码检测到中断事件的发生。<br>    主要含义是：响应中断一定发生在发起中断之后。</p><h3 id="1-4-9-对象终结规则"><a href="#1-4-9-对象终结规则" class="headerlink" title="1.4.9. 对象终结规则"></a>1.4.9. 对象终结规则</h3><p>就是一个对象的初始化的完成，也就是构造函数执行的结束一定 happens-before 它的 finalize() 方法。</p><p>一个 happens-before 规则对应于一个或多个编译器和处理器重排序规则。</p><blockquote><p>as-if-serial 和 happens-before 的主要作用都是：在保证不改变程序运行结果的前提下，允许部分指令的重排序，最大限度的提升程序执行的效率。</p></blockquote><h1 id="2-实战经验"><a href="#2-实战经验" class="headerlink" title="2. 实战经验"></a>2. 实战经验</h1><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><p><a href="https://www.jianshu.com/p/e6cda45d58d0">https://www.jianshu.com/p/e6cda45d58d0</a></p><p><a href="https://zhuanlan.zhihu.com/p/271701839">https://zhuanlan.zhihu.com/p/271701839</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-10、内存屏障</title>
      <link href="/2022/11/24/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/"/>
      <url>/2022/11/24/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/</url>
      
        <content type="html"><![CDATA[<p>其实从头看到尾就会发现，一个技术点的出现往往是为了填补另一个的坑。<br>为了解决处理器与主内存之间的速度鸿沟，引入了高速缓存，却又导致了缓存一致性问题<br>为了解决缓存一致性问题，引入了如MESI等技术，又导致了处理器等待问题<br>为了解决处理器等待问题，引入了写缓冲和无效化队列，又导致了重排序和可见性问题<br>为了解决重排序和可见性问题，引入了内存屏障</p><h1 id="1-屏障分类"><a href="#1-屏障分类" class="headerlink" title="1. 屏障分类"></a>1. 屏障分类</h1><p>为何而来？为了解决计算机的乱序问题而来。那为什么要有乱序，为了性能，ok。那有什么乱序需要用屏障来避免？<a href="/2022/11/26/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-11%E3%80%81%E4%B9%B1%E5%BA%8F%E9%97%AE%E9%A2%98/" title="并发基础-11、乱序问题">并发基础-11、乱序问题</a></p><h2 id="1-1-编译器屏障-优化屏障"><a href="#1-1-编译器屏障-优化屏障" class="headerlink" title="1.1. 编译器屏障(优化屏障)"></a>1.1. 编译器屏障(优化屏障)</h2><p><strong>Optimization barrier</strong>，解决编译器优化乱序问题。</p><p>编译器编译源代码时，会将源代码进行优化，将源代码的指令进行重排序，以适合于CPU的并行执行。然而，内核同步必须避免指令重新排序，优化屏障（Optimization barrier）是避免编译器的重排序优化操作，保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行。<br>Linux用宏barrier实现优化屏障，gcc编译器的优化屏障宏定义列出如下（在include&#x2F;linux&#x2F;compiler-gcc.h中）：   </p><p><code>#define barrier() __asm__ __volatile__(&quot;&quot;: : :&quot;memory&quot;)</code></p><p>上述定义中，“__asm__”表示插入了汇编语言程序，“__volatile__”表示阻止编译器对该值进行优化，确保变量使用了用户定义的精确地址，而不是装有同一信息的一些别名。“memory”表示指令修改了内存单元。</p><h2 id="1-2-内存屏障-机器屏障"><a href="#1-2-内存屏障-机器屏障" class="headerlink" title="1.2. 内存屏障(机器屏障)"></a>1.2. 内存屏障(机器屏障)</h2><p><strong>Memory barrier</strong>，解决CPU的指令执行乱序和内存写入乱序问题。</p><p><a href="https://monkeysayhi.github.io/2017/12/28/%E4%B8%80%E6%96%87%E8%A7%A3%E5%86%B3%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/">https://monkeysayhi.github.io/2017/12/28/%E4%B8%80%E6%96%87%E8%A7%A3%E5%86%B3%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/</a></p><p><a href="https://www.jianshu.com/p/06717ac8312c">https://www.jianshu.com/p/06717ac8312c</a></p><ul><li>**smp_mb(StoreLoad)**：<br>smp_mb 包含的语义有些“重”，既包含了 Store Buffer 的 flush(首先会使得 CPU 在后续变量变更写入之前，把 Store Buffer 的变更写入 flush 到缓存；CPU 要么就等待 flush 完成后写入，要么就把后续的写入变更放到 Store Buffer 中，直到 Store Buffer 数据顺序刷到缓存。)，又包含了 Invalidate Queue 的等待环节，但现实场景下，我们可能只需要与其中一个数据结构打交道即可。于是，CPU 的设计者把 smp_mb 屏障进一步拆分，一分为二， smp_rmb 称之为读内存屏障，smp_wmb 称之为写内存屏障。他们分别的语义也相应做了简化：</li></ul><p> mfence指令实现了Full Barrier，相当于StoreLoad Barriers。</p><ul><li>**smp_wmb(StoreStore)**：执行后需等待 Store Buffer 中的写入变更 flush 完全到缓存后，后续的写操作才能继续执行，保证执行前后的写操作对其他 CPU 而言是顺序执行的；</li><li>**smp_rmb(LoadLoad)**：执行后需等待 Invalidate Queue 完全应用到缓存后，后续的读操作才能继续执行，保证执行前后的读操作对其他 CPU 而言是顺序执行的；</li></ul><h3 id="1-2-1-mb-与mfence"><a href="#1-2-1-mb-与mfence" class="headerlink" title="1.2.1. mb()与mfence"></a>1.2.1. mb()与mfence</h3><ul><li>**<em>在x86 UP体系架构中</em>**，smp_mb、smp_rmb、smp_wmb被翻译成barrier：</li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">#define barrier() __asm__ __volatile__(&quot;&quot;: : :&quot;memory&quot;)<br></code></pre></td></tr></table></figure><p>__volatile告诉编译器此条语句不进行任何优化，””: : :”memory” 内存单元已被修改、需要重新读入。</p><ul><li>**<em>在x86 SMP体系架构中</em>**，smp_mb、smp_rmb、smp_wmb如下定义：</li></ul><figure class="highlight text"><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 text">/arch/x86/include/asm/system.h：<br>352 /*<br>353  * Force strict CPU ordering.<br>354  * And yes, this is required on UP too when we&#x27;re talking<br>355  * to devices.<br>356  */<br>357 #ifdef CONFIG_X86_32<br>358 /*<br>359  * Some non-Intel clones support out of order store. wmb() ceases to be a<br>360  * nop for these.<br>361  */<br>362 #define mb() alternative(&quot;lock; addl $0,0(%%esp)&quot;, &quot;mfence&quot;, X86_FEATURE_XMM2)<br>363 #define rmb() alternative(&quot;lock; addl $0,0(%%esp)&quot;, &quot;lfence&quot;, X86_FEATURE_XMM2)<br>364 #define wmb() alternative(&quot;lock; addl $0,0(%%esp)&quot;, &quot;sfence&quot;, X86_FEATURE_XMM)<br>365 #else<br>366 #define mb()    asm volatile(&quot;mfence&quot;:::&quot;memory&quot;)<br>367 #define rmb()   asm volatile(&quot;lfence&quot;:::&quot;memory&quot;)<br>368 #define wmb()   asm volatile(&quot;sfence&quot; ::: &quot;memory&quot;)<br>369 #endif<br></code></pre></td></tr></table></figure><ul><li><p>362<del>364行针对x86的32位CPU，366</del>368行针对x86的64位CPU。</p></li><li><p>在x86的64位CPU中，mb()宏实际为：</p></li></ul><figure class="highlight text"><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 text">asm volatile(&quot;sfence&quot; ::: &quot;memory&quot;)。<br>volatile告诉编译器严禁在此处汇编语句与其它语句重组优化，memory强制编译器假设RAM所有内存单元均被汇编指令修改，&quot;sfence&quot; ::: 表示在此插入一条串行化汇编指令sfence。<br>mfence：串行化发生在mfence指令之前的读写操作<br>lfence：串行化发生在mfence指令之前的读操作、但不影响写操作<br>sfence：串行化发生在mfence指令之前的写操作、但不影响读操作<br></code></pre></td></tr></table></figure><ul><li>在x86的32位CPU中，mb()宏实际为：</li></ul><figure class="highlight text"><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 text">mb() alternative(&quot;lock; addl $0,0(%%esp)&quot;, &quot;mfence&quot;, X86_FEATURE_XMM2)<br><br>由于x86的32位CPU有可能不提供mfence、lfence、sfence三条汇编指令的支持，故在不支持mfence的指令中使用：&quot;lock; addl $0,0(%%esp)&quot;, &quot;mfence&quot;。lock表示将“addl $0,0(%%esp)”语句作为内存屏障。<br></code></pre></td></tr></table></figure><h2 id="1-3-JVM屏障"><a href="#1-3-JVM屏障" class="headerlink" title="1.3. JVM屏障"></a>1.3. JVM屏障</h2><p>JVM 按前后分别有读、写两种操作以全排列方式一共提供了四种 Barrier，名称就是左右两边操作的名字拼接。比如 <code>LoadLoad</code> Barrier 就是放在两次 Load 操作中间的 Barrier，<code>LoadStore</code> 就是放在 Load 和 Store 中间的 Barrier。Barrier 类型及其含义如下：</p><ul><li><code>LoadLoad</code>，操作序列 Load1, LoadLoad, Load2，用于保证访问 Load2 的读取操作一定不能重排到 Load1 之前。类似于前面说的 <code>Read Barrier</code>，需要先处理 Invalidate Queue 后再读 Load2；</li><li><code>StoreStore</code>，操作序列 Store1, StoreStore, Store2，用于保证 Store1 及其之后写出的数据一定先于 Store2 写出，即别的 CPU 一定先看到 Store1 的数据，再看到 Store2 的数据。可能会有一次 Store Buffer 的刷写，也可能通过所有写操作都放入 Store Buffer 排序来保证；</li><li><code>LoadStore</code>，操作序列 Load1, LoadStore, Store2，用于保证 Store2 及其之后写出的数据被其它 CPU 看到之前，Load1 读取的数据一定先读入缓存。甚至可能 Store2 的操作依赖于 Load1 的当前值。这个 Barrier 的使用场景可能和上一节讲的 Cache 架构模型很难对应，毕竟那是一个极简结构，并且只是一种具体的 Cache 架构，而 JVM 的 Barrier 要足够抽象去应付各种不同的 Cache 架构。如果跳出上一节的 Cache 架构来说，我理解用到这个 Barrier 的场景可能是说某种 CPU 在写 Store2 的时候，认为刷写 Store2 到内存，将其它 CPU 上 Store2 所在 Cache Line 设置为无效的速度要快于从内存读取 Load1，所以做了这种重排。</li><li><code>StoreLoad</code>，操作序列 Store1, StoreLoad, Load2，用于保证 Store1 写出的数据被其它 CPU 看到后才能读取 Load2 的数据到缓存。如果 Store1 和 Load2 操作的是同一个地址，StoreLoad Barrier 需要保证 Load2 不能读 Store Buffer 内的数据，得是从内存上拉取到的某个别的 CPU 修改过的值。<code>StoreLoad</code> 一般会认为是最重的 Barrier 也是能实现其它所有 Barrier 功能的 Barrier。</li></ul><p>对上面四种 Barrier 解释最好的是来自这里：<a href="https://github.com/openjdk/jdk/blob/6bab0f539fba8fb441697846347597b4a0ade428/src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.code/src/jdk/vm/ci/code/MemoryBarriers.java">jdk&#x2F;MemoryBarriers.java at 6bab0f539fba8fb441697846347597b4a0ade428 · openjdk&#x2F;jdk · GitHub</a></p><table><thead><tr><th>屏障类型</th><th>指令示例</th><th>说明</th></tr></thead><tbody><tr><td>LoadLoad Barriers</td><td>Load1;LoadLoad;Load2</td><td>该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作</td></tr><tr><td>StoreStore Barriers</td><td>Store1;StoreStore;Store2</td><td>该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作</td></tr><tr><td>LoadStore Barriers</td><td>Load1;LoadStore;Store2</td><td>确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作</td></tr><tr><td>StoreLoad Barriers</td><td>Store1;StoreLoad;Load2</td><td>该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作。它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令</td></tr></tbody></table><p><code>StoreLoad</code> 为什么能实现其它 Barrier 的功能？</p><p>这个也是从前一个问题结果能看出来的。<code>StoreLoad</code> 因为对读写操作均有要求，所以它能实现其它 Barrier 的功能。其它 Barrier 都是只对读写之中的一个方面有要求。</p><p>不过这四个 Barrier 只是 Java 为了跨平台而设计出来的，实际上根据 CPU 的不同，对应 CPU 平台上的 JVM 可能可以优化掉一些 Barrier。比如很多 CPU 在读写同一个变量的时候能保证它连续操作的顺序性，那就不用加 Barrier 了。比如 <code>Load x; Load x.field</code> 读 x 再读 x 下面某个 field，如果访问同一个内存 CPU 能保证顺序性，两次读取之间的 Barrier 就不再需要了，根据字节码编译得到的汇编指令中，本来应该插入 Barrier 的地方会被替换为 <code>nop</code>，即空操作。在 x86 上，实际只有 <code>StoreLoad</code> 这一个 Barrier 是有效的，x86 上没有 Invalidate Queue，每次 Store 数据又都会去 Store Buffer 排队，所以 <code>StoreStore</code>， <code>LoadLoad</code> 都不需要。x86 又能保证 Store 操作都会走 Store Buffer 异步刷写，Store 不会被重排到 Load 之前，<code>LoadStore</code> 也是不需要的。只剩下一个 <code>StoreLoad</code> Barrier 在 x86 平台的 JVM 上被使用。<br>来自这里：<a href="https://github.com/openjdk/jdk/blob/9a69bb807beb6693c68a7b11bee435c0bab7ceac/src/hotspot/cpu/x86/assembler_x86.hpp">jdk&#x2F;assembler_x86.hpp at 9a69bb807beb6693c68a7b11bee435c0bab7ceac · openjdk&#x2F;jdk · GitHub</a> 看到 x86 下使用的是 lock 来实现 <code>StoreLoad</code>，并且只处理了 <code>StoreLoad</code>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116140129.png"></p><p>对于x86架构的cpu来说，在单核上来看，其保证了Sequential consistency，因此对于开发者，我们可以完全不用担心单核上的乱序优化会给我们的程序带来正确性问题。在多核上来看，其保证了x86-tso模型，使用mfence就可以将store buffer中的数据，写入到cache中。而且，<span style="background-color:#00ff00">由于x86架构下，store buffer是FIFO的和不存在invalid queue，mfence能够保证多核间的数据可见性，以及顺序性</span>。</p><p>对于arm和power架构的cpu来说，编程就变得危险多了。除了存在数据依赖，控制依赖以及地址依赖等的前后指令不能被乱序之外，其余指令间都有可能存在乱序。而且，它们的store buffer并不是FIFO的，而且还可能存在invalid queue，这些也同样让并发编程变得困难重重。因此需要引入不同类型的barrier来完成不同的需求。</p><p>根据上面表格我们可以看到 x86 平台下，只有 StoreLoad 才有具体的指令对应，而其他三个屏障均是 no-op (空操作)。</p><p>关于 StoreLoad 又有三个具体的指令对应，分别是 <span style="background-color:#ffff00">mfence、cpuid、以及 locked insn</span>，他们都能很好地实现 StoreLoad 的屏障效果。但毕竟不可能同时用三种指令，这里可能意思是，三种均能达到效果，具体实现交由 JVM 设计者决断。</p><p>我们随便写一段代码，查看下JVM采用的是哪一种命令(需要下载 hsdis-amd64.dylib 然后移动到 jre lib 目录)</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs mipsasm"><span class="hljs-keyword">javac </span>VolatileTest.<span class="hljs-keyword">java </span>&amp;&amp; <span class="hljs-keyword">java </span>-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly<br></code></pre></td></tr></table></figure><figure class="highlight csharp"><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 csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">VolatileTest</span> &#123;<br><br><span class="hljs-keyword">volatile</span> <span class="hljs-keyword">static</span> <span class="hljs-built_in">int</span> a = <span class="hljs-number">1</span>;<br><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(<span class="hljs-params">String[] args</span>)</span> &#123;<br>test();<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">test</span>()</span>&#123;<br>a++;<br>&#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>可以看到这里的 StoreLoad 用到的具体指令是lock</p><figure class="highlight x86asm"><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 x86asm"><span class="hljs-number">0x000000010dfedef8</span>: <span class="hljs-keyword">lock</span> addl <span class="hljs-number">$0</span>x0,(%rsp)<br><span class="hljs-number">0x000000010dfedefd</span>: cmpl   <span class="hljs-number">$0</span>x0,-<span class="hljs-number">0x32f1197</span>(%rip)        # <span class="hljs-number">0x000000010acfcd70</span><br>                                              <span class="hljs-comment">;   &#123;external_word&#125;</span><br><span class="hljs-number">0x000000010dfedf07</span>: <span class="hljs-keyword">jne</span>    <span class="hljs-number">0x000000010dfedf1b</span><br></code></pre></td></tr></table></figure><p>lock用于在多处理器中执行指令时对共享内存的独占使用。它的副作用是能够将当前处理器对应缓存的内容刷新到内存，并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。</p><p>简单来说，这句指令的作用就是保证了可见性以及内存屏障。</p><ul><li>执行 a 的写操作后执行到 StoreLoad 内存屏障；</li><li>发出 Lock 指令，锁总线 或 a 的缓存行，那么其他 CPU 不能对已上锁的缓存行有任何操作；</li><li>让其他 CPU 持有的 a 的缓存行失效；</li><li>将 a 的变更写回主内存，保证全局可见；</li></ul><p>上面执行完后，该 CPU 方可执行后续操作。</p><p>JVM 是如何分别插入上面四种内存屏障到指令序列之中的呢？这里的设计相当巧妙。</p><p>对于 volatile 读 or monitor enter</p><figure class="highlight csharp"><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 csharp"><span class="hljs-built_in">int</span> t = x; <span class="hljs-comment">// x 是 volatile 变量</span><br>[<span class="hljs-meta">LoadLoad</span>]<br>[<span class="hljs-meta">LoadStore</span>]<br>&lt;other ops&gt;<br></code></pre></td></tr></table></figure><p>对于 volatile 写 or monitor exit</p><figure class="highlight csharp"><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 csharp">&lt;other ops&gt;<br>[<span class="hljs-meta">StoreStore</span>]<br>[<span class="hljs-meta">LoadStore</span>]<br>x = <span class="hljs-number">1</span>; <span class="hljs-comment">// x 是 volatile 变量</span><br>[<span class="hljs-meta">StoreLoad</span>] <span class="hljs-comment">// 这里带了个尾巴</span><br></code></pre></td></tr></table></figure><h3 id="1-3-1-其他分类"><a href="#1-3-1-其他分类" class="headerlink" title="1.3.1. 其他分类"></a>1.3.1. 其他分类</h3><p>1、按照可见性划分，也就是解决并发问题的可见性：</p><p>加载屏障（Load Barrier）：相当于上面的LoadStoreBarrier，也对应JMM 8种基本操作中的load（载入）</p><p>存储屏障（Store Barrier）：相当于上面的StoreLoadBarrier，也对应JMM 8种基本操作中的Store（存储）</p><p>2、按照有序性划分，也就是解决了并发问题中的有序性：</p><p>获取屏障（Acquire Barrier）：相当于上面的LoadLoadBarrier和LoadStoreBarrier组合</p><p>释放屏障（Release Barrier）：相当于LoadStoreBarrier和StoreStoreBarrier组合</p><p><a href="https://blog.csdn.net/it_lihongmin/article/details/109169260">https://blog.csdn.net/it_lihongmin/article/details/109169260</a></p><h3 id="1-3-2-StoreLoad-最重"><a href="#1-3-2-StoreLoad-最重" class="headerlink" title="1.3.2. StoreLoad 最重"></a>1.3.2. StoreLoad 最重</h3><p>所谓的重实际就是跟内存交互次数，交互越多延迟越大，也就是越重。<code>StoreStore</code>， <code>LoadLoad</code> 两个都不提了，因为它俩要么只限制读，要么只限制写，也即只有一次内存交互。只有 <code>LoadStore</code> 和 <code>StoreLoad</code> 看上去有可能对读写都有限制。但 <code>LoadStore</code> 里实际限制的更多的是读，即 Load 数据进来，它并不对最后的 Store 存出去数据的可见性有要求，只是说 Store 不能重排到 Load 之前。而反观 <code>StoreLoad</code>，它是说不能让 Load 重排到 Store 之前，这么一来得要求在 Load 操作前刷写 Store Buffer 到内存。不去刷 Store Buffer 的话，就可能导致先执行了读取操作，之后再刷 Store Buffer 导致写操作实际被重排到了读之后。而数据一旦刷写出去，别的 CPU 就能看到，看到之后可能就会修改，下一步 Load 操作的内存导致 Load 操作的内存所在 Cache Line 无效。如果允许 Load 操作从一个可能被 Invalidate 的 Cache Line 里读数据，则表示 Load 从实际意义上来说被重排到了 Store 之前，因为这个数据可能是 Store 前就在 Cache 中的，相当于读操作提前了。为了避免这种事发生，Store 完成后一定要去处理 Invalidate Queue，去判断自己 Load 操作的内存所在 Cache Line 是否被设置为无效。这么一来为了满足 <code>StoreLoad</code> 的要求，一方面要刷 Store Buffer，一方面要处理 Invalidate Queue，则最差情况下会有两次内存操作，读写分别一次，所以它最重。</p><h1 id="2-内存模型"><a href="#2-内存模型" class="headerlink" title="2. 内存模型"></a>2. 内存模型</h1><p>Acquire与Release语义<br>对于Acquire来说，保证Acquire后的读写操作不会发生在Acquire动作之前<br>对于Release来说，保证Release前的读写操作不会发生在Release动作之后<br>Acquire &amp; Release 语义保证内存操作仅在acquire和release屏障之间发生</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221125191646.png"></p><p>X86-64中Load读操作本身满足Acquire语义，Store写操作本身也是满足Release语义。但Store-Load操作间等于没有保护，因此仍需要靠mfence或lock等指令才可以满足到Synchronizes-with规则。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221125195347.png"></p><h1 id="3-LOCK指令"><a href="#3-LOCK指令" class="headerlink" title="3. LOCK指令"></a>3. LOCK指令</h1><ol><li>锁总线，其它CPU对内存的读写请求都会被阻塞，直到锁释放，因为锁总线的开销比较大，后来的处理器都采用锁缓存替代锁总线，在无法使用缓存锁的时候会降级使用总线锁</li><li>lock期间的写操作会回写已修改的数据到主内存，同时通过缓存一致性协议让其它CPU相关缓存行失效</li></ol><p>x86 体系下的 LOCK 信号（在汇编里给指令加上 LOCK 前缀），通过锁定总线，禁止其他 CPU 对内存的操作来保证原子性。但这样的锁粒度太粗，其他无关的内存操作也会被阻塞，大幅降低系统性能，而随着核数逐渐增加该问题会愈发显著 —— 要知道现在连家用 CPU 都有16核了。</p><p>因此 Intel 在 Pentium 486 开始引入了用于保证缓存一致性的 MESI 协议，通过锁定对应的 cache line，使得其他 core 无法修改指定内存，从而实现了原子操作（缓存锁）</p><p><code>总线锁</code>、<code>缓存锁</code>可以保证<code>原子性</code>，<code>缓存一致性协议</code>可以保证<code>可见性</code>，</p><h2 id="3-1-总线锁"><a href="#3-1-总线锁" class="headerlink" title="3.1. 总线锁"></a>3.1. 总线锁</h2><p>总线索就是使用处理器提供的一个<code>LOCK#</code>信号，当一个处理器在总线上输出此信号，其他处理器的请求将被阻塞，那么该处理器就可以独占共享锁。</p><p>  在多 cpu 下，当其中一个处理器要对共享内存进行操作的时候，在总线上发出一个 LOCK# 信号，这个信号使得其他处理器无法通过总线来访问到共享内存中的数据，总线锁定把 CPU 和内存之间的通信锁住了，这使得锁定期间，其他处理器<strong>不能操作其他内存地址的数据</strong>，<strong>总线锁定的开销比较大</strong>，这种机制显然是<strong>不合适</strong>的。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">总线锁的力度太大了，最好的方法就是控制锁的保护粒度，只需要保证对于被多个 CPU 缓存的同一份数据是一致的就可以了。所以引入了缓存锁。<br></code></pre></td></tr></table></figure><h2 id="3-2-缓存锁"><a href="#3-2-缓存锁" class="headerlink" title="3.2. 缓存锁"></a>3.2. 缓存锁</h2><p>由于，我们只需要保证对某个内存地址的操作是原子的即可，但是总线锁把CPU和内存之间的通信锁住了，这使得在锁定期间，其他处理器也不能操作其他内存地址的数据，所以总线锁的开销比较大。目前，处理器在某些场合下使用缓存锁来代替总线索进行优化。</p><p><code>缓存锁</code>就是指内存区域如果被缓存在处理器的缓存行中，并且在<code>LOCK#</code>操作期间，那么当它执行操作回写到内存时，处理器不在总线上声言<code>LOCK#</code>信号，而是修改内部的内存地址，并允许它的缓存一致性机制来保证操作的原子性，因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据，其他处理器回写已被锁定的缓存行的数据时，就会使缓存无效</p><p>相比总线锁，缓存锁即降低了锁的力度。核心机制是基于缓存一致性协议来实现的。</p><h2 id="3-3-使用场景"><a href="#3-3-使用场景" class="headerlink" title="3.3. 使用场景"></a>3.3. 使用场景</h2><p>如上面所说，在多数情况下，处理器还是使用缓存锁来代替总线锁，但是在下面两种情况下，我们还是使用总线锁来完成相应保证一致性。</p><ol><li>情况1：当操作的数据不能被缓存在处理器内部，或者操作的数据跨多个缓存行时，则处理器会字调用总线锁锁定。</li><li>情况2：有些处理器不支持缓存行锁定。</li></ol><h2 id="3-4-lock的实现"><a href="#3-4-lock的实现" class="headerlink" title="3.4. lock的实现"></a>3.4. lock的实现</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">关于lock的实现：cpu上有一根pin #HLOCK连到北桥,lock前缀会在执行这条指令前先去拉这根pin，持续到这个指令结束时放开#HLOCK pin，在这期间，北桥会屏蔽掉一切外设以及AGP的内存操作。也就保证了这条指令的atomic。<br></code></pre></td></tr></table></figure><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;内存屏障 – Part 1 - innohub]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;内核同步机制-优化屏障和内存屏障-阿里云开发者社区]]</p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><p><a href="https://www.cnblogs.com/Courage129/p/14360186.html">https://www.cnblogs.com/Courage129/p/14360186.html</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-9、JVM-HSDB</title>
      <link href="/2022/11/19/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-9%E3%80%81JVM-%E8%B0%83%E4%BC%98%E5%B7%A5%E5%85%B7/"/>
      <url>/2022/11/19/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-9%E3%80%81JVM-%E8%B0%83%E4%BC%98%E5%B7%A5%E5%85%B7/</url>
      
        <content type="html"><![CDATA[<h1 id="1-反编译-反汇编"><a href="#1-反编译-反汇编" class="headerlink" title="1. 反编译 反汇编"></a>1. 反编译 反汇编</h1><p><a href="https://juejin.im/post/5b72860e51882560eb6b5269">https://juejin.im/post/5b72860e51882560eb6b5269</a></p><p><a href="https://www.jianshu.com/p/6a8997560b05">https://www.jianshu.com/p/6a8997560b05</a></p><h4 id="1-1-反编译"><a href="#1-1-反编译" class="headerlink" title="1.1. 反编译"></a>1.1. 反编译</h4><p>将 .class 文件逆向成 java源代码</p><h4 id="1-2-反汇编"><a href="#1-2-反汇编" class="headerlink" title="1.2. 反汇编"></a>1.2. 反汇编</h4><p>或者叫字节码解析</p><p>1、javap是jdk自带的反解析工具。它的作用就是根据class字节码文件，反解析出当前类对应的code区（汇编指令）、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。</p><p>一般常用的是-v -l -c三个选项。<br> javap -v classxx，不仅会输出行号、本地变量表信息、反编译汇编代码，还会输出当前类用到的常量池等信息。<br> javap -l 会输出行号和本地变量表信息。<br> javap -c 会对当前class字节码进行反编译生成汇编代码</p><p>安装了Java开发环境的电脑上，可以通过命令⾏ 输⼊ ：javap -c XXX.class ⽂件来查看该class⽂件的编</p><p>译过程。</p><p>javap的⽤法格式： javap 其中classes就是你要反编译的class⽂件。 在命令⾏中直接输⼊javap或</p><p>javap -help可以看到javap的options有如下选项：</p><p>⼀般常⽤的是-v -l -c三个选项。 javap -v classxx，不仅会输出⾏号、本地变量表信息、反编译汇编代</p><p>码，还会输出当前类⽤到的常量池等信息。 javap -l 会输出⾏号和本地变量表信息。 javap -c 会对当前</p><p>class字节码进⾏反编译⽣成汇编代码。 查看汇编代码时，需要知道⾥⾯的jvm指令，可以参考官⽅⽂</p><p>档： <a href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html">https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html</a> </p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118182521.png"></p><p>2、jclasslib工具<br>[[..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221109-IDEA字节码学习查看神器jclasslib bytecode viewer介绍_明明如月学长的博客-CSDN博客_bytecode viewer]]</p><p>3、Hsdis 结合 JITWatch 查看机器汇编代码</p><p><a href="https://juejin.im/post/5b72860e51882560eb6b5269">https://juejin.im/post/5b72860e51882560eb6b5269</a></p><p>HSDIS 是由Project Kenai（kenai.com&#x2F;projects&#x2F;ba… VM JIT编译代码的反汇编插件，作⽤是让</p><p>HotSpot的 -XX:+PrintAssembly 指令调⽤它来把动态⽣成的本地代码还原为汇编代码输出，同时还⽣成了⼤量⾮常有价值的注释，这样我们就可以通过输出的代码来分析问题。</p><p>查看HSDIS是否工作</p><p><code>java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -version</code></p><p>✅ [[..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221109-Mac使用hsdis查看java字节码的汇编命令 - 掘金]]<br>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;000-工具箱专题&#x2F;字节码查看</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221109-idea配置查看代码汇编指令插件_感恩的韭菜的博客-CSDN博客]]</p><h1 id="2-HSDB"><a href="#2-HSDB" class="headerlink" title="2. HSDB"></a>2. HSDB</h1><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230512-0742%%</span>❕ ^nbanwp</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ruby"><span class="hljs-variable">$ </span>sudo java -cp ,<span class="hljs-symbol">:/Library/Java/JavaVirtualMachines/jdk1</span>.<span class="hljs-number">7.0_80</span>.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.<span class="hljs-variable constant_">HSDB</span><br></code></pre></td></tr></table></figure><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ruby"><span class="hljs-variable">$ </span>sudo java -cp ,<span class="hljs-symbol">:/Library/Java/JavaVirtualMachines/jdk1</span>.<span class="hljs-number">8.0_221</span>.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.<span class="hljs-variable constant_">HSDB</span><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></pre></td><td class="code"><pre><code class="hljs java">cd /Library/Java/JavaVirtualMachines/jdk-<span class="hljs-number">11.0</span><span class="hljs-number">.13</span>.jdk/Contents/Home/bin/<br><br>jhsdb hsdb<br></code></pre></td></tr></table></figure><h1 id="3-实战经验"><a href="#3-实战经验" class="headerlink" title="3. 实战经验"></a>3. 实战经验</h1><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;解读HSDB - 简书]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;HSDB（Hotspot Debugger）从入门到实战  Java程序员进阶之路]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;JVM系列之：关于HSDB的一点心得  HeapDump性能社区]]</p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>001-基础知识专题-关键字和接口-3、String和StringTable</title>
      <link href="/2022/11/18/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-3%E3%80%81String%E5%92%8CStringTable%E2%99%A8%EF%B8%8F/"/>
      <url>/2022/11/18/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-3%E3%80%81String%E5%92%8CStringTable%E2%99%A8%EF%B8%8F/</url>
      
        <content type="html"><![CDATA[<h1 id="1-String-基本特性"><a href="#1-String-基本特性" class="headerlink" title="1. String 基本特性"></a>1. String 基本特性</h1><p><strong>String: 代表不可变的字符序列。简称: 不可变性。</strong></p><ul><li>当对字符串 <strong>重新赋值</strong> 时，需要重写指定内存区域赋值，不能使用原有的 value 进行赋值。</li><li>当对现有的字符串进行 <strong>连接操作</strong> 时，也需要重新指定内存区域赋值，不能使用原有的 value 进行赋值。</li><li>当调用 String 的 <strong>replace() 方法修改</strong> 指定字符或字符串时，也需要重新指定内存区域赋值，不能使用原有的 value 进行赋值。</li><li>String 实现了 Serializable 接口: 表示字符串是支持序列化的。实现了 Comparable 接口: 表示 String 可以比较大小</li><li>通过 <strong>字面量的方式</strong>(区别于 new) 给一个字符串赋值，此时的字符串值 <strong>声明在字符串常量池中。</strong></li></ul><h1 id="2-存储结构变更"><a href="#2-存储结构变更" class="headerlink" title="2. 存储结构变更"></a>2. 存储结构变更</h1><p>String 在 <strong>jdk8</strong> 及以前内部定义了 <code>final char[] value</code> 用于存储字符串数据。<strong>jdk9</strong> 时改为 <code>final byte[] value</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221121212634.png"></p><h1 id="3-StringTable"><a href="#3-StringTable" class="headerlink" title="3. StringTable"></a>3. StringTable</h1><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230427-1530%%</span>❕ ^2hufrh</p><h2 id="3-1-特性"><a href="#3-1-特性" class="headerlink" title="3.1. 特性"></a>3.1. 特性</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221121214131.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311130236.jpg" alt="image-20200131011131973"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311173341.png" alt="image.png"></p><h2 id="3-2-存放内容"><a href="#3-2-存放内容" class="headerlink" title="3.2. 存放内容"></a>3.2. 存放内容</h2><p>字符串常量</p><h2 id="3-3-位置"><a href="#3-3-位置" class="headerlink" title="3.3. 位置"></a>3.3. 位置</h2><h3 id="3-3-1-版本演变"><a href="#3-3-1-版本演变" class="headerlink" title="3.3.1. 版本演变"></a>3.3.1. 版本演变</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118190923.jpg"></p><p><strong>总结：</strong></p><p>原来 1.6 时，串池放在永久代，之后 fullGC 时才能垃圾回收，增加了字符串内存占用时间，容易导致永久代内存不足，所以从 1.7 之后，把串池放到堆中，miniGC 就可以对串池进行垃圾回收。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311130127.png" alt="image.png"></p><h3 id="3-3-2-用代码验证-stringtable-位置⭐️🔴"><a href="#3-3-2-用代码验证-stringtable-位置⭐️🔴" class="headerlink" title="3.3.2. 用代码验证 stringtable 位置⭐️🔴"></a>3.3.2. 用代码验证 stringtable 位置⭐️🔴</h3><p><span style="display:none">%%<br>▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1306%%</span>❕ ^ownhou</p><p><strong>JDK1.6</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311130143.jpg" alt="image-20200131021306594"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311201208.png" alt="image.png"></p><p><strong>JDK1.8</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311201223.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311130151.jpg" alt="image-20200131021234495"></p><p>处理办法：</p><h3 id="3-3-3-静态变量本身存放位置"><a href="#3-3-3-静态变量本身存放位置" class="headerlink" title="3.3.3. 静态变量本身存放位置"></a>3.3.3. 静态变量本身存放位置</h3><p>JDK9 之后查看工具：<a href="/2022/11/19/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-9%E3%80%81JVM-%E8%B0%83%E4%BC%98%E5%B7%A5%E5%85%B7/" title="性能调优专题-基础-9、JVM-调优工具">性能调优专题-基础-9、JVM-调优工具</a></p><p><span style="background-color:#ff0000">静态变量位置的变化：指的是静态变量的引用，而不是静态变量等号后面的对象，等号后面 new 的对象始终都在堆上。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118205752.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512075155.png" alt="image.png"></p><h2 id="3-4-调优⭐️🔴"><a href="#3-4-调优⭐️🔴" class="headerlink" title="3.4. 调优⭐️🔴"></a>3.4. 调优⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311130205.jpg" alt="image-20200131094814790"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311130159.jpg" alt="image-20200131094522452"></p><p><strong>line 改为 line.intern()，总结：如果项目中存在大量字符串，并且有大量重复值，则可以用入池动作来减少堆内存的消耗</strong></p><p><a href="https://www.javatt.com/p/47643">https://www.javatt.com/p/47643</a></p><p><span style="background-color:#ff00ff">但是如果大量且不重复，就不能使用 intern 功能！否则 YGC 时间会变长</span></p><a href="/2023/03/04/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-2%E3%80%81%E7%B3%BB%E7%BB%9F%E8%B0%83%E4%BC%98%E6%A1%88%E4%BE%8B%E2%99%A8%EF%B8%8F/" title="性能调优-进阶-2、系统调优案例♨️">性能调优-进阶-2、系统调优案例♨️</a><h2 id="3-5-参考"><a href="#3-5-参考" class="headerlink" title="3.5. 参考"></a>3.5. 参考</h2><p><a href="https://blog.csdn.net/u011635492/article/details/81046174">https://blog.csdn.net/u011635492/article/details/81046174</a><br><a href="https://blog.csdn.net/qq_26222859/article/details/73135660">https://blog.csdn.net/qq_26222859/article/details/73135660</a><br><a href="https://www.bilibili.com/video/av70549061?p=36">https://www.bilibili.com/video/av70549061?p=36</a></p><p>[[JVM系列-第9章-StringTable(字符串常量池)]]</p><h1 id="4-拼接操作"><a href="#4-拼接操作" class="headerlink" title="4. 拼接操作"></a>4. 拼接操作</h1><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?t=351.8&amp;p=122">https://www.bilibili.com/video/BV1PJ411n7xZ?t=351.8&amp;p=122</a></p><p>1．常量与常量的拼接结果在常量池，原理是编译期优化</p><p>2．常量池中不会存在相同内容的常量。</p><p>3．<span style="background-color:#ff00ff">只要其中有一个是变量，结果就在堆中，相当于 new String()。变量拼接的原理是 StringBuilder</span> <span style="display:none">%%<br>▶16.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1853%%</span>❕ ^1el21s</p><p>4．如果拼接的结果调用 intern() 方法，则主动将常量池中还没有的字符串对象放入池中，并返回此对象地址。</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211006082900880.png" alt="image-20211006082900880"></p><h2 id="4-1-编译期优化"><a href="#4-1-编译期优化" class="headerlink" title="4.1. 编译期优化"></a>4.1. 编译期优化</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123084430.png"></p><p><span style="background-color:#00ff00">“a”、”b” 都是常量，运行期不会再改变，所以编译期直接优化为 “ab”</span></p><h2 id="4-2-常量拼接"><a href="#4-2-常量拼接" class="headerlink" title="4.2. 常量拼接"></a>4.2. 常量拼接</h2><p><span style="display:none">%%<br>▶17.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1902%%</span>❕ ^rgva19</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230311185824.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122074428.png"></p><h2 id="4-3-append-与"><a href="#4-3-append-与" class="headerlink" title="4.3. append 与 +"></a>4.3. append 与 +</h2><p>JDK1.6 之后，字符串的 + 操作被优化成 StringBuilder 的 append 方法，所以问 String 和 StringBuffer 的区别，在使用的时候，本质是 StringBuilder 和 StringBuffer 的区别，区别就是 StringBuffer 是线程安全的，StringBuilder 是线程不安全的。</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">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">StringTest5</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>        <span class="hljs-type">long</span> <span class="hljs-variable">timeStart</span> <span class="hljs-operator">=</span> System.currentTimeMillis();<br>        <span class="hljs-type">StringTest5</span> <span class="hljs-variable">stringTest5</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringTest5</span>();<br>       <span class="hljs-comment">// stringTest5.methodByString(100000);              //用时： 5174</span><br>        stringTest5.methodByStringBuilder(<span class="hljs-number">100000</span>);  <span class="hljs-comment">//用时：  4</span><br>        <span class="hljs-type">long</span> <span class="hljs-variable">timeEnd</span> <span class="hljs-operator">=</span> System.currentTimeMillis();<br>        System.out.println(<span class="hljs-string">&quot;用时：&quot;</span>+ (timeEnd-timeStart));<br>    &#125;<br>     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">methodByString</span><span class="hljs-params">(<span class="hljs-type">int</span> times)</span>&#123;<br>        String str=<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; times; i++) &#123;<br>            str= str+ <span class="hljs-string">&quot;a&quot;</span>;  <span class="hljs-comment">// 每次循环都创建一个StringBuilder</span><br>        &#125;<br>    &#125;<br><br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">methodByStringBuilder</span><span class="hljs-params">(<span class="hljs-type">int</span> times)</span>&#123;<br>        <span class="hljs-type">StringBuilder</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();   <span class="hljs-comment">// new StringBuilder(times);</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; times; i++) &#123;<br>            str.append(<span class="hljs-string">&quot;a&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>很显然，对于任意的一个 demo，当执行次数越大，拼接的效率远低于 StringBuild 的 append()，花费的时间比远超两三个数量级</p><blockquote><ul><li>体会执行效率: 通过 StringBuilder 的 append() 的方式添加字符串的效率要远高于使用 string 的字符串拼接方式!</li><li>① StringBuilder 的 append() 的方式: 自始至终中只创建过一个 StringBuilder 的对象<br>使用 string 的字符串拼接方式: 创建过多个 StringBuilder 和 String 的对象</li><li>② 使用 String 的字符串拼接方式: 内存中由于创建了较多的 StringBuilder 和 String 的对象，内存占用更大:</li></ul></blockquote><p>改进：<br><span style="background-color:#00ff00">在实际开发中，如果基本确定要前前后后添加的字符串长度不高于某个限定值 highlevel 的情况下建议使用构造器实例化</span>:<br>stringBuilder s &#x3D; new <strong>StringBuilder(highLevel);</strong>&#x2F;&#x2F;等价于 <code>new char[highLevel]</code></p><h1 id="5-几个对象⭐️🔴"><a href="#5-几个对象⭐️🔴" class="headerlink" title="5. 几个对象⭐️🔴"></a>5. 几个对象⭐️🔴</h1><p><span style="display:none">%%<br>▶18.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1927%%</span>❕ ^8yadcm</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122143549.png"></p><p><span style="background-color:#ff0000"><code>new String(&quot;ab&quot;)</code> 是创建了 2 个对象，但返回出去的是哪个对象的地址？返回的是堆中对象的地址 </span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122134357.png"></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_">StringIntern</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">String</span> <span class="hljs-variable">s</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;1&quot;</span>);<br>        s.intern();<br>        <span class="hljs-type">String</span> <span class="hljs-variable">s2</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;1&quot;</span>;<br>        System.out.println(s == s2);  <span class="hljs-comment">//JDK7/8 :  FALSE  JDK6:  FALSE</span><br><br>      <span class="hljs-comment">/*  String s3 = new String(&quot;1&quot;) + new String(&quot;1&quot;);</span><br><span class="hljs-comment">        s3.intern();</span><br><span class="hljs-comment">        String s4 = &quot;11&quot;;</span><br><span class="hljs-comment">        System.out.println(s3 == s4);  //JDK7/8 :  TRUE  JDK6:  FALSE</span><br><span class="hljs-comment">        */</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122140739.png"><br><span style="display:none">%%<br>▶19.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1939%%</span>❕ ^sc631z</p><h1 id="6-intern⭐️🔴"><a href="#6-intern⭐️🔴" class="headerlink" title="6. intern⭐️🔴"></a>6. intern⭐️🔴</h1><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_">StringIntern</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">/*  String s = new String(&quot;1&quot;);</span><br><span class="hljs-comment">        s.intern();  //调用此方法之前 字符串常量池中已经有1了，所以什么也不做</span><br><span class="hljs-comment">        String s2 = &quot;1&quot;;</span><br><span class="hljs-comment">        System.out.println(s == s2);  //JDK7/8 :  false  JDK6:  FALSE*/</span><br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">s3</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;1&quot;</span>) + <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(<span class="hljs-string">&quot;1&quot;</span>);   <span class="hljs-comment">// s3 变量的地址： new String(&quot;11&quot;)</span><br>        <span class="hljs-comment">//执行完上一行代码后，字符串常量池是否存在 11  ，答  没有</span><br>        <span class="hljs-comment">// 原因是  s3 地址是 StringBuilder 中的 toString 方法的new String() ，此new String () 比较特殊</span><br>        <span class="hljs-comment">// ,只会执行在堆中分配 ， 不会执行在字符串常量池中分配</span><br>        s3.intern();         <span class="hljs-comment">//在字符串常量池中生成  11</span><br>        <span class="hljs-comment">// jdk6：创建一个新的对象“11”，有了新的地址</span><br>        <span class="hljs-comment">// jdk7：此时常量池中没有创建11 ，而是创新了一个指向堆空间中new String(&quot;11&quot;)的地址 。</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">s4</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;11&quot;</span>;    <span class="hljs-comment">//s4变量记录的地址：使用的是上一行代码执行时，在常量池中生成的&quot;11&quot;的地址</span><br>        System.out.println(s3 == s4);  <span class="hljs-comment">//JDK7/8 :  TRUE  JDK6:  FALSE</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122090707.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122090622.png"></p><p>总结 String 的 intern() 的使用:</p><h2 id="6-1-放对象-copy"><a href="#6-1-放对象-copy" class="headerlink" title="6.1. 放对象 copy"></a>6.1. 放对象 copy</h2><ul><li>jdk1.6 中，将这个字符串对象尝试放入串池。<span style="background-color:#00ff00">【放 copy 对象】</span><ul><li>如果串池中有，直接返回已有的串池中的对象的地址</li><li>如果没有，会从堆中 <strong>将对象复制一份</strong>，<span style="background-color:#ff0000">创建一个新对象放入串池</span>，并返回串池中的对象地址</li></ul></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122092319.png"></p><h2 id="6-2-放指针"><a href="#6-2-放指针" class="headerlink" title="6.2. 放指针"></a>6.2. 放指针</h2><p>Jdk1.7 起，将这个字符串对象尝试放入串池。<span style="background-color:#00ff00">【放指针】</span></p><ul><li>如果串池中有，直接返回已有的串池中的对象的地址</li><li>如果没有，则会 <strong>拿堆中对象的引用地址复制一份</strong>，放入串池，并返回串池中的引用地址</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122091559.png"></p><h2 id="6-3-总结"><a href="#6-3-总结" class="headerlink" title="6.3. 总结"></a>6.3. 总结</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122151432.png"></p><h2 id="6-4-练习题"><a href="#6-4-练习题" class="headerlink" title="6.4. 练习题"></a>6.4. 练习题</h2><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-comment">//复习demo</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">StringExer2</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">//执行完以后，会在字符串常量池中会生成&quot;ab”</span><br><span class="hljs-type">string</span> <span class="hljs-variable">s1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">string</span>(<span class="hljs-string">&quot;ab&quot;</span>); <span class="hljs-comment">//false</span><br><span class="hljs-comment">//下述执行完以后，不会在字符串常量池中会生成&quot;ab&quot;</span><br><span class="hljs-comment">//string s1 = new string(&quot;a&quot;) + new String(&quot;b&quot;); //true</span><br>s1.intern();<br><span class="hljs-type">String</span> <span class="hljs-variable">s2</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;ab&quot;</span> ;<br>System.out.println(s1 == s2);<span class="hljs-comment">//false</span><br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122153106.png"></p><h1 id="7-3-个池子"><a href="#7-3-个池子" class="headerlink" title="7. 3 个池子"></a>7. 3 个池子</h1><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-3%E3%80%81JVM-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C3%E4%B8%AA%E6%B1%A0%E5%AD%90/" title="性能调优专题-基础-3、JVM-方法区和3个池子">性能调优专题-基础-3、JVM-方法区和3个池子</a><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h2 id="8-1-空间查看"><a href="#8-1-空间查看" class="headerlink" title="8.1. 空间查看"></a>8.1. 空间查看</h2><p> -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails</p><h2 id="8-2-节省空间"><a href="#8-2-节省空间" class="headerlink" title="8.2. 节省空间"></a>8.2. 节省空间</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122154115.png"></p><h2 id="8-3-G1-对-String-去重优化"><a href="#8-3-G1-对-String-去重优化" class="headerlink" title="8.3. G1 对 String 去重优化"></a>8.3. G1 对 String 去重优化</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122154938.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122155138.png"></p><h1 id="9-参考与感谢"><a href="#9-参考与感谢" class="headerlink" title="9. 参考与感谢"></a>9. 参考与感谢</h1><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><p><a href="https://blog.csdn.net/wei198621/article/details/112389917">https://blog.csdn.net/wei198621/article/details/112389917</a></p><p><a href="https://codeantenna.com/a/mu1CwajTRu#33_StringBuildappend_186">https://codeantenna.com/a/mu1CwajTRu#33_StringBuildappend_186</a></p>]]></content>
      
      
      <categories>
          
          <category> 001-基础知识专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 关键字和接口 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题 - 基础 -7、JVM- 堆和对象创建</title>
      <link href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/"/>
      <url>/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/</url>
      
        <content type="html"><![CDATA[<hr><h1 id="1-堆结构"><a href="#1-堆结构" class="headerlink" title="1. 堆结构"></a>1. 堆结构</h1><p>JDK7 中永久代到了 JDK8 中变成元空间</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106110113.jpg" alt="image-20200323004558129"></p><h1 id="2-调整参数"><a href="#2-调整参数" class="headerlink" title="2. 调整参数"></a>2. 调整参数</h1><p>配置新生代与老年代在堆结构的占比。</p><ul><li>默认 <code>-XX:NewRatio=2</code>，表示新生代占 1，老年代占 2，新生代占整个堆的 1&#x2F;3</li><li>可以修改 <code>-XX:NewRatio=4</code>，表示新生代占 1，老年代占 4，新生代占整个堆的 1&#x2F;5</li></ul><p>在 HotSpot 中，Eden 空间和另外两个 survivor 空间缺省所占的比例是 8：1：1</p><p>当然开发人员可以通过选项“<code>-xx:SurvivorRatio</code>”调整这个空间比例。比如 <code>-xx:SurvivorRatio=8</code></p><p>几乎所有的 Java 对象都是在 Eden 区被 new 出来的。绝大部分的 Java 对象的销毁都在新生代进行了。</p><ul><li>IBM 公司的专门研究表明，新生代中 80% 的对象都是“朝生夕死”的。</li></ul><p>可以使用选项 “<code>-Xmn</code>“ 设置新生代最大内存大小，这个参数一般使用默认值就可以了。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221122185932.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106110139.jpg" alt="image-20200130174446381"></p><p>-Xmx3550m：设置 JVM 最大可用内存为 3550M。<br>-Xms3550m：设置 JVM 促使内存为 3550m。此值可以设置与 -Xmx 相同，以避免每次垃圾回收完成后 JVM 重新分配内存。</p><p>-Xms: 用于设置堆区（新生代 + 老年代）的起始内存，等价于 -XX:InitialHeapSize<br>-X : jvm 的运行参数<br>ms: memory start</p><p>-Xmx: 最大内存</p><h1 id="3-堆内存诊断"><a href="#3-堆内存诊断" class="headerlink" title="3. 堆内存诊断"></a>3. 堆内存诊断</h1><p>OutOfMemoryError: Java heap space</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106110246.png" alt="image-20230106110246694"></p><h1 id="4-对象创建"><a href="#4-对象创建" class="headerlink" title="4. 对象创建"></a>4. 对象创建</h1><h2 id="4-1-创建对象的方式⭐️🔴"><a href="#4-1-创建对象的方式⭐️🔴" class="headerlink" title="4.1. 创建对象的方式⭐️🔴"></a>4.1. 创建对象的方式⭐️🔴</h2><p>对比类加载的触发时机：<a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/" title="性能调优-基础-6、JVM-类装载子系统">性能调优-基础-6、JVM-类装载子系统</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221120090452.png"></p><p>破坏单例的情况：[[内功心法专题-设计模式-3、单例模式#^xk9ru2]]<br>objenesis ：<a href="/2022/12/03/002-%E6%A1%86%E6%9E%B6%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/Spring-1%E3%80%81%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/" title="Spring-1、基本原理">Spring-1、基本原理</a><br><span style="background-color:#ff00ff">Cglib 底层就是用了 Objenesis</span></p><h2 id="4-2-创建对象的过程⭐️🔴"><a href="#4-2-创建对象的过程⭐️🔴" class="headerlink" title="4.2. 创建对象的过程⭐️🔴"></a>4.2. 创建对象的过程⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221120090531.png"></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-1%E3%80%81JVM-GC%E8%B0%83%E4%BC%98/" title="性能调优-进阶-1、JVM-GC调优">性能调优-进阶-1、JVM-GC调优</a><p>❕<span style="display:none">%%<br>🏡⭐️◼️对象创建过程有 6 步◼️⭐️-point-202301202222%%</span><br><a href="https://www.cnblogs.com/mazhilin/p/16640489.html">https://www.cnblogs.com/mazhilin/p/16640489.html</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109165001.png"></p><h3 id="4-2-1-加载、链接、初始化对象对应的类"><a href="#4-2-1-加载、链接、初始化对象对应的类" class="headerlink" title="4.2.1. 加载、链接、初始化对象对应的类"></a>4.2.1. 加载、链接、初始化对象对应的类</h3><p><span style="background-color:#00ff00">虚拟机遇到一条 new 指令，首先去检查这个指令的参数能否在 Metaspace 的常量池中定位到一个类的符号引用，并且检查这个符号引用代表的类是否已经被加载、解析和初始化。（即判断类元信息是否存在）</span>。如果没有，那么在双亲委派模式下，使用当前类加载器以 ClassLoader＋包名＋类名为 Key 进行查找对应的. class 文件。如果没有找到文件，则抛出 ClassNotFoundException 异常，如果找到，则进行类加载，并生成对应的 Class 类对象 ❕<span style="display:none">%%<br>2146-🏡⭐️◼️new 关键字涉及了 2 大步骤 ?🔜MSTM📝 new 关键字：主动使用与创建对象◼️⭐️-point-202302062146%%</span></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/" title="性能调优-基础-6、JVM-类装载子系统">性能调优-基础-6、JVM-类装载子系统</a><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a><h3 id="4-2-2-为对象分配内存"><a href="#4-2-2-为对象分配内存" class="headerlink" title="4.2.2. 为对象分配内存"></a>4.2.2. 为对象分配内存</h3><p>首先计算对象占用空间大小，接着在堆中划分一块内存给新对象。<br>如果实例成员变量是引用变量，仅分配引用变量空间即可，即 4 个字节大小。</p><h4 id="4-2-2-1-如果内存规整，使用指针碰撞"><a href="#4-2-2-1-如果内存规整，使用指针碰撞" class="headerlink" title="4.2.2.1. 如果内存规整，使用指针碰撞"></a>4.2.2.1. 如果内存规整，使用指针碰撞</h4><p>如果内存是规整的，那么虚拟机将采用的是指针碰撞法（Bump The Pointer）来为对象分配内存。意思是所有用过的内存在一边，空闲的内存在另外一边，中间放着一个指针作为分界点的指示器，分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。如果垃圾收集器选择的是 Serial、ParNew 这种基于压缩算法的，虚拟机采用这种分配方式。一般使用带有 compact（整理）过程的收集器时，使用指针碰撞。</p><h4 id="4-2-2-2-如果内存不规整，使用空闲列表分配"><a href="#4-2-2-2-如果内存不规整，使用空闲列表分配" class="headerlink" title="4.2.2.2. 如果内存不规整，使用空闲列表分配"></a>4.2.2.2. 如果内存不规整，使用空闲列表分配</h4><p>如果内存不规整，虚拟机需要维护一个列表，使用空闲列表分配<br>如果内存不是规整的，已使用的内存和未使用的内存相互交错，那么虚拟机将采用的是空闲列表法来为对象分配内存。意思是虚拟机维护了一个列表，记录上哪些内存块是可用的，再分配的时候从列表中找到一块足够大的空间划分给对象实例，并更新列表上的内容。这种分配方式成为“空闲列表（Free List）”。</p><p>说明：选择哪种分配方式由 Java 堆是否规整决定，而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。</p><h3 id="4-2-3-处理并发安全问题"><a href="#4-2-3-处理并发安全问题" class="headerlink" title="4.2.3. 处理并发安全问题"></a>4.2.3. 处理并发安全问题</h3><p>在分配内存空间时，另外一个问题是及时保证 new 对象时候的线程安全性：创建对象是非常频繁的操作，虚拟机需要解决并发问题。虚拟机采用了两种方式解决并发问题：</p><p>·CAS（Compare And Swap）失败重试、区域加锁：保证指针更新操作的原子性；</p><p>·TLAB 把内存分配的动作按照线程划分在不同的空间之中进行，即每个线程在 Java 堆中预先分配一小块内存，称为本地线程分配缓冲区，（TLAB，Thread Local Allocation Buffer）虚拟机是否使用 TLAB，可以通过 -XX：＋／-UseTLAB 参数来设定。<span style="background-color:#ff00ff">默认设定为占用 Eden Space 的 1%</span>。<br>❕<span style="display:none">%%<br>▶3.🏡⭐️◼️TLAB 默认大小 ?🔜MSTM📝 Eden 区的 1%◼️⭐️-point-20230226-0946%%</span></p><h3 id="4-2-4-初始化分配到的空间"><a href="#4-2-4-初始化分配到的空间" class="headerlink" title="4.2.4. 初始化分配到的空间"></a>4.2.4. 初始化分配到的空间</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123113010.png"></p><p>内存分配结束，虚拟机将分配到的内存空间都初始化为零值（不包括对象头）。这一步保证了对象的实例字段在 Java 代码中可以不用赋初始值就可以直接使用，程序能访问到这些字段的数据类型所对应的零值。</p><h3 id="4-2-5-设置对象的对象头"><a href="#4-2-5-设置对象的对象头" class="headerlink" title="4.2.5. 设置对象的对象头"></a>4.2.5. 设置对象的对象头</h3><h4 id="4-2-5-1-对象头介绍⭐️🔴"><a href="#4-2-5-1-对象头介绍⭐️🔴" class="headerlink" title="4.2.5.1. 对象头介绍⭐️🔴"></a>4.2.5.1. 对象头介绍⭐️🔴</h4><a href="/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/" title="对象创建-1、对象内存">对象创建-1、对象内存</a><h4 id="4-2-5-2-对象头的生成"><a href="#4-2-5-2-对象头的生成" class="headerlink" title="4.2.5.2. 对象头的生成"></a>4.2.5.2. 对象头的生成</h4><p>在加载处理完 ClassX 类的一系列逻辑之后，Hotspot 会在堆中为其实例对象 x 开辟一块内存空间存放实例数据，即 JVM 在实例化 ClassX 时，又会创建一个 instanceOop，<span style="background-color:#00ff00">这个 instanceOop 就是 ClassX 对象实例 x 在内存中的对等体，用来存储实例对象的成员变量</span>。<br>让我们来看下 instanceOop.hpp（位于 OpenJDKhotspotsrcsharevmoops 目录下），发现 instanceOop 是继承自 oopDesc 的（class instanceOopDesc : public oopDesc），继续看下 Oop 的结构就会发现：</p><figure class="highlight plaintext"><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 plaintext">class oopDesc &#123;<br>    //友元类<br>    friend class VMStructs;<br>    private:<br>        volatile markOop  _mark;<br>        union _metadata &#123;<br>            Klass*      _klass;<br>            narrowKlass _compressed_klass;<br>        &#125; _metadata;<br><br>    // Fast access to barrier set.  Must be initialized.<br>    //用于内存屏障<br>    static BarrierSet* _bs;<br>    ........<br>&#125;<br></code></pre></td></tr></table></figure><p><code>_mark</code> 和 <code>_metadata</code> 加起来就是我们传说中的对象头了，其中 markOop 的变量 _mark 用来标识 GC 分代信息、线程状态、并发锁等信息。而 _metadata 的联合结构体则用来标识元数据（指向元数据的指针），什么是元数据呢，概念一点就是这个类包含的父类、方法、变量、接口等各种信息，通俗一点来说就是我们上文中所提到的 instanceKlass。仔细看 _metadata 包含两个元素，根据名我们也可以轻易地看出来 _klass 是个正常的指针&#x2F;或者说宽指针（OpenJDK7 以前叫做 wideKlassOop），而 _compressed_klass 则是被压缩过得指针。<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;从new Class()入手浅谈Java的oop-klass模型 - 豆大侠的菜地]]</p><h3 id="4-2-6-执行-init-方法进行初始化"><a href="#4-2-6-执行-init-方法进行初始化" class="headerlink" title="4.2.6. 执行 init 方法进行初始化"></a>4.2.6. 执行 init 方法进行初始化</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123113051.png"></p><p>在 Java 程序的视角看来，初始化才正式开始。初始化成员变量，执行实例化代码块，调用类的构造方法，并把堆内对象的首地址赋值给引用变量。</p><p>因此一般来说（由字节码中是否跟随有 invokespecial 指令所决定），new 指令之后会接着就是执行方法，把对象按照程序员的意愿进行初始化，这样一个真正可用的对象才算完全创建出来。</p><h1 id="5-对象内存布局"><a href="#5-对象内存布局" class="headerlink" title="5. 对象内存布局"></a>5. 对象内存布局</h1><a href="/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/" title="对象创建-1、对象内存">对象创建-1、对象内存</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119092656.jpg"></p><h1 id="6-GC"><a href="#6-GC" class="headerlink" title="6. GC"></a>6. GC</h1><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><h2 id="7-1-尚硅谷-宋红康"><a href="#7-1-尚硅谷-宋红康" class="headerlink" title="7.1. 尚硅谷 - 宋红康"></a>7.1. 尚硅谷 - 宋红康</h2><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=57&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ?p=57&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>配套笔记： <a href="https://www.yuque.com/u21195183/jvm/ar6bqp">https://www.yuque.com/u21195183/jvm/ar6bqp</a><br>配套代码： <a href="https://gitee.com/vectorx/NOTE_JVM">https://gitee.com/vectorx/NOTE_JVM</a><br>已下载：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;NOTE_JVM</p><h2 id="7-2-网络笔记"><a href="#7-2-网络笔记" class="headerlink" title="7.2. 网络笔记"></a>7.2. 网络笔记</h2><p><a href="https://juejin.cn/post/6998389585927487495#heading-29">https://juejin.cn/post/6998389585927487495#heading-29</a><br><a href="https://blog.csdn.net/wei198621/article/details/112389917">https://blog.csdn.net/wei198621/article/details/112389917</a></p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
            <tag> 内存布局 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-5、JVM-虚拟机栈</title>
      <link href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/"/>
      <url>/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/</url>
      
        <content type="html"><![CDATA[<h1 id="1-总体结构"><a href="#1-总体结构" class="headerlink" title="1. 总体结构"></a>1. 总体结构</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119100519.jpg"></p><h1 id="2-基本内容"><a href="#2-基本内容" class="headerlink" title="2. 基本内容"></a>2. 基本内容</h1><h2 id="2-1-线程独立"><a href="#2-1-线程独立" class="headerlink" title="2.1. 线程独立"></a>2.1. 线程独立</h2><p><span style="background-color:#00ff00">每个线程对应各自的虚拟机栈，线程独立</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119101059.png"></p><h2 id="2-2-大小设置"><a href="#2-2-大小设置" class="headerlink" title="2.2. 大小设置"></a>2.2. 大小设置</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119101348.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119101521.png"></p><h1 id="3-栈帧"><a href="#3-栈帧" class="headerlink" title="3. 栈帧"></a>3. 栈帧</h1><h2 id="3-1-基本内容"><a href="#3-1-基本内容" class="headerlink" title="3.1. 基本内容"></a>3.1. 基本内容</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119102039.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119101719.png"></p><h2 id="3-2-运行原理"><a href="#3-2-运行原理" class="headerlink" title="3.2. 运行原理"></a>3.2. 运行原理</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119101821.jpg"></p><h2 id="3-3-本地变量表"><a href="#3-3-本地变量表" class="headerlink" title="3.3. 本地变量表"></a>3.3. 本地变量表</h2><h3 id="3-3-1-基本内容"><a href="#3-3-1-基本内容" class="headerlink" title="3.3.1. 基本内容"></a>3.3.1. 基本内容</h3><p>用来存储<span style="background-color:#00ff00">形参、局部变量、返回值等内容</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119102259.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119102507.png"></p><h3 id="3-3-2-作用影响"><a href="#3-3-2-作用影响" class="headerlink" title="3.3.2. 作用影响"></a>3.3.2. 作用影响</h3><p>主要影响栈帧大小，进而影响栈帧个数，以及是否容易发生溢出</p><h3 id="3-3-3-Slot-运行原理"><a href="#3-3-3-Slot-运行原理" class="headerlink" title="3.3.3. Slot 运行原理"></a>3.3.3. Slot 运行原理</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119103457.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119104354.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119112116.png"></p><p><span style="background-color:#ff00ff">本地变量表 (局部变量表) 是重要的垃圾回收根节点</span></p><h2 id="3-4-操作数栈"><a href="#3-4-操作数栈" class="headerlink" title="3.4. 操作数栈"></a>3.4. 操作数栈</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119113504.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119114926.png"></p><h2 id="3-5-动态链接⭐️🔴"><a href="#3-5-动态链接⭐️🔴" class="headerlink" title="3.5. 动态链接⭐️🔴"></a>3.5. 动态链接⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119131854.png"></p><h3 id="3-5-1-class-文件常量池和运行时常量池"><a href="#3-5-1-class-文件常量池和运行时常量池" class="headerlink" title="3.5.1. class 文件常量池和运行时常量池"></a>3.5.1. class 文件常量池和运行时常量池</h3><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-3%E3%80%81JVM-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C3%E4%B8%AA%E6%B1%A0%E5%AD%90/" title="性能调优专题-基础-3、JVM-方法区和3个池子">性能调优专题-基础-3、JVM-方法区和3个池子</a><h3 id="3-5-2-静态绑定和动态绑定"><a href="#3-5-2-静态绑定和动态绑定" class="headerlink" title="3.5.2. 静态绑定和动态绑定"></a>3.5.2. 静态绑定和动态绑定</h3><p><span style="background-color:#ffff00">将所调用方法的符号引用转为直接引用的方式有 2 种：</span><span style="background-color:#ff00ff">静态绑定和动态绑定</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119132950.png"></p><h3 id="3-5-3-虚方法和非虚方法"><a href="#3-5-3-虚方法和非虚方法" class="headerlink" title="3.5.3. 虚方法和非虚方法"></a>3.5.3. 虚方法和非虚方法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119133744.png"></p><p>静态方法不能重写： <a href="https://blog.csdn.net/gao_zhennan/article/details/72892946">https://blog.csdn.net/gao_zhennan/article/details/72892946</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119133853.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221124143458.png"></p><pre><code>JDK7新增加invokedynamic方法</code></pre><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221124143639.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221124143744.png"></p><p><code>invokestatic</code>, <code>invokespecial</code> 指令调用的方法为非虚方法<br><code>invokevirtual</code>, <code>invokeinterface</code>, <code>invokedynamic</code> 指令调用的方法为虚方法</p><h4 id="3-5-3-1-版本变化"><a href="#3-5-3-1-版本变化" class="headerlink" title="3.5.3.1. 版本变化"></a>3.5.3.1. 版本变化</h4><p>但是！作为私有方法的 <code>private Method</code> 方法，却在字节码中被编译为使用 <code>invokevirtrual</code> 指令来调用。这是为什么呢？</p><p>笔者查阅资料后，发现在 <a href="http://openjdk.java.net/jeps/181">JEP181</a> 中，对方法调用字节码指令进行了一定程度上的修改。在 Java11 版本及以后，嵌套类之间的私有方法的访问权限控制，就从编译期转移到了运行时，从而这样的私有方法也被使用 <code>invokevirtual</code> 指令来调用，</p><p><strong>总而言之，在 Java11 及以后，类中的私有方法往往用 <code>invokevirtual</code> 来调用，接口中的私有方法往往用 <code>invokeinterface</code> 调用，<code>invokespecial</code> 往往仅用于实例构造器方法和父类中的方法。</strong></p><h2 id="3-6-方法出口"><a href="#3-6-方法出口" class="headerlink" title="3.6. 方法出口"></a>3.6. 方法出口</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119134054.png"></p><p><a href="https://github.com/youthlql/JavaYouth/blob/main/docs/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC4%E7%AB%A0-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88.md">异常处理参考</a></p><h1 id="4-多态的本质⭐️🔴"><a href="#4-多态的本质⭐️🔴" class="headerlink" title="4. 多态的本质⭐️🔴"></a>4. 多态的本质⭐️🔴</h1><h2 id="4-1-动态分派"><a href="#4-1-动态分派" class="headerlink" title="4.1. 动态分派"></a>4.1. 动态分派</h2><p>方法重写的本质<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;JVM是如何实现多态的  个人博客]]</p><p><code>invokevirtual</code> 指令的运行时解析过程大致分为以下几个步骤：</p><ul><li>找到<span style="background-color:#00ff00">操作数栈顶的第一个元素</span>所<span style="background-color:#ff00ff">指向的对象的实际类型</span>，记为 C。</li><li>到类型 C 的方法元数据中进行搜索，如果找到与常量池中描述符和简单名称一样的方法，则进行访问权限校验，如果通过则返回这个方法的直接引用，查找过程结束；如果不通过，返回 <code>java.lang.IllegalAccessError</code> 异常。</li><li>否则，按照继承关系从下到上依次对 C 的各个父类进行搜索和验证。</li><li>如果还没有找到合适的方法，则抛出 <code>java.lang.AbstractMethodError</code> 异常。</li></ul><p>由于 <code>invokevirtual</code> 指令执行的第一步就是在运行期间确定接收者的实际类型，所以两次调用中的 <code>invokevirtual</code> 指令<span style="background-color:#00ff00">把常量池中的类方法符号引用解析到了不同的直接引用上</span>，这个过程就是 Java 语言中方法重写的本质。这种在运行期根据实际类型确定方法执行版本的分派过程叫做动态分派。❕<span style="display:none">%%<br>0917-🏡⭐️◼️invokevirtual 方法在寻找方法过程中，可以将常量池中的符号引用，根据实际情况解析到不同的直接引用上，这就是多态的本质◼️⭐️-point-202301300917%%</span><br>案例 ：[[内功心法专题-设计模式-22、访问者模式#4 双分派]]</p><h2 id="4-2-JVM-实现"><a href="#4-2-JVM-实现" class="headerlink" title="4.2. JVM 实现"></a>4.2. JVM 实现</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109154933.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221124102013.png"></p><p>虚方法表中存放着各个方法的 <code>实际入口地址</code>。如果某个方法在子类中没有被重写，那子类的虚方法表里面的地址入口 <code>和父类相同方法的地址入口</code> 是一致的，都指向父类的实现入口。如果子类重写了父类的方法，子类方法表中的地址会替换为 <code>指向子类实现版本的入口地址</code>。</p><p>为了程序实现上的方便，具有相同签名的方法，在父类和子类的虚方法表中都应该具有一样的索引号，这样当类型变换时，仅仅需要 <code>变更查找的方法表</code>，就可以从不同的虚方法表中按索引转换出所需的入口地址。</p><h3 id="4-2-1-方法表"><a href="#4-2-1-方法表" class="headerlink" title="4.2.1. 方法表"></a>4.2.1. 方法表</h3><h4 id="4-2-1-1-方法表-Method-Table"><a href="#4-2-1-1-方法表-Method-Table" class="headerlink" title="4.2.1.1. 方法表 (Method Table)"></a>4.2.1.1. 方法表 (Method Table)</h4><p>介绍了虚分派，接下来介绍是它的一种实现方法—方法表。类似于 C++ 虚函数表 vtbl。</p><p>在有的 JVM 实现中，使用了方法表机制实现虚分派，而有时候，为了节省内存可能不采用方法表的实现。</p><p>不要被方法表这个名字迷惑，它并不是记录所有方法的表。它是为虚分派服务，不会记录用 invokestatic 调用的静态方法和用 invokespecial 调用的构造函数和私有方法。</p><p>JVM 会在链接类的过程中，给类分配相应的方法表内存空间。每个类对应一个方法表。这些都是存在于 method area 区中的。这里与 C++ 略有不同，C++ 中每个对象的第一个指针就是指向了相应的虚函数表。而 Java 中每个实例对象 (<code>InstanceOopDesc</code>) 通过对象头中的 <code>MarkWord中的Klass Pointer</code> 索引到对应的类 (<code>InstanceKlass</code>)，在对应的类数据中对应一个方法表 (<code>vtable</code>)。  <a href="/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/" title="对象创建-1、对象内存">对象创建-1、对象内存</a></p><h4 id="4-2-1-2-方法表的一种实现"><a href="#4-2-1-2-方法表的一种实现" class="headerlink" title="4.2.1.2. 方法表的一种实现"></a>4.2.1.2. 方法表的一种实现</h4><p>父类的方法比子类的方法先得到解析，即父类的方法相对于子类的方法位于表的前列。</p><p>表中每项对应于一个方法，索引到实际方法的实际代码上。如果子类重写了父类中某个方法的代码，则该方法第一次出现的位置的索引更换到子类的实现代码上，而不会在方法表中出现新的项。</p><p>JVM 运行时，当代码索引到一个方法时，是根据它在方法表中的偏移量来实现访问。（第一次执行到调用指令时，会执行解释，将符号索引替换为对应的直接索引）。</p><h4 id="4-2-1-3-invokeinterface-与-invokevirtual-的比较"><a href="#4-2-1-3-invokeinterface-与-invokevirtual-的比较" class="headerlink" title="4.2.1.3. invokeinterface 与 invokevirtual 的比较"></a>4.2.1.3. invokeinterface 与 invokevirtual 的比较</h4><p>当使用 invokeinterface 来调用方法时，由于不同的类可以实现同一 interface,我们无法确定在某个类中的 interface 中的方法处在哪个位置。于是，也就无法解释 CONSTANT_interfaceMethodref-info 为直接索引，而必须每次都执行一次在 methodtable 中的搜索了。所以，在这种实现中，通过 invokeinterface 访问方法比通过 invokevirtual 访问明显慢很多。</p><h2 id="4-3-C-实现"><a href="#4-3-C-实现" class="headerlink" title="4.3. C++ 实现"></a>4.3. C++ 实现</h2><h3 id="4-3-1-虚函数"><a href="#4-3-1-虚函数" class="headerlink" title="4.3.1. 虚函数"></a>4.3.1. 虚函数</h3><p><strong>虚函数</strong> 是在基类中使用关键字 <strong>virtual</strong> 声明的函数。在派生类中重新定义基类中定义的虚函数时，会告诉编译器不要静态链接到该函数。</p><p>我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数，这种操作被称为 <strong>动态链接</strong>，或 <strong>后期绑定</strong>。</p><p>编译器在编译的时候，发现 Father 类中有虚函数，此时编译器会为<span style="background-color:#ffff00">每个</span>包含虚函数的类创建<span style="background-color:#ffff00">一个</span>虚表 (即 vtable)，该表是一个一维数组，在这个数组中存放每个虚函数的地址，</p><p><span style="background-color:#ff00ff">每个包含虚函数的类创建一个虚表，而 Java 中万物皆对象，总不能每个对象都对应一个虚表，会浪费很多空间，所以 Java 中虚函数表是放在类型信息中的</span><br><img src="https://images2015.cnblogs.com/blog/1019006/201611/1019006-20161117144006873-661542828.png"><br>❕<span style="display:none">%%<br>▶6.🏡⭐️◼️Java 中虚函数表放在哪里 ?🔜MSTM📝 方法区中的类型信息中，准确的说是方法区中的 InstanceKlass 中◼️⭐️-point-20230226-2134%%</span><br>那么如何定位虚表呢？编译器另外<span style="background-color:#00ff00">还为每个对象提供了一个虚表指针 (即 vptr)</span>，这个指针<span style="background-color:#00ff00">指向了对象所属类的虚表</span>，在程序运行时，根据对象的类型去初始化 vptr，从而让 vptr 正确的指向了所属类的虚表，从而在调用虚函数的时候，能够找到正确的函数，对于第二段代码程序，由于 pFather 实际指向的对象类型是 Son，因此 vptr 指向的 Son 类的 vtable，当调用 pFather-&gt;Son() 时，根据虚表中的函数地址找到的就是 Son 类的 Say() 函数.</p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h2 id="5-1-调整参数-Xss"><a href="#5-1-调整参数-Xss" class="headerlink" title="5.1. 调整参数 -Xss"></a>5.1. 调整参数 -Xss</h2><p>当我们定义的方法参数和局部变量过多，字节过大，考虑到可能会导致栈深度变小，所以要把线程栈大小调大，就能支持更多的方法调用，也就是能存储更多的栈帧</p><p><a href="https://www.cnblogs.com/rocky-fang/p/8367018.html">https://www.cnblogs.com/rocky-fang/p/8367018.html</a></p><p><a href="https://www.itzhai.com/jvm/how-stack-frame-can-a-thread-hold.html">https://www.itzhai.com/jvm/how-stack-frame-can-a-thread-hold.html</a></p><p>java -Xss2m -cp “C:\Users\rocky fang\Documents\mycode” JavaStackTest</p><p>结论：</p><ul><li>随着线程栈的大小越大，能够支持越多的方法调用，也即是能够存储更多的栈帧；</li><li>局部变量表内容越多，那么栈帧就越大，栈深度就越小。</li></ul><p>我们在评审写代码的时候，发现了堆栈溢出，可以查看下对应类的本地变量表，是不是太多了，可不可以优化下代码，比如，<strong>去掉不必要的参数、变量，以及变量是否太大</strong>。或者加大下线程栈的大小，以增加栈的深度。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105819.jpg" alt="image-20200130215912480"></p><p>因为物理内存是一定的，所以单个栈太大，导致并发线程数变少</p><p><strong>典型案例：</strong></p><p>部门里有员工 list 属性，员工里有部门属性，没有加单向引用设置@JsonIgnore，导致循环引用，出现栈内存溢出</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105829.jpg" alt="image-20200409182716136"></p><h2 id="5-2-线程诊断"><a href="#5-2-线程诊断" class="headerlink" title="5.2. 线程诊断"></a>5.2. 线程诊断</h2><p>cup 占用居高不下</p><p>1.使用 top 查看进程号</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105842.jpg" alt="image-20200130221806323"></p><p>2.查看线程号</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105910.jpg" alt="image-20200130221910911"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105920.jpg" alt="image-20200130222121176"></p><p>3.将线程号换算成 16 进制找到代码行数</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105937.jpg" alt="image-20200130222654058"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105951.jpg" alt="image-20200130222722187"></p><h2 id="5-3-死锁"><a href="#5-3-死锁" class="headerlink" title="5.3. 死锁"></a>5.3. 死锁</h2><p>使用 jstack 分析死锁</p><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-尚硅谷-宋红康"><a href="#6-1-尚硅谷-宋红康" class="headerlink" title="6.1. 尚硅谷 - 宋红康"></a>6.1. 尚硅谷 - 宋红康</h2><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=57&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ?p=57&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a><h2 id="6-2-网络笔记"><a href="#6-2-网络笔记" class="headerlink" title="6.2. 网络笔记"></a>6.2. 网络笔记</h2><p>c++ 教程： <a href="https://www.runoob.com/cplusplus/cpp-polymorphism.html">https://www.runoob.com/cplusplus/cpp-polymorphism.html</a><br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;C++ 多态的实现及原理 - evilsnake - 博客园]]</p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
            <tag> 栈 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优-基础-6、JVM-类加载器</title>
      <link href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/"/>
      <url>/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-6%E3%80%81JVM-%E7%B1%BB%E8%A3%85%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F/</url>
      
        <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102744.jpg" alt="image-20200331161747432"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102910.jpg" alt="image-20200131182420860"></p><h1 id="1-类加载器分类"><a href="#1-类加载器分类" class="headerlink" title="1. 类加载器分类"></a>1. 类加载器分类</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102935.jpg" alt="image-20200409164120724"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102947.jpg" alt="image-20200409164159104"></p><h2 id="1-1-启动类加载器⭐️🔴"><a href="#1-1-启动类加载器⭐️🔴" class="headerlink" title="1.1. 启动类加载器⭐️🔴"></a>1.1. 启动类加载器⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102956.jpg" alt="image-20200409161833646"></p><h2 id="1-2-扩展类加载器"><a href="#1-2-扩展类加载器" class="headerlink" title="1.2. 扩展类加载器"></a>1.2. 扩展类加载器</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103010.jpg" alt="image-20200409162347881"></p><h2 id="1-3-系统类加载器"><a href="#1-3-系统类加载器" class="headerlink" title="1.3. 系统类加载器"></a>1.3. 系统类加载器</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103029.jpg" alt="image-20200409162508361"></p><h2 id="1-4-线程上下文类加载器⭐️🔴"><a href="#1-4-线程上下文类加载器⭐️🔴" class="headerlink" title="1.4. 线程上下文类加载器⭐️🔴"></a>1.4. 线程上下文类加载器⭐️🔴</h2><h3 id="1-4-1-是什么"><a href="#1-4-1-是什么" class="headerlink" title="1.4.1. 是什么"></a>1.4.1. 是什么</h3><p><code>ContextClassLoader</code> 是一种与线程相关的类加载器，<span style="background-color:#ff00ff">类似 <code>ThreadLocal</code>，每个线程对应一个上下文类加载器，主要是用了打破类加载器中的委托机制的</span>。在 SPI 中用来加载实现类。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102648.jpg" alt="image-20200131184244239"></p><p>线程上下文类加载器（Thread Context ClassLoader）<span style="background-color:#00ff00">可以通过 java.lang.Thread 类的 setContextClassLoader() 方法人为设置</span>，<span style="background-color:#ffff00">创建线程时候未指定的话，则默认从父线程中继承</span>。</p><p>那父线程中也没指定呢？那么会<span style="background-color:#ff00ff">默认为应用程序的类加载器</span>。例如：main 方法的线程上下文类加载器就是 sun.misc.Launcher$AppClassLoader。</p><h3 id="1-4-2-用途"><a href="#1-4-2-用途" class="headerlink" title="1.4.2. 用途"></a>1.4.2. 用途</h3><p>SPI 的接口是 Java 核心库的一部分，是由 <strong>引导类加载器</strong>(Bootstrap Classloader) 来加载的；SPI 的实现类是由 <strong>系统类加载器</strong> 来加载的。</p><h3 id="1-4-3-设置时机⭐️🔴"><a href="#1-4-3-设置时机⭐️🔴" class="headerlink" title="1.4.3. 设置时机⭐️🔴"></a>1.4.3. 设置时机⭐️🔴</h3><p>为什么默认的线程上下文类加载器就是系统类加载器呢？肯定是在某个地方给设置了，其实它是在 Launcher 中进行设置的</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108205137.png"></p><p>可以看到 Launcher 类初始化时，先初始化了 ExtClassLoader，然后又初始化了 AppClassLoader，然后把 ExtClassLoader 作为 AppClassLoader 的父 loader，以及设置线程上下文类加载器。<br><span style="background-color:#ff00ff">Launcher 类是由 启动类加载器 加载的，即启动类加载器加载 sun.misc.Launcher 类，Launcher 加载扩展类加载器、系统类加载器以及设置线程上下文类加载器；</span></p><a href="/2022/12/03/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81JVM-%E5%90%AF%E5%8A%A8/" title="性能调优专题-基础-10、JVM-启动">性能调优专题-基础-10、JVM-启动</a><p>#todo<br><span style="display:none"></p><ul><li><input disabled="" type="checkbox"> 🚩 - 虚拟机的启动流程 - 🏡 2023-01-24 14:22</span><span style="background-color:#ff00ff">SpringBoot 的 Fat jar 启动也有一个 Launcher 类</span>[[3、SpringBoot-基础#^lf3ecs]]</li></ul><h2 id="1-5-自定义类加载器"><a href="#1-5-自定义类加载器" class="headerlink" title="1.5. 自定义类加载器"></a>1.5. 自定义类加载器</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103106.jpg" alt="image-20200409163524365"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106102604.jpg" alt="image-20200131184601146"></p><h3 id="1-5-1-获取-classloader-方法"><a href="#1-5-1-获取-classloader-方法" class="headerlink" title="1.5.1. 获取 classloader 方法"></a>1.5.1. 获取 classloader 方法</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221120085819.png"></p><h3 id="1-5-2-Demo"><a href="#1-5-2-Demo" class="headerlink" title="1.5.2. Demo"></a>1.5.2. Demo</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103138.jpg" alt="image-20200409163116504"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103148.jpg" alt="image-20200409163650936"></p><h3 id="1-5-3-API-区别⭐️🔴"><a href="#1-5-3-API-区别⭐️🔴" class="headerlink" title="1.5.3. API 区别⭐️🔴"></a>1.5.3. API 区别⭐️🔴</h3><p>^w4qf6s<br>class.forName() 与 ClassLoader.getSystemClassLoader().loadClass() 的区别</p><p>Class.forName 加载类时将类进了初始化 (第二个参数表示是否初始化，默认为 true)，<font color=#ff0000>而 ClassLoader 的 loadClass 并没有对类进行初始化，只是把类加载到了虚拟机中</font>。loadClass 的参数中默认为 false，表示不进行链接过程中的解析动作，即链接阶段不确定完成，所以初始化肯定不会完成。</p><h4 id="1-5-3-1-Class-forName"><a href="#1-5-3-1-Class-forName" class="headerlink" title="1.5.3.1. Class.forName"></a>1.5.3.1. Class.forName</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109093359.png"><br><span style="background-color:#ff00ff">单个 className 入参的 forName 方法，调用 forName0 时传入的 initialize 参数值默认为 true</span></p><h5 id="1-5-3-1-1-resolveBeanClass"><a href="#1-5-3-1-1-resolveBeanClass" class="headerlink" title="1.5.3.1.1. resolveBeanClass"></a>1.5.3.1.1. resolveBeanClass</h5><p>由 BeanDefinition 种的 String 类型的 beanName 变为 Class 时，并未进行初始化动作<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109101932.png"></p><h4 id="1-5-3-2-ClassLoader-loadClass"><a href="#1-5-3-2-ClassLoader-loadClass" class="headerlink" title="1.5.3.2. ClassLoader.loadClass()"></a>1.5.3.2. ClassLoader.loadClass()</h4><p><span style="background-color:#ff00ff">而 Spring 中传入的 initialize 参数值默认为 false，即默认先不要初始化，达到懒加载的目的</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124141521.png" alt="image.png"></p><h4 id="1-5-3-3-find-load-define-区别"><a href="#1-5-3-3-find-load-define-区别" class="headerlink" title="1.5.3.3. find-load-define 区别"></a>1.5.3.3. find-load-define 区别</h4><p>findClass 类加载逻辑<br>loadClass 如果父类加载器加载失败则会调用自定义的 findClass 方法<br>defineClass 把类字节数组变成类</p><p>如果不打破双亲委派机制，只需重写 findClass 方法即可<br>如果打破双亲委派机制，需要重写整个 loadClass 方法<br>URLClassLoader 实现了 findClass 方法</p><h4 id="1-5-3-4-应用场景"><a href="#1-5-3-4-应用场景" class="headerlink" title="1.5.3.4. 应用场景"></a>1.5.3.4. 应用场景</h4><p>在我们熟悉的 Spring 框架中的 IOC 的实现就是使用的 ClassLoader。</p><p>而在我们使用 JDBC 时通常是使用 Class.forName() 方法来加载数据库连接驱动。这是因为在 JDBC 规范中明确要求 Driver(数据库驱动) 类必须向 DriverManager 注册自己。<br><a href="https://www.cnblogs.com/jimoer/p/9185662.html">https://www.cnblogs.com/jimoer/p/9185662.html</a><br><a href="https://juejin.cn/post/6996669656190681119">https://juejin.cn/post/6996669656190681119</a><br><a href="https://kilric.com/post/classloader/">https://kilric.com/post/classloader/</a><br><a href="http://codefun007.xyz/a/article_detail/779.htm">http://codefun007.xyz/a/article_detail/779.htm</a></p><h1 id="2-类加载器初始化流程"><a href="#2-类加载器初始化流程" class="headerlink" title="2. 类加载器初始化流程"></a>2. 类加载器初始化流程</h1><p>sun.misc.Launcher 类是 java 的入口，在启动 java 应用的时候会首先创建 Launcher 类，创建 Launcher 类的时候回准备应用程序运行中需要的类加载器。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108210329.png"></p><a href="/2022/12/03/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81JVM-%E5%90%AF%E5%8A%A8/" title="性能调优专题-基础-10、JVM-启动">性能调优专题-基础-10、JVM-启动</a><h1 id="3-类加载机制"><a href="#3-类加载机制" class="headerlink" title="3. 类加载机制"></a>3. 类加载机制</h1><h2 id="3-1-双亲委派机制⭐️🔴"><a href="#3-1-双亲委派机制⭐️🔴" class="headerlink" title="3.1. 双亲委派机制⭐️🔴"></a>3.1. 双亲委派机制⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103215.jpg" alt="image-20200331191303146"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103227.jpg" alt="image-20200331191329706"></p><h3 id="3-1-1-全盘委托和双亲委派"><a href="#3-1-1-全盘委托和双亲委派" class="headerlink" title="3.1.1. 全盘委托和双亲委派"></a>3.1.1. 全盘委托和双亲委派</h3><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;(209条消息) 类加载机制：全盘负责和双亲委托_zhangzeyuaaa的博客-CSDN博客_beanfactory父子关系与全盘委托]]</p><h4 id="3-1-1-1-全盘委托"><a href="#3-1-1-1-全盘委托" class="headerlink" title="3.1.1.1. 全盘委托"></a>3.1.1.1. 全盘委托</h4><p>“全盘负责”是指当一个 <a href="https://so.csdn.net/so/search?q=ClassLoader&spm=1001.2101.3001.7020">ClassLoader</a> 装载一个类时，除非显示地使用另一个 ClassLoader，则该类所依赖及引用的类也由这个 CladdLoader 载入。</p><p>例如，系统 <a href="https://so.csdn.net/so/search?q=%E7%B1%BB%E5%8A%A0%E8%BD%BD&spm=1001.2101.3001.7020">类加载</a> 器 AppClassLoader 加载入口类（含有 main 方法的类）时，会把 main 方法所依赖的类及引用的类也载入，依此类推。“全盘负责”机制也可称为当前类加载器负责机制。显然，入口类所依赖的类及引用的类的当前类加载器就是入口类的类加载器。</p><h4 id="3-1-1-2-双亲委派"><a href="#3-1-1-2-双亲委派" class="headerlink" title="3.1.1.2. 双亲委派"></a>3.1.1.2. 双亲委派</h4><p>以上步骤只是调用了 ClassLoader.loadClass(name) 方法，并没有真正定义类。真正加载 class 字节码文件生成 Class 对象由“双亲委派”机制完成。</p><h3 id="3-1-2-源码分析"><a href="#3-1-2-源码分析" class="headerlink" title="3.1.2. 源码分析"></a>3.1.2. 源码分析</h3><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;ClassLoader双亲委派机制源码分析 - 简书]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108224158.png"></p><p><span style="background-color:#ff00ff">如果 parent 不为 null，则由 parent 来进行类加载，依次递归到 ExtClassLoader</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108225459.png"></p><p>ExtClassLoader 的 parent 在 Launcher 构造过程中被设置为了 null，所以会执行 else 的逻辑，调用 <code>findBootstrapClassOrNull()</code>，而该方法最终为 native 方法 <code>private native Class findBootstrapClass(String name)</code>，实际上就是调用 openjdk 中 BootStrap ClassLoader 的实现去加载该类。如果不是启动类加载器负责的路径，那么 c 为 null，递归出来又进到 loadClass 方法中，此时 parent 不为 null，就由 parent 进行加载，依次类推。</p><h2 id="3-2-沙箱安全机制"><a href="#3-2-沙箱安全机制" class="headerlink" title="3.2. 沙箱安全机制"></a>3.2. 沙箱安全机制</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103237.jpg" alt="image-20200331191447294"></p><h2 id="3-3-类相同判断⭐️🔴"><a href="#3-3-类相同判断⭐️🔴" class="headerlink" title="3.3. 类相同判断⭐️🔴"></a>3.3. 类相同判断⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103246.jpg" alt="image-20200331191618945"></p><h2 id="3-4-指定加载器"><a href="#3-4-指定加载器" class="headerlink" title="3.4. 指定加载器"></a>3.4. 指定加载器</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106103257.jpg" alt="image-20200131182841258"></p><p>添加 jar 包到扩展类加载器路径</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105041.jpg" alt="image-20200131183320186"></p><p>打完包后放到 ext 目录下，验证双亲委托加载机制，ext 会加载，应用类加载器就不会再加载</p><h2 id="3-5-类加载过程⭐️🔴"><a href="#3-5-类加载过程⭐️🔴" class="headerlink" title="3.5. 类加载过程⭐️🔴"></a>3.5. 类加载过程⭐️🔴</h2><h3 id="3-5-1-加载"><a href="#3-5-1-加载" class="headerlink" title="3.5.1. 加载"></a>3.5.1. 加载</h3><h4 id="3-5-1-1-加载过程"><a href="#3-5-1-1-加载过程" class="headerlink" title="3.5.1.1. 加载过程"></a>3.5.1.1. 加载过程</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230120220634.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221120102444.png"></p><h4 id="3-5-1-2-加载结果⭐️🔴"><a href="#3-5-1-2-加载结果⭐️🔴" class="headerlink" title="3.5.1.2. 加载结果⭐️🔴"></a>3.5.1.2. 加载结果⭐️🔴</h4><h5 id="3-5-1-2-1-InstanceKlass"><a href="#3-5-1-2-1-InstanceKlass" class="headerlink" title="3.5.1.2.1. InstanceKlass"></a>3.5.1.2.1. InstanceKlass</h5><p>类的字节码被加载到<span style="background-color:#00ff00">元空间的方法区</span>中，生成 <code>InstanceKlass类</code>(包含了类型信息、域信息、方法信息、运行时常量池)，</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221203105613.png"></p><p>InstanceKlass 存着<span style="background-color:#00ff00">Java 类型的名字、继承关系、实现接口关系，字段信息，方法信息，运行时常量池的指针</span>，还有内嵌的<span style="background-color:#ff00ff">虚方法表（vtable）、接口方法表（itable）</span>和记录对象里什么位置上有 GC 会关心的指针（oop map）等等。</p><p>与方法调用紧密相关 <a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/" title="性能调优专题-基础-5、JVM-虚拟机栈">性能调优专题-基础-5、JVM-虚拟机栈</a></p><h5 id="3-5-1-2-2-InstanceMirrorKlass"><a href="#3-5-1-2-2-InstanceMirrorKlass" class="headerlink" title="3.5.1.2.2. InstanceMirrorKlass"></a>3.5.1.2.2. InstanceMirrorKlass</h5><p>同时在 heap 中生成一个 Class 对象 (<code>即InstanceMirrorKlass对象</code>)，<span style="background-color:#00ff00">它持有方法区中 instanceKlass 的内存地址。同时 instanceKlass 中的 _java_mirror 也持有堆中 Class 对象的内存地址</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221121163914.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105246.jpg" alt="image-20200131174616290"></p><h4 id="3-5-1-3-Class-与-Klass-关系"><a href="#3-5-1-3-Class-与-Klass-关系" class="headerlink" title="3.5.1.3. Class 与 Klass 关系"></a>3.5.1.3. Class 与 Klass 关系</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221120123302.png"></p><h4 id="3-5-1-4-总结⭐️🔴"><a href="#3-5-1-4-总结⭐️🔴" class="headerlink" title="3.5.1.4. 总结⭐️🔴"></a>3.5.1.4. 总结⭐️🔴</h4><ol><li>JVM 能执行的就是 Class 文件，所有计算机语言只要最后生成了 Class 文件，都可以交给 JVM 执行。Kotlin、Groovy、JRuby、Jython、Scala 等语言就是如此。</li><li>由于 JVM 是由 C&#x2F;C++ 编写的，<span style="background-color:#ff0000">所以每一个 Java 类加载到 JVM 时都会生成一个对应的 C++ 类，即 InstanceKlass，存放在方法区 (元空间)。同时生成一个 InstanceKlass 的实例对象 (是 InstanceKlass 的子类，也是 C++)，即 InstanceMirrorKlass，放在堆区。</span> ❕<span style="display:none">%%<br>1707-🏡⭐️◼️JVM 类加载完成最终产物是什么？在方法区生成一个 InstanceKlass 对象，在堆中生成一个 InstanceMirrorKlass 对象，并相互持有彼此的引用。我们平时说的 Class 就是这个 InstanceMirrorKlass 对象◼️⭐️-point-202301261707%%</span></li><li>JVM 类加载机制分为，加载、验证、准备、解析、初始化五个阶段。</li><li>通过类的全限定名获取存储该类的 class 文件，并对其进行解析 解析后生成对应的 C++ 模板类，即<span style="background-color:#00ff00">InstanceKlass 实例，存放在元空间，用于 JVM 内部使用 </span><span style="background-color:#ffff00">在堆区生成该类的 Class 对象实例，即 InstanceMirrorKlass，用于其他系统或程序进行调用</span>。</li></ol><p>静态变量跟随 Class 放在了堆里，还有字符串常量池也放在了堆里，具体演变请看👇🏻</p><p>在 JDK 6 及之前的 HotSpot VM 里，静态字段依附在 InstanceKlass 对象的末尾；而在 JDK 7 开始的 HotSpot VM 里，静态字段依附在 java.lang.Class 对象的末尾，所以 7 之后包装成了 InstanceMirrorKlass</p><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-3%E3%80%81JVM-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C3%E4%B8%AA%E6%B1%A0%E5%AD%90/" title="性能调优专题-基础-3、JVM-方法区和3个池子">性能调优专题-基础-3、JVM-方法区和3个池子</a><h3 id="3-5-2-链接"><a href="#3-5-2-链接" class="headerlink" title="3.5.2. 链接"></a>3.5.2. 链接</h3><p>#todo</p><span style="display:none">- [ ] 🚩 - https://cloud.tencent.com/developer/article/1572163 - 🏡 2023-01-24 14:44</span><p>NEW-TOOL:BinaryViewer  idea 有插件</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105315.jpg" alt="image-20200331175222886"></p><h4 id="3-5-2-1-链接-验证"><a href="#3-5-2-1-链接-验证" class="headerlink" title="3.5.2.1. 链接 - 验证"></a>3.5.2.1. 链接 - 验证</h4><p>验证类是否符合 JVM 规范，是安全性检查</p><h4 id="3-5-2-2-链接-准备⭐️🔴"><a href="#3-5-2-2-链接-准备⭐️🔴" class="headerlink" title="3.5.2.2. 链接 - 准备⭐️🔴"></a>3.5.2.2. 链接 - 准备⭐️🔴</h4><p>❕<span style="display:none">%%<br>1523-🏡⭐️◼️链接中的准备阶段做了什么事情？🔜📝：静态变量进行零值初始化，即分配空间赋初始值；final 修饰的常量零值初始化已经在前面的编译阶段完成，在准备阶段进行的是赋值动作，静态变量的赋值动作在类的初始化阶段，即 clinit 方法中◼️⭐️-point-202301241523%%</span></p><h5 id="3-5-2-2-1-静态变量"><a href="#3-5-2-2-1-静态变量" class="headerlink" title="3.5.2.2.1. 静态变量"></a>3.5.2.2.1. 静态变量</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123084014.png"></p><p>注意<span style="background-color:#ffff00">final 的静态引用类型变量</span>这个特例</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105339.jpg" alt="image-20200409221010361"></p><h5 id="3-5-2-2-2-常量区别"><a href="#3-5-2-2-2-常量区别" class="headerlink" title="3.5.2.2.2. 常量区别"></a>3.5.2.2.2. 常量区别</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123083351.png"></p><p>静态变量的赋值动作，是在这个 static 块，即 clinit 方法中被执行的；<br>而 final 修饰的常量是在编译阶段已经完成零值初始化，在准备阶段进行赋值动作。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123083707.png"></p><h5 id="3-5-2-2-3-其他变量"><a href="#3-5-2-2-3-其他变量" class="headerlink" title="3.5.2.2.3. 其他变量"></a>3.5.2.2.3. 其他变量</h5><p>实例变量：随着对象的创建，会在堆空间中分配实例变量空间，并进行默认赋值<br>局部变量：在使用前，必须进行显示赋值，否则编译不通过</p><h5 id="3-5-2-2-4-零值"><a href="#3-5-2-2-4-零值" class="headerlink" title="3.5.2.2.4. 零值"></a>3.5.2.2.4. 零值</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221121164245.png"></p><h4 id="3-5-2-3-链接-解析"><a href="#3-5-2-3-链接-解析" class="headerlink" title="3.5.2.3. 链接 - 解析"></a>3.5.2.3. 链接 - 解析</h4><p><span style="background-color:#ff00ff">new 方法会触发解析动作</span></p><p><strong>对于一个方法的调用，编译器会生成一个包含目标方法所在的类、目标方法名、接收参数类型以及返回值类型的符号引用，来指代要调用的方法。</strong></p><p><strong>解析阶段的目的，就是将这些符号引用解析为实际引用。</strong></p><p>如果符号引用指向一个未被加载的类，或者未被加载类的字段或方法，那么解析将触发这个类的加载（但未必会触发解析与初始化）。</p><p><span style="background-color:#ffff00">将<font color=#ff0000>常量池</font>中的符号引用</span>转为直接引用，能够确切的知道引用类或方法的具体位置和内容。<span style="background-color:#00ff00">除了解析外，其他阶段是顺序发生的，而解析可以与这些阶段交叉进行，因为 Java 支持动态绑定（晚期绑定），需要运行时才能确定具体类型</span></p><h5 id="3-5-2-3-1-解析案例"><a href="#3-5-2-3-1-解析案例" class="headerlink" title="3.5.2.3.1. 解析案例"></a>3.5.2.3.1. 解析案例</h5><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP?p=146&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1yE411Z7AP?p=146&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>代码：[[Load2.java]]</p><p><span style="background-color:#ff00ff">new 方法会触发解析动作，loadclass 方法只是加载 class，并没有触发解析动作</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221127201754.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221127200941.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221127200546.png"></p><h3 id="3-5-3-初始化"><a href="#3-5-3-初始化" class="headerlink" title="3.5.3. 初始化"></a>3.5.3. 初始化</h3><h4 id="3-5-3-1-作用"><a href="#3-5-3-1-作用" class="headerlink" title="3.5.3.1. 作用"></a>3.5.3.1. 作用</h4><p>初始化阶段是执行初始化方法 <code>&lt;clinit&gt; ()</code> 方法的过程，是类加载的最后一步，这一步 JVM 才开始真正执行类中定义的 Java 程序代码 (字节码)。</p><blockquote><p>说明： <code>&lt;clinit&gt; ()</code> 方法是编译之后自动生成的。</p></blockquote><p>对于 <code>&lt;clinit&gt; ()</code> 方法的调用，虚拟机会自己确保其在多线程环境中的安全性。因为 <code>&lt;clinit&gt; ()</code> 方法是<span style="background-color:#ff0000">带锁线程安全</span>，所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞，并且这种阻塞很难被发现。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230122145355.png" alt="image.png"></p><h4 id="3-5-3-2-clinit"><a href="#3-5-3-2-clinit" class="headerlink" title="3.5.3.2. clinit"></a>3.5.3.2. clinit</h4><p>虚拟机会<span style="background-color:#ff00ff">收集类及父类中的类变量及类方法</span>组合为<code>&lt;clinit&gt;</code>方法，根据定义的顺序进行初始化。虚拟机会保证子类的&lt; clinit&gt;执行之前，父类的<code>&lt;clinit&gt;</code>方法先执行完毕。</p><p>因此，虚拟机中第一个被执行完毕的&lt; clinit&gt;方法肯定是 java.lang.Object 方法。</p><p>如果类或者父类中都没有静态变量及方法，虚拟机不会为其生成<code>&lt;clinit&gt;</code>方法。</p><p>接口与类不同的是，执行接口的<code>&lt;clinit&gt;</code>方法不需要先执行父接口的<code>&lt;clinit&gt;</code>方法。只有当父接口中定义的变量使用时，父接口才会初始化。另外，接口的实现类在初始化时也一样不会执行接口的<code>&lt;clinit&gt;</code>方法。</p><p><strong>虚拟机会保证一个类的<code>&lt;clinit&gt;</code>方法在多线程环境中被正确地加锁和同步，如果多个线程同时去初始化一个类，那么只有一个线程去执行这个类的<code>&lt;clinit&gt;</code>方法，其他线程都需要阻塞等待，直到活动线程执行&lt; clinit&gt;方法完毕。</strong></p><h4 id="3-5-3-3-发生时机"><a href="#3-5-3-3-发生时机" class="headerlink" title="3.5.3.3. 发生时机"></a>3.5.3.3. 发生时机</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106105459.jpg" alt="image-20200131180345247"></p><p><strong>PS: 注意多线程下加载时，如果加载出现问题，会导致其他线程都进入阻塞状态。</strong></p><h4 id="3-5-3-4-主动被动使用⭐️🔴"><a href="#3-5-3-4-主动被动使用⭐️🔴" class="headerlink" title="3.5.3.4. 主动被动使用⭐️🔴"></a>3.5.3.4. 主动被动使用⭐️🔴</h4><p>❕<span style="display:none">%%<br>1735-🏡⭐️◼️new 的作用？🔜📝 属于主动使用，会触发类初始化，同时也是类实例化的关键字，所以也会触发类实例化，即创建一个对象◼️⭐️-point-202301221735%%</span><br><strong>主动使用：意味着会调用类的 <code>&lt;clinit&gt;()</code>，即执行了类的初始化阶段<br>被动使用：就是指没有调用类的 <code>&lt;clinit&gt;()</code>，没有执行初始化阶段</strong></p><p><a href="https://www.sukaidev.top/2021/03/30/d90e9b80/#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E8%BF%87%E7%A8%8B">https://www.sukaidev.top/2021/03/30/d90e9b80/#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E8%BF%87%E7%A8%8B</a></p><p>[[ActiveUse1.java]]</p><p>需要注意的是，在上述 7 个阶段中，只有加载、验证、准备、初始化和卸载这 5 个阶段的顺序是确定的，类的加载过程必须按照这种顺序按部就班地开始，并且是互相交叉地混合式进行。解析阶段在某些情况下可以在初始化阶段之后再开始，<span style="background-color:#00ff00">直接是为了支持 Java 语言的运行时绑定（也称为动态绑定或晚期绑定）</span>。</p><p>那么什么时候会触发这个流程呢？《Java 虚拟机规范》中并没有进行强制约束，这点可以交给虚拟机的具体实现来自由把握。不过，规范中严格规定了有且只有以下 8 种情况必须立即对类进行“初始化”：</p><ol><li>当创建一个类的实例时，比如使用<span style="background-color:#00ff00">new 关键字</span>，或者通过<span style="background-color:#00ff00">反射</span>、<span style="background-color:#00ff00">克隆</span>、<span style="background-color:#00ff00">反序列化</span></li><li>当<span style="background-color:#00ff00">调用类的静态方法时</span>，即当使用了字节码 invokestatic 指令。</li><li>当<span style="background-color:#00ff00">使用类、接口的静态字段时 (final 修饰需分情况考虑)</span>，比如，使用 getstatic 或者 putstatic 指令。（对应访问变量、赋值变量操作）❕<span style="display:none">%%<br>1752-🏡⭐️◼️final 修饰的静态变量一定不会触发初始化吗？🔜📝如果调用类 final 修饰的静态变量，是否初始化要看变量有没有涉及到计算，比如方法调用、构造器调用，如果不涉及就是在链接阶段的准备阶段赋值，否则就在初始化阶段赋值。方法调用比较隐晦的包装类的自动装箱，也是在初始化的时候赋值的◼️⭐️-point-202301221752%%</span></li></ol><p><span style="background-color:#ff00ff">下面例子中变量 c 虽然使用了 final 修饰，但是会潜在一个 Integer 自动装箱的动作，需要初始化的赋值动作，所以触发了 E 的初始化。而 a 和 b 因为是基本数据类型和字符串字面量，在链接准备阶段就完成初始化赋值，所以不会再触发后面的初始化动作</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230110105224.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230110105351.png"></p><p><span style="background-color:#ff0000">static 与 final 的搭配使用</span>-&gt;[[001-基础知识专题-关键字和接口-2、final]]</p><ol start="4"><li>使用 java.lang.reflect 包的方法<span style="background-color:#00ff00">对类型的方法进行反射调用的时候</span>，如果类型没有进行过初始化，则需要先触发其初始化。比如：Class.forName(“com.atguigu.java.Test”)</li><li>当初始化类的时候，如果发现其父类还没有进行过初始化，则需要先触发其父类的初始化。<span style="background-color:#ffff00">但是这条规则并不适用于接口。<font color=#ff9900>但如果接口中定义了 default 方法</font>，那么实现了该接口的子类初始化就会触发接口的初始化 ，即下面第 7 条所述内容</span> ❕<span style="display:none">%%<br>1733-🏡⭐️◼️接口和实现类触发初始化的条件？🔜📝子类初始化会触发父类初始化，但实现类初始化不一定会触发接口初始化，除非实现的方法是 default 类型的方法◼️⭐️-point-202301221733%%</span></li><li><span style="background-color:#00ff00">当一个接口中定义了 JDK 8 新加入的默认方法（被 default 关键字修饰的接口方法）时，如果有这个接口的实现类发生了初始化，那该接口要在其之前被初始化。</span></li><li>当虚拟机启动时，用户需要指定一个要执行的主类（包含 main() 方法的那个类），虚拟机会先初始化这个主类。![[JVM源码分析之JVM启动流程_猿灯塔_InfoQ写作社区#^75pqt1]]</li><li>当使用 JDK 7 新加入的动态语言支持时，如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄，并且这个方法句柄对应的类没有进行过初始化，则需要先触发其初始化。</li></ol><p>这 8 种场景中的行为称为对一个类型进行 <strong>主动引用</strong>，那么对应的就存在 <strong>被动引用</strong> 了，被动引用时将不会˝触发初始化过程。例如有 4 个被动引用的场景：</p><ol><li>通过子类引用父类的静态字段，父子类都加载了，但父类会初始化，而不会导致子类初始化。❕<span style="display:none">%%<br>1734-🏡⭐️◼️子类引用父类静态字段初始化情况是什么？🔜📝 父子类都会加载，但父类初始化，子类并不会初始化◼️⭐️-point-202301221734%%</span>可以通过启动参数 <code>-XX:+TraceClassLoading</code> 查看类加载情况</li><li>通过数组定义引用的类，不会触发此类的初始化。</li></ol><p><strong>对于数组类的话，数组类的元素类型本身也需要进行类加载 (不会初始化)，不过数组类本身并不通过类加载器创建，而是直接由 Java 虚拟机在内存中动态构造出来。</strong><br>❕<span style="display:none">%%<br>1601-🏡⭐️◼️数组元素引用的类会加载但不会初始化◼️⭐️-point-202301221601%%</span><br>因为对象内存模型中，array 是另一个子类，[[Java的对象模型——Oop-Klass模型（二） - 掘金#Klass的继承体系]]</p><p>SpringIOC 中类加载也有体现数组类型的创建是由虚拟机调用了本地方法：<a href="https://www.processon.com/diagraming/63bb6db902b9ad68cd932a68">https://www.processon.com/diagraming/63bb6db902b9ad68cd932a68</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230111111935.png" alt="image.png"></p><ol start="3"><li>常量在编译阶段会存入调用类的常量池中，本质上没有直接引用到定义常量的类，因此不会触发定义常量的类的初始化。</li><li>类对象.class</li><li>ClassLoader 的 loadclass，如下图所示<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230124141521.png" alt="image.png"><br>loadclass 默认是不解析的，即链接阶段未完成，所以肯定不会是初始化完成的<br>❕<span style="display:none">%%<br>1418-🏡⭐️◼️loadClass 的 resolve 参数含义？🔜📝 不进行解析动作，表示链接阶段未完成，所以肯定不会是初始化完成的类。我们平时调用的单参数的 loadClass 就是不进行解析动作的◼️⭐️-point-202301241418%%</span></li><li>class.forName 第二个参数为 false，表示不进行初始化</li></ol><h4 id="3-5-3-5-其他补充"><a href="#3-5-3-5-其他补充" class="headerlink" title="3.5.3.5. 其他补充"></a>3.5.3.5. 其他补充</h4><p>当 Java 虚拟机初始化一个类时，要求它的所有父类都已经被初始化，<span style="background-color:#ffff00">但是这条规则并不适用于接口。 </span></p><blockquote><p>在初始化一个类时，并不会先初始化它所实现的接口<br>在初始化一个接口时，并不会先初始化它的父接口</p></blockquote><p> 因此，<span style="background-color:#ff00ff">一个父接口并不会因为它的子接口或者实现类的初始化而初始化</span>。只有当程序首次使用特定接口的静态字段时，才会导致该接口的初始化。</p><h1 id="4-初始化与实例化区别⭐️🔴"><a href="#4-初始化与实例化区别⭐️🔴" class="headerlink" title="4. 初始化与实例化区别⭐️🔴"></a>4. 初始化与实例化区别⭐️🔴</h1><p>实例化内容：<a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123092414.png"></p><h2 id="4-1-实例化总体流程"><a href="#4-1-实例化总体流程" class="headerlink" title="4.1. 实例化总体流程"></a>4.1. 实例化总体流程</h2><ol><li>父类普通成员变量和普通代码块；</li><li>父类的构造函数；</li><li>子类普通成员变量和普通代码块；</li><li>子类的构造函数。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221123081907.png"></p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;Java类的 初始化 和 实例化区别_Fox_bert的博客-CSDN博客_java实例化和初始化的区别]]</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;类加载、对象实例化知识点一网打尽 - jimuzz - 博客园]]</p><h1 id="5-实战经验"><a href="#5-实战经验" class="headerlink" title="5. 实战经验"></a>5. 实战经验</h1><h2 id="5-1-SPI-是否破坏了双亲委派机制"><a href="#5-1-SPI-是否破坏了双亲委派机制" class="headerlink" title="5.1. SPI 是否破坏了双亲委派机制"></a>5.1. SPI 是否破坏了双亲委派机制</h2><h3 id="5-1-1-双亲委派"><a href="#5-1-1-双亲委派" class="headerlink" title="5.1.1. 双亲委派"></a>5.1.1. 双亲委派</h3><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3MTM1NDIx,size_16,color_FFFFFF,t_70.jpeg" alt="双亲委派模型示意图"></p><p><a href="https://www.jianshu.com/p/3a3edbcd8f24">https://www.jianshu.com/p/3a3edbcd8f24</a></p><p><a href="https://www.zhihu.com/question/49667892">https://www.zhihu.com/question/49667892</a></p><p><strong>首先双亲委派原则本身并非 JVM 强制模型。</strong></p><h3 id="5-1-2-SPI⭐️🔴"><a href="#5-1-2-SPI⭐️🔴" class="headerlink" title="5.1.2. SPI⭐️🔴"></a>5.1.2. SPI⭐️🔴</h3><p>SPI ，全称为 Service Provider Interface，是一种服务发现机制。它通过在 ClassPath 路径下的 META-INF&#x2F;services 文件夹查找文件，文件内容为类的全限定名，自动加载文件里所定义的类。</p><p>这一机制为很多框架扩展提供了可能，比如在 Dubbo、JDBC 中都使用到了 SPI 机制。</p><h4 id="5-1-2-1-原理剖析"><a href="#5-1-2-1-原理剖析" class="headerlink" title="5.1.2.1. 原理剖析"></a>5.1.2.1. 原理剖析</h4><p>SPI 的<span style="background-color:#00ff00">调用方和接口定义方</span>很可能都在 <span style="background-color:#00ff00">Java 的核心类库</span>之中，<span style="background-color:#ffff00">而实现类交由开发者实现，然而实现类并不会被启动类加载器所加载，基于双亲委派的可见性原则，SPI 调用方无法拿到实现类。</span><br>❕<span style="display:none">%%<br>1727-🏡⭐️◼️SPI 的发生场景？🔜📝 核心接口在核心类库中定义，比如 rt.jar 中，这种 jar 包是由 bootstrap 类加载器加载，而接口的实现一般是由第三方实现，比如数据库连接的 mysql 和 Oracle 的实现，这些实现的 jar 包所在路径不是 bootstrap 类加载器的负责范围，所以需要更换一个加载器加载，就向下委托给了线程上下文加载器。线程上下文加载器如果没有人为指定，会在虚拟机启动时赋值为 main 函数的类加载器，即应用类加载器◼️⭐️-point-202301221727%%</span><br>SPI Serviceloader 通过线程上下文获取能够加载实现类的 classloader，一般情况下是 application classloader，绕过了这层限制，逻辑上打破了双亲委派原则。</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210911224636314.png" alt="image-20210911224636314"></p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210911221924019.png" alt="image-20210911221924019"></p><p><a href="https://blog.csdn.net/m0_37135421/article/details/103770096">https://blog.csdn.net/m0_37135421/article/details/103770096</a></p><p>JVM 规范：全盘委托和双亲委派</p><p>全盘委托：加载某类时，其引用的类的加载也由同一个加载器加载</p><p>双亲委派：加载类时，向上查找 (先查找父加载器的缓存，然后负责的路径)，父加载器找不到，再往下查找加载</p><p>SPI 的情况是<span style="background-color:#ffff00">bootstrap 类加载器负责的 rt.jar 里，定义了接口规范，比如数据库连接的接口，由 bootstrap 类加载器加载</span>，<span style="background-color:#ff00ff">但接口中引用了各个厂商实现类，根据全盘委托机制，也应该由 bootstrap 类加载器加载</span>，<span style="background-color:#ff0000">但是这些实现类所在目录不在 bootstrap 类加载器的负责范围之内，此时使用了在启动类中设置为上下文类加载器的应用类加载器，所以打破了双亲委派机制。</span></p><h4 id="5-1-2-2-触发逻辑"><a href="#5-1-2-2-触发逻辑" class="headerlink" title="5.1.2.2. 触发逻辑"></a>5.1.2.2. 触发逻辑</h4><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230401-0759%%</span>❕ ^3e5oac</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210911224008630.png" alt="image-20210911224008630"><br><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20210911224057589.png" alt="image-20210911224057589"></p><p><strong>因为类加载器收到加载范围的限制，在某些情况下父加载器无法加载到需要的文件，就需要委托给子类加载器去加载 class 文件。</strong><br>JDBC 的 Driver 接口定义在 JDK 中，其实现有数据库的各个厂商提供。<code>DriverManager类中要加载各个实现了Driver接口的类统一进行管理，Driver类位于JAVA_HOME中jre/lib/rt.jar中</code>，应该由 Bootstrap 类加载器进行加载，而各个 Driver 的实现类位于各个服务商提供的 jar 包中。根据类加载机制，当被加载的类引用了另外一个类时，虚拟机就会使用装载第一个类的类加载器装在被引用的类，也就是说应该使用 Bootstrap 类加载器去加载各个厂商提供的 Driver 类。但是，Bootstrap 类加载器只负责加载 JAVA_HOME 中 jre&#x2F;lib&#x2F;rt.jar 中所有的 class，所以需要由子类加载器去加载 Driver 的实现类，这就破坏了双亲委派模型。</p><p>在 Java 应用中存在着很多服务提供者接口（Service Provider Interface，SPI），这些接口允许第三方为它们提供实现，如常见的 SPI 有 JDBC、JNDI 等，这些 SPI 的接口属于 Java 核心库，一般存在 rt.jar 包中，由 Bootstrap 类加载器加载。而 Bootstrap 类加载器无法直接加载 SPI 的实现类，同时由于双亲委派模式的存在，Bootstrap 类加载器也无法反向委托 AppClassLoader 加载器 SPI 的实现类。在这种情况下，我们就需要一种特殊的类加载器来加载第三方的类库，而线程上下文类加载器（双亲委派模型的破坏者）就是很好的选择。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221204102527.png"></p><p>从图可知 rt.jar 核心包是有 Bootstrap 类加载器加载的，其内包含 SPI 核心接口类，由于 SPI 中的类经常需要调用外部实现类的方法，而 jdbc.jar 包含外部实现类 (jdbc.jar 存在于 classpath 路径) 无法通过 Bootstrap 类加载器加载，因此只能委派线程上下文类加载器把 jdbc.jar 中的实现类加载到内存以便 SPI 相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”，它在执行过程中抛弃双亲委派加载链模式，使程序可以逆向使用类加载器，当然这也使得 Java 类加载器变得更加灵活。</p><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-尚硅谷-宋红康"><a href="#6-1-尚硅谷-宋红康" class="headerlink" title="6.1. 尚硅谷 - 宋红康"></a>6.1. 尚硅谷 - 宋红康</h2><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>[[..&#x2F;..&#x2F;..&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;007-性能调优专题&#x2F;001-JVM&#x2F;黑马程序员JVM完整教程&#x2F;讲义&#x2F;3_类加载与字节码技术.pdf]]<br><a href="https://javaguide.cn/java/jvm/class-loading-process.html">https://javaguide.cn/java/jvm/class-loading-process.html</a><br><a href="https://blog.csdn.net/m0_37135421/article/details/103770096">https://blog.csdn.net/m0_37135421/article/details/103770096</a><br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;【没啥用的知识】一砖一瓦皆根基 —— Java类加载之Class对象到Klass模型 - 掘金]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;【JVM】类加载_zxfhahaha的博客-CSDN博客]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;Java的对象模型——Oop-Klass模型（一） - 掘金]]</p><p>OOP-Klass 模型：<a href="https://juejin.cn/post/6998389585927487495">https://juejin.cn/post/6998389585927487495</a></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/" title="性能调优专题-基础-5、JVM-虚拟机栈">性能调优专题-基础-5、JVM-虚拟机栈</a><p>[[shishenm]]</p><p>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;JVMDemo</p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a><h2 id="6-2-黑马程序员"><a href="#6-2-黑马程序员" class="headerlink" title="6.2. 黑马程序员"></a>6.2. 黑马程序员</h2><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP?p=146&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1yE411Z7AP?p=146&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>示例代码：&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;jvm</p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-3、JVM-方法区和3个池子</title>
      <link href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-3%E3%80%81JVM-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C3%E4%B8%AA%E6%B1%A0%E5%AD%90/"/>
      <url>/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-3%E3%80%81JVM-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C3%E4%B8%AA%E6%B1%A0%E5%AD%90/</url>
      
        <content type="html"><![CDATA[<p>方法区也需要进行垃圾回收，<span style="background-color:#ffff00">大约5%</span>的垃圾回收发生在方法区，但回收率并不高。只有在full GC时才会回收。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108170254.jpg" alt="image-20200409174152551"></p><h1 id="1-不同实现"><a href="#1-不同实现" class="headerlink" title="1. 不同实现"></a>1. 不同实现</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118161210.jpg"></p><p>JDK7用永久代实现方法区</p><p>JDK8后用元空间实现方法区，并且将方法区中的字符串常量池和类的静态变量放到堆中，只保留下类的元数据信息（方法元信息、类元信息）</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118161440.png"></p><h1 id="2-内存设置"><a href="#2-内存设置" class="headerlink" title="2. 内存设置"></a>2. 内存设置</h1><h2 id="2-1-默认大小"><a href="#2-1-默认大小" class="headerlink" title="2.1. 默认大小"></a>2.1. 默认大小</h2><p>#todo</p><span style="display:none">- [ ] 🚩 - 方法区笔记细化：https://www.yuque.com/u21195183/jvm/nbkm46 - 🏡 2023-01-26 20:14</span><h2 id="2-2-内存溢出"><a href="#2-2-内存溢出" class="headerlink" title="2.2. 内存溢出"></a>2.2. 内存溢出</h2><p>1.加载类的二进制字节码</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108170314.jpg" alt="image-20200130232757295"></p><p>2.jdk1.8设置元空间大小模拟方法区内存溢出</p><p>-XX:MaxMetaspaceSize&#x3D;8m</p><p>3.jdk1.6设置永久代大小模拟方法区内存溢出</p><p>-XX:MaxPermSize&#x3D;8m</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108170325.jpg" alt="image-20200130233431899"></p><p><code>框架中的cglib会产生大量的字节码</code></p><h1 id="3-内部结构"><a href="#3-内部结构" class="headerlink" title="3. 内部结构"></a>3. 内部结构</h1><h2 id="3-1-类型信息"><a href="#3-1-类型信息" class="headerlink" title="3.1. 类型信息"></a>3.1. 类型信息</h2><p>对每个加载的类型（类class、接口interface、枚举enum、注解annotation），JVM必须在方法区中存储以下类型信息：</p><ol><li>这个类型的完整有效名称（全名&#x3D;包名.类名）</li><li>这个类型直接父类的完整有效名（对于interface或是java.lang.object，都没有父类）</li><li>这个类型的修饰符（public，abstract，final的某个子集）</li><li>这个类型直接接口的一个有序列表</li></ol><h2 id="3-2-域（Field）信息"><a href="#3-2-域（Field）信息" class="headerlink" title="3.2. 域（Field）信息"></a>3.2. 域（Field）信息</h2><p>JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。</p><p>域的相关信息包括：域名称、域类型、域修饰符（public，private，protected，static，final，volatile，transient的某个子集）</p><h2 id="3-3-方法（Method）信息"><a href="#3-3-方法（Method）信息" class="headerlink" title="3.3. 方法（Method）信息"></a>3.3. 方法（Method）信息</h2><p>JVM必须保存所有方法的以下信息，同域信息一样包括声明顺序：</p><ol><li>方法名称</li><li>方法的返回类型（或void）</li><li>方法参数的数量和类型（按顺序）</li><li>方法的修饰符（public，private，protected，static，final，synchronized，native，abstract的一个子集）</li><li>方法的字节码（bytecodes）、操作数栈、局部变量表及大小（abstract和native方法除外）</li><li><span style="background-color:#ff00ff">异常表（abstract和native方法除外）</span><br><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230413-1054%%</span>❕ ^jf3el2</li></ol><ul><li><span style="background-color:#00ff00">每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引</span></li></ul><h2 id="3-4-non-final的类变量"><a href="#3-4-non-final的类变量" class="headerlink" title="3.4. non-final的类变量"></a>3.4. non-final的类变量</h2><ul><li>静态变量和类关联在一起，随着类的加载而加载，他们成为类数据在逻辑上的一部分</li><li>类变量被类的所有实例共享，即使没有类实例时，你也可以访问它</li></ul><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_">MethodAreaTest</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">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.hello();<br>        System.out.println(order.count);<br>    &#125;<br>&#125;<br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Order</span> &#123;<br>    <span class="hljs-keyword">public</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">1</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">hello</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;hello!&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-5-补充说明：全局常量（static-final）"><a href="#3-5-补充说明：全局常量（static-final）" class="headerlink" title="3.5. 补充说明：全局常量（static final）"></a>3.5. 补充说明：全局常量（static final）</h2><p>被声明为final的类变量的处理方法则不同，每个全局常量在编译的时候就会被分配了。</p><h1 id="4-演进细节"><a href="#4-演进细节" class="headerlink" title="4. 演进细节"></a>4. 演进细节</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118190651.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118190923.jpg"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118192137.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118192206.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118192234.png"></p><h2 id="4-1-为什么要替换永久代⭐️🔴"><a href="#4-1-为什么要替换永久代⭐️🔴" class="headerlink" title="4.1. 为什么要替换永久代⭐️🔴"></a>4.1. 为什么要替换永久代⭐️🔴</h2><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=97&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204#t=964.183677">16:04</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118195153.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118200524.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118200342.png"></p><h3 id="4-1-1-永久代设置内存大小是很难确定的"><a href="#4-1-1-永久代设置内存大小是很难确定的" class="headerlink" title="4.1.1. 永久代设置内存大小是很难确定的"></a>4.1.1. 永久代设置内存大小是很难确定的</h3><h4 id="4-1-1-1-方法区内存溢出"><a href="#4-1-1-1-方法区内存溢出" class="headerlink" title="4.1.1.1. 方法区内存溢出"></a>4.1.1.1. 方法区内存溢出</h4><p><span style="display:none">%%<br>▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230311-1252%%</span>❕ ^m58oc6</p><p>如果系统定义了太多的类，可能导致方法区溢出，虚拟机同样会抛出内存溢出错误：java.lang.OutofMemoryError：PermGen space 或者java.lang.OutOfMemoryError:Metaspace；</p><p>比如下面三种情况：</p><ol><li><strong>加载大量的第三方的jar包；</strong></li><li><strong>Tomcat部署的工程过多；</strong></li><li><strong>大量动态的生成反射类；</strong></li></ol><h3 id="4-1-2-永久代调优是很困难的"><a href="#4-1-2-永久代调优是很困难的" class="headerlink" title="4.1.2. 永久代调优是很困难的"></a>4.1.2. 永久代调优是很困难的</h3><p><span style="display:none">%%<br>1830-🏡⭐️◼️导致方法区内存溢出的情况有哪些？1. 加载太多的第三方jar包；2. tomcat部署太多工程；3. 大量使用反射，比如cglib会生成很多字节码◼️⭐️-point-202301261830%%</span></p><p>发散动态分派 <a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/" title="性能调优专题-基础-5、JVM-虚拟机栈">性能调优专题-基础-5、JVM-虚拟机栈</a></p><h2 id="4-2-为什么要调整StringTable位置⭐️🔴"><a href="#4-2-为什么要调整StringTable位置⭐️🔴" class="headerlink" title="4.2. 为什么要调整StringTable位置⭐️🔴"></a>4.2. 为什么要调整StringTable位置⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118195932.png"><br>温故知新：</p><h1 id="5-三个池子"><a href="#5-三个池子" class="headerlink" title="5. 三个池子"></a>5. 三个池子</h1><h2 id="5-1-class-静态-常量池"><a href="#5-1-class-静态-常量池" class="headerlink" title="5.1. class(静态)常量池"></a>5.1. class(静态)常量池</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108170341.jpg" alt="image-20200401102006738"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118181346.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118181750.png"></p><p><span style="background-color:#00ff00">静态常量池也就是Class文件中的常量池，下面用一张图来看看静态常量池在Class文件中的位置：</span></p><p><strong><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/cb1ec3d8c70411fe51e7dd6d55c4b367.png" alt="img"></strong></p><p><strong>从上图可以看出，Class文件中包括：</strong></p><p><strong>魔数：它的唯一作用是确定这个文件是否可以被JVM接受。很多文件储存标准中都使用魔数来进行身份识别的，其占用这个文件的前四个字节。</strong></p><p><strong>版本号：第5和第6个字节是副版本号，第7个和第8 个是主版本号。</strong></p><p><strong>常量池计数器：也就是常量池的入口，代表常量池的容量计数器。</strong></p><p>**常量池：<span style="background-color:#00ff00">常量池中主要存放2类常量：字面量和符号引用</span>。</p><ul><li><strong>字面量</strong>：字面量就是指由字母、数字等构成的字符串或者数值常量。字面量只可以右值出现，所谓右值是指等号右边的值，如：<code>int a=1</code> 这里的<code>a</code>为左值，<code>1</code>为右值。在这个例子中<code>1</code>就是字面量。</li><li><strong>符号引用</strong>：上面的<code>a、b</code>就是字段名称，就是一种符号引用，符号引用可以是：<ul><li>类和接口的全限定名 <code>com.xx.User</code></li><li>字段的名称和描述符 <code>name</code></li><li>方法的名称和描述符 <code>set()</code></li></ul></li></ul><p><strong>静态常量池</strong>用于存放编译期生成的各种字面量和符号引用，这部分内容将在类加载后存放到方法区的运行时常量池中。其中符号引用其实引用的就是常量池里面的字符串，但符号引用不是直接存储字符串，而是存储字符串在常量池里的索引。</p><p><em><strong>当Class文件被加载完成后，java虚拟机会将静态常量池里的内容转移到运行时常量池里，在静态常量池的符号引用有一部分是会被转变为直接引用的，比如说类的静态方法或私有方法，实例构造方法，父类方法，这是因为这些方法不能被重写其他版本，所以能在加载的时候就可以将符号引用转变为直接引用，而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。</strong></em></p><h2 id="5-2-运行时常量池"><a href="#5-2-运行时常量池" class="headerlink" title="5.2. 运行时常量池"></a>5.2. 运行时常量池</h2><p>运行时常量池是方法区的一部分，是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。<strong>一个类加载到 JVM 中后对应一个运行时常量池</strong>，运行时常量池相对于 Class 文件常量池来说具备动态性，Class 文件常量只是一个静态存储结构，里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引，通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息，这涉及到方法的调用和字段获取。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108170354.jpg" alt="image-20200130235459086"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108170404.jpg" alt="image-20200131161527174"></p><h2 id="5-3-字符串常量池"><a href="#5-3-字符串常量池" class="headerlink" title="5.3. 字符串常量池"></a>5.3. 字符串常量池</h2><a href="/2022/11/18/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-3%E3%80%81String%E5%92%8CStringTable%E2%99%A8%EF%B8%8F/" title="关键字和接口-3、String和StringTable♨️">关键字和接口-3、String和StringTable♨️</a><h2 id="5-4-总结"><a href="#5-4-总结" class="headerlink" title="5.4. 总结"></a>5.4. 总结</h2><ul><li>1.全局常量池在每个VM中只有一份，存放的是字符串常量的引用值。</li><li>2.class常量池是在编译的时候每个class都有的，在编译阶段，存放的是常量的符号引用。</li><li>3.运行时常量池是在类加载完成之后，将每个class常量池中的符号引用值转存到运行时常量池中，也就是说，<span style="background-color:#ff00ff">每个class都有一个运行时常量池</span>，类在解析之后，将符号引用替换成直接引用，与全局常量池中的引用值保持一致。</li></ul><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><h2 id="6-1-尚硅谷宋红康"><a href="#6-1-尚硅谷宋红康" class="headerlink" title="6.1. 尚硅谷宋红康"></a>6.1. 尚硅谷宋红康</h2><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=263&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ?p=263&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>资料已下载：&#x2F;Volumes&#x2F;Seagate Bas&#x2F;001-ArchitectureRoad&#x2F;JVM上篇：内存与垃圾回收篇</p><p>配套网络笔记： <a href="https://www.yuque.com/u21195183/jvm/rq9lt4">https://www.yuque.com/u21195183/jvm/rq9lt4</a></p><h2 id="6-2-黑马程序员"><a href="#6-2-黑马程序员" class="headerlink" title="6.2. 黑马程序员"></a>6.2. 黑马程序员</h2><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;007-性能调优专题&#x2F;001-JVM&#x2F;黑马程序员JVM完整教程</p><h2 id="6-3-网络笔记"><a href="#6-3-网络笔记" class="headerlink" title="6.3. 网络笔记"></a>6.3. 网络笔记</h2><p><a href="https://www.yuque.com/u21195183/jvm/nbkm46">https://www.yuque.com/u21195183/jvm/nbkm46</a></p><p><a href="https://github.com/youthlql/JavaYouth/blob/main/docs/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC6%E7%AB%A0-%E6%96%B9%E6%B3%95%E5%8C%BA.md">https://github.com/youthlql/JavaYouth/blob/main/docs/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC6%E7%AB%A0-%E6%96%B9%E6%B3%95%E5%8C%BA.md</a><br><a href="https://blog.csdn.net/sj15814963053/article/details/110371508">https://blog.csdn.net/sj15814963053/article/details/110371508</a></p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-4、JVM-堆和GC</title>
      <link href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/"/>
      <url>/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/</url>
      
        <content type="html"><![CDATA[<h1 id="1-分类及触发条件⭐️🔴"><a href="#1-分类及触发条件⭐️🔴" class="headerlink" title="1. 分类及触发条件⭐️🔴"></a>1. 分类及触发条件⭐️🔴</h1><h2 id="1-1-堆分区及对应-GC"><a href="#1-1-堆分区及对应-GC" class="headerlink" title="1.1. 堆分区及对应 GC"></a>1.1. 堆分区及对应 GC</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173734.jpg"></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-1%E3%80%81JVM-GC%E8%B0%83%E4%BC%98/" title="性能调优-进阶-1、JVM-GC调优">性能调优-进阶-1、JVM-GC调优</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118120815.png"></p><h2 id="1-2-Minor-GC-YGC"><a href="#1-2-Minor-GC-YGC" class="headerlink" title="1.2. Minor GC(YGC)"></a>1.2. Minor GC(YGC)</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118203409.png"><br>❕<span style="display:none">%%<br>1827-🏡⭐️◼️新生代 GC 的特点是什么？新生代分为 Eden，S0，S1，Eden 满了就发生 YGC，新生代 GC 是非常频繁的，Survivor 区满了不会发生 GC，是被动 GC。YGC 会引发短时间的 STW◼️⭐️-point-202301281827%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309105921.png" alt="image.png"></p><h2 id="1-3-Major-GC-Old-GC"><a href="#1-3-Major-GC-Old-GC" class="headerlink" title="1.3. Major GC(Old GC)"></a>1.3. Major GC(Old GC)</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118203558.png"></p><h2 id="1-4-Full-GC⭐️🔴"><a href="#1-4-Full-GC⭐️🔴" class="headerlink" title="1.4. Full GC⭐️🔴"></a>1.4. Full GC⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221118203823.png"></p><h3 id="1-4-1-System-gc-方法的调用"><a href="#1-4-1-System-gc-方法的调用" class="headerlink" title="1.4.1. System.gc() 方法的调用"></a>1.4.1. System.gc() 方法的调用</h3><p>此方法的调用是建议 JVM 进行 Full GC, 虽然只是建议而非一定, 但很多情况下它会触发 Full GC, 从而增加 Full GC 的频率, 也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用，让虚拟机自己去管理它的内存，可通过通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc。</p><h3 id="1-4-2-老年代代空间不足"><a href="#1-4-2-老年代代空间不足" class="headerlink" title="1.4.2. 老年代代空间不足"></a>1.4.2. 老年代代空间不足</h3><p>老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象，当执行 Full GC 后空间仍然不足，则抛出如下错误：<br>java.lang.OutOfMemoryError: Java heap space<br>为避免以上两种状况引起的 Full GC，调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间；不要创建过大的对象及数组。 ^0m8k82</p><h3 id="1-4-3-方法区空间不足"><a href="#1-4-3-方法区空间不足" class="headerlink" title="1.4.3. 方法区空间不足"></a>1.4.3. 方法区空间不足</h3><p>JVM 规范中运行时数据区域中的方法区，在 HotSpot 虚拟机中又被习惯称为永生代或者永生区，Permanet Generation 中存放的为一些 class 的信息、常量、静态变量等数据，当系统中要 <strong>加载的类、反射的类和调用的方法较多</strong> 时，Permanet Generation 可能会被占满，在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了，那么 JVM 会抛出如下错误信息：<br>java.lang.OutOfMemoryError: PermGen space<br>为避免 Perm Gen 占满造成 Full GC 现象，可采用的方法为增大 Perm Gen 空间或转为使用 CMS GC。</p><h3 id="1-4-4-CMS-GC-时出现-promotion-failed-和-concurrent-mode-failure"><a href="#1-4-4-CMS-GC-时出现-promotion-failed-和-concurrent-mode-failure" class="headerlink" title="1.4.4. CMS GC 时出现 promotion failed 和 concurrent mode failure"></a>1.4.4. CMS GC 时出现 promotion failed 和 concurrent mode failure</h3><p>对于采用 CMS 进行老年代 GC 的程序而言，尤其要注意 GC 日志中是否有 promotion failed 和 concurrent mode failure 两种状况，当这两种状况出现时可能会触发 Full GC。</p><p><code>promotion failed</code> 是在进行 Minor GC 时，<span style="background-color:#00ff00">survivor space 放不下、对象只能放入老年代，而此时老年代也放不下造成的</span>；<br><code>concurrent mode failure</code> 是在执行 CMS GC 的过程中同时有对象要放入老年代，而此时老年代空间不足造成的（有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC）。<br>应对措施为：增大 survivor space、老年代空间或调低触发并发 GC 的比率，但在 JDK 5.0+、6.0+ 的版本中有可能会由于 JDK 的 bug29 导致 CMS 在 remark 完毕后很久才触发 sweeping 动作。对于这种状况，可通过设置 -XX: CMSMaxAbortablePrecleanTime&#x3D;5（单位为 ms）来避免。</p><h3 id="1-4-5-Minor-GC-晋升到老年代的平均大小大于老年代的剩余空间"><a href="#1-4-5-Minor-GC-晋升到老年代的平均大小大于老年代的剩余空间" class="headerlink" title="1.4.5. Minor GC 晋升到老年代的平均大小大于老年代的剩余空间"></a>1.4.5. Minor GC 晋升到老年代的平均大小大于老年代的剩余空间</h3><p>这是一个较为复杂的触发情况，Hotspot 为了避免由于新生代对象晋升到老年代导致老年代空间不足的现象，在进行 Minor GC 时，做了一个判断，如果之前统计所得到的 Minor GC 晋升到老年代的平均大小大于老年代的剩余空间，那么就直接触发 Full GC。</p><p>例如程序第一次触发 Minor GC 后，有 6MB 的对象晋升到老年代，那么当下一次 Minor GC 发生时，首先检查老年代的剩余空间是否大于 6MB，如果小于 6MB，则执行 Full GC。</p><p>当新生代采用 PS GC 时，方式稍有不同，PS GC 是在 Minor GC 后也会检查，例如上面的例子中第一次 Minor GC 后，PS GC 会检查此时老年代的剩余空间是否大于 6MB，如小于，则触发对老年代的回收。</p><p>除了以上 4 种状况外，对于使用 RMI 来进行 RPC 或管理的 Sun JDK 应用而言，默认情况下会一小时执行一次 Full GC。可通过在启动时通过 - java -Dsun.rmi.dgc.client.gcInterval&#x3D;3600000 来设置 Full GC 执行的间隔时间或通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc。</p><p><span style="background-color:#ff00ff">注意这种情况与动态年龄判断的区分</span>：一个是触发 fullGC，一个是触发晋升到老年代</p><a href="/2022/11/17/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81JVM-%E5%A0%86%E5%92%8CGC%E7%90%86%E8%AE%BA/" title="性能调优专题-基础-4、JVM-堆和GC理论">性能调优专题-基础-4、JVM-堆和GC理论</a><p>6、堆中分配很大的对象</p><p>所谓大对象，是指需要大量连续内存空间的 java 对象，例如很长的数组，此种对象会直接进入老年代，而老年代虽然有很大的剩余空间，但是无法找到足够大的连续空间来分配给当前对象，此种情况就会触发 JVM 进行 Full GC。</p><p>为了解决这个问题，CMS 垃圾收集器提供了一个可配置的参数，即 -XX:+UseCMSCompactAtFullCollection 开关参数，用于在“享受”完 Full GC 服务之后额外免费赠送一个碎片整理的过程，内存整理的过程无法并发的，空间碎片问题没有了，但停顿时间不得不变长了，JVM 设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction, 这个参数用于设置在执行多少次不压缩的 Full GC 后, 跟着来一次带压缩的。</p><p>原文链接： <a href="https://blog.csdn.net/chenleixing/article/details/46706039">https://blog.csdn.net/chenleixing/article/details/46706039</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309105620.png" alt="image.png"></p><h1 id="2-内存分配过程⭐️🔴"><a href="#2-内存分配过程⭐️🔴" class="headerlink" title="2. 内存分配过程⭐️🔴"></a>2. 内存分配过程⭐️🔴</h1><ol><li>new 的对 象先放伊甸园区。此区有大小限制。</li><li>当伊甸园的空间填满时，程序又需要创建对象，JVM 的垃圾回收器将对伊甸园区进行垃圾回收（MinorGC），将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区</li><li>然后将伊甸园中的剩余对象移动到幸存者 0 区。</li><li>如果再次触发垃圾回收，此时上次幸存下来的放到幸存者 0 区的，如果没有回收，就会放到幸存者 1 区。</li><li>如果再次经历垃圾回收，此时会重新放回幸存者 0 区，接着再去幸存者 1 区。</li><li>啥时候能去养老区呢？可以设置次数。默认是 15 次。</li></ol><ul><li>可以设置参数：进行设置 <code>-Xx:MaxTenuringThreshold= N</code><br>为什么是 15 次？<a href="/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/" title="对象创建-1、对象内存">对象创建-1、对象内存</a></li></ul><ol start="7"><li>在养老区，相对悠闲。当养老区内存不足时，再次触发 GC：Major GC，进行养老区的内存清理</li><li>若养老区执行了 Major GC 之后，发现依然无法进行对象的保存，就会产生 OOM 异常。</li></ol><h2 id="2-1-流程图"><a href="#2-1-流程图" class="headerlink" title="2.1. 流程图"></a>2.1. 流程图</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119180905.jpg"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221119181254.png"></p><h2 id="2-2-总结"><a href="#2-2-总结" class="headerlink" title="2.2. 总结"></a>2.2. 总结</h2><ul><li>针对幸存者 s0，s1 区的总结：<span style="background-color:#ff00ff">复制之后有交换，谁空谁是 to</span></li><li>关于垃圾回收：频繁在新生区收集，很少在老年代收集，几乎不在永久代和元空间进行收集</li></ul><h2 id="2-3-为什么是-15-次⭐️🔴"><a href="#2-3-为什么是-15-次⭐️🔴" class="headerlink" title="2.3. 为什么是 15 次⭐️🔴"></a>2.3. 为什么是 15 次⭐️🔴</h2><p>对象头中的分代年龄，用于分代 GC。分代 GC 我们在后面的章节会详细讲述，这里只是看一些特性。</p><p>记录分代年龄一共 4 bit，所以最大为 2^4 - 1 &#x3D; 15。所以配置最大分代年龄 -XX:MaxTenuringThreshold&#x3D;n 这个 n 不能大于 15，当然也不能小于 0.等于 0 的话，就直接入老年代。等于 16 的话，就是从不进入老年代，这样不符合 JVM 规范，所以不能大于 15。默认是 15。</p><h1 id="3-内存分配策略⭐️🔴"><a href="#3-内存分配策略⭐️🔴" class="headerlink" title="3. 内存分配策略⭐️🔴"></a>3. 内存分配策略⭐️🔴</h1><p>如果对象<span style="background-color:#00ff00">在 Eden 出生并经过第一次 Minor GC 后仍然存活，并且能被 Survivor 容纳的话，将被移动到 survivor 空间中，并将对象年龄设为 1</span>。对象在 survivor 区中每熬过一次 MinorGC，年龄就增加 1 岁，当它的年龄增加到一定程度（默认为 15 岁，其实每个 JVM、每个 GC 都有所不同）时，就会被晋升到老年代 ❕<span style="display:none">%%<br>1306-🏡⭐️◼️对象年龄是如何开始计算的？🔜📝 对象在 Eden 创建之后，经过第一次 YGC 还存活并且能够在 Survivor 中放得下，那么进入 Survivor 之后年龄设置为 1，每经过一次 YGC 年龄就 +1◼️⭐️-point-202301301306%%</span></p><p>对象晋升老年代的年龄阀值，可以通过选项 <code>-XX:MaxTenuringThreshold</code> 来设置</p><p>针对不同年龄段的对象分配原则如下所示：</p><ol><li>优先分配到 Eden</li><li>大对象直接分配到老年代（尽量避免程序中出现过多的大对象）</li><li>长期存活的对象分配到老年代</li><li><strong>动态对象年龄判断</strong>：如果 survivor 区中<span style="background-color:#00ff00">【相同年龄的】所有对象大小的总和</span>大于 <span style="background-color:#ff00ff">Survivor 空间的一半</span>，年龄大于或等于该年龄的对象可以直接进入老年代，无须等到 <code>MaxTenuringThreshold</code> 中要求的年龄。 ^eblx0q</li><li>空间分配担保： <code>-XX:HandlePromotionFailure</code></li></ol><h1 id="4-GC-作用域"><a href="#4-GC-作用域" class="headerlink" title="4. GC 作用域"></a>4. GC 作用域</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171428.jpg" alt="image-20200322225622428"></p><h1 id="5-垃圾判断"><a href="#5-垃圾判断" class="headerlink" title="5. 垃圾判断"></a>5. 垃圾判断</h1><p><a href="https://www.bilibili.com/video/av75859780?p=951">https://www.bilibili.com/video/av75859780?p=951</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171438.jpg" alt="image-20200322233137394"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208081251.png"></p><h2 id="5-1-GC-roots⭐️🔴"><a href="#5-1-GC-roots⭐️🔴" class="headerlink" title="5.1. GC roots⭐️🔴"></a>5.1. GC roots⭐️🔴</h2><h3 id="5-1-1-查询位置"><a href="#5-1-1-查询位置" class="headerlink" title="5.1.1. 查询位置"></a>5.1.1. 查询位置</h3><p>❕<span style="display:none">%%<br>0829-🏡⭐️◼️GCroot 的查询位置有什么🔜MSTM📝 1. 虚拟机栈中本地变量表中引用的对象；2. 方法区中静态变量引用的对象；3. 方法区中常量引用的对象；4. 本地方法栈中 JNI 引用的对象◼️⭐️-point-202301310829%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171450.jpg" alt="image-20200322233601844"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171459.jpg" alt="image-20200322234212539"></p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81JVM-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88/" title="性能调优专题-基础-5、JVM-虚拟机栈">性能调优专题-基础-5、JVM-虚拟机栈</a><h3 id="5-1-2-包含类型⭐️🔴"><a href="#5-1-2-包含类型⭐️🔴" class="headerlink" title="5.1.2. 包含类型⭐️🔴"></a>5.1.2. 包含类型⭐️🔴</h3><p><span style="display:none">%%<br>▶16.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1507%%</span>❕ ^605ynj</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208080931.png"><br>❕<span style="display:none">%%<br>1300-🏡⭐️◼️GCroot 包含类型🔜MSTM📝 1. 虚拟机栈中局部变量表中引用的对象；2. 方法区中静态变量、常量引用的对象；3. 本地方法栈中引用的对象；4.synchronized 引用的对象，即锁对象；5.JVM 内部引用对象，比如基本数据类型对应的 Class 对象、系统类加载器、异常对象；6. 反映虚拟机内部情况的 JMXBean、JVMTI 注册的回调、本地代码缓存◼️⭐️-point-202302011300%%</span></p><p><span style="background-color:#ff00ff">无论 G1 还是其他分代收集器，JVM 都是使用 Remembered Set 来避免全局扫描，每个 region 的记忆集 (Remembered Set) 也是 GC Root 的查找范围。</span> ^nvygi7</p><h2 id="5-2-三色标记"><a href="#5-2-三色标记" class="headerlink" title="5.2. 三色标记"></a>5.2. 三色标记</h2><p><span style="display:none">%%<br>▶31.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1706%%</span>❕ ^tw0udc</p><a href="/2023/03/10/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-12%E3%80%81GC-%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E7%AE%97%E6%B3%95/" title="性能调优-基础-12、GC-三色标记算法">性能调优-基础-12、GC-三色标记算法</a><h2 id="5-3-分析工具"><a href="#5-3-分析工具" class="headerlink" title="5.3. 分析工具"></a>5.3. 分析工具</h2><h3 id="5-3-1-GC-日志分析"><a href="#5-3-1-GC-日志分析" class="headerlink" title="5.3.1. GC 日志分析"></a>5.3.1. GC 日志分析</h3><p><a href="https://www.bilibili.com/video/BV1MJ411E7V4?p=77&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1MJ411E7V4?p=77&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307223948.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307224121.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230307224153.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230130122602.jpg" alt="image-20200323011750780"></p><h3 id="5-3-2-统一日志管理"><a href="#5-3-2-统一日志管理" class="headerlink" title="5.3.2. 统一日志管理"></a>5.3.2. 统一日志管理</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230309151627.png" alt="image.png"></p><h3 id="5-3-3-MAT"><a href="#5-3-3-MAT" class="headerlink" title="5.3.3. MAT"></a>5.3.3. MAT</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171514.jpg" alt="image-20200131104247666"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171613.jpg" alt="image-20200131104312174"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171622.jpg" alt="image-20200131104344365"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171633.jpg" alt="image-20200131104540890"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171642.jpg" alt="image-20200131104616396"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171652.jpg" alt="image-20200131104709994"></p><h2 id="5-4-四种引用"><a href="#5-4-四种引用" class="headerlink" title="5.4. 四种引用"></a>5.4. 四种引用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171703.jpg" alt="image-20200131105211811"></p><p>实线表示强引用，强引用：沿着 gc root 对象可达的对象的引用</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171713.jpg" alt="image-20200131105311476"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171725.jpg" alt="image-20200131105548453"></p><h3 id="5-4-1-架构"><a href="#5-4-1-架构" class="headerlink" title="5.4.1. 架构"></a>5.4.1. 架构</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171739.jpg" alt="image-20200323085705810"></p><h3 id="5-4-2-强引用"><a href="#5-4-2-强引用" class="headerlink" title="5.4.2. 强引用"></a>5.4.2. 强引用</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171749.jpg" alt="image-20200323085831208"></p><h3 id="5-4-3-软引用"><a href="#5-4-3-软引用" class="headerlink" title="5.4.3. 软引用"></a>5.4.3. 软引用</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171759.jpg" alt="image-20200323090016016"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171808.jpg" alt="image-20200323094300109"></p><h3 id="5-4-4-弱引用"><a href="#5-4-4-弱引用" class="headerlink" title="5.4.4. 弱引用"></a>5.4.4. 弱引用</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171817.jpg" alt="image-20200323094047025"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171826.jpg" alt="image-20200323090612301"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171833.jpg" alt="image-20200323095627811"></p><h3 id="5-4-5-虚引用"><a href="#5-4-5-虚引用" class="headerlink" title="5.4.5. 虚引用"></a>5.4.5. 虚引用</h3><p>主要做对象回收的监控</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171842.jpg" alt="image-20200323094547003"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171851.jpg" alt="image-20200323095910530"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171910.jpg" alt="image-20200410090348591"></p><h3 id="5-4-6-应用场景⭐️🔴"><a href="#5-4-6-应用场景⭐️🔴" class="headerlink" title="5.4.6. 应用场景⭐️🔴"></a>5.4.6. 应用场景⭐️🔴</h3><h4 id="5-4-6-1-软引用-图片缓存"><a href="#5-4-6-1-软引用-图片缓存" class="headerlink" title="5.4.6.1. 软引用-图片缓存"></a>5.4.6.1. 软引用-图片缓存</h4><p><span style="display:none">%%<br>▶17.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1510%%</span>❕ ^i773fb</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171921.jpg" alt="image-20200323091024695"></p><p><strong>软引用的回收</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171931.jpg" alt="image-20200401221308306"></p><h4 id="5-4-6-2-弱引用-ThreadLocal⭐️🔴"><a href="#5-4-6-2-弱引用-ThreadLocal⭐️🔴" class="headerlink" title="5.4.6.2. 弱引用 -ThreadLocal⭐️🔴"></a>5.4.6.2. 弱引用 -ThreadLocal⭐️🔴</h4><a href="/2023/01/05/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-18%E3%80%81ThreadLocal/" title="并发基础-18、ThreadLocal">并发基础-18、ThreadLocal</a><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106214801.png"></p><h4 id="5-4-6-3-虚引用-directByteBuffer-释放直接内存⭐️🔴"><a href="#5-4-6-3-虚引用-directByteBuffer-释放直接内存⭐️🔴" class="headerlink" title="5.4.6.3. 虚引用 -directByteBuffer 释放直接内存⭐️🔴"></a>5.4.6.3. 虚引用 -directByteBuffer 释放直接内存⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171943.jpg" alt="image-20200401211253916"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108171959.jpg" alt="image-20200401181607451"></p><p>分析：</p><p>当 DirectByteBuffer 被垃圾回收后，虚引用会被放到引用队列中，线程 ReferenceHandler 会通过调用 cleaner 的 clean 方法，调用 Unsafe.freeMemmory 方法，从而完成堆外内存的回收。</p><h3 id="5-4-7-WeakHashMap"><a href="#5-4-7-WeakHashMap" class="headerlink" title="5.4.7. WeakHashMap"></a>5.4.7. WeakHashMap</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108172010.jpg" alt="image-20200323100241365"></p><p><strong>有 GC 就清空，适用于高速缓存和内存敏感相关应用的开发</strong></p><h3 id="5-4-8-总结"><a href="#5-4-8-总结" class="headerlink" title="5.4.8. 总结"></a>5.4.8. 总结</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108172021.jpg" alt="image-20200323100135004"></p><h1 id="6-垃圾回收"><a href="#6-垃圾回收" class="headerlink" title="6. 垃圾回收"></a>6. 垃圾回收</h1><h2 id="6-1-常见算法"><a href="#6-1-常见算法" class="headerlink" title="6.1. 常见算法"></a>6.1. 常见算法</h2><h4 id="6-1-1-引用计数"><a href="#6-1-1-引用计数" class="headerlink" title="6.1.1. 引用计数"></a>6.1.1. 引用计数</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108172032.jpg" alt="image-20200322230252844"></p><h4 id="6-1-2-复制"><a href="#6-1-2-复制" class="headerlink" title="6.1.2. 复制"></a>6.1.2. 复制</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108172041.jpg" alt="image-20200322230521859"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108172049.jpg" alt="image-20200322230451410"></p><h4 id="6-1-3-标记清除"><a href="#6-1-3-标记清除" class="headerlink" title="6.1.3. 标记清除"></a>6.1.3. 标记清除</h4><h4 id="6-1-4-标记整理"><a href="#6-1-4-标记整理" class="headerlink" title="6.1.4. 标记整理"></a>6.1.4. 标记整理</h4><h4 id="6-1-5-对比"><a href="#6-1-5-对比" class="headerlink" title="6.1.5. 对比"></a>6.1.5. 对比</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208095459.png"></p><h2 id="6-2-回收过程"><a href="#6-2-回收过程" class="headerlink" title="6.2. 回收过程"></a>6.2. 回收过程</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173821.jpg" alt="image-20200131121029469"></p><h2 id="6-3-参数设置"><a href="#6-3-参数设置" class="headerlink" title="6.3. 参数设置"></a>6.3. 参数设置</h2><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP?p=64">https://www.bilibili.com/video/BV1yE411Z7AP?p=64</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173838.jpg" alt="image-20200131121103656"></p><h2 id="6-4-4-大回收策略思想"><a href="#6-4-4-大回收策略思想" class="headerlink" title="6.4. 4 大回收策略思想"></a>6.4. 4 大回收策略思想</h2><h4 id="6-4-1-串行"><a href="#6-4-1-串行" class="headerlink" title="6.4.1. 串行"></a>6.4.1. 串行</h4><p>新生代用复制，老年代用标记 - 整理算法</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173851.jpg" alt="image-20200131134408499"></p><h4 id="6-4-2-并行"><a href="#6-4-2-并行" class="headerlink" title="6.4.2. 并行"></a>6.4.2. 并行</h4><p>新生代用复制算法，老年代用标记 - 整理算法</p><p>会暂停用户进程</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173917.jpg" alt="image-20200131134328362"></p><h4 id="6-4-3-并发"><a href="#6-4-3-并发" class="headerlink" title="6.4.3. 并发"></a>6.4.3. 并发</h4><p>基于标记 - 清除垃圾回收算法的回收器</p><p>不会暂停用户进程</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230108173931.jpg" alt="image-20200131134512431"></p><h4 id="6-4-4-G1-区域化分代式"><a href="#6-4-4-G1-区域化分代式" class="headerlink" title="6.4.4. G1- 区域化分代式"></a>6.4.4. G1- 区域化分代式</h4><p>区域化分代式</p><h4 id="6-4-5-并发与并行区别"><a href="#6-4-5-并发与并行区别" class="headerlink" title="6.4.5. 并发与并行区别"></a>6.4.5. 并发与并行区别</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221208112219.png"></p><h2 id="6-5-七大垃圾回收器"><a href="#6-5-七大垃圾回收器" class="headerlink" title="6.5. 七大垃圾回收器"></a>6.5. 七大垃圾回收器</h2><a href="/2023/03/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-11%E3%80%817%E5%A4%A7%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/" title="性能调优-基础-11、7大垃圾回收器">性能调优-基础-11、7大垃圾回收器</a><h2 id="6-6-GC-调优"><a href="#6-6-GC-调优" class="headerlink" title="6.6. GC 调优"></a>6.6. GC 调优</h2><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E8%BF%9B%E9%98%B6-1%E3%80%81JVM-GC%E8%B0%83%E4%BC%98/" title="性能调优-进阶-1、JVM-GC调优">性能调优-进阶-1、JVM-GC调优</a><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p>❕ ^7fgp3j</p><h2 id="7-1-尚硅谷雷丰阳"><a href="#7-1-尚硅谷雷丰阳" class="headerlink" title="7.1. 尚硅谷雷丰阳"></a>7.1. 尚硅谷雷丰阳</h2><p><span style="display:none">%%<br>▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️%%</span>❕ ^uwx6yh</p><h3 id="7-1-1-视频"><a href="#7-1-1-视频" class="headerlink" title="7.1.1. 视频"></a>7.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=57&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1PJ411n7xZ?p=57&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-1-2-资料"><a href="#7-1-2-资料" class="headerlink" title="7.1.2. 资料"></a>7.1.2. 资料</h3><p>已下载：&#x2F;Users&#x2F;Enterprise&#x2F;0003-Architecture&#x2F;011-Java&#x2F;尚硅谷&#x2F;JVM 上篇：内存与垃圾回收篇</p><a href="/2022/11/18/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-7%E3%80%81JVM-%E5%A0%86/" title="对象创建-7、JVM-堆">对象创建-7、JVM-堆</a><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/brain-mapping/docs/宋老师讲的JVM课程视频课件脑图（下篇）<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/NOTE_JVM/JVM下篇：性能监控与调优篇<br></code></pre></td></tr></table></figure><h2 id="7-2-黑马程序员"><a href="#7-2-黑马程序员" class="headerlink" title="7.2. 黑马程序员"></a>7.2. 黑马程序员</h2><h3 id="7-2-1-视频"><a href="#7-2-1-视频" class="headerlink" title="7.2.1. 视频"></a>7.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1yE411Z7AP/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-2-2-资料"><a href="#7-2-2-资料" class="headerlink" title="7.2.2. 资料"></a>7.2.2. 资料</h3><p>已下载：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/007-性能调优专题/001-JVM/黑马程序员JVM完整教程<br></code></pre></td></tr></table></figure><h2 id="7-3-马士兵"><a href="#7-3-马士兵" class="headerlink" title="7.3. 马士兵"></a>7.3. 马士兵</h2><p><a href="https://www.bilibili.com/video/BV1fi4y1U76z?p=21&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1fi4y1U76z?p=21&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="7-4-网络笔记"><a href="#7-4-网络笔记" class="headerlink" title="7.4. 网络笔记"></a>7.4. 网络笔记</h2><p><a href="https://www.yuque.com/u21195183/jvm/lep6m9">https://www.yuque.com/u21195183/jvm/lep6m9</a></p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
            <tag> GC </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>001-基础知识专题-关键字和接口-2、final</title>
      <link href="/2022/11/16/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-2%E3%80%81final/"/>
      <url>/2022/11/16/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-2%E3%80%81final/</url>
      
        <content type="html"><![CDATA[<h1 id="1-final-的内存语义"><a href="#1-final-的内存语义" class="headerlink" title="1. final 的内存语义"></a>1. final 的内存语义</h1><ol><li>在构造函数内对一个 final 域的写入，与随后把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。也就是说只有将对象实例化完成后，才能将对象引用赋值给变量。</li><li>初次读一个包含 final 域的对象的引用，与随后初次读这个 final 域，这两个操作之间不能重排序。也就是下面示例的 4 和 5 不能重排序。</li><li>当 final 域为引用类型时，在构造函数内对一个 final 引用的对象的成员域的写入，与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。</li></ol><p>下面通过代码在说明一下：</p><figure class="highlight csharp"><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></pre></td><td class="code"><pre><code class="hljs csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">FinalExample</span> &#123;<br>    <span class="hljs-built_in">int</span> i;   <span class="hljs-comment">// 普通变量</span><br>    final <span class="hljs-built_in">int</span> j;   <span class="hljs-comment">// final变量</span><br>    <span class="hljs-keyword">static</span> FinalExample obj;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FinalExample</span>()</span> &#123; <span class="hljs-comment">// 构造函数</span><br>        i = <span class="hljs-number">1</span>;<span class="hljs-comment">// 写普通域</span><br>        j = <span class="hljs-number">2</span>;<span class="hljs-comment">// 写final域</span><br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">writer</span>()</span> &#123;   <span class="hljs-comment">// 写线程A执行</span><br>        <span class="hljs-comment">// 这一步实际上有三个指令，如下：</span><br>        <span class="hljs-comment">// memory = allocate();　　// 1：分配对象的内存空间</span><br>        <span class="hljs-comment">// ctorInstance(memory);　// 2：初始化对象</span><br>        <span class="hljs-comment">// instance = memory;　　// 3：设置instance指向刚分配的内存地址</span><br>        obj = <span class="hljs-keyword">new</span> FinalExample();<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">reader</span>()</span> &#123;   <span class="hljs-comment">// 读线程B执行            </span><br>        FinalExample <span class="hljs-built_in">object</span> = obj;  <span class="hljs-comment">// 4\. 读对象引用</span><br>        <span class="hljs-built_in">int</span> a = <span class="hljs-built_in">object</span>.i; <span class="hljs-comment">// 5\. 读普通域</span><br>        <span class="hljs-built_in">int</span> b = <span class="hljs-built_in">object</span>.j; <span class="hljs-comment">// 读final域</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="2-final-语义在处理器中的实现"><a href="#2-final-语义在处理器中的实现" class="headerlink" title="2. final 语义在处理器中的实现"></a>2. final 语义在处理器中的实现</h1><ul><li>会要求编译器在 final 域的写之后，构造函数 return 之前插入一个 StoreStore 障屏。</li><li>读 final 域的重排序规则要求编译器在读 final 域的操作前面插入一个 LoadLoad 屏障。</li></ul><h1 id="3-内联优化"><a href="#3-内联优化" class="headerlink" title="3. 内联优化"></a>3. 内联优化</h1><p><a href="https://juejin.cn/post/7107909896804237319#heading-1">https://juejin.cn/post/7107909896804237319#heading-1</a></p><p>我们先看直接声明就初始化这种情况：</p><figure class="highlight arduino"><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 arduino"><span class="hljs-keyword">class</span> <span class="hljs-title class_">A</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">static</span> <span class="hljs-type">int</span> a = <span class="hljs-number">7</span>;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title">getA</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">return</span> a;<br>    &#125;<br>&#125;<br>复制代码<br></code></pre></td></tr></table></figure><p>编译再反编译一下：</p><figure class="highlight css"><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 css"># 我的文件名是Demo<span class="hljs-selector-class">.java</span><br>javac Demo<span class="hljs-selector-class">.java</span><br>javap -<span class="hljs-selector-tag">p</span> -v -c <span class="hljs-selector-tag">A</span><br>复制代码<br></code></pre></td></tr></table></figure><p>可以看到这个 <code>getA()</code> 的反编译结果，编译器已经知道了 <code>a=7</code>，并且由于它是一个 final 变量，不会变，所以直接写死编译进去了。相当于直接把 <code>return a</code> 替换成了 <code>return 7</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><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 yaml">  <span class="hljs-string">public</span> <span class="hljs-string">static</span> <span class="hljs-string">int</span> <span class="hljs-string">getA();</span><br>    <span class="hljs-attr">descriptor:</span> <span class="hljs-string">()I</span><br>    <span class="hljs-attr">flags:</span> <span class="hljs-string">ACC_PUBLIC,</span> <span class="hljs-string">ACC_STATIC</span><br>    <span class="hljs-attr">Code:</span><br>      <span class="hljs-string">stack=1,</span> <span class="hljs-string">locals=0,</span> <span class="hljs-string">args_size=0</span><br>         <span class="hljs-attr">0:</span> <span class="hljs-string">bipush</span>        <span class="hljs-number">7</span><br>         <span class="hljs-attr">2:</span> <span class="hljs-string">ireturn</span><br>      <span class="hljs-attr">LineNumberTable:</span><br>        <span class="hljs-attr">line 21:</span> <span class="hljs-number">0</span><br><span class="hljs-string">​</span><br><span class="hljs-string">复制代码</span><br></code></pre></td></tr></table></figure><p>这其实是一个编译器的优化，专业的称呼叫“内联优化”。其实不只是 final 变量会被内联优化。一个方法也有可能被内联优化，特别是热点方法。JIT 大部分的优化都是在内联的基础上进行的，方法内联是即时编译器中非常重要的一环。</p><p>一般来说，内联的方法越多，生成代码的执行效率越高。但是对于即时编译器来说，内联的方法越多，编译时间也就越长，程序达到峰值性能的时刻也就比较晚。有一些参数可以控制方法是否被内联：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230109191901.png"></p><p>回到最开始的问题，这种能被编译器内联优化的 final 变量，是会在编译成字节码的时候，就赋值了，所以在类加载的准备阶段，不会给这个变量初始化为默认值。</p><h1 id="4-与-static-搭配使用"><a href="#4-与-static-搭配使用" class="headerlink" title="4. 与 static 搭配使用"></a>4. 与 static 搭配使用</h1><p>static final 用来修饰成员变量和成员方法，可简单理解为“全局常量”！<br>对于变量，表示一旦给值就不可修改，并且通过类名可以访问。<br>对于方法，表示不可覆盖，并且可以通过类名直接访问。</p><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><p><a href="https://www.jianshu.com/p/e6cda45d58d0">https://www.jianshu.com/p/e6cda45d58d0</a></p><p>内联优化<br><a href="https://juejin.cn/post/7107909896804237319#heading-1">https://juejin.cn/post/7107909896804237319#heading-1</a></p>]]></content>
      
      
      <categories>
          
          <category> 001-基础知识专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 关键字和接口 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>性能调优专题-基础-1、JVM-编译器、解释器、执行器</title>
      <link href="/2022/11/15/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-2%E3%80%81JVM-%E7%BC%96%E8%AF%91%E5%99%A8%E3%80%81%E8%A7%A3%E9%87%8A%E5%99%A8%E3%80%81%E6%89%A7%E8%A1%8C%E5%99%A8/"/>
      <url>/2022/11/15/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98-%E5%9F%BA%E7%A1%80-2%E3%80%81JVM-%E7%BC%96%E8%AF%91%E5%99%A8%E3%80%81%E8%A7%A3%E9%87%8A%E5%99%A8%E3%80%81%E6%89%A7%E8%A1%8C%E5%99%A8/</url>
      
        <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221127112446.jpg"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116183606.png"></p><h1 id="1-JVM编译器"><a href="#1-JVM编译器" class="headerlink" title="1. JVM编译器"></a>1. JVM编译器</h1><h2 id="1-1-前端编译器"><a href="#1-1-前端编译器" class="headerlink" title="1.1. 前端编译器"></a>1.1. 前端编译器</h2><p>如javac，将java文件编译为class文件。<span style="background-color:#ffff00"><font color=#ff0000>此阶段不会发生指令重排序。</font></span></p><h2 id="1-2-后端编译器"><a href="#1-2-后端编译器" class="headerlink" title="1.2. 后端编译器"></a>1.2. 后端编译器</h2><p>JIT 是将一些字节码编译为机器码，并存入 Code Cache，下次遇到相同的代码，直接执行，无需再编译。<span style="background-color:#ffff00"><font color=#ff0000>此阶段可能会发生指令重排序。</font></span> <span style="background-color:#ff0000">和解释器一样，起点都是编译后的 class 文件，而不是.java 源文件。</span></p><p>HotSpot中内置了两个即时编译器，分别称为 Client Compiler和 Server Compiler ，或者简称为 C1 编译器和 C2 编译器。目前的 HotSpot 编译器默认的是解释器和其中一个即时编译器配合的方式工作，具体是哪一个编译器，取决于虚拟机运行的模式，HotSpot 虚拟机会根据自身版本与计算机的硬件性能自动选择运行模式，用户也可以使用 -client 和 -server 参数强制指定虚拟机运行在 Client 模式或者 Server 模式。这种配合使用的方式称为“混合模式”（Mixed Mode），用户可以使用参数 -Xint 强制虚拟机运行于 “解释模式”（Interpreted Mode），这时候编译器完全不介入工作。另外，使用 -Xcomp 强制虚拟机运行于 “编译模式”（Compiled Mode），这时候将优先采用编译方式执行，但是解释器仍然要在编译无法进行的情况下接入执行过程。通过虚拟机 -version 命令可以查看当前默认的运行模式。</p><blockquote><p><img src="https://img-blog.csdn.net/20140909113432698?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvenE2MDIzMTY0OTg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"></p></blockquote><p>C2编译器是专门面向服务端的，并为服务端的性能配置特别调整过的编译器，也是一个充分优化过的高级编译器，<span style="background-color:#00ff00">几乎能达到 GNU C++ 编译器使用<font color=#ff0000>-O2</font> 参数时的优化强度</span>，它会执行所有的经典的优化动作，如 无用代码消除（Dead Code Elimination）、循环展开（Loop Unrolling）、循环表达式外提（Loop Expression Hoisting）、消除公共子表达式（Common Subexpression Elimination）、常量传播（Constant Propagation）、基本块冲排序（Basic Block Reordering）等，还会实施一些与 Java 语言特性密切相关的优化技术，如范围检查消除（Range Check Elimination）、空值检查消除（Null Check Elimination ，不过并非所有的空值检查消除都是依赖编译器优化的，有一些是在代码运行过程中自动优化 了）等。</p><h1 id="2-JVM解释器"><a href="#2-JVM解释器" class="headerlink" title="2. JVM解释器"></a>2. JVM解释器</h1><p><a href="https://cloud.tencent.com/developer/article/1398588">https://cloud.tencent.com/developer/article/1398588</a></p><p>hotspot解释器模块(<code>hotspot\src\share\vm\interpreter</code>)有两个实现：基于C++的解释器和基于汇编的模板解释器。<span style="background-color:#ffff00">hotspot默认使用比较快的模板解释器</span>。 其中</p><p> C++解释器 &#x3D; <code>bytecodeInterpreter*</code> + <code>cppInterpreter*</code><br> 模板解释器 &#x3D; <code>templateTable*</code> + <code>templateInterpreter*</code></p><p>它们前者负责字节码的解释，后者负责解释器的运行时，共同完成解释功能。这里我们只关注模板解释器。</p><ul><li>C++解释器 &#x3D; <code>bytecodeInterpreter*</code> + <code>cppInterpreter*</code>    <span style="background-color:#00ff00">零汇编:只和编译器有关，与CPU无关</span><br>volatile：语义就是禁止编译器优化</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114190841.png"></p><ul><li>模板解释器 &#x3D; <code>templateTable*</code> + <code>templateInterpreter*</code>   <span style="background-color:#00ff00">涉及CPU指令和汇编</span></li></ul><p>volatile：lock的作用锁缓存行、刷MOB(ROB和StoreBuffer)</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114222229.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114223110.png"></p><h1 id="3-JVM执行器"><a href="#3-JVM执行器" class="headerlink" title="3. JVM执行器"></a>3. JVM执行器</h1><p>解释执行：包括C++解释器和模板解释器。解释执行并不是每次执行字节码时动态把它编译成机器码，而是将根据字节码的类型，转到对应的机器码去执行，即一个派发（switch）的过程。而C++解释器派发到的是由字节码对应的C++代码所编译成的机器码，模板解释器派发到的是字节码对应的汇编模板所生成的机器码。由于C++代码由编译器编译成机器码，比较冗余，所以执行速度慢，而模板解释器的汇编模板是直接由汇编代码专门编写的，执行效率高。<span style="background-color:#ffff00">解释执行执行速度较慢，并不是每次将字节码动态编码生成机器码的原因，这是错误的。而是对于每个字节码都派发到对应的机器码上执行，而不是从上到下的顺序执行机器码，多了很多判断、跳转的指令，所以效率较低。</span></p><p>编译执行：JIT对于热点代码，编译成运行效率高的机器码。这里与模板解释器的区别在于JIT针对的是代码段生成机器码，而模板解释器是针对每个字节码指令生成机器码，以及JIT是动态生成的，模板解释器是在JVM启动时就把字节码对应的汇编模板转换为机器码。某种意义上模板解释器也属于JIT的范畴。当JIT把整段代码直接编译成机器码时，在执行时就可以自上而下的获取执行机器码，而不用对于每条字节码指令跳转到对应的机器码上，执行效率获得提升。</p><h1 id="4-参考与感谢"><a href="#4-参考与感谢" class="headerlink" title="4. 参考与感谢"></a>4. 参考与感谢</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;JVM的编译器和解释器(随笔) - 魅力峰值 - 博客园]]</p>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-7、Thread</title>
      <link href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-7%E3%80%81Thread/"/>
      <url>/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-7%E3%80%81Thread/</url>
      
        <content type="html"><![CDATA[<h1 id="1-实现"><a href="#1-实现" class="headerlink" title="1. 实现"></a>1. 实现</h1><h1 id="2-状态⭐️🔴"><a href="#2-状态⭐️🔴" class="headerlink" title="2. 状态⭐️🔴"></a>2. 状态⭐️🔴</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528070836.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230528071035.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202164530.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230321134743.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221203083359.png"><br><a href="https://blog.51cto.com/u_15080031/4547025">https://blog.51cto.com/u_15080031/4547025</a></p><h2 id="2-1-状态流转"><a href="#2-1-状态流转" class="headerlink" title="2.1. 状态流转"></a>2.1. 状态流转</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113165225.png"></p><p>结合线程状态解释一下执行过程。(状态装换参考自《深入理解 Java 虚拟机》)</p><ol><li>新建（New），新建后尚未启动的线程</li><li>运行（Runnable），Runnable 包括了操作系统线程状态中的 Running 和 Ready</li><li>无限期等待（Waiting），不会被分配 CPU 执行时间，要等待被其他线程显式的唤醒。例如调用没有设置 Timeout 参数的 Object.wait() 方法</li><li>限期等待（Timed Waiting），不会被分配 CPU 执行时间，不过无需等待其他线程显示的唤醒，在一定时间之后会由系统自动唤醒。例如调用 Thread.sleep() 方法</li><li>阻塞（Blocked），线程被阻塞了，“阻塞状态”与“等待状态”的区别是：“阻塞状态”在等待获取着一个排他锁，这个事件将在另外一个线程放弃这个锁的时候发生，而“等待状态”则是在等待一段时间，或者唤醒动作的发生。在程序等待进入同步区域的时候，线程将进入这种状态</li><li>结束（Terminated）：线程结束执行</li></ol><h2 id="2-2-枚举"><a href="#2-2-枚举" class="headerlink" title="2.2. 枚举"></a>2.2. 枚举</h2><p>首先明确一点，当我们讨论一个线程的状态，指的是 Thread 类中 threadStatus 的值。</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> <span class="hljs-variable">threadStatus</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;  <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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">State</span> &#123;  <br>    NEW,<br>    RUNNABLE,<br>    BLOCKED,<br>    WAITING,<br>    TIMED_WAITING,<br>    TERMINATED;<br>&#125;<br></code></pre></td></tr></table></figure><p>也就是说，线程的具体状态，看 threadStatus 就行了。</p><h3 id="2-2-1-NEW"><a href="#2-2-1-NEW" class="headerlink" title="2.2.1. NEW"></a>2.2.1. NEW</h3><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">Thread</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>();  <br></code></pre></td></tr></table></figure><p>这个时候，线程 t 就处于新建状态。但他还不是“线程”。</p><h3 id="2-2-2-RUNNABLE"><a href="#2-2-2-RUNNABLE" class="headerlink" title="2.2.2. RUNNABLE"></a>2.2.2. RUNNABLE</h3><p>然后调用 start() 方法。</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">t.start();  <br></code></pre></td></tr></table></figure><p>调用 start() 后，会执行一个 native 方法创建内核线程，以 linux 为例：</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">native</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">start0</span><span class="hljs-params">()</span>;<br><br><span class="hljs-comment">// 最后走到这</span><br>hotspot/src/os/linux/vm/os_linux.cpp  <br><span class="hljs-title function_">pthread_create</span><span class="hljs-params">(...)</span>;  <br></code></pre></td></tr></table></figure><p>这时候才有一个真正的线程创建出来，并即刻开始运行。这个内核线程与线程 t 进行 1：1 的映射。这时候 t 具备运行能力，进入 RUNNABLE 状态。 RUNNABLE 可以细分为 READY 和 RUNNING，两者的区别只是是否等待到了资源并开始运行。<br>处于 RUNNABLE 且未运行的线程，会进入一个就绪队列中，等待操作系统的调度。处于就绪队列的线程都在等待资源，这个资源可以是 cpu 的时间片、也可以是系统的 IO。 JVM 并不关系 READY 和 RUNNING 这两种状态，毕竟上述的枚举类都不对 RUNNABLE 进行细分。</p><h3 id="2-2-3-TERMINATED"><a href="#2-2-3-TERMINATED" class="headerlink" title="2.2.3. TERMINATED"></a>2.2.3. TERMINATED</h3><p>当一个线程执行完毕（或者调用已经不建议的 stop 方法），线程的状态就变为 TERMINATED。进入 TERMINATED 后，线程的状态不可逆，无法再复活。</p><h3 id="2-2-4-WAITING"><a href="#2-2-4-WAITING" class="headerlink" title="2.2.4. WAITING"></a>2.2.4. WAITING</h3><p>等待中的线程状态，下面几个方法的调用会导致线程进入 WAITING 状态：</p><ul><li>Object.wait()</li><li>Thread.join()</li><li>LockSupport.park()</li></ul><p><strong>关于 BLOCKED、WAITING、TIMED_WAITING</strong></p><p>BLOCKED、WAITING、TIMED_WAITING 都是带有同步语义的状态，我们先看一下 <code>wait</code> 和 <code>notify</code> 方法的底层实现。</p><h4 id="2-2-4-1-wait-方法"><a href="#2-2-4-1-wait-方法" class="headerlink" title="2.2.4.1. wait 方法"></a>2.2.4.1. wait 方法</h4><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><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 c++"><span class="hljs-comment">//1.调用ObjectSynchronizer::wait方法</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ObjectSynchronizer::wait</span><span class="hljs-params">(Handle obj, jlong millis, TRAPS)</span> </span>&#123;<br>  <span class="hljs-comment">/*省略 */</span><br>  <span class="hljs-comment">//2.获得Object的monitor对象(即内置锁)</span><br>  ObjectMonitor* monitor = ObjectSynchronizer::<span class="hljs-built_in">inflate</span>(THREAD, <span class="hljs-built_in">obj</span>());<br>  <span class="hljs-built_in">DTRACE_MONITOR_WAIT_PROBE</span>(monitor, <span class="hljs-built_in">obj</span>(), THREAD, millis);<br>  <span class="hljs-comment">//3.调用monitor的wait方法</span><br>  monitor-&gt;<span class="hljs-built_in">wait</span>(millis, <span class="hljs-literal">true</span>, THREAD);<br>  <span class="hljs-comment">/*省略*/</span><br>&#125;<br>  <span class="hljs-comment">//4.在wait方法中调用addWaiter方法</span><br>  <span class="hljs-function"><span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> <span class="hljs-title">ObjectMonitor::AddWaiter</span><span class="hljs-params">(ObjectWaiter* node)</span> </span>&#123;<br>  <span class="hljs-comment">/*省略*/</span><br>  <span class="hljs-keyword">if</span> (_WaitSet == <span class="hljs-literal">NULL</span>) &#123;<br>    <span class="hljs-comment">//_WaitSet为null，就初始化_waitSet</span><br>    _WaitSet = node;<br>    node-&gt;_prev = node;<br>    node-&gt;_next = node;<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-comment">//否则就尾插</span><br>    ObjectWaiter* head = _WaitSet ;<br>    ObjectWaiter* tail = head-&gt;_prev;<br>    <span class="hljs-built_in">assert</span>(tail-&gt;_next == head, <span class="hljs-string">&quot;invariant check&quot;</span>);<br>    tail-&gt;_next = node;<br>    head-&gt;_prev = node;<br>    node-&gt;_next = head;<br>    node-&gt;_prev = tail;<br>  &#125;<br>&#125;<br>  <span class="hljs-comment">//5.然后在ObjectMonitor::exit释放锁，接着 thread_ParkEvent-&gt;park  也就是wait</span><br></code></pre></td></tr></table></figure><p>总结：通过 object 获得内置锁 (objectMonitor)，通过内置锁将 Thread 封装成 OjectWaiter 对象，然后 addWaiter 将它插入以 _waitSet 为首结点的等待线程链表中去，最后释放锁。</p><h4 id="2-2-4-2-notify-方法"><a href="#2-2-4-2-notify-方法" class="headerlink" title="2.2.4.2. notify 方法"></a>2.2.4.2. notify 方法</h4><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><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 c++"><span class="hljs-comment">//1.调用ObjectSynchronizer::notify方法</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ObjectSynchronizer::notify</span><span class="hljs-params">(Handle obj, TRAPS)</span> </span>&#123;<br>    <span class="hljs-comment">/*省略*/</span><br>    <span class="hljs-comment">//2.调用ObjectSynchronizer::inflate方法</span><br>    ObjectSynchronizer::<span class="hljs-built_in">inflate</span>(THREAD, <span class="hljs-built_in">obj</span>())-&gt;<span class="hljs-built_in">notify</span>(THREAD);<br>&#125;<br>    <span class="hljs-comment">//3.通过inflate方法得到ObjectMonitor对象</span><br>    <span class="hljs-function">ObjectMonitor * ATTR <span class="hljs-title">ObjectSynchronizer::inflate</span> <span class="hljs-params">(Thread * Self, oop object)</span> </span>&#123;<br>    <span class="hljs-comment">/*省略*/</span><br>     <span class="hljs-keyword">if</span> (mark-&gt;<span class="hljs-built_in">has_monitor</span>()) &#123;<br>          ObjectMonitor * inf = mark-&gt;<span class="hljs-built_in">monitor</span>() ;<br>          <span class="hljs-built_in">assert</span> (inf-&gt;<span class="hljs-built_in">header</span>()-&gt;<span class="hljs-built_in">is_neutral</span>(), <span class="hljs-string">&quot;invariant&quot;</span>);<br>          <span class="hljs-built_in">assert</span> (inf-&gt;<span class="hljs-built_in">object</span>() == object, <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>          <span class="hljs-built_in">assert</span> (ObjectSynchronizer::<span class="hljs-built_in">verify_objmon_isinpool</span>(inf), <span class="hljs-string">&quot;monitor is inva;lid&quot;</span>);<br>          <span class="hljs-keyword">return</span> inf <br>      &#125;<br>    <span class="hljs-comment">/*省略*/</span> <br>      &#125;<br>    <span class="hljs-comment">//4.调用ObjectMonitor的notify方法</span><br>    <span class="hljs-type">void</span> ObjectMonitor::<span class="hljs-built_in">notify</span>(TRAPS) &#123;<br>    <span class="hljs-comment">/*省略*/</span><br>    <span class="hljs-comment">//5.调用DequeueWaiter方法移出_waiterSet第一个结点</span><br>    ObjectWaiter * iterator = <span class="hljs-built_in">DequeueWaiter</span>() ;<br>    <span class="hljs-comment">//6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作</span><br>    <span class="hljs-comment">/**省略*/</span><br>  &#125;<br></code></pre></td></tr></table></figure><p>总结：通过 object 获得内置锁 (objectMonitor)，调用内置锁的 notify 方法，通过 _waitset 结点移出等待链表中的首结点，将它置于 _EntrySet 中去，等待获取锁。注意：notifyAll 根据 policy 不同可能移入 _EntryList 或者 _cxq 队列中，此处不详谈。</p><p>通过 object 获得 objectMonitor，调用 objectMonitor 的 <code>notify</code> 方法。这个 notify 最后会走到 <code>ObjectMonitor::DequeueWaiter</code> 方法，获取 waitSet 列表中的第一个 ObjectWaiter 节点。并根据不同的策略，将取出来的 ObjectWaiter 节点，加入到 <code>EntryList</code> 或 <code>cxq</code> 中。 <code>notifyAll</code> 的实现类似于 <code>notify</code>，主要差别在多了个 for 循环。</p><p><code>notify</code> 和 <code>notifyAll</code> 并不会立即释放所占有的 ObjectMonitor 对象，其真正释放 ObjectMonitor 的时间点是在执行 <code>monitorexit</code> 指令。</p><p>一旦释放 <code>ObjectMonitor</code> 对象了，<code>entryList</code> 和 <code>cxq</code> 中的 ObjectWaiter 节点会依据 <code>QMode</code> 所配置的策略，通过 ExitEpilog 方法唤醒取出来的 ObjectWaiter 节点。被唤醒的线程，继续参与 monitor 的竞争。若竞争失败，重新进入 BLOCKED 状态，再回顾一下 monitor 的核心结构。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113172034.png"></p><h4 id="2-2-4-3-join-方法"><a href="#2-2-4-3-join-方法" class="headerlink" title="2.2.4.3. join 方法"></a>2.2.4.3. join 方法</h4><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">final</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">join</span><span class="hljs-params">(<span class="hljs-type">long</span> millis)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;  <br>    ...<br>  <span class="hljs-keyword">if</span> (millis == <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-keyword">while</span> (isAlive()) &#123;<br>      wait(<span class="hljs-number">0</span>);<br>    &#125;<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-keyword">while</span> (isAlive()) &#123;<br>      <span class="hljs-type">long</span> <span class="hljs-variable">delay</span> <span class="hljs-operator">=</span> millis - now;<br>      <span class="hljs-keyword">if</span> (delay &lt;= <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-keyword">break</span>;<br>      &#125;<br>      wait(delay);<br>      now = System.currentTimeMillis() - base;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><code>join</code> 的本质仍然是 <code>wait()</code> 方法。在使用 <code>join</code> 时，JVM 会帮我们隐式调用 <code>notify</code>，因此我们不需要主动 notify 唤醒主线程。而 <code>sleep()</code> 方法最终是调用 <code>SleepEvent</code> 对象的 park 方法：</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> os::sleep(Thread* thread, jlong millis, bool interruptible) &#123;  <br>  <span class="hljs-comment">//获取thread中的_SleepEvent对象</span><br>  ParkEvent * <span class="hljs-type">const</span> <span class="hljs-variable">slp</span> <span class="hljs-operator">=</span> thread-&gt;_SleepEvent ;<br>  ...<br>  <span class="hljs-comment">//如果是允许被打断</span><br>  <span class="hljs-keyword">if</span> (interruptible) &#123;<br>    <span class="hljs-comment">//记录下当前时间戳，这是时间比较的基准</span><br>    <span class="hljs-type">jlong</span> <span class="hljs-variable">prevtime</span> <span class="hljs-operator">=</span> javaTimeNanos();<br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>      <span class="hljs-comment">//检查打断标记，如果打断标记为true，则直接返回</span><br>      <span class="hljs-keyword">if</span> (os::is_interrupted(thread, <span class="hljs-literal">true</span>)) &#123;<br>        <span class="hljs-keyword">return</span> OS_INTRPT;<br>      &#125;<br>      <span class="hljs-comment">//线程被唤醒后的当前时间戳</span><br>      <span class="hljs-type">jlong</span> <span class="hljs-variable">newtime</span> <span class="hljs-operator">=</span> javaTimeNanos();<br>      <span class="hljs-comment">//睡眠毫秒数减去当前已经经过的毫秒数</span><br>      millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;<br>      <span class="hljs-comment">//如果小于0，那么说明已经睡眠了足够多的时间，直接返回</span><br>      <span class="hljs-keyword">if</span> (millis &lt;= <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-keyword">return</span> OS_OK;<br>      &#125;<br>      <span class="hljs-comment">//更新基准时间</span><br>      prevtime = newtime;<br>      <span class="hljs-comment">//调用_SleepEvent对象的park方法，阻塞线程</span><br>      slp-&gt;park(millis);<br>    &#125;<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-comment">//如果不能打断，除了不再返回OS_INTRPT以外，逻辑是完全相同的</span><br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>      ...<br>      slp-&gt;park(millis);<br>      ...<br>    &#125;<br>    <span class="hljs-keyword">return</span> OS_OK ;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><code>Thread.sleep</code> 在 jvm 层面上是调用 thread 中 <code>SleepEvent</code> 对象的 <code>park()</code> 方法实现阻塞线程，在此过程中会通过判断时间戳来决定线程的睡眠时间是否达到了指定的毫秒。看到这里，对于 <code>sleep</code> 和 <code>wait</code> 的区别应该会有更深入的理解。</p><p><code>park</code>、<code>unpark</code> 方法也与同步语义无关。每个线程都与一个许可 (permit) 关联。<code>unpark</code> 函数为线程提供 permit，线程调用 <code>park</code> 函数则等待并消耗 permit。park 和 unpark 方法具体实现比较复杂，这里不展开。到此为止，我们可以整理出如下的线程状态转换图。</p><h3 id="2-2-5-BLOCKED"><a href="#2-2-5-BLOCKED" class="headerlink" title="2.2.5. BLOCKED"></a>2.2.5. BLOCKED</h3><p>等待 Monitor 锁的阻塞线程的线程状态，处于阻塞状态的线程正在等待 Monitor 锁进入 synchronized Block 或者 Method，或者在调用 Object.wait 后重新进入同步块&#x2F;方法。简单的说，就是线程等待 synchronized 形式的锁时的状态</p><h4 id="2-2-5-1-与-Waiting-区别⭐️🔴"><a href="#2-2-5-1-与-Waiting-区别⭐️🔴" class="headerlink" title="2.2.5.1. 与 Waiting 区别⭐️🔴"></a>2.2.5.1. 与 Waiting 区别⭐️🔴</h4><p>BLOCKED 和 WAITING 都是属于线程的阻塞等待状态。 BLOCKED 状态是指线程在等待监视器锁的时候的阻塞状态。 （如图）也就是在多个线程去竞争 Synchronized 同步锁的时候，没有竞争到锁资源的线程，会被阻塞等待，这个时候线程状态就是 BLOCKED。在线程的整个生命周期里面，<span style="background-color:#ff00ff">只有 Synchronized 同步锁等待才会存在这个状态</span>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230613221943.png" alt="image.png"></p><p>WAITING 状态，表示线程的等待状态，在这种状态下，线程需要等待某个线程的特定操作才会被唤醒。我们可以使用 Object.wait()、Object.join()、LockSupport.park() 这些方法使得线程进入到 WAITING 状态，在这个状态下，必须要等待特定的方法来唤醒，比如 Object.notify 方法可以唤醒 Object.wait()方法阻塞的线程 LockSupport.unpark()可以唤醒 LockSupport.park()方法阻塞的线程。 </p><p>所以，在我看来，BLOCKED 和 WAITING 两个状态最大的区别有两个：</p><ol><li>BLOCKED 是锁竞争失败后被被动触发的状态，WAITING 是人为的主动触发的状态</li><li>BLCKED 的唤醒时自动触发的，而 WAITING 状态是必须要通过特定的方法来主动唤醒以上就是我对这个问题的理解。</li></ol><p><a href="https://segmentfault.com/a/1190000039044989">https://segmentfault.com/a/1190000039044989</a></p><p><a href="https://blog.51cto.com/u_15080031/4547025">https://blog.51cto.com/u_15080031/4547025</a><br>BLOCKED 状态可以视作是一种特殊的 WAITING，特指等待锁。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113173735.svg"></p><h2 id="2-3-查看状态"><a href="#2-3-查看状态" class="headerlink" title="2.3. 查看状态"></a>2.3. 查看状态</h2><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;Java线程状态分析  Format’s Notes]]</p><h2 id="2-4-线程中断⭐️🔴"><a href="#2-4-线程中断⭐️🔴" class="headerlink" title="2.4. 线程中断⭐️🔴"></a>2.4. 线程中断⭐️🔴</h2><h3 id="2-4-1-什么是线程中断"><a href="#2-4-1-什么是线程中断" class="headerlink" title="2.4.1. 什么是线程中断"></a>2.4.1. 什么是线程中断</h3><p><span style="background-color:#00ff00">而 Thread.interrupt 的作用其实也不是中断线程，而是「<font color=#ff0000>通知线程应该中断了</font>」，具体到底中断还是继续运行，应该由被通知的线程自己处理。</span></p><p>具体来说，当对一个线程，调用 interrupt() 时，</p><p>① 如果线程处于被阻塞状态（例如处于 sleep, wait, join 等状态），那么线程将<span style="background-color:#ff00ff">立即退出被阻塞状态</span>，并抛出一个 InterruptedException 异常。仅此而已。</p><p>② 如果线程处于正常活动状态，那么会将该线程的中断标志设置为 true，仅此而已。被设置中断标志的线程将继续正常运行，不受影响。</p><p>interrupt() 并不能真正的中断线程，需要被调用的线程自己进行配合才行。</p><p>也就是说，一个线程如果有被中断的需求，那么就可以这样做</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105193428.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105214558.png"></p><h4 id="2-4-1-1-interrupt-与-interrupted-与-isInterrupted⭐️🔴"><a href="#2-4-1-1-interrupt-与-interrupted-与-isInterrupted⭐️🔴" class="headerlink" title="2.4.1.1. interrupt 与 interrupted 与 isInterrupted⭐️🔴"></a>2.4.1.1. interrupt 与 interrupted 与 isInterrupted⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105193840.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105194002.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105221141.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105221213.png"></p><h3 id="2-4-2-如何中断线程"><a href="#2-4-2-如何中断线程" class="headerlink" title="2.4.2. 如何中断线程"></a>2.4.2. 如何中断线程</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220170534.png"></p><h4 id="2-4-2-1-线程休眠后如何唤醒线程-唤醒线程的方法"><a href="#2-4-2-1-线程休眠后如何唤醒线程-唤醒线程的方法" class="headerlink" title="2.4.2.1. 线程休眠后如何唤醒线程 (唤醒线程的方法)"></a>2.4.2.1. 线程休眠后如何唤醒线程 (唤醒线程的方法)</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220171211.png"></p><h4 id="2-4-2-2-中断补偿⭐️🔴"><a href="#2-4-2-2-中断补偿⭐️🔴" class="headerlink" title="2.4.2.2. 中断补偿⭐️🔴"></a>2.4.2.2. 中断补偿⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220171243.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105220113.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105220727.png"></p><p>温故知新：<a href="/2022/12/19/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-12%E3%80%81AQS/" title="并发基础-12、AQS">并发基础-12、AQS</a></p><h2 id="2-5-优雅退出-中断线程-⭐️🔴"><a href="#2-5-优雅退出-中断线程-⭐️🔴" class="headerlink" title="2.5. 优雅退出 (中断线程)⭐️🔴"></a>2.5. 优雅退出 (中断线程)⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105194604.png"></p><h3 id="2-5-1-volatile"><a href="#2-5-1-volatile" class="headerlink" title="2.5.1. volatile"></a>2.5.1. volatile</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220171503.png"></p><h3 id="2-5-2-atomicBoolean"><a href="#2-5-2-atomicBoolean" class="headerlink" title="2.5.2. atomicBoolean"></a>2.5.2. atomicBoolean</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220171517.png"></p><h3 id="2-5-3-interrupt-isInterrupted"><a href="#2-5-3-interrupt-isInterrupted" class="headerlink" title="2.5.3. interrupt+isInterrupted"></a>2.5.3. interrupt+isInterrupted</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221220174439.png"></p><h4 id="2-5-3-1-源码分析"><a href="#2-5-3-1-源码分析" class="headerlink" title="2.5.3.1. 源码分析"></a>2.5.3.1. 源码分析</h4><h1 id="3-start-线程开启-c-源码分析"><a href="#3-start-线程开启-c-源码分析" class="headerlink" title="3. start 线程开启 c 源码分析"></a>3. start 线程开启 c 源码分析</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105142221.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105142206.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105142258.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105142304.png"></p><h1 id="4-实战经验"><a href="#4-实战经验" class="headerlink" title="4. 实战经验"></a>4. 实战经验</h1><p><a href="https://juejin.cn/post/6857365822445191182">https://juejin.cn/post/6857365822445191182</a><br><a href="https://www.cnblogs.com/Chary/p/16522149.html">https://www.cnblogs.com/Chary/p/16522149.html</a></p><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><p><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a><br><a href="https://fangjian0423.github.io/2016/06/04/java-thread-state/">https://fangjian0423.github.io/2016/06/04/java-thread-state/</a></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-9、Java各种锁</title>
      <link href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-9%E3%80%81Java%E5%90%84%E7%A7%8D%E9%94%81/"/>
      <url>/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-9%E3%80%81Java%E5%90%84%E7%A7%8D%E9%94%81/</url>
      
        <content type="html"><![CDATA[<h2 id="1-Synchronized-8-锁问题⭐️🔴"><a href="#1-Synchronized-8-锁问题⭐️🔴" class="headerlink" title="1. Synchronized 8 锁问题⭐️🔴"></a>1. Synchronized 8 锁问题⭐️🔴</h2><p>示例代码：[[Lock_8.java]]</p><p>❕<span style="display:none">%%<br>0915-🏡⭐️◼️阿里规约🔜MSTM📝 能用对象锁就不用类锁。尽可能使得加锁代码块工作量变小，避免在锁块中调用 RPC 方法◼️⭐️-point-202301230915%%</span></p><figure class="highlight plaintext"><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 plaintext">*1 标准访问，请问先打印邮件还是短信<br>*2 暂停4秒钟在邮件方法，请问先打印邮件还是短信<br>*3 新增普通sayHello方法，请问先打印邮件还是hello<br>*4 两部手机，请问先打印邮件还是短信<br>*5 两个静态同步方法，同一部手机，请问先打印邮件还是短信<br>*6 两个静态同步方法，2部手机，请问先打印邮件还是短信<br>*7 1个静态同步方法，1个普通同步方法,同一部手机，请问先打印邮件还是短信<br>*8 1个静态同步方法，1个普通同步方法,2部手机，请问先打印邮件还是短信<br></code></pre></td></tr></table></figure><h3 id="1-1-锁对象"><a href="#1-1-锁对象" class="headerlink" title="1.1. 锁对象"></a>1.1. 锁对象</h3><h4 id="1-1-1-情况-1-和-2⭐️🔴"><a href="#1-1-1-情况-1-和-2⭐️🔴" class="headerlink" title="1.1.1. 情况 1 和 2⭐️🔴"></a>1.1.1. 情况 1 和 2⭐️🔴</h4><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230512-1442%%</span>❕ ^tcdpjx</p><p>锁的是&#x3D;&#x3D;当前对象 this&#x3D;&#x3D;，被锁定后，其它的线程都不能进入到当前对象的其它的 synchronized 方法<br><span style="background-color:#ffff00">一个对象里面如果有多个 synchronized 方法</span>，某一个时刻内，只要一个线程去调用其中的一个 synchronized 方法了，<span style="background-color:#00ff00">其它的线程都只能等待</span>，换句话说，<span style="background-color:#ff0000">某一个时刻内，只能有唯一一个线程去访问这些 synchronized 方法</span>❕<span style="display:none">%%<br>0918-🏡⭐️◼️实例锁同一时刻🔜MSTM📝 只允许 1 个线程访问该实例的任何一个 synchronized 方法◼️⭐️-point-202301230918%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512144134.png" alt="image.png"></p><h4 id="1-1-2-情况-3"><a href="#1-1-2-情况-3" class="headerlink" title="1.1.2. 情况 3"></a>1.1.2. 情况 3</h4><p>加个普通方法后发现和同步锁无关</p><h4 id="1-1-3-情况-4"><a href="#1-1-3-情况-4" class="headerlink" title="1.1.3. 情况 4"></a>1.1.3. 情况 4</h4><p>换成两个对象后，不是同一把锁了，互不影响。</p><h3 id="1-2-锁类"><a href="#1-2-锁类" class="headerlink" title="1.2. 锁类"></a>1.2. 锁类</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512144209.png" alt="image.png"></p><h4 id="1-2-1-情况-5-和-6"><a href="#1-2-1-情况-5-和-6" class="headerlink" title="1.2.1. 情况 5 和 6"></a>1.2.1. 情况 5 和 6</h4><p>对于静态同步方法，锁是当前类的 Class 对象。</p><h4 id="1-2-2-情况-7-和-8"><a href="#1-2-2-情况-7-和-8" class="headerlink" title="1.2.2. 情况 7 和 8"></a>1.2.2. 情况 7 和 8</h4><p>  synchronized 实现同步的基础：Java 中的每一个对象都可以作为锁。<br> 具体表现为以下 3 种形式：</p><ul><li>对于普通同步方法，锁是当前实例对象,锁的是当前对象 this，</li><li>对于同步方法块，锁是 Synchonized 括号里配置的对象</li><li>对于静态同步方法，锁是当前类的 Class 对象。</li></ul><h3 id="1-3-总结"><a href="#1-3-总结" class="headerlink" title="1.3. 总结"></a>1.3. 总结</h3><p>synchronized 实现同步的基础：Java 中的每一个对象都可以作为锁。<br>具体表现为以下 3 种形式。<br>对于普通同步方法，锁是当前实例对象，即 this，比如例子中的一部部手机。<br>对于静态同步方法，锁是当前类的 Class 对象，比如例子中的 Phone.class 这个 Class 对象。<br>对于同步方法块，锁是 Synchonized 括号里配置的对象。</p><h3 id="1-4-原理概括"><a href="#1-4-原理概括" class="headerlink" title="1.4. 原理概括"></a>1.4. 原理概括</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105185757.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105185914.png"></p><h2 id="2-乐观锁和悲观锁"><a href="#2-乐观锁和悲观锁" class="headerlink" title="2. 乐观锁和悲观锁"></a>2. 乐观锁和悲观锁</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105173839.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105173907.png"></p><h2 id="3-公平锁和非公平锁"><a href="#3-公平锁和非公平锁" class="headerlink" title="3. 公平锁和非公平锁"></a>3. 公平锁和非公平锁</h2><p>公平锁&#x2F;非公平锁：并发包中 ReentrantLock 的创建可以指定构造函数的 boolean 类型来得到公平锁或非公平锁，默认是非公平锁。</p><p>关于两者区别：</p><p>公平锁：Threads acquire a fair lock in the order in which they requested it.</p><p>公平锁，就是很公平，在并发情况下，每个线程在获取锁时会查看此锁维护的等待队列，如果为空，或者当前线程是等待队列的第一个，就占有锁，否则就会加入到等待队列中【比如 AQS 中的 CLH 队列】，以后会按照 FIFO 的规则从队列中取到自己。</p><p>非公平锁：非公平锁比较粗鲁，上来就直接尝试占有锁，如果尝试失败，就再采取类似公平锁那种方式。</p><h3 id="3-1-Synchronized"><a href="#3-1-Synchronized" class="headerlink" title="3.1. Synchronized"></a>3.1. Synchronized</h3><p>是 非公平锁</p><h3 id="3-2-ReentrantLock"><a href="#3-2-ReentrantLock" class="headerlink" title="3.2. ReentrantLock"></a>3.2. ReentrantLock</h3><p>默认创建非公平锁，可以设置为 true，创建公平锁</p><h2 id="4-可重入锁"><a href="#4-可重入锁" class="headerlink" title="4. 可重入锁"></a>4. 可重入锁</h2><h3 id="4-1-ReentrantLock"><a href="#4-1-ReentrantLock" class="headerlink" title="4.1. ReentrantLock"></a>4.1. ReentrantLock</h3><p>ReentrantLock: 是基于 AQS 同步器实现的可重入锁，手动上几次锁，就需要手动释放多少次锁</p><h2 id="5-独占锁-共享锁"><a href="#5-独占锁-共享锁" class="headerlink" title="5. 独占锁 共享锁"></a>5. 独占锁 共享锁</h2><p><a href="https://blog.csdn.net/varyall/article/details/80330216">https://blog.csdn.net/varyall/article/details/80330216</a></p><p><a href="https://zhuanlan.zhihu.com/p/105991128">https://zhuanlan.zhihu.com/p/105991128</a></p><h3 id="5-1-独占锁"><a href="#5-1-独占锁" class="headerlink" title="5.1. 独占锁"></a>5.1. 独占锁</h3><p>#todo</p><span style="display:none">- [ ] 🚩 - 锁分类整理 - 🏡 2023-01-26 22:58</span><h4 id="5-1-1-Synchronized"><a href="#5-1-1-Synchronized" class="headerlink" title="5.1.1. Synchronized"></a>5.1.1. Synchronized</h4><h4 id="5-1-2-ReentrantLock"><a href="#5-1-2-ReentrantLock" class="headerlink" title="5.1.2. ReentrantLock"></a>5.1.2. ReentrantLock</h4><h4 id="5-1-3-CyclicBarrier"><a href="#5-1-3-CyclicBarrier" class="headerlink" title="5.1.3. CyclicBarrier"></a>5.1.3. CyclicBarrier</h4><h5 id="5-1-3-1-原理"><a href="#5-1-3-1-原理" class="headerlink" title="5.1.3.1. 原理"></a>5.1.3.1. 原理</h5><p>通过可重入独占锁所实现的同步用障碍锁，CyclicBarrier 基于 Condition 来实现的，<span style="background-color:#ff00ff">是 ReentrantLock 和 Condition 的组合使用</span></p><h5 id="5-1-3-2-使用场景"><a href="#5-1-3-2-使用场景" class="headerlink" title="5.1.3.2. 使用场景"></a>5.1.3.2. 使用场景</h5><p>用于多线程计算数据，最后合并计算结果的场景。每个 <code>parter</code> 负责一部分计算，最后的线程 <code>barrierAction</code> 线程进行数据汇总。</p><h3 id="5-2-共享锁"><a href="#5-2-共享锁" class="headerlink" title="5.2. 共享锁"></a>5.2. 共享锁</h3><h4 id="5-2-1-ReentrantReadWriteLock"><a href="#5-2-1-ReentrantReadWriteLock" class="headerlink" title="5.2.1. ReentrantReadWriteLock"></a>5.2.1. ReentrantReadWriteLock</h4><p>读读可共享  读写 写写不共享</p><p>读写锁，读锁是共享锁，写锁是独占锁</p><h4 id="5-2-2-CountDownLatch"><a href="#5-2-2-CountDownLatch" class="headerlink" title="5.2.2. CountDownLatch"></a>5.2.2. CountDownLatch</h4><p><a href="https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Multithread/AQS.md#42-countdownlatch-%E7%9A%84%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B">https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Multithread/AQS.md#42-countdownlatch-%E7%9A%84%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B</a></p><p>&#x3D;&#x3D;可重入共享锁，基于 AQS 的共享模式的使用&#x3D;&#x3D;</p><h5 id="5-2-2-1-原理⭐️🔴"><a href="#5-2-2-1-原理⭐️🔴" class="headerlink" title="5.2.2.1. 原理⭐️🔴"></a>5.2.2.1. 原理⭐️🔴</h5><p>CountDownLatch 允许 count 个线程阻塞在一个地方，直至所有线程的任务都执行完毕。</p><p>CountDownLatch 是共享锁的一种实现，它&#x3D;&#x3D;默认构造 AQS 的 state 值为 count&#x3D;&#x3D;。当线程使用 countDown 方法时，其实使用了 <code>tryReleaseShared</code> 方法以 CAS 的操作来减少 state， 直至 state 为 0 就代表所有的线程都调用了 countDown 方法。当调用 await 方法的时候，如果 state 不为 0，就代表仍然有线程没有调用 countDown 方法，那么就<span style="background-color:#00ff00">把已经调用过 countDown 的线程都放入阻塞队列 Park，并自旋 CAS 判断 state &#x3D;&#x3D; 0，直至最后一个线程调用了 countDown，使得 state &#x3D;&#x3D; 0，于是阻塞的线程便判断成功，全部往下执行。</span><br>❕<span style="display:none">%%<br>0918-🏡⭐️◼️CountDownLatch 原理🔜MSTM📝 JVM 构造一个 state 是 count 的 AQS，有线程调用 countdown 方法 state 就减一直至为 0，否则将所有已经调用过的线程 park 住◼️⭐️-point-202301230918%%</span></p><h5 id="5-2-2-2-Demo"><a href="#5-2-2-2-Demo" class="headerlink" title="5.2.2.2. Demo"></a>5.2.2.2. Demo</h5><p>thread0308：[[CountDownLatchDemo.java]]<br>case_java8 ：[[TestCountDownLatch.java]]</p><h5 id="5-2-2-3-使用场景⭐️🔴"><a href="#5-2-2-3-使用场景⭐️🔴" class="headerlink" title="5.2.2.3. 使用场景⭐️🔴"></a>5.2.2.3. 使用场景⭐️🔴</h5><ol><li>某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n ：<code>new CountDownLatch(n)</code>，每当一个任务线程执行完毕，就将计数器减 1 <code>countdownlatch.countDown()</code>，当计数器的值变为 0 时，在 <code>CountDownLatch上 await()</code> 的线程就会被唤醒。<span style="background-color:#00ff00">一个典型应用场景就是启动一个服务时，主线程需要等待多个组件加载完毕，之后再继续执行</span>。❕<span style="display:none">%%<br> 0920-🏡⭐️◼️CountDownLatch 的典型应用场景：在所有的资源、监控等辅助线程启动完毕之后启动程序的主线程◼️⭐️-point-202301230920%%</span></li><li>实现多个线程开始执行任务的最大并行性。注意是并行性，不是并发，强调的是多个线程在某一时刻同时开始执行。类似于赛跑，将多个线程放到起点，等待发令枪响，然后同时开跑。做法是初始化一个共享的 <code>CountDownLatch</code> 对象，将其计数器初始化为 1 ：<code>new CountDownLatch(1)</code>，多个线程在开始执行任务前首先 <code>coundownlatch.await()</code>，当主线程调用 countDown() 时，计数器变为 0，多个线程同时被唤醒。</li></ol><h4 id="5-2-3-Semaphore"><a href="#5-2-3-Semaphore" class="headerlink" title="5.2.3. Semaphore"></a>5.2.3. Semaphore</h4><h5 id="5-2-3-1-原理⭐️🔴"><a href="#5-2-3-1-原理⭐️🔴" class="headerlink" title="5.2.3.1. 原理⭐️🔴"></a>5.2.3.1. 原理⭐️🔴</h5><p>&#x3D;&#x3D;不可重入的共享锁&#x3D;&#x3D;</p><p><span style="background-color:#00ff00">基于计数的信号量</span>，可以用来控制同时访问特定资源的线程数量，阈值范围内，多个线程争抢获取许可信号，完成自己的任务后归还许可信号。<span style="background-color:#00ff00">超过阈值，申请许可信号的线程将会被阻塞，直到有新的许可信号可以使用</span>。<br>❕<span style="display:none">%%<br>0921-🏡⭐️◼️Semaphore 原理是什么🔜MSTM📝 基于计数的信号量，用于控制访问特定资源的线程数量。阈值范围内，多个线程争抢获取信号量，完成自己的功能后释放信号量。阈值范围外，争抢信号量的线程将会被阻塞，直到有信号量可以使用。◼️⭐️-point-202301230921%%</span></p><h5 id="5-2-3-2-demo"><a href="#5-2-3-2-demo" class="headerlink" title="5.2.3.2. demo"></a>5.2.3.2. demo</h5><p>thread0308: [[CyclicBarrierDemo.java]]<br>case_java8 ：[[TestCyclicBarrier.java]]</p><h5 id="5-2-3-3-使用场景⭐️🔴"><a href="#5-2-3-3-使用场景⭐️🔴" class="headerlink" title="5.2.3.3. 使用场景⭐️🔴"></a>5.2.3.3. 使用场景⭐️🔴</h5><p>Semaphore 可以用来做流量控制，<span style="background-color:#00ff00">特别公用资源有限的应用场景，比如数据库连接。</span>假设有一个需求，要读取几万个文件的数据，因为都是 IO 密集型任务，我们可以启动几十个线程并发的读取，但是如果读到内存后，还需要进行存储到数据库中，而数据库的连接数只有 10 几个，这时我们必须控制只有十个线程同时获取数据库连接保存数据，否则会报错无法获取数据库连接。这个时候，我们就可以使用 Semaphore 来做流控。<br>❕<span style="display:none">%%<br>0922-🏡⭐️◼️Semaphore 适合什么场景？共用资源有限的场景，比如数据库连接◼️⭐️-point-202301230922%%</span></p><h3 id="5-3-比较-CyclicBarrier-与-CountDownLatch⭐️🔴"><a href="#5-3-比较-CyclicBarrier-与-CountDownLatch⭐️🔴" class="headerlink" title="5.3. 比较 CyclicBarrier 与 CountDownLatch⭐️🔴"></a>5.3. 比较 CyclicBarrier 与 CountDownLatch⭐️🔴</h3><h4 id="5-3-1-相同点"><a href="#5-3-1-相同点" class="headerlink" title="5.3.1. 相同点"></a>5.3.1. 相同点</h4><p>这两个类都可以实现&#x3D;&#x3D;一组线程在到达某个条件之前进行等待&#x3D;&#x3D;，它们内部都有一个计数器，当计数器的值不断的减为 0 的时候所有阻塞的线程将会被唤醒。</p><h4 id="5-3-2-不同点"><a href="#5-3-2-不同点" class="headerlink" title="5.3.2. 不同点"></a>5.3.2. 不同点</h4><ol><li><strong>CyclicBarrier 的计数器由 CyclicBarrier 自己控制</strong>，而 <strong>CountDownLatch 的计数器则由使用者通过调用 countDown 来控制</strong>，在 CyclicBarrier 中线程调用 await 方法不仅会将自己阻塞还会将计数器减 1，而在 CountDownLatch 中线程调用 await 方法只是将自己阻塞而不会减少计数器的值，减少计数器的值是通过调用 countDown 方法来完成的。</li><li><strong>CountDownLatch 只能拦截一轮</strong>，而 <strong>CyclicBarrier 可以实现循环拦截</strong>。一般来说用 CyclicBarrier 可以实现 CountDownLatch 的功能，而反之则不能，例如上面的赛马程序就只能使用 CyclicBarrier 来实现。</li><li>CyclicBarrier 还提供了：reset()、getNumberWaiting()、isBroken() 等比较有用的方法。</li><li>对于 CountDownLatch 来说，重点是“一个线程（多个线程）等待”，而其他的 N 个线程在完成“某件事情”之后，可以终止，也可以等待。而对于 CyclicBarrier，重点是多个线程，在任意一个线程没有完成，所有的线程都必须等待。</li><li><span style="background-color:#ff00ff">CountDownLatch 是计数器，线程完成一个记录一个，只不过计数不是递增而是递减，而 CyclicBarrier 更像是一个阀门，需要所有线程都到达，阀门才能打开，然后继续执行。</span></li></ol><h4 id="5-3-3-案例"><a href="#5-3-3-案例" class="headerlink" title="5.3.3. 案例"></a>5.3.3. 案例</h4><p>19 | CountDownLatch 和 CyclicBarrier：如何让多线程步调一致？</p><h2 id="6-读写锁⭐️🔴"><a href="#6-读写锁⭐️🔴" class="headerlink" title="6. 读写锁⭐️🔴"></a>6. 读写锁⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230122141315.png" alt="image.png"><br>❕<span style="display:none">%%<br>1550-🏡⭐️◼️锁的发展过程：synchronized 太重，底层使用 mutex，发展出使用 CAS 的 ReentrantLock，但是它读读不共享，又出现了 ReentrantReadWriteLock，解决了 ReentrantLock 读读不共享问题，但是它有写饥饿和锁降级问题，所以又出现了 StampedLock◼️⭐️-point-202301241550%%</span><br><a href="https://www.bilibili.com/video/BV1ar4y1x727?p=156&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ar4y1x727?p=156&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106225103.png"></p><h3 id="6-1-ReentrantReadWriteLock"><a href="#6-1-ReentrantReadWriteLock" class="headerlink" title="6.1. ReentrantReadWriteLock"></a>6.1. ReentrantReadWriteLock</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106225340.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106225923.png"></p><h4 id="6-1-1-锁降级⭐️🔴"><a href="#6-1-1-锁降级⭐️🔴" class="headerlink" title="6.1.1. 锁降级⭐️🔴"></a>6.1.1. 锁降级⭐️🔴</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106231033.png"></p><h5 id="6-1-1-1-可以写后读"><a href="#6-1-1-1-可以写后读" class="headerlink" title="6.1.1.1. 可以写后读"></a>6.1.1.1. 可以写后读</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107084740.png"></p><h5 id="6-1-1-2-不可读后写"><a href="#6-1-1-2-不可读后写" class="headerlink" title="6.1.1.2. 不可读后写"></a>6.1.1.2. 不可读后写</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107085154.png"></p><p><span style="background-color:#ff00ff">如果有线程在读，那么写线程是无法获取写锁的，是悲观锁的策略</span><br>线程获取读锁是不能直接升级为写入锁的。❕<span style="display:none">%%<br>0916-🏡⭐️◼️在 ReentrantReadWriteLock 中，如果读锁在使用?那么尝试获取写锁的线程都会被阻塞，直接所有读锁释放。这是一种悲观锁的策略。◼️⭐️-point-202301230916%%</span></p><p>写锁和读锁是互斥的（这里的互斥是指线程间的互斥，当前线程可以获取到写锁又获取到读锁，但是获取到了读锁不能继续获取写锁），这是因为读写锁要<span style="background-color:#ff00ff">保持写操作的可见性。</span><br>因为，<span style="background-color:#ffff00">如果允许读锁在被获取的情况下对写锁的获取，那么正在运行的其他读线程无法感知到当前写线程的操作。</span></p><p>因此，分析读写锁 ReentrantReadWriteLock，会发现它有个潜在的问题：<br><span style="background-color:#ff0000">读锁全完，写锁有望；写锁独占，读写全堵;</span><br>如果有线程正在读，写线程需要等待读线程释放锁后才能获取写锁，见前面 Case《code 演 LockDownGradingDemo》即 ReadWriteLock 读的过程中不允许写，<span style="background-color:#ff0000">只有等待线程都释放了读锁，当前线程才能获取写锁</span>，也就是写入必须等待，这是一种悲观的读锁：<span style="background-color:#00ff00">还在读，就不能写，省的数据乱。</span> ❕<span style="display:none">%%<br>2301-🏡⭐️◼️ReentrantReadWriteLock 读锁不释放其他线程能否获取到写锁？◼️⭐️-point-202301262301%%</span></p><h4 id="6-1-2-缓存案例"><a href="#6-1-2-缓存案例" class="headerlink" title="6.1.2. 缓存案例"></a>6.1.2. 缓存案例</h4><p>以 <span style="background-color:#00ff00">使用 ReentrantReadWriteLock 更新缓存</span> 为例说明，锁降级保证写操作可见性的原理<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230122110407.png" alt="image.png"></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></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_">CacheData</span> &#123;  <br>    Object data;  <br>    <span class="hljs-comment">// 是否有效，如果失效，需要重新计算 data    </span><br>    <span class="hljs-keyword">volatile</span> <span class="hljs-type">boolean</span> cacheValid;  <br>    <span class="hljs-keyword">final</span> <span class="hljs-type">ReentrantReadWriteLock</span> <span class="hljs-variable">rwl</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReentrantReadWriteLock</span>();  <br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">processCachedData</span><span class="hljs-params">()</span> &#123;  <br>        rwl.readLock().lock();  <br>        <span class="hljs-keyword">if</span> (!cacheValid) &#123; <span class="hljs-comment">// 数据失效 </span><br>            <span class="hljs-comment">// 获取写锁前必须释放读锁  </span><br>            rwl.readLock().unlock();  <br>            rwl.writeLock().lock();  <br>            <span class="hljs-keyword">try</span> &#123;  <br>                <span class="hljs-comment">// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新  </span><br>                <span class="hljs-keyword">if</span> (!cacheValid) &#123;  <br>                    data = <span class="hljs-string">&quot;xxx&quot;</span>;  <br>                    cacheValid = <span class="hljs-literal">true</span>;  <br>                &#125;  <br>                <span class="hljs-comment">// 释放写锁前降级为读锁，这样能够让其它线程读取缓存  </span><br>                <span class="hljs-comment">// 读锁同时会锁住缓存，让其他线程无法获取到写锁，保证数据一致性  </span><br>                rwl.readLock().lock();  <br>            &#125; <span class="hljs-keyword">finally</span> &#123;  <br>                rwl.writeLock().unlock();  <br>            &#125;  <br>        &#125;  <br>        <span class="hljs-comment">// 自己用完数据, 释放读锁   </span><br>        <span class="hljs-keyword">try</span> &#123;  <br>            use(data);  <br>        &#125; <span class="hljs-keyword">finally</span> &#123;  <br>            rwl.readLock().unlock();  <br>        &#125;  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><p>总结：释放写锁之前获取读锁，即锁降级的作用<br><span style="display:none">%%<br>▶80.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-1846%%</span>❕ ^puhqp0</p><ol><li><span style="background-color:#00ff00">一个线程的锁降级过程，相当于锁重入。</span></li><li>保证当前线程在赋值完成后使用时，<span style="background-color:#ff00ff">不会被其他线程变更</span></li><li>降级为读锁，<span style="background-color:#ff00ff">其他线程也可以读取到新数据，保证数据可见性 </span>❕<span style="display:none">%%<br>2303-🏡⭐️◼️锁降级在缓存中的作用？相当于锁重入；加了读锁后，其他线程无法获取写锁，保证了当前数据的有效性；加了读锁降级为读锁，可以让其他线程进行读操作，保证数据的可见性◼️⭐️-point-202301262303%%</span><br>❕<span style="display:none">%%<br>0916-🏡⭐️◼️ReentrantReadWriteLock 在缓存更新中加解读写锁的顺序是什么？先获取读锁，然后需要写入的时候，释放读锁，获取写锁，写完之后，先获取读锁，再释放写锁，最后再释放读锁◼️⭐️-point-202301230916%%</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230122110437.png" alt="image.png"></li></ol><p><span style="background-color:#ff00ff">想写后读，那么必须要先获取读锁，然后再释放写锁。</span></p><h4 id="6-1-3-底层原理"><a href="#6-1-3-底层原理" class="headerlink" title="6.1.3. 底层原理"></a>6.1.3. 底层原理</h4><p>#todo</p><span style="display:none">- [ ] 🚩 - 底层原理深入学习 - 🏡 2023-01-27 06:35</span>[[并发编程_原理.pdf]]<h3 id="6-2-StampedLock"><a href="#6-2-StampedLock" class="headerlink" title="6.2. StampedLock"></a>6.2. StampedLock</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230122141315.png" alt="image.png"></p><h4 id="6-2-1-是什么"><a href="#6-2-1-是什么" class="headerlink" title="6.2.1. 是什么"></a>6.2.1. 是什么</h4><p>ReentrantReadWriteLock 解决了<span style="background-color:#ff00ff">ReentrantLock 读读不共享</span>，影响效率的问题<br>而 StampedLock<span style="background-color:#00ff00">解决 ReentrantReadWriteLock 的<font color=#ff0000>写饥饿</font>问题</span></p><p><span style="background-color:#ff00ff">同时 StampedLock 可以读写并发执行，读的过程中允许写锁介入，不同于获取 ReentrantReadWriteLock 必须读锁全部解锁才能获取写锁</span> ❕<span style="display:none">%%<br>2306-🏡⭐️◼️StampedLock 解决的问题是什么？ReentrantLock 解决了 Synchronized 效率低的问题，ReentrantReadWriteLock 解决了 ReentrantLock 读读不共享问题，StampedLock 解决了 ReentrantReadWrite 读写互斥的问题。◼️⭐️-point-202301262306%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107085621.png"></p><p>StampedLock（邮戳锁,也叫票据锁）是 JDK1.8 中新增的一个读写锁，也是对 JDK1.5 中的读写锁 ReentrantReadWriteLock 的优化。<br><strong>stamp（戳记，long 类型）</strong>：代表了锁的状态。当 stamp 返回零时，表示线程获取锁失败。并且，<span style="background-color:#ff00ff">当释放锁或者转换锁的时候，</span><span style="background-color:#ff00ff">都要传入最初获取的 stamp 值</span>。</p><h4 id="6-2-2-写饥饿问题⭐️🔴"><a href="#6-2-2-写饥饿问题⭐️🔴" class="headerlink" title="6.2.2. 写饥饿问题⭐️🔴"></a>6.2.2. 写饥饿问题⭐️🔴</h4><p>ReentrantReadWriteLock 实现了读写分离，但是一旦读操作比较多的时候，想要获取写锁就变得比较困难了，假如当前 1000 个线程，999 个读，1 个写，有可能 999 个读取线程长时间抢到了锁，那 1 个写线程就悲剧了因为当前有可能会一直存在读锁，而无法获得写锁，根本没机会写。</p><p><strong>如何解决锁饥饿问题?</strong></p><ul><li>使用“公平”策略可以一定程度上缓解这个问题<ul><li>new ReentrantReadWriteLock(true);</li></ul></li><li>但是“公平”策略是以牺牲系统吞吐量为代价的</li></ul><h4 id="6-2-3-StampedLock-的优化⭐️🔴"><a href="#6-2-3-StampedLock-的优化⭐️🔴" class="headerlink" title="6.2.3. StampedLock 的优化⭐️🔴"></a>6.2.3. StampedLock 的优化⭐️🔴</h4><p><span style="display:none">%%<br>▶81.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-1856%%</span>📙❕ ^ev7eip</p><p><strong>ReentrantReadWriteLock</strong><br>允许多个线程同时读，但是只允许一个线程写，在线程获取到写锁的时候，其他写操作和读操作都会处于阻塞状态，<br>读锁和写锁也是互斥的，所以在读的时候是不允许写的，读写锁比传统的 synchronized 速度要快很多，原因就是在于 ReentrantReadWriteLock 支持读并发，读读可共享。</p><p>存在问题：</p><ol><li>读写互斥，即读的时候，无法获取写锁</li><li>存在写饥饿问题</li></ol><p><strong>StampedLock</strong><br><span style="background-color:#00ff00">ReentrantReadWriteLock 的读锁被占用的时候，其他线程尝试获取写锁的时候会被阻塞。</span><br><span style="background-color:#00ff00">但是，StampedLock 采取乐观锁模式，获取读锁后，其他线程尝试获取写锁时不会被阻塞，这其实是对读锁的优化，所以，在获取乐观读锁后，还需要对结果进行校验。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107091031.png"></p><h4 id="6-2-4-作用原理"><a href="#6-2-4-作用原理" class="headerlink" title="6.2.4. 作用原理"></a>6.2.4. 作用原理</h4><p><span style="background-color:#ff00ff">读的过程中也允许获取写锁介入</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107091128.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230107091231.png"></p><p><span style="background-color:#ff0000">如果验戳失败则会由乐观读升级为读锁，使得其他线程无法获取写锁</span></p><h4 id="6-2-5-缺点⭐️🔴"><a href="#6-2-5-缺点⭐️🔴" class="headerlink" title="6.2.5. 缺点⭐️🔴"></a>6.2.5. 缺点⭐️🔴</h4><ul><li>StampedLock <span style="background-color:#ff00ff">不支持重入</span>，没有 Re 开头</li><li>StampedLock 的悲观读锁和写锁都不支持条件变量（Condition），这个也需要注意。</li><li><span style="background-color:#ff0000">使用 StampedLock 一定不要调用中断操作，即不要调用 interrupt() 方法</span></li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603183522.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603183547.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603183614.png" alt="image.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603183713.png" alt="image.png"></p><h4 id="6-2-6-Demo"><a href="#6-2-6-Demo" class="headerlink" title="6.2.6. Demo"></a>6.2.6. Demo</h4><p>黑马 case_java8 ：[[TestStampedLock.java]]</p><h1 id="锁优化"><a href="#锁优化" class="headerlink" title="锁优化"></a>锁优化</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230603184237.png" alt="image.png"></p><h2 id="7-死锁"><a href="#7-死锁" class="headerlink" title="7. 死锁"></a>7. 死锁</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230412-0904%%</span>❕ ^vvvrv6</p><h3 id="7-1-Demo"><a href="#7-1-Demo" class="headerlink" title="7.1. Demo"></a>7.1. Demo</h3><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/*</span><br><span class="hljs-comment">* 死锁是指两个或者两个以上的进程在执行过程中，因抢夺资源而造成的一种互相等待的现象，</span><br><span class="hljs-comment">* 若无外力干涉它们将都无法推进下去，如果系统资源充足，进程的资源请求都能够得到满足，</span><br><span class="hljs-comment">* 死锁出现的可能性也就很低，否则就会因争夺有限的资源而陷入死锁。</span><br><span class="hljs-comment">* */</span><br><br><span class="hljs-keyword">import</span> java.util.concurrent.TimeUnit;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">HoldLockThread</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Runnable</span>&#123;<br>    <span class="hljs-keyword">private</span> String lockA;<br>    <span class="hljs-keyword">private</span> String lockB;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">HoldLockThread</span><span class="hljs-params">(String lockA,String lockB)</span>&#123;<br>        <span class="hljs-built_in">this</span>.lockA = lockA;<br>        <span class="hljs-built_in">this</span>.lockB = lockB;<br>    &#125;<br><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-keyword">synchronized</span> (lockA)&#123;<br>            System.out.println(Thread.currentThread().getName()+<span class="hljs-string">&quot;\t自己持有：&quot;</span>+lockA+<span class="hljs-string">&quot;\t尝试获得：&quot;</span>+lockB);<br>            <span class="hljs-comment">//暂停一下</span><br>            <span class="hljs-keyword">try</span>&#123; TimeUnit.SECONDS.sleep(<span class="hljs-number">2</span>); &#125;<span class="hljs-keyword">catch</span> (InterruptedException e)&#123;e.printStackTrace();&#125;<br><br>            <span class="hljs-keyword">synchronized</span> (lockB)&#123;<br>                System.out.println(Thread.currentThread().getName()+<span class="hljs-string">&quot;\t自己持有：&quot;</span>+lockB+<span class="hljs-string">&quot;\t尝试获得：&quot;</span>+lockA);<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DeadLockDemo</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">String</span> <span class="hljs-variable">lockA</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;lockA&quot;</span>;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">lockB</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;lockB&quot;</span>;<br><br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HoldLockThread</span>(lockA,lockB),<span class="hljs-string">&quot;ThreadAAA&quot;</span>).start();<br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HoldLockThread</span>(lockB,lockA),<span class="hljs-string">&quot;ThreadBBB&quot;</span>).start();<br><br>        <span class="hljs-comment">/*</span><br><span class="hljs-comment">        * linux  ps -ef|grep xxxx    ls -l查看当前进程的命令</span><br><span class="hljs-comment">        * windows下的java运行程序，也有类似ps的查看进程的命令，但是目前我们需要查看的只是java</span><br><span class="hljs-comment">        *           jps = java ps      jps -l</span><br><span class="hljs-comment">        *           jstack</span><br><span class="hljs-comment">        * */</span><br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><h3 id="7-2-查找"><a href="#7-2-查找" class="headerlink" title="7.2. 查找"></a>7.2. 查找</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105185141.png"></p><h3 id="7-3-解决方案"><a href="#7-3-解决方案" class="headerlink" title="7.3. 解决方案"></a>7.3. 解决方案</h3><h4 id="7-3-1-线上-dump-步骤和注意事项⭐️🔴"><a href="#7-3-1-线上-dump-步骤和注意事项⭐️🔴" class="headerlink" title="7.3.1. 线上 dump 步骤和注意事项⭐️🔴"></a>7.3.1. 线上 dump 步骤和注意事项⭐️🔴</h4><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230412-0818%%</span>❕ ^a8qnjr</p><h3 id="7-4-面试题"><a href="#7-4-面试题" class="headerlink" title="7.4. 面试题"></a>7.4. 面试题</h3><a href="/2023/03/31/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-7%E3%80%81%E5%85%AB%E8%82%A1%E6%96%87/" title="面试专题-7、八股文">面试专题-7、八股文</a><h2 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h2><h3 id="8-1-黑马程序员"><a href="#8-1-黑马程序员" class="headerlink" title="8.1. 黑马程序员"></a>8.1. 黑马程序员</h3><h4 id="8-1-1-Java-虚拟机"><a href="#8-1-1-Java-虚拟机" class="headerlink" title="8.1.1. Java 虚拟机"></a>8.1.1. Java 虚拟机</h4><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP?p=177">https://www.bilibili.com/video/BV1yE411Z7AP?p=177</a><br>资料已下载：013-DemoCode&#x2F;jvm</p><h4 id="8-1-2-JUC-并发编程"><a href="#8-1-2-JUC-并发编程" class="headerlink" title="8.1.2. JUC 并发编程"></a>8.1.2. JUC 并发编程</h4><p><a href="https://www.bilibili.com/video/BV16J411h7Rd?p=272&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV16J411h7Rd?p=272&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>资料已下载：heima-concurrent</p><h3 id="8-2-尚硅谷-周阳"><a href="#8-2-尚硅谷-周阳" class="headerlink" title="8.2. 尚硅谷 - 周阳"></a>8.2. 尚硅谷 - 周阳</h3><p><a href="https://www.bilibili.com/video/BV1ar4y1x727?p=156&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1ar4y1x727?p=156&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>示例代码：013-DemoCode&#x2F;thread0308</p><h3 id="8-3-网络笔记"><a href="#8-3-网络笔记" class="headerlink" title="8.3. 网络笔记"></a>8.3. 网络笔记</h3><p><a href="https://blog.csdn.net/m0_46701838/article/details/111441290">https://blog.csdn.net/m0_46701838/article/details/111441290</a></p><p><a href="https://blog.csdn.net/varyall/article/details/80330216">https://blog.csdn.net/varyall/article/details/80330216</a></p><h4 id="8-3-1-lockdowngrading"><a href="#8-3-1-lockdowngrading" class="headerlink" title="8.3.1. lockdowngrading"></a>8.3.1. lockdowngrading</h4><p><a href="https://blog.csdn.net/qq_43478625/article/details/121598530">https://blog.csdn.net/qq_43478625/article/details/121598530</a></p><h4 id="8-3-2-StampedLockDemo"><a href="#8-3-2-StampedLockDemo" class="headerlink" title="8.3.2. StampedLockDemo"></a>8.3.2. StampedLockDemo</h4><p><a href="https://www.yuque.com/liuyanntes/sibb9g/gkbe3l">https://www.yuque.com/liuyanntes/sibb9g/gkbe3l</a></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-8、Volatile</title>
      <link href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-8%E3%80%81Volatile/"/>
      <url>/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-8%E3%80%81Volatile/</url>
      
        <content type="html"><![CDATA[<h1 id="1-简要回答"><a href="#1-简要回答" class="headerlink" title="1. 简要回答"></a>1. 简要回答</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512160700.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512160916.png" alt="image.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512160948.png" alt="image.png"></p><h1 id="2-乱序问题"><a href="#2-乱序问题" class="headerlink" title="2. 乱序问题"></a>2. 乱序问题</h1><a href="/2022/11/26/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-11%E3%80%81%E4%B9%B1%E5%BA%8F%E9%97%AE%E9%A2%98/" title="并发基础-11、乱序问题">并发基础-11、乱序问题</a><h1 id="3-前置知识"><a href="#3-前置知识" class="headerlink" title="3. 前置知识"></a>3. 前置知识</h1><h2 id="3-1-JMM-原子操作"><a href="#3-1-JMM-原子操作" class="headerlink" title="3.1. JMM 原子操作"></a>3.1. JMM 原子操作</h2><h3 id="3-1-1-JMM-及-MESI"><a href="#3-1-1-JMM-及-MESI" class="headerlink" title="3.1.1. JMM 及 MESI"></a>3.1.1. JMM 及 MESI</h3><p>请看 <a href="/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-1%E3%80%81JMM%E4%B8%8EMESI/" title="并发基础-1、JMM与MESI">并发基础-1、JMM与MESI</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221102090503.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113222951.png"></p><p><span style="background-color:#ff00ff">Volatile 保证了 JMM 三大特性的可见性和有序性，但无法保证原子性 (Synchronized 保证了可见性、有序性和原子性)。</span></p><h2 id="3-2-可见性"><a href="#3-2-可见性" class="headerlink" title="3.2. 可见性"></a>3.2. 可见性</h2><h3 id="3-2-1-可见性原理"><a href="#3-2-1-可见性原理" class="headerlink" title="3.2.1. 可见性原理"></a>3.2.1. 可见性原理</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121637.jpg" alt="image-20200128202356235"></p><p>1、<strong>在对变量进行 assign 操作时</strong>，加了 Volatile 修饰的变量，计算机底层会加一个 <strong>lock 前缀指令</strong>（这个操作利用了 MESI 协议对 M 状态变量的处理逻辑，向总线发送 Invalid 信息，并立即写回主内存）。</p><p>2、同时，<strong>lock 前缀指令</strong> 有内存屏障的作用，在 assign 的时候就对变量施加了 <strong>缓存锁</strong>，防止读取未修改的数据。</p><h3 id="3-2-2-JVM-源码实现"><a href="#3-2-2-JVM-源码实现" class="headerlink" title="3.2.2. JVM 源码实现"></a>3.2.2. JVM 源码实现</h3><p>bytecodeinterpreter.cpp</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></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-type">int</span> field_offset = cache-&gt;<span class="hljs-built_in">f2_as_index</span>();  <br>          <span class="hljs-keyword">if</span> (cache-&gt;<span class="hljs-built_in">is_volatile</span>()) &#123;  <br>            <span class="hljs-keyword">if</span> (support_IRIW_for_not_multiple_copy_atomic_cpu) &#123;  <br>              OrderAccess::<span class="hljs-built_in">fence</span>();  <br>            &#125;<br></code></pre></td></tr></table></figure><p>orderaccess_linux_x86.inline.hpp</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><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 c++"><span class="hljs-function"><span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> <span class="hljs-title">OrderAccess::fence</span><span class="hljs-params">()</span> </span>&#123;  <br>  <span class="hljs-keyword">if</span> (os::<span class="hljs-built_in">is_MP</span>()) &#123;  <br>    <span class="hljs-comment">// always use locked addl since mfence is sometimes expensive  </span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> AMD64  </span><br>    <span class="hljs-function">__asm__ <span class="hljs-title">volatile</span> <span class="hljs-params">(<span class="hljs-string">&quot;lock; addl $0,0(%%rsp)&quot;</span> : : : <span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-string">&quot;memory&quot;</span>)</span></span>;  <br><span class="hljs-meta">#<span class="hljs-keyword">else</span>  </span><br>    <span class="hljs-function">__asm__ <span class="hljs-title">volatile</span> <span class="hljs-params">(<span class="hljs-string">&quot;lock; addl $0,0(%%esp)&quot;</span> : : : <span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-string">&quot;memory&quot;</span>)</span></span>;  <br><span class="hljs-meta">#<span class="hljs-keyword">endif</span>  </span><br>  &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-3-有序性"><a href="#3-3-有序性" class="headerlink" title="3.3. 有序性"></a>3.3. 有序性</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121653.jpg" alt="image-20200409104711867"></p><h3 id="3-3-1-指令重排序"><a href="#3-3-1-指令重排序" class="headerlink" title="3.3.1. 指令重排序"></a>3.3.1. 指令重排序</h3><p><a href="https://www.jianshu.com/p/40cb45484f1e">https://www.jianshu.com/p/40cb45484f1e</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121705.jpg" alt="image-20200321232720870"></p><p>举例：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121726.jpg" alt="image-20200407142617599"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121739.jpg" alt="image-20200409110052812"></p><h3 id="3-3-2-volatile-禁止重排的原理"><a href="#3-3-2-volatile-禁止重排的原理" class="headerlink" title="3.3.2. volatile 禁止重排的原理"></a>3.3.2. volatile 禁止重排的原理</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121747.jpg" alt="image-20200321233516250"></p><p><a href="https://juejin.im/post/5ae9b41b518825670b33e6c4">https://juejin.im/post/5ae9b41b518825670b33e6c4</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121757.jpg" alt="image-20200407143515457"></p><h4 id="3-3-2-1-读操作"><a href="#3-3-2-1-读操作" class="headerlink" title="3.3.2.1. 读操作"></a>3.3.2.1. 读操作</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122144.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122328.png"></p><h4 id="3-3-2-2-写操作"><a href="#3-3-2-2-写操作" class="headerlink" title="3.3.2.2. 写操作"></a>3.3.2.2. 写操作</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122438.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116210838.webp"></p><h3 id="3-3-3-JVM-实现"><a href="#3-3-3-JVM-实现" class="headerlink" title="3.3.3. JVM 实现"></a>3.3.3. JVM 实现</h3><p><a href="#5-2-CPU%E4%B9%B1%E5%BA%8F%E6%9D%A5%E6%BA%90">CPU乱序来源</a></p><h2 id="3-4-不保证原子性"><a href="#3-4-不保证原子性" class="headerlink" title="3.4. 不保证原子性"></a>3.4. 不保证原子性</h2><h4 id="3-4-1-为什么"><a href="#3-4-1-为什么" class="headerlink" title="3.4.1. 为什么"></a>3.4.1. 为什么</h4><p>1、诸葛结合了 volatile，把原因归结为写无效，比如 2 个线程都 use 后计算了新值，线程 2 出了问题没来得及 assign，没有触发 MESI 协议，但是第一个线程正常触发 MESI 协议，线程 2 也通过总线嗅探机制，收到了已经变化的信号，那么第 2 个线程没来得及 assign 的新值就失效了，那么也就白加了，导致了丢失 ++ 操作</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121810.jpg" alt="image-20200321222329981"></p><p>2、尚硅谷 - 周阳讲把原因归结为写覆盖，比如 2 个线程都 use 后计算出了新值，线程 2 出了问题没来得及 assign 触发 MESI 协议，但是第一个线程正常触发 MESI 协议，但是线程 2 由于某种原因没有收到了已经变化的信号，那么第 2 个线程 assign 的新值也执行了 store-write 操作，那么第一个线程的加操作被覆盖了，导致了丢失 ++ 操作</p><p>一句话：将要 assign-store-write 的这个原子性操作，被挂起或者其他原因，没有收到其他线程的 M 消息，也就没有正常去失效自己之前将要的 assign-store-write 的新变量值，线程恢复时去更改主内存，那么就覆盖其他线程的操作。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121819.jpg" alt="image-20200321222558008"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121827.jpg" alt="image-20200321222749028"></p><p>我们看到 jvm 通过 lock 实现了 volatile 的内存屏障，但是 volatile 并不具有原子性。原因很简单，不同 CPU 依旧可以对同一个缓存行持有，一个 CPU 对同一个缓存行的修改不能让另一个 CPU 及时感知，因此出现并发冲突。线程安全还是需要用锁来保障，锁能有效的让 CPU 在同一个时刻独占某个缓存行，执行完并释放锁后，其他 CPU 才能访问该缓存行。</p><p>L1 缓存中的变量有两种赋值方式, 一种是从内存加载进来, 另一种是从寄存器回写过来的. </p><p>因为缓存一致性协议只能失效缓存行的数据, 而不能失效寄存器的数据, 导致 volatile 不能做到原子性.</p><p>volatile 和 cas 都是基于 lock 前缀实现，但 volatile 却无法保证原子性这是因为：Lock 前缀只能保证缓存一致性，但不能保证寄存器中数据的一致性，如果指令在 lock 的缓存刷新生效之前把数据写入了寄存器，那么寄存器中的数据不会因此失效而是继续被使用，就好像数据库中的事务执行失败却没有回滚，原子性就被破坏了。以被 volatile 修饰的 i 作 i++ 为例，实际上分为 4 个步骤：<br>mov　　　　 0xc(%r10),%r8d ; 把 i 的值赋给寄存器<br>inc　　　　　%r8d　　　　　 ; 寄存器的值 +1<br>mov　　　　 %r8d,0xc(%r10) ; 把寄存器的值写回<br><span style="background-color:#ff00ff">lock addl　　$0x0,(%rsp)　　; 内存屏障，禁止指令重排序，并同步所有缓存</span><br>　<br>如果两个线程 AB 同时把 i 读进自己的寄存器，此时 B 线程等待，A 线程继续工作，把 i++ 后放回内存。按照原子性的性质，此时 B 应该回滚，重新从内存中读取 i，但因为此时 i 已经拷贝到寄存器中，所以 B 线程会继续运行，原子性被破坏。<br>　<br>而 cas 没有这个问题，因为 cas 操作对应指令只有一个：<br><code>lock cmpxchg dword ptr [edx], ecx ;  </code><br>　<br>该指令确保了直接从内存拿数据（ptr [edx]），然后放回内存这一系列操作都在 lock 状态下，所以是原子性的。<br>　<br>总结：volatile 之所以不是原子性的原因是 jvm 对 volatile 语义的实现只是在 volatile 写后面加一个内存屏障，而内存屏障前的操作不在 lock 状态下，这些操作可能会把数据放入寄存器从而导致无法有效同步；cas 能保证原子性是因为 cas 指令只有一个，这个指令从头到尾都是在 lock 状态下而且从内存到内存，所以它是原子性的。</p><h4 id="3-4-2-如何保证"><a href="#3-4-2-如何保证" class="headerlink" title="3.4.2. 如何保证"></a>3.4.2. 如何保证</h4><p>使用原子包装的整形类，比如 AtomicInteger，原子类的底层是 CAS 原理</p><h2 id="3-5-Happens-Before"><a href="#3-5-Happens-Before" class="headerlink" title="3.5. Happens-Before"></a>3.5. Happens-Before</h2><p><a href="https://www.bilibili.com/video/BV1yE411Z7AP?p=174">https://www.bilibili.com/video/BV1yE411Z7AP?p=174</a></p><p><code>HappenBefore，规定了哪些写操作对其他线程的读操作可见，它是可见性和有序性一些规则的总和，解决的是可见性问题</code><br>    定义：前一个操作的结果对于后续操作是可见的。在 JMM 中，如果一个操作执行的结果需要对另一个操作可见，那么这两个操作必须要存在 happens-before 关系。这两个操作可以是同一个线程，也可以是不同的线程。</p><p>JMM 中有哪些方法建立 happen-before 规则：</p><ul><li>1、<strong>as-if-serial 规则（程序顺序执行）</strong>：单个线程中的代码顺序不管怎么重排序，对于结果来说是不变的。</li><li>2、<strong>volatile 变量规则</strong>，对于 volatile 修饰的变量的写的操作， 一定 happen-before 后续对于 volatile 变量的读操作;</li><li>3、<strong>监视器锁规则（monitor lock rule）</strong>：对一个监视器的解锁，happens-before 于随后对这个监视器的加锁。</li><li>4、<strong>传递性规则</strong>：如果 A happens-before B，且 B happens-before C，那么 A happens-before C。</li><li>5、<strong>start 规则</strong>：如果线程 A 执行操作 ThreadB.start(),那么线程 A 的 ThreadB.start() 操作 happens-before 线程 B 中的任意操作。</li><li>6、<strong>join 规则</strong>：如果线程 A 执行操作 ThreadB.join() 并成功返回，那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回。</li></ul><h3 id="3-5-1-具体案例"><a href="#3-5-1-具体案例" class="headerlink" title="3.5.1. 具体案例"></a>3.5.1. 具体案例</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121837.jpg" alt="image-20200402165219887"></p><p><strong>线程之间解锁、加锁传递时，之前的写是可见的</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121844.jpg" alt="image-20200402165252509"></p><p><strong>开始前的自己可以看，结束后的别人可以看</strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121855.jpg" alt="image-20200402165400484"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121907.jpg" alt="image-20200402165418405"></p><p>这里主线程 (或其他线程) 得知 t2 被打断之后，读取 x 的值是修改后的，当然 while 循环中 t2 被打断后，t2 自己的读也是可以读到的。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121916.jpg" alt="image-20200402165638180"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121923.jpg" alt="image-20200407131343816"></p><h2 id="3-6-与-MESI-的关系"><a href="#3-6-与-MESI-的关系" class="headerlink" title="3.6. 与 MESI 的关系"></a>3.6. 与 MESI 的关系</h2><p><strong>在多核 cpu 中通过缓存一致性协议保证了每个缓存中使用的共享变量的副本是一致的</strong>。</p><p>当一个 CPU 进行写入时，首先会给其它 CPU 发送 Invalid 消息，然后把当前写入的数据写入到 Store Buffer 中。然后异步在某个时刻真正的写入到 Cache 中。当前 CPU 核如果要读 Cache 中的数据，需要先扫描 Store Buffer 之后再读取 Cache。但是此时其它 CPU 核是看不到当前核的 Store Buffer 中的数据的，要等到 Store Buffer 中的数据被刷到了 Cache 之后才会触发失效操作。而当一个 CPU 核收到 Invalid 消息时，会把消息写入自身的 Invalidate Queue 中，随后异步将其设为 Invalid 状态。和 Store Buffer 不同的是，当前 CPU 核心使用 Cache 时并不扫描 Invalidate Queue 部分，所以可能会有极短时间的脏读问题。MESI 协议，可以保证缓存的一致性，但是无法保证实时性。所以我们需要通过内存屏障在执行到某些指令的时候强制刷新缓存来达到一致性。</p><p><strong>但是 MESI 只是一种抽象的协议规范，在不同的 cpu 上都会有不同的实现</strong>，对于 x86 架构来说，store buffer 是 FIFO，写入顺序就是刷入 cache 的顺序。但是对于 ARM&#x2F;Power 架构来说，store buffer 并未保证 FIFO，因此先写入 store buffer 的数据，是有可能比后写入 store buffer 的数据晚刷入 cache 的</p><p>而对于 JAVA 而言，他必须要屏蔽各个处理器的差异，所以才有了 java 内存模型 (JMM),volatile 只是内存模型的一小部分，实现了变量的可见性和禁止指令重排序优化的功能。整个内存模型必须要实现可见性，原子性，和有序性。而 volatile 实现了其中的可见性和有序性。</p><p>在我的理解中，<span style="background-color:#00ff00">MESI 协议是实现 volatile 的所有语义的基础</span>，在我们对一个变量加上 volitile 之后，该变量的操作的指令前就会带有 LOCK#前缀，该前缀在 intel 的文档里面说的很清楚,可以通过上面的链接查看，这里只列举出部分</p><p>Lock 前缀具有如下作用：</p><p>带有 lock 前缀的指令在执行的时候会锁住总线或者利用 MESI 协议这两种方式来保证指令执行的原子性，<br>禁止该指令，与之前和之后的读和写指令重排序<br>把缓冲区的所有数据刷新到内存中</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><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></pre></td><td class="code"><pre><code class="hljs c++">Argument <span class="hljs-number">0</span> is unknown.RIP: <span class="hljs-number">0x7f96b93132a0</span> Code size: <span class="hljs-number">0x00000150</span><br>[Entry Point]<br>[Verified Entry Point]<br>[Constants]<br>  # &#123;method&#125; &#123;<span class="hljs-number">0x00007f96b7106238</span>&#125; <span class="hljs-string">&#x27;main&#x27;</span> <span class="hljs-string">&#x27;([Ljava/lang/String;)V&#x27;</span> in <span class="hljs-string">&#x27;TestVolatile&#x27;</span><br>  # parm0:    rsi:rsi   = <span class="hljs-string">&#x27;[Ljava/lang/String;&#x27;</span><br>  #           [sp+<span class="hljs-number">0x40</span>]  (sp of caller)<br>  <span class="hljs-meta">#main方法入口</span><br>  <span class="hljs-number">0x00007f96b93132a0</span>: mov     %eax,<span class="hljs-number">0xfffffffffffec000</span>(%rsp)<br>  <span class="hljs-number">0x00007f96b93132a7</span>: push    %rbp<br>  <span class="hljs-number">0x00007f96b93132a8</span>: sub     $<span class="hljs-number">0x30</span>,%rsp<br>  <span class="hljs-number">0x00007f96b93132ac</span>: movabs  $<span class="hljs-number">0x7f96b7106300</span>,%rdi  ;   &#123;<span class="hljs-built_in">metadata</span>(method data <span class="hljs-keyword">for</span> &#123;method&#125; &#123;<span class="hljs-number">0x00007f96b7106238</span>&#125; <span class="hljs-string">&#x27;main&#x27;</span> <span class="hljs-string">&#x27;([Ljava/lang/String;)V&#x27;</span> in <span class="hljs-string">&#x27;TestVolatile&#x27;</span>)&#125;<br>  <span class="hljs-number">0x00007f96b93132b6</span>: mov     <span class="hljs-number">0xdc</span>(%rdi),%ebx<br>  <span class="hljs-number">0x00007f96b93132bc</span>: add     $<span class="hljs-number">0x8</span>,%ebx<br>  <span class="hljs-number">0x00007f96b93132bf</span>: mov     %ebx,<span class="hljs-number">0xdc</span>(%rdi)<br>  <span class="hljs-number">0x00007f96b93132c5</span>: movabs  $<span class="hljs-number">0x7f96b7106238</span>,%rdi  ;   &#123;<span class="hljs-built_in">metadata</span>(&#123;method&#125; &#123;<span class="hljs-number">0x00007f96b7106238</span>&#125; <span class="hljs-string">&#x27;main&#x27;</span> <span class="hljs-string">&#x27;([Ljava/lang/String;)V&#x27;</span> in <span class="hljs-string">&#x27;TestVolatile&#x27;</span>)&#125;<br>  <span class="hljs-number">0x00007f96b93132cf</span>: <span class="hljs-keyword">and</span>     $<span class="hljs-number">0x0</span>,%ebx<br>  <span class="hljs-number">0x00007f96b93132d2</span>: cmp     $<span class="hljs-number">0x0</span>,%ebx<br>  <span class="hljs-number">0x00007f96b93132d5</span>: je      <span class="hljs-number">0x7f96b931330c</span>    ;*bipush<br>                                                ; - TestVolatile::main@<span class="hljs-number">0</span> (line <span class="hljs-number">5</span>)<br> <br>  <span class="hljs-number">0x00007f96b93132db</span>: movabs  $<span class="hljs-number">0xf066da08</span>,%rsi  ;   &#123;<span class="hljs-built_in">oop</span>(a <span class="hljs-string">&#x27;java/lang/Class&#x27;</span> = <span class="hljs-string">&#x27;TestVolatile&#x27;</span>)&#125;<br>  #将<span class="hljs-number">9</span>赋值给edi寄存器<br>  <span class="hljs-number">0x00007f96b93132e5</span>: mov     $<span class="hljs-number">0x9</span>,%edi<br>  #将edi寄存器的值赋值给value<br>  <span class="hljs-number">0x00007f96b93132ea</span>: mov     %edi,<span class="hljs-number">0x68</span>(%rsi)<br>  #带lock前缀的加指令，把rsp所指向的地址中值加<span class="hljs-number">0</span>，这个指令没啥用，主要使用lock前缀做内存屏障的<br>  #防止lock之后的指令在lock之前执行，这里没使用mfence指令，主要是mfence在某些情况下比lock效率慢<br>  <span class="hljs-number">0x00007f96b93132ed</span>: lock addl $<span class="hljs-number">0x0</span>,(%rsp)     ;*putstatic value<br>                                                ; - TestVolatile::main@<span class="hljs-number">5</span> (line <span class="hljs-number">6</span>)<br>  #将value的值赋值给edi寄存器<br>  <span class="hljs-number">0x00007f96b93132f2</span>: mov     <span class="hljs-number">0x68</span>(%rsi),%edi   ;*getstatic value<br>                                                ; - TestVolatile::main@<span class="hljs-number">8</span> (line <span class="hljs-number">7</span>)<br>  #将edi寄存器加<span class="hljs-number">10</span><br>  <span class="hljs-number">0x00007f96b93132f5</span>: add     $<span class="hljs-number">0xa</span>,%edi<br>  #将edi寄存器赋值给value<br>  <span class="hljs-number">0x00007f96b93132f8</span>: mov     %edi,<span class="hljs-number">0x68</span>(%rsi)<br>  #加lock前缀做内存屏障，防止lock后的指令跑到lock前执行<br>  <span class="hljs-number">0x00007f96b93132fb</span>: lock addl $<span class="hljs-number">0x0</span>,(%rsp)     ;*putstatic value<br>                                                ; - TestVolatile::main@<span class="hljs-number">13</span> (line <span class="hljs-number">7</span>)<br>  #从main方法返回<br>  <span class="hljs-number">0x00007f96b9313300</span>: add     $<span class="hljs-number">0x30</span>,%rsp<br>  <span class="hljs-number">0x00007f96b9313304</span>: pop     %rbp<br>  <span class="hljs-number">0x00007f96b9313305</span>: test    %eax,<span class="hljs-number">0x165dcdf5</span>(%rip)  ;   &#123;poll_return&#125;<br>  <span class="hljs-number">0x00007f96b931330b</span>: retq<br></code></pre></td></tr></table></figure><p>看出来了什么没有，即使是加了 volatile 之后的变量，对应到的读取和写入指令都没有加上 Lock#前缀，从汇编语言中可以看到在对 volatile 变量赋值后会加一条 <code>lock addl $0x0,(%rsp)</code> 指令，lock 指令具有内存屏障的作用，lock 前后的指令不会重排序，<code>addl $0x0,(%rsp)</code> 是一条无意义的指令。所以说我们对 volatile 变量的操作其实还是不具有原子性，因为只是利用了#Lock 前缀保证了写操作会被马上刷新到内存而已，并没有保证 <code>读写改</code> 三个操作的原子性。<br>为什么是在写操作后面插入一条带有 Lock#的指令？<br>这一条指令其实是起到内存屏障的所用，LOCK 前缀虽然不是内存屏障指令，但是他能起到内存屏障的效果。因为我的测试环境是 X86 平台，在 X86 平台上，只会存在 StoreLoad 重排序，所以说 java 编译器在编译 volatile 变量的操作的时候，只需要<span style="background-color:#00ff00">在所有的 volatile 写的后面插入一个 StoreLoad 屏障，以此来实现可见性。</span>lock 前缀让本核操作内存时锁定其他核，addl xxx 是个无意义的内存操作，可令 CPU 清空 WB，也起到了内存屏障的作用了。<br>CAS 就能保证原子性，CAS 也是加 LOCK#前缀啊，这又是为什么？因为 CAS 操作是在一条单独的指令 cmpxchg 前加上了#Lock 前缀 ，所以它具有原子性，LOCK#能保证一条指令执行的原子性。</p><p>原文链接：<a href="https://blog.csdn.net/P19777/article/details/103120433">https://blog.csdn.net/P19777/article/details/103120433</a></p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;CAS 原理和缺陷  huzb的博客]]</p><h1 id="4-深层剖析"><a href="#4-深层剖析" class="headerlink" title="4. 深层剖析"></a>4. 深层剖析</h1><h2 id="4-1-字节码层面"><a href="#4-1-字节码层面" class="headerlink" title="4.1. 字节码层面"></a>4.1. 字节码层面</h2><p>查看字节码我们发现，在 volatile 读写的前后并没有内存屏障信息的生成。即一个属性有没有加 volatile 进行修饰，<span style="background-color:#ff00ff">对 java 代码编译成字节码指令没有影响</span>，生成的字节码指令都一样的。这一点同时也说明前端编译 (即 javac 编译) 不会产生乱序，java 的编译期优化是发生在后端编译 (即 JIT 编译器)。</p><p><span style="background-color:#ff00ff">虽然生成的字节码指令是一样的。但是我们还是能发现属性描述的不同。</span></p><p>当属性被修饰为 volatile 时，在生成的字节码的 class 内属性对应 access_flags 是不一样的（比如上文字节码的代码行数 59 和 63 的地方）。添加了 volatile 的属性，对应的字节码属性描述中，access_flag 会多了一个 <code>ACC_VOLATILE</code> 的标记。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221126064610.png"></p><h2 id="4-2-JDK-源码层面"><a href="#4-2-JDK-源码层面" class="headerlink" title="4.2. JDK 源码层面"></a>4.2. JDK 源码层面</h2><h3 id="4-2-1-运行逻辑"><a href="#4-2-1-运行逻辑" class="headerlink" title="4.2.1. 运行逻辑"></a>4.2.1. 运行逻辑</h3><p>通过 javap 可以看到 volatile 字节码层面有个关键字 <code>ACC_VOLATILE</code>，如上图所示👆🏻，通过这个关键字定位到 <code>accessFlags.hpp</code> 文件 <a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/utilities/accessFlags.hpp#:~:text=bool%20is_volatile%20%20%20%20()%20const%20%20%20%20%20%20%20%20%20%7B%20return%20(_flags%20%26%20JVM_ACC_VOLATILE%20%20%20%20)%20!%3D%200%3B%20%7D">accessFlags.hpp</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221126071957.png"></p><p>再搜索关键字 <code>is_volatile</code>，在<font color=#ffff00>bytecodeInterpreter.cpp</font>可以看到如下代码<br><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp">bytecodeInterpreter.cpp</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221126072251.png"></p><p>在这段代码中，会先判断 tos_type，后面分别有不同的基础类型的实现，比如 int 就调用 <code>release_int_field_put</code>，byte 就调用 release_byte_field_put 等等。以 int 类型为例，继续搜索方法 <code>release_int_field_put</code>，在<font color=#ffff00>oop.hpp</font>可以看到如下代码：<br><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/oops/oop.hpp">oop.hpp</a></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">void release_int_field_put(int offset, jint contents);<br></code></pre></td></tr></table></figure><p>这段代码实际是内联<font color=#ffff00>oop.inline.hpp</font>，具体的实现是这样的：<br><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/oops/oop.inline.hpp">oop.inline.hpp</a></p><figure class="highlight text"><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 text">inline void oopDesc::release_int_field_put(int offset, jint contents)       &#123; <br>OrderAccess::release_store(int_field_addr(offset), contents);  &#125;<br></code></pre></td></tr></table></figure><p>继续看 <code>OrderAccess::release_store</code>，可以在<font color=#ffff00>orderAccess.hpp</font>找到对应的实现方法：<br><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/runtime/orderAccess.hpp">orderAccess.hpp</a></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">static void release_store(volatile jint*    p, jint    v);<br></code></pre></td></tr></table></figure><p>实际上这个方法的实现又有很多内联的针对不同的 CPU 有不同的实现的，在 src&#x2F;os_cpu 目录下可以看到不同的实现，以<font color=#ffff00>orderAccess.inline.hpp</font>为例，是这么实现的：<br><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/runtime/orderAccess.inline.hpp">orderAccess.inline.hpp</a></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">inline void OrderAccess::release_store(volatile jint* p, jint v) &#123; specialized_release_store(p, v); &#125;<br></code></pre></td></tr></table></figure><p>可以看到其实 Java 的 volatile 操作，在 JVM 实现层面第一步是给予了 C++ 的原语实现</p><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/runtime/orderAccess.inline.hpp#:~:text=template%3Ctypename%20T%3E%20inline%20void%20OrderAccess%3A%3Aspecialized_release_store%20%20%20%20%20%20(volatile%20T*%20p%2C%20T%20v)%20%20%7B%20ordered_store%3CT%2C%20RELEASE_X%3E(p%2C%20v)%3B%20%20%20%20%20%20%20%7D">specialized_release_store</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221130075959.png"></p><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/runtime/orderAccess.inline.hpp#:~:text=template%3C%3E%20inline%20void%20ScopedFenceGeneral%3CRELEASE_X%3E%3A%3Aprefix()%20%20%20%20%20%20%20%20%7B%20OrderAccess%3A%3Arelease()%3B%20%7D">RELEASE_X</a><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221130080113.png"></p><p>releasestore 只在 store 之前插入了 release 函数.而并没有插入 storeload，storeload 是在最下面加上去的。<a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp#:~:text=OrderAccess%3A%3Astoreload()%3B">位置</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221130081619.png"></p><p>上面的分析是 volatile 写的分支逻辑，我们再把 bytecodeInterpreter.cpp 里面读的整体逻辑加上，读的逻辑调用的是 load_acquire，整体逻辑简化概括一下可得：<br>bytecodeInterpreter.cpp(int_field_acquire)<br>–&gt; oop.hpp(int_field_acquire)<br>–&gt; oop.inline.hpp(int_field_acquire)<br>–&gt; orderAccess.hpp(load_acquire)<br>–&gt; orderAccess.inline.hpp(load_acquire)<br>–&gt; orderAccess.inline.hpp(specialized_load_acquire)<br>–&gt; orderAccess.inline.hpp(<X_ACQUIRE>::postfix())<br>–&gt; orderAccess.inline.hpp(OrderAccess::acquire())</p><p>至此我们可以看出运行逻辑是 JVM 运行时访问方法区中的变量时，如果发现 flag 是 volatile 修饰，则<span style="background-color:#ff00ff">会分别给读写方法加上相应的内存屏障</span></p><figure class="highlight plaintext"><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 plaintext">if(属性读指令操作)&#123;<br>     if(常量池拿到的属性描述 is volatile)&#123;<br>         读操作；<br>         OrderAccess::load_acquire(最后跟出来是OrderAccess::acquire())<br>     &#125;else&#123;<br>         正常读<br>     &#125;<br>     ......<br>&#125;else if(属性写指令操作)&#123;<br>    if(常量池拿到的属性描述 is volatile)&#123;<br>        OrderAccess::release_store(最后跟出来是OrderAccess::release())<br>        写操作；<br>        OrderAccess::storeload()<br>    &#125;else&#123;<br>        正常写<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221129220535.png"></p><p>参考鸣谢：<a href="https://blog.csdn.net/u013291050/article/details/117335477">https://blog.csdn.net/u013291050/article/details/117335477</a><br><a href="https://blog.csdn.net/w329636271/article/details/54616543">https://blog.csdn.net/w329636271/article/details/54616543</a></p><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp">https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp</a></p><h3 id="4-2-2-源码跟踪"><a href="#4-2-2-源码跟踪" class="headerlink" title="4.2.2. 源码跟踪"></a>4.2.2. 源码跟踪</h3><p>在 hotSpot 中对 volatile 的实现的地方有多处,这里主要看的是从 oops 中的实现<br><a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/oops/oop.hpp">https://github.com/openjdk/jdk/blob/master/src/hotspot/share/oops/oop.hpp</a></p><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp">https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp</a></p><figure class="highlight plaintext"><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 plaintext">class oopDesc &#123;<br>....<br> public:<br>  void obj_field_put_volatile(int offset, oop value);<br>&#125;<br></code></pre></td></tr></table></figure><p>方法实现</p><figure class="highlight plaintext"><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 plaintext">void oopDesc::obj_field_put_volatile(int offset, oop value) &#123;<br> OrderAccess::release();<br> obj_field_put(offset, value);<br> OrderAccess::fence();<br>&#125;<br>oop oopDesc::obj_field_acquire(int offset) const &#123;<br> return UseCompressedOops ?<br>  decode_heap_oop((narrowOop)<br>  OrderAccess::load_acquire(obj_field_addr&lt;narrowOop&gt;(offset)))<br>  : decode_heap_oop((oop)<br>  OrderAccess::load_ptr_acquire(obj_field_addr&lt;oop&gt;(offset)));<br></code></pre></td></tr></table></figure><p>可以得出下图第二部分，正是内存屏障层面的实现原理，在下面章节中有具体介绍<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221130065648.png"></p><p><a href="https://blog.csdn.net/w329636271/article/details/54616543">https://blog.csdn.net/w329636271/article/details/54616543</a></p><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/oops/oop.inline.hpp">https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/oops/oop.inline.hpp</a></p><h2 id="4-3-内存屏障层面"><a href="#4-3-内存屏障层面" class="headerlink" title="4.3. 内存屏障层面"></a>4.3. 内存屏障层面</h2><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/runtime/orderAccess.hpp">https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/share/vm/runtime/orderAccess.hpp</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221130072149.png"></p><p>acquire 等价于 LoadLoad 屏障或 LoadStore 屏障。<br>release 等价于 LoadStore 屏障或 StoreStore 屏障。</p><h3 id="4-3-1-Acquire-和-Release"><a href="#4-3-1-Acquire-和-Release" class="headerlink" title="4.3.1. Acquire 和 Release"></a>4.3.1. Acquire 和 Release</h3><p>如上，关键涉及 OrderAccess::load_acquire,OrderAccess::release_store，OrderAccess::storeload 这三个方法。</p><p>很明显，OrderAccess::storeload 就对应 java 虚拟机抽象出来的 StoreLoad 屏障指令。</p><p>而 OrderAccess::release_store， OrderAccess::load_acquire 又是什么东西呢。</p><p>OrderAccess 就是 openjdk8 路径&#x2F;hotspot&#x2F;src&#x2F;share&#x2F;vm&#x2F;runtime 下的 orderAccess.hpp 文件。在 orderAccess.hpp 的代码头部注释里有着对这些方法的详细描述。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221125195347.png"></p><p>获取屏障（Acquire Barrier）：相当于上面的 <strong>LoadLoadBarrier</strong> 或者 <strong>LoadStoreBarrier</strong><br>释放屏障（Release  Barrier）：相当于 <strong>LoadStoreBarrier</strong> 或者 <strong>StoreStoreBarrier</strong></p><p>Java 中又定义了 release 和 acquire,fence 三种不同的语境的内存栅栏. </p><p>如上图, loadLoad 和 loadStore 两种栅栏对应的都是 acquire 语境,acquire 语境一般定义在 java 的读之前; 在编译器阶段和 cpu 执行的时候, acquire 之后的所有的 (读和写) 操作不能越过 acquire, 重排到 acquire 之前, acquire 指令之后所有的读都是具有可见性的.</p><p>如上图, StoreStore 和 LoadStore 对应的是 release 语境, release 语境一般定义在 java 的写之后, 在编译器和 cpu 执行的时候, 所有 release 之前的所有的 (读和写) 操作都不能越过 release, 重排到 release 之后, release 指令之前所有的写都会刷新到主存中去, 其他核的 cpu 可以看到刷新的最新值.</p><p>对于 fence, 是由 storeload 栅栏组成的, 比较消耗性能. 在编译器阶段和 cpu 执行时候, 保证 fence 之前的任何操作不能重排到屏障之后, fence 之后的任何操作不能重排到屏障之前. fence 具有 acquire 和 release 这两个都有的语境, 即可以将 fence 之前的写刷新到内存中, fence 之后的读都是具有可见性的.</p><p>经上面分析可得 volatile 的 JVM 屏障逻辑如下图所示：</p><h3 id="4-3-2-读操作"><a href="#4-3-2-读操作" class="headerlink" title="4.3.2. 读操作"></a>4.3.2. 读操作</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221126074530.png"></p><p>如上图，<del>加载屏障（<code>LoadStore屏障</code>）除了使 volatile 读操作不会与之前的写操作发生重排序外，还会刷新处理器缓存，使 volatile 变量读取的为最新值。</del><br><span style="background-color:#ffff00">从代码中没有看到该屏障，暂且持怀疑态度！</span></p><p>Acquire Barrier: 获取屏障（<code>LoadLoad屏障</code> 或 <code>LoadStore屏障</code>）禁止了 volatile 读操作与其之后的任何读写操作进行重排序。保障了 volatile 变量读操作之后的任何读写操作，volatile 的写线程的更新已经对其可见。</p><h3 id="4-3-3-写操作"><a href="#4-3-3-写操作" class="headerlink" title="4.3.3. 写操作"></a>4.3.3. 写操作</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221126074511.png"></p><p>如上图，Release Barrier: 释放屏障（<code>LoadStore屏障</code> 或 <code>StoreStore屏障</code>）保证了 volatile 写操作与该操作之前的任何读、写操作都不会进行重排序。从而保证了 <span style="background-color:#ff00ff">volatile 写操作之前，任何的读写操作都会先于 volatile 被提交</span>。</p><p>而 Store Barrier: 存储屏障（<code>StoreLoad屏障</code>）除了使 volatile 写操作不会与之后的读操作重排序外，还会冲刷处理器缓存，<span style="background-color:#ff00ff">使 volatile 变量的写更新对其他线程可见，该内存屏障与读操作的加载屏障一起保障了可见性</span>。</p><p>参考鸣谢： <a href="https://www.jianshu.com/p/43af2cc32f90">https://www.jianshu.com/p/43af2cc32f90</a></p><p>内存屏障详细内容请看 <a href="/2022/11/24/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/" title="并发编程专题-基础-10、内存屏障">并发编程专题-基础-10、内存屏障</a></p><h2 id="4-4-汇编实现层面"><a href="#4-4-汇编实现层面" class="headerlink" title="4.4. 汇编实现层面"></a>4.4. 汇编实现层面</h2><p>分析到 orderAccess.hpp，OrderAccess 可以理解为是一个接口，根据不同操作系统不同 CPU 对应不同的实现。JSR-133 Cookbook 里定义了一堆 Barrier，但 JVM 虚拟机上实际还会定义更多一些 Barrier 在 <a href="https://link.segmentfault.com/?enc=4nvS/qoXNImI+l3uVxJuVg==.YDGKtIXxBNmoPCcLv5vquYwrxlQ95cJyunKmKZB2D0TqHO1LXpXvYSG3VYheZN6cM22DAenQjkqdavd0rms50I7DMDLt0YzICFmQ/YiYkDa2btjPZ3akek057RaZPStmmE0ZCkGZwekwmiP8D/AylnEC38noSrLTtQ1S8ZUUJRA=">src&#x2F;hotspot&#x2F;share&#x2F;runtime&#x2F;orderAccess.hpp</a></p><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk9/blob/master/hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp">orderAccess_linux_x86.inline.hpp</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221130070932.png"></p><p>此处  <strong>asm</strong> volatile(“” : : : “memory”); 是内嵌汇编.<br>解释:<br><strong>asm</strong> : 代表汇编代码开始.<br><strong>volatile</strong>: 禁止编译器对代码进行某些优化.</p><p>memory: memory 代表是内存; 这边用”memory”,来通知编译器内存的内容已经发生了修改,要重新生成加载指令 (不可以从缓存寄存器中取).因为存在着内存的改变,不可以将前后的代码进行乱序.</p><p>asm volatile(“” :::”memory”),这句内嵌汇编作为编译器屏障，可以防止编译器对相邻指令进行乱序，但是<span style="background-color:#00ff00">它无法阻止 CPU 的乱序</span>；也就是说它仅仅禁止了编译器的乱序优化，不会阻止 CPU 的乱序执行。</p><p>compiler_barrier() 只是为了不做指令重排，但是对应的是空操作。看到上面只有 StoreLoad 是实际有效的，对应的是 fence()，看到 fence() 的实现是用 lock。</p><p>多核处理器才会执行处理，单核就不需要内存屏障了。实际指令是 <code>lock; addl $0,0(%%esp)</code>。rsp 是 esp 对应的 64 位指令。</p><p><code>addl $0,0(%%rsp)</code> 的意思就是把寄存器里的值加 0，也就是说这是个空操作。重点在于前缀</p><p>我们再回想起来上一篇文章中 MESI 中的内容。加了 lock 前缀的指令会严格保证 MESI 协议中的数据一致性，保证对某个内存的独占使用，保证该 CPU 对应缓存行为独占，其他 CPU 的缓存行则失效。在 x86 上，任何带 lock 前缀的指令都可以可以当成一个 StoreLoad 屏障。</p><p><code>IA32</code> 中对 lock 的说明是</p><blockquote><p>The LOCK # signal is asserted during execution of the instruction following the lock prefix. This signal can be used in a multiprocessor system to ensure exclusive use of shared memory while LOCK # is asserted</p></blockquote><p><span style="background-color:#ff00ff">LOCK 用于在多处理器中执行指令时对共享内存的独占使用。它的作用是能够将当前处理器对应缓存的内容刷新到内存，并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。</span></p><p><strong>正是 lock 实现了 volatile 的「防止指令重排」「内存可见」的特性</strong></p><h1 id="5-使用场景"><a href="#5-使用场景" class="headerlink" title="5. 使用场景"></a>5. 使用场景</h1><p>链接： <a href="https://www.jianshu.com/p/9abb4a23ab05">https://www.jianshu.com/p/9abb4a23ab05</a></p><p>必须具备以下两个条件（其实就是先保证原子性）：</p><ul><li>对变量的写 <strong>不依赖当前值</strong>（比如 ++ 操作）</li><li>该变量没有包含在 <strong>具有其他变量的不等式</strong> 中</li></ul><p>比如：</p><ul><li>状态标记（while(flag){})</li><li><strong>double check（单例模式）</strong></li><li><strong>读写锁的缓存中</strong></li><li>其他一写多读的场景</li></ul><h2 id="5-1-DCL"><a href="#5-1-DCL" class="headerlink" title="5.1. DCL"></a>5.1. DCL</h2><h5 id="5-1-1-二次验证的单例"><a href="#5-1-1-二次验证的单例" class="headerlink" title="5.1.1. 二次验证的单例"></a>5.1.1. 二次验证的单例</h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106121957.jpg" alt="image-20200131205812557"></p><p><span style="background-color:#ff00ff">加了二次判断，严谨了一些，但是仍然会有问题，就是初始化过程和引用赋值过程可能发生重排序，那么如果不加 volatile，就可能第二个非空判断跳过，但是实例对象有地址，但无内容，是一个空壳子对象。</span></p><p>所以要更加严谨一些，像 AQS 中的 hasQueuedPredecessors 一样，涉及多线程，就要判断任何有过程的中间过程，比如 <strong>初始化未完成的情况</strong></p><h5 id="5-1-2-加-volatile-必要性⭐️🔴"><a href="#5-1-2-加-volatile-必要性⭐️🔴" class="headerlink" title="5.1.2. 加 volatile 必要性⭐️🔴"></a>5.1.2. 加 volatile 必要性⭐️🔴</h5><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230409-1308%%</span>❕❕</p><p>![image-20200407150424535](<a href="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122007.jpg">https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122007.jpg</a> ^bicvoh)</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122017.jpg" alt="image-20200407150459902"></p><p>解析：因为步骤 2、3 不存在数据依赖关系，所以多线程下可能出现指令重拍。那么就可能导致 2 在 3 之前执行，而此时如果另一个线程的第一次 null 判断会得到不为空的结论，但是 instance 的内容却是空的，从而导致了线程安全问题。所以需要 volatile 来修饰 instance 对象，禁止 instance 对象操作的重排序</p><h2 id="5-2-读写锁缓存"><a href="#5-2-读写锁缓存" class="headerlink" title="5.2. 读写锁缓存"></a>5.2. 读写锁缓存</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106122024.jpg" alt="image-20200407153136831"></p><p>【参考】volatile 解决多线程内存不可见问题对于一写多读，是可以解决变量同步问题，但是如果多</p><p>写，同样无法解决线程安全问题。<br>说明：如果是 count++ 操作，使用如下类实现：</p><p>AtomicInteger count &#x3D; new AtomicInteger();<br>count.addAndGet(1);<br>如果是 JDK8，推荐使用 LongAdder 对象，比 AtomicLong 性能更好（减少乐观锁的重试次数）。</p><h2 id="5-3-JUC"><a href="#5-3-JUC" class="headerlink" title="5.3. JUC"></a>5.3. JUC</h2><ul><li><a href="https://monkeysayhi.github.io/2017/09/27/%E9%9D%A2%E8%AF%95%E4%B8%AD%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E6%9C%89%E5%87%A0%E7%A7%8D%E5%86%99%E6%B3%95%EF%BC%9F/">面试中单例模式有几种写法？</a>：“饱汉 - 变种 3”在 DCL 的基础上，使用 volatile 修饰单例，以保证单例的可见性。</li></ul><p>复杂的利用 volatile 变量规则（结合了程序顺序规则、传递性）保证变量本身及周围其他变量的偏序：</p><ul><li><a href="https://monkeysayhi.github.io/2017/12/05/%E6%BA%90%E7%A0%81%7C%E5%B9%B6%E5%8F%91%E4%B8%80%E6%9E%9D%E8%8A%B1%E4%B9%8BReentrantLock%E4%B8%8EAQS%EF%BC%881%EF%BC%89%EF%BC%9Alock%E3%80%81unlock/">源码|并发一枝花之ReentrantLock与AQS（1）：lock、unlock</a>：exclusiveOwnerThread 借助于 volatile 变量 state 保证其相对于 state 的偏序。</li><li><a href="https://monkeysayhi.github.io/2017/10/24/%E6%BA%90%E7%A0%81%7C%E5%B9%B6%E5%8F%91%E4%B8%80%E6%9E%9D%E8%8A%B1%E4%B9%8BCopyOnWriteArrayList/">源码|并发一枝花之CopyOnWriteArrayList</a>：CopyOnWriteArrayList 借助于 volatile 变量 array，对外提供偏序语义。</li></ul><h1 id="6-synchronized-与-volatile-区别"><a href="#6-synchronized-与-volatile-区别" class="headerlink" title="6. synchronized 与 volatile 区别"></a>6. synchronized 与 volatile 区别</h1><ul><li>Volatile 保证线程可见性，当工作内存中副本数据无效之后，主动读取主内存中数据</li><li>Volatile 可以禁止重排序的问题，底层使用内存屏障</li><li>Volatile 不会导致线程阻塞，不能够保证线程安全问题 (不能保证原子性)，synchronized 会导致线程阻塞，能够保证线程安全问题</li></ul><h1 id="7-参考与感谢"><a href="#7-参考与感谢" class="headerlink" title="7. 参考与感谢"></a>7. 参考与感谢</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;计算机与服务器底层世界-CPU架构篇-Core - 知乎]]</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;x86-TSO  适用于x86体系架构并发编程的内存模型 - 执生 - 博客园]]</p><p><a href="https://blog.csdn.net/it_lihongmin/article/details/109169260">https://blog.csdn.net/it_lihongmin/article/details/109169260</a></p><p><a href="https://github.com/JetBrains/jdk8u_hotspot/blob/master/src/share/vm/runtime/orderAccess.hpp">https://github.com/JetBrains/jdk8u_hotspot/blob/master/src/share/vm/runtime/orderAccess.hpp</a></p><p>应用场景： <a href="https://www.51cto.com/article/684725.html">https://www.51cto.com/article/684725.html</a></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-1、Java-相关名词</title>
      <link href="/2022/11/11/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-0%E3%80%81Java%E7%9B%B8%E5%85%B3%E5%90%8D%E8%AF%8D/"/>
      <url>/2022/11/11/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86-0%E3%80%81Java%E7%9B%B8%E5%85%B3%E5%90%8D%E8%AF%8D/</url>
      
        <content type="html"><![CDATA[<h1 id="1-new-Object"><a href="#1-new-Object" class="headerlink" title="1. new Object()"></a>1. new Object()</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114084231.png"></p><h1 id="2-线程集合"><a href="#2-线程集合" class="headerlink" title="2. 线程集合"></a>2. 线程集合</h1><p>JVM 维护了一个集合存放所有存活的线程，通过遍历该集合判断某个线程是否存活。</p><h1 id="3-栈高低位"><a href="#3-栈高低位" class="headerlink" title="3. 栈高低位"></a>3. 栈高低位</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-内存栈分配从高到低为什么 - 墨天轮]]</p><h1 id="4-JNDI"><a href="#4-JNDI" class="headerlink" title="4. JNDI"></a>4. JNDI</h1><p><a href="https://www.cnblogs.com/wlzjdm/p/7856356.html">https://www.cnblogs.com/wlzjdm/p/7856356.html</a></p><h1 id="5-JCP-JSR"><a href="#5-JCP-JSR" class="headerlink" title="5. JCP JSR"></a>5. JCP JSR</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;阿里巴巴入选的JCP最高执行委员会，何方神圣？_方向盘(YourBatman)的博客-CSDN博客]]</p><h1 id="6-深拷贝-深克隆"><a href="#6-深拷贝-深克隆" class="headerlink" title="6. 深拷贝 (深克隆)"></a>6. 深拷贝 (深克隆)</h1><a href="/2023/01/11/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-5%E3%80%81%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/" title="设计模式-5、原型模式">设计模式-5、原型模式</a><p><a href="https://juejin.cn/post/6844903693100417038">https://juejin.cn/post/6844903693100417038</a><br><a href="https://juejin.cn/post/6986071906273198094">https://juejin.cn/post/6986071906273198094</a></p><h1 id="7-DO-DTO-AO-VO-BO-POJO"><a href="#7-DO-DTO-AO-VO-BO-POJO" class="headerlink" title="7. DO DTO AO VO BO POJO"></a>7. DO DTO AO VO BO POJO</h1><p>[[阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义 - EasonJim - 博客园]]</p><p>[[JavaBean, POJO, DTO, VO, 傻傻分不清楚？  卡瓦邦噶！]]</p><h2 id="7-1-JavaBean"><a href="#7-1-JavaBean" class="headerlink" title="7.1. JavaBean"></a>7.1. JavaBean</h2><p>JavaBean 是一种 JAVA 语言写成的可重用组件。JavaBean 符合一定规范编写的 Java 类，不是一种技术，而是一种规范。大家针对这种规范，总结了很多开发技巧、工具函数。符合这种规范的类，可以被其它的程序员或者框架使用。它的方法命名，构造及行为必须符合特定的约定：</p><ol><li>所有属性为 private。</li><li>这个类必须有一个公共的缺省构造函数。即是提供无参数的构造器。</li><li>这个类的属性使用 getter 和 setter 来访问，其他方法遵从标准命名规范。</li><li>这个类应是可序列化的。实现 serializable 接口。</li></ol><p>因为这些要求主要是靠约定而不是靠实现接口，所以许多开发者把 JavaBean 看作遵从特定命名约定的 POJO。</p><p><strong>两者有什么区别</strong></p><ol><li>POJO 其实是比 javabean 更纯净的简单类或接口。POJO 严格地遵守简单对象的概念，而一些 JavaBean 中往往会封装一些简单逻辑。</li><li>POJO 主要用于数据的临时传递，它只能装载数据， 作为数据存储的载体，而不具有业务逻辑处理的能力。</li><li>Javabean 虽然数据的获取与 POJO 一样，但是 javabean 当中可以有其它的方法。</li></ol><p>Java 对象 POJO 和 JavaBean 的区别<br><a href="https://blog.51cto.com/u_15127523/3929859">https://blog.51cto.com/u_15127523/3929859</a></p><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1>]]></content>
      
      
      <categories>
          
          <category> 性能调优 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JVM </tag>
            
            <tag> 性能调优 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-6、CAS</title>
      <link href="/2022/11/10/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-6%E3%80%81CAS/"/>
      <url>/2022/11/10/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-6%E3%80%81CAS/</url>
      
        <content type="html"><![CDATA[<h1 id="1-JUC-中的-CAS"><a href="#1-JUC-中的-CAS" class="headerlink" title="1. JUC 中的 CAS"></a>1. JUC 中的 CAS</h1><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106143516.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106143602.png"></p><h2 id="1-2-底层原理"><a href="#1-2-底层原理" class="headerlink" title="1.2. 底层原理"></a>1.2. 底层原理</h2><h3 id="1-2-1-保证可见性"><a href="#1-2-1-保证可见性" class="headerlink" title="1.2.1. 保证可见性"></a>1.2.1. 保证可见性</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106144524.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106144815.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106144841.png"></p><h3 id="1-2-2-保证原子性"><a href="#1-2-2-保证原子性" class="headerlink" title="1.2.2. 保证原子性"></a>1.2.2. 保证原子性</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106143737.png"></p><h2 id="1-3-汇编实现"><a href="#1-3-汇编实现" class="headerlink" title="1.3. 汇编实现"></a>1.3. 汇编实现</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106150453.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106150801.png"></p><h2 id="1-4-synchronized-中的-CAS"><a href="#1-4-synchronized-中的-CAS" class="headerlink" title="1.4. synchronized 中的 CAS"></a>1.4. synchronized 中的 CAS</h2><h3 id="1-4-1-Atomic-cmpxchg-ptr"><a href="#1-4-1-Atomic-cmpxchg-ptr" class="headerlink" title="1.4.1. Atomic::cmpxchg_ptr"></a>1.4.1. Atomic::cmpxchg_ptr</h3><p>轻量级锁加锁的时候，如果处于无锁状态，则会通过 CAS 操作将锁对象的 Mark Word 更新为指向 Lock Record 的指针，相关代码如下：</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><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 c++"><span class="hljs-keyword">if</span> (mark-&gt;<span class="hljs-built_in">is_neutral</span>()) &#123;  <br>  <span class="hljs-comment">// Anticipate successful CAS -- the ST of the displaced mark must  </span><br>  <span class="hljs-comment">// be visible &lt;= the ST performed by the CAS.  </span><br>  lock-&gt;<span class="hljs-built_in">set_displaced_header</span>(mark);  <br>  <span class="hljs-keyword">if</span> (mark == (markOop) Atomic::<span class="hljs-built_in">cmpxchg_ptr</span>(lock, <span class="hljs-built_in">obj</span>()-&gt;<span class="hljs-built_in">mark_addr</span>(), mark)) &#123;  <br>    <span class="hljs-built_in">TEVENT</span> (slow_enter: release stacklock) ;  <br>    <span class="hljs-keyword">return</span> ;  <br>  &#125;  <br>  <span class="hljs-comment">// Fall through to inflate() ...  </span><br>&#125;<br></code></pre></td></tr></table></figure><p>其中 CAS 操作是通过 <code>Atomic::cmpxchg_ptr(lock, obj()-&gt;mark_addr(), mark)</code> 这段代码实现的。它有 3 个参数：</p><ol><li>第一个参数是 exchange_value（新值）</li><li>第二个参数是 dest（目标地址）</li><li>第三个参数是 compare_value（原值）</li></ol><p>它的含义是：</p><ol><li>如果目标地址的值是原值，即 <code>dest==compare_value</code>，则将其更新为新值，并且返回 <code>compare_value（原值）</code>。</li><li>否则，不做更新，并且返回第一个入参，<code>exchange_value（新值）</code>。</li><li>因此，当返回结果是 <code>compare_value（原值）</code> 时，则说明<span style="background-color:#ffff00">更新成功</span></li></ol><p>虽然函数最后返回的是第一个入参 <code>exchange_value（新值）</code>，但是返回之前，汇编底层将第二个入参 <code>dest</code> 的值赋值给了第一个参数 <code>exchange_value</code>，所以最终返回的这个实际原值就是 <code>dest</code> 指向的值</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><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 c++"><span class="hljs-comment">// Adding a lock prefix to an instruction on MP machine</span><br><span class="hljs-comment">// VC++ doesn&#x27;t like the lock prefix to be on a single line</span><br><span class="hljs-comment">// so we can&#x27;t insert a label after the lock prefix.</span><br><span class="hljs-comment">// By emitting a lock prefix, we can define a label after it.</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> LOCK_IF_MP(mp) __asm cmp mp, 0  \</span><br><span class="hljs-meta">                       __asm je L0      \</span><br><span class="hljs-meta">                       __asm _emit 0xF0 \</span><br><span class="hljs-meta">                       __asm L0:</span><br><br><span class="hljs-function"><span class="hljs-keyword">inline</span> jint <span class="hljs-title">Atomic::cmpxchg</span><span class="hljs-params">(jint exchange_value, <span class="hljs-keyword">volatile</span> jint* dest, jint compare_value)</span> </span>&#123;<br>  <span class="hljs-comment">// alternative for InterlockedCompareExchange</span><br>  <span class="hljs-type">int</span> mp = os::<span class="hljs-built_in">is_MP</span>();<br>  __asm &#123;<br>    mov edx, dest<span class="hljs-comment">// 将dest保存到edx寄存器</span><br>    mov ecx, exchange_value<span class="hljs-comment">// 将exchange_value保存到ecx寄存器</span><br>    mov eax, compare_value<span class="hljs-comment">// 将compare_value保存到ecx寄存器</span><br>    <span class="hljs-built_in">LOCK_IF_MP</span>(mp)<span class="hljs-comment">// lock</span><br>    cmpxchg dword ptr [edx], ecx <span class="hljs-comment">// 交换</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="1-4-2-返回值"><a href="#1-4-2-返回值" class="headerlink" title="1.4.2. 返回值"></a>1.4.2. 返回值</h3><h4 id="1-4-2-1-返回第一个参数"><a href="#1-4-2-1-返回第一个参数" class="headerlink" title="1.4.2.1. 返回第一个参数"></a>1.4.2.1. 返回第一个参数</h4><p>1、<br><a href="https://gorden5566.com/post/1055.html#waline">https://gorden5566.com/post/1055.html#waline</a><br>　<br>2、<br><a href="https://blog.csdn.net/aileitianshi/article/details/108844586">https://blog.csdn.net/aileitianshi/article/details/108844586</a></p><h4 id="1-4-2-2-返回第二个参数✅"><a href="#1-4-2-2-返回第二个参数✅" class="headerlink" title="1.4.2.2. 返回第二个参数✅"></a>1.4.2.2. 返回第二个参数✅</h4><p>1、<br><a href="https://juejin.cn/post/7104638789456232478">https://juejin.cn/post/7104638789456232478</a></p><p>2、<br><a href="https://cgiirw.github.io/2018/10/14/Blocked02/">https://cgiirw.github.io/2018/10/14/Blocked02/</a></p><h4 id="1-4-2-3-实际原值"><a href="#1-4-2-3-实际原值" class="headerlink" title="1.4.2.3. 实际原值"></a>1.4.2.3. 实际原值</h4><p>1、<br><a href="https://www.javazhiyin.com/24364.html">https://www.javazhiyin.com/24364.html</a></p><p>2、<br><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a></p><figure class="highlight cpp"><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 cpp"><span class="hljs-function"><span class="hljs-keyword">inline</span> jint     <span class="hljs-title">Atomic::cmpxchg</span>    <span class="hljs-params">(jint     exchange_value, <span class="hljs-keyword">volatile</span> jint*     dest, jint     compare_value)</span> </span>&#123;<br>  <span class="hljs-type">int</span> mp = os::<span class="hljs-built_in">is_MP</span>();<br>  <span class="hljs-function">__asm__ <span class="hljs-title">volatile</span> <span class="hljs-params">(LOCK_IF_MP(%<span class="hljs-number">4</span>) <span class="hljs-string">&quot;cmpxchgl %1,(%3)&quot;</span></span></span><br><span class="hljs-params"><span class="hljs-function">                    : <span class="hljs-string">&quot;=a&quot;</span> (exchange_value)</span></span><br><span class="hljs-params"><span class="hljs-function">                    : <span class="hljs-string">&quot;r&quot;</span> (exchange_value), <span class="hljs-string">&quot;a&quot;</span> (compare_value), <span class="hljs-string">&quot;r&quot;</span> (dest), <span class="hljs-string">&quot;r&quot;</span> (mp)</span></span><br><span class="hljs-params"><span class="hljs-function">                    : <span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-string">&quot;memory&quot;</span>)</span></span>;<br>  <span class="hljs-keyword">return</span> exchange_value;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight cpp"><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 cpp">%<span class="hljs-number">0</span>：<span class="hljs-string">&quot;=a&quot;</span> (exchange_value) <span class="hljs-comment">// 第1个输出参数。cmpxchgl指令执行结束后，将寄存器eax的值保存到exchange_value，在方法执行结束后返回</span><br>%<span class="hljs-number">1</span>：<span class="hljs-string">&quot;r&quot;</span> (exchange_value) <span class="hljs-comment">//  第1个输入参数。编译器会选择任意一个可用的寄存器来存储exchange_value，例如使用寄存器ecx。</span><br>%<span class="hljs-number">2</span>：<span class="hljs-string">&quot;a&quot;</span> (compare_value) <span class="hljs-comment">// 第2个输入参数。使用寄存器eax来存储待比较的值compare_value，执行cmpxchgl指令时会用到寄存器eax。</span><br>%<span class="hljs-number">3</span>：<span class="hljs-string">&quot;r&quot;</span> (dest) <span class="hljs-comment">// 第3个输入参数。编译器会选择任意一个可用的寄存器来存储目标地址dest，例如使用寄存器edx。</span><br>%<span class="hljs-number">4</span>：<span class="hljs-string">&quot;r&quot;</span> (mp) <span class="hljs-comment">//  第4个输入参数。编译器会选择任意一个可用的寄存器来存储多处理器标志</span><br></code></pre></td></tr></table></figure><h1 id="2-面试题"><a href="#2-面试题" class="headerlink" title="2. 面试题"></a>2. 面试题</h1><h2 id="2-1-底层原理"><a href="#2-1-底层原理" class="headerlink" title="2.1. 底层原理"></a>2.1. 底层原理</h2><p>CompareAndSwap 是一个 native 方法，实际上它最终还是会面临同样的问题，就是 先从内存地址中读取 state 的值，然后去比较，最后再修改。 这个过程不管是在什么层面上实现，都会存在原子性问题。 所以呢，CompareAndSwap 的底层实现中，在多核 CPU 环境下，会增加一个 Lock 指令对缓存或者总线加锁，从而保证比较并替换这两个指令的原子性。</p><h2 id="2-2-应用场景"><a href="#2-2-应用场景" class="headerlink" title="2.2. 应用场景"></a>2.2. 应用场景</h2><ol><li>第一个是 J.U.C 里面 Atomic 的原子实现，比如 AtomicInteger，AtomicLong。</li><li>第二个是实现多线程对共享资源竞争的互斥性质，比如在 AQS、 ConcurrentHashMap、ConcurrentLinkedQueue 等都有用到。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230523073423.png" alt="image.png"></p><h1 id="3-参考与感谢"><a href="#3-参考与感谢" class="headerlink" title="3. 参考与感谢"></a>3. 参考与感谢</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;CAS 原理和缺陷  huzb的博客]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;探底分析Java原子类CAS的实现原理—从HotSpot源码到CPU指令cmpxchg  HeapDump性能社区]]</p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-4、信号量与管程</title>
      <link href="/2022/11/09/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81%E4%BF%A1%E5%8F%B7%E9%87%8F%E4%B8%8E%E7%AE%A1%E7%A8%8B/"/>
      <url>/2022/11/09/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-4%E3%80%81%E4%BF%A1%E5%8F%B7%E9%87%8F%E4%B8%8E%E7%AE%A1%E7%A8%8B/</url>
      
        <content type="html"><![CDATA[<h3 id="信号量-vs-管程"><a href="#信号量-vs-管程" class="headerlink" title="信号量 vs 管程"></a>信号量 vs 管程</h3><p>并发编程这个技术领域已经发展了半个世纪了，相关的理论和技术纷繁复杂。那有没有一种核心技术可以很方便地解决我们的并发问题呢？事实上，锁机制的实现方案有两种：</p><ul><li>**信号量(Semaphere)**：操作系统提供的一种协调共享资源访问的方法。和用软件实现的同步比较，软件同步是平等线程间的的一种同步协商机制，不能保证原子性。而信号量则由操作系统进行管理，地位高于进程，操作系统保证信号量的原子性。</li><li>**管程(Monitor)**：解决信号量在临界区的 PV 操作上的配对的麻烦，把配对的 PV 操作集中在一起，生成的一种并发编程方法。其中使用了条件变量这种同步机制。</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110135952.png"></p><p><strong>说明：</strong> 信号量将共享变量 S 封装起来，对共享变量 S 的所有操作都只能通过 PV 进行，这是不是和面向对象的思想是不是很像呢？事实上，封装共享变量是并发编程的常用手段。</p><p>在信号量中，当 P 操作无法获取到锁时，将当前线程添加到**同步队列(syncQueue)<strong>中。当其余线程 V 释放锁时，从同步队列中唤醒等待线程。但当有多个线程通过信号量 PV 配对时会异常复杂，所以管程中引入了</strong>等待队列(waitQueue)**的概念，进一步封装这些复杂的操作。</p><h1 id="Mutex"><a href="#Mutex" class="headerlink" title="Mutex"></a>Mutex</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221111-Linux并发与同步专题 (4) Mutex互斥量 - ArnoldLu - 博客园]]</p><h2 id="管程与mutex"><a href="#管程与mutex" class="headerlink" title="管程与mutex"></a>管程与mutex</h2><p><strong>互斥量 mutex</strong> 互斥量是信号量的一种特例，它的值只有0和1，当我们不需要用到信号量的计数能力时候，我们可以使用互斥量，实际上就是同一时间只允许一个进程进入临界区，而信号量是允许多个进程同时进入。</p><h1 id="参考与感谢"><a href="#参考与感谢" class="headerlink" title="参考与感谢"></a>参考与感谢</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-锁原理 - 信号量 vs 管程：JDK 为什么选择管程 - binarylei - 博客园]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-OS：6-管程与信号量PV操作 - cpaulyz - 博客园]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-信号量与 PV 操作的 Java 讲解 - 掘金]]</p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-5、进程 线程 纤程 协程 管程</title>
      <link href="/2022/11/09/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81%E8%BF%9B%E7%A8%8B%20%E7%BA%BF%E7%A8%8B%20%E7%BA%A4%E7%A8%8B%20%E5%8D%8F%E7%A8%8B%20%E7%AE%A1%E7%A8%8B/"/>
      <url>/2022/11/09/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-5%E3%80%81%E8%BF%9B%E7%A8%8B%20%E7%BA%BF%E7%A8%8B%20%E7%BA%A4%E7%A8%8B%20%E5%8D%8F%E7%A8%8B%20%E7%AE%A1%E7%A8%8B/</url>
      
        <content type="html"><![CDATA[<h1 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230105142719.png"></p><h2 id="线程模型"><a href="#线程模型" class="headerlink" title="线程模型"></a>线程模型</h2><p>[[java线程模型  不忘初心方得始终]]</p><p><strong>一句话总结：Java 的线程是映射到操作系统的原生线程之上的。</strong></p><p>JVM 没有限定 Java 线程需要使用哪种线程模型来实现， JVM 只是封装了底层操作系统的差异，而不同的操作系统可能使用不同的线程模型，例如 Linux 和 windows 可能使用了一对一模型，solaris 和 unix 某些版本可能使用多对多模型。所以一谈到 Java 语言的多线程模型，需要针对具体 JVM 实现。</p><p>比如 Sun JDK 1.2 开始，线程模型都是基于操作系统原生线程模型来实现，它的 Window 版和 Linux 版<span style="background-color:#ff00ff">都是使用系统的 1:1 的线程模型实现</span>的。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.bilibili.com/video/BV1yL4y1B7za/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1yL4y1B7za/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-管程，进程及线程之间的区别_qq_35212671的博客-CSDN博客_线程与管程的区别]]</p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-3、对象内存</title>
      <link href="/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/"/>
      <url>/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/</url>
      
        <content type="html"><![CDATA[<h1 id="1-OOP-Klass-模型"><a href="#1-OOP-Klass-模型" class="headerlink" title="1. OOP-Klass 模型"></a>1. OOP-Klass 模型</h1><p>这里的 <strong>Oop</strong> 并非是 Object-oriented programming，而是 <strong>Ordinary object pointer</strong>（普通对象指针），是 HotSpot 用来表示 Java 对象的实例信息的一个体系。其中 <code>oop</code> 是 Oop 体系中的最高父类，整个继承体系如下所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230207102311.png" alt="image.png"><br><a href="https://juejin.cn/post/6844904054561193992">https://juejin.cn/post/6844904054561193992</a></p><h2 id="1-1-JVM-实现⭐️🔴"><a href="#1-1-JVM-实现⭐️🔴" class="headerlink" title="1.1. JVM 实现⭐️🔴"></a>1.1. JVM 实现⭐️🔴</h2><p>^phycx3</p><ul><li>JVM 在加载 class 时，通过类的全限定名获取存储该类的 class 文件，创建 <code>instanceKlass</code>，表示其元数据，存放在<span style="background-color:#ff00ff">方法区</span>；并在<span style="background-color:#ff00ff">堆区</span>生成该类的 Class 对象，即 <code>instanceMirrorKlass</code> 对象。</li><li>在 new 一个对象时，JVM 创建 <code>instanceOopDesc</code>，来表示这个对象，存放在堆区；它用来表示对象的实例信息，看起来像个指针实际上是藏在指针里的对象；<span style="background-color:#00ff00">instanceOopDesc 对应 java 中的对象实例</span></li><li>HotSpot 并不把 instanceKlass 暴露给 Java，而会另外创建对应的 java.lang.Class 对象（对应 <code>InstanceMirrorKlass</code>），并将 Class 对象称为前者的 “Java 镜像 “</li><li>klass 持有指向 class 对象引用 (java_mirror 便是该 instanceKlass 对 Class 对象的引用)，<span style="background-color:#ff00ff">镜像机制</span>被认为是良好的面向对象的反射与元编程设计的重要机制</li><li>HotSopt JVM 的设计者<span style="background-color:#ff00ff">不想让每个对象中都含有一个 vtable（虚函数表）</span>，所以就把对象模型拆成 klass 和 oop，其中 oop 中不含有任何虚函数，而 Klass 就含有虚函数表，可以进行 method dispatch。❕<span style="display:none">%%<br>0649-🏡⭐️◼️Oop-Klass 模型的 JVM 实现 ?🔜MSTM📝 为了避免每个实例对象都维护一个虚函数表 vtable，JVM 将 Class 和实例对象分别对应 InstanceKlass 和 InstanceOopDesc 这两个 C++ 结构。其中 instanceKlass 中存放在方法区，维护类的元数据，其中就包括 vtable。而 instanceOopDesc 就是我们创建的实例对象。Oop-Klass 模型中还包括一个 instanceMirrorKlass 结构，存放在堆中，是 instanceKlass 的对外访问结构，与 instanceKlass 相互持有引用。◼️⭐️-point-202302070649%%</span></li></ul><p>[[深入理解JVM（九）一一 对象实例化和内存布局 - 掘金]]</p><p>HotSpot 采用 Oop-Klass 模型来表示 Java 对象，<span style="background-color:#ff00ff">其中 Klass 对应着 Java 对象的类型（Class），而 Oop 则对应着 Java 对象的实例（Instance）</span>。Oop 是一个继承体系，其中 <code>oop</code> 是体系中的最高父类，它的存储结构可以分成对象头和对象体。对象头存储的是对象的一些元数据，对象体存储的是具体的成员属性。值得注意的是，<strong>如果成员属性属于普通对象类型，则 <code>oop</code> 只存储它的地址</strong>。</p><p>我们都知道 Java 中的普通方法（没有 static 和 final 修饰）是动态绑定的，在 C++ 中，动态绑定通过 <strong>虚函数</strong> 来实现，代价是每个 C++ 对象都必须维护一张 <strong>虚函数表</strong>。</p><p>Java 的特点就是一切皆是对象，如果每个对象都维护一张虚函数表，内存开销将会非常大。<em>JVM 对此做了优化，虚函数表不再由每个对象维护，改成由 Class 类型维护，所有属于该类型的对象共用一张虚函数表</em>。因此我们并没有在 <code>oop</code> 上找到方法调用的相关逻辑，这部分的代码被放在了 <code>klass</code> 里面。<br>[[Java的对象模型——Oop-Klass模型（一） - 掘金]]</p><h2 id="1-2-结构关系"><a href="#1-2-结构关系" class="headerlink" title="1.2. 结构关系"></a>1.2. 结构关系</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221125105247.png"></p><p><span style="background-color:#ff0000">oop→instanceOop 对应类实例对象，klass→instanceKlass 对应 java 类 Class</span></p><h1 id="2-创建对象逻辑"><a href="#2-创建对象逻辑" class="headerlink" title="2. 创建对象逻辑"></a>2. 创建对象逻辑</h1><p>在加载处理完 ClassX 类的一系列逻辑之后，Hotspot 会在堆中为其实例对象 x 开辟一块内存空间存放实例数据，即 JVM 在实例化 ClassX 时，会创建一个 instanceOop，这个 instanceOop 就是 ClassX 对象实例 x 在内存中的对等体，用来存储实例对象的成员变量。让我们来看下 instanceOop.hpp（位于 OpenJDKhotspotsrcsharevmoops 目录下），<span style="background-color:#00ff00">发现 instanceOop 是继承自 oopDesc 的（class instanceOopDesc : public oopDesc）</span>，继续看下 Oop 的结构就会发现：</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><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 c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">oopDesc</span> &#123;  <br>    <span class="hljs-comment">//友元类  </span><br>    <span class="hljs-keyword">friend</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">VMStructs</span>;  <br>    <span class="hljs-keyword">private</span>:  <br>        <span class="hljs-keyword">volatile</span> markOop  _mark;  <br>        <span class="hljs-keyword">union</span> <span class="hljs-title class_">_metadata</span> &#123;  <br>            Klass*      _klass;  <br>            narrowKlass _compressed_klass;  <br>        &#125; _metadata;   <br>    <span class="hljs-comment">// Fast access to barrier set.  Must be initialized.  </span><br>    <span class="hljs-comment">//用于内存屏障  </span><br>    <span class="hljs-type">static</span> BarrierSet* _bs;  <br>    ........  <br>&#125;<br></code></pre></td></tr></table></figure><p><code>_mark</code> 和 <code>_metadata</code> 加起来就是我们传说中的对象头了，其中 markOop 的变量 _mark 用来标识 GC 分代信息、线程状态、并发锁等信息。<span style="background-color:#ff00ff">而 _metadata 的联合结构体则用来标识元数据（指向 instanceKlass 元数据的指针）</span>，就是这个类包含的父类、方法、变量、接口等各种信息。</p><h1 id="3-对象内存布局"><a href="#3-对象内存布局" class="headerlink" title="3. 对象内存布局"></a>3. 对象内存布局</h1><p>Hotspot 虚拟机中，对象在内存中存储的布局可以分为三块区域：<span style="background-color:#ff00ff">对象头（Header）、实例数据（Instance Data）和对齐填充（Padding）</span>。</p><ul><li><strong>对象头</strong>：比如 hash 码，对象所属的年代，对象锁，锁状态标志，偏向锁（线程）ID，偏向时间，数组长度（数组对象才有）等。</li><li><strong>实例数据</strong>：存放类的属性数据信息，包括父类的属性信息；</li><li><strong>对齐填充</strong>：由于虚拟机要求 <strong>对象起始地址必须是 8 字节的整数倍</strong>。填充数据不是必须存在的，仅仅是为了字节对齐。</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110085022.png"><br>❕<span style="display:none">%%<br>▶4.🏡⭐️◼️instanceOopDesc 中包含但不限于对象头，还有屏障等其他信息◼️⭐️-point-20230226-1045%%</span></p><h2 id="3-1-对象头"><a href="#3-1-对象头" class="headerlink" title="3.1. 对象头"></a>3.1. 对象头</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230527193116.png" alt="image.png"></p><ul><li><strong>Mark Word</strong>(核心) : 记录了这个对象当前锁机制的记录信息</li><li><strong>Klass</strong> : klass pointer 指向元数据区中 (JDK1.8) 该对象所代表的<span style="background-color:#ff00ff">类的描述元数据对象 InstanceKlass</span></li><li><strong>ArrayLength</strong> : 这个部分是基于数组对象,而对于普通对象,该部分的是没有占位的</li></ul><blockquote><ul><li>Mark Word 里默认存储锁状态信息是无锁状态的，即存储的是 HashCode、分代年龄、是否偏向锁、锁标志位等信息，64bit 默认存储结构如下图 :</li></ul></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221111091444.jpg"></p><blockquote><ul><li>在运行期间，Mark Word 里存储的数据会随着是否偏向锁、锁标志位的变化而变化，如下图五种状态中其中一种，即同一时刻 MarkWord 只能表示其中一种锁状态。</li></ul></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221111091349.jpg"></p><h3 id="3-1-1-Mark-Word⭐️🔴"><a href="#3-1-1-Mark-Word⭐️🔴" class="headerlink" title="3.1.1. Mark Word⭐️🔴"></a>3.1.1. Mark Word⭐️🔴</h3><p>synchronized 同步锁相关信息保存到锁对象的对象头里面的 Mark Word 中，锁升级功能主要是依赖 Mark Word 中锁标志位和是否偏向锁标志位来实现的。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202103858.png"></p><p>从上图我们可以看到，无锁对应的锁标志位是“01”，是否偏向锁标志是“0”。</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_">NoLock</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">Object</span> <span class="hljs-variable">objLock</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>();<br>        <span class="hljs-comment">// 需要注意，只有调用了hashCode()，对象头中的MarkWord才会保存对应的hashCode值，否则全部是0</span><br>        System.out.println(<span class="hljs-string">&quot;10进制: &quot;</span> + objLock.hashCode());<br>        System.out.println(<span class="hljs-string">&quot;2进制: &quot;</span> + Integer.toBinaryString(objLock.hashCode()));<br>        System.out.println(<span class="hljs-string">&quot;16进制: &quot;</span> + Integer.toHexString(objLock.hashCode()));<br>        System.out.println(ClassLayout.parseInstance(objLock).toPrintable());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202104013.png"></p><p><span style="background-color:#ff0000">对象头中 MarkWord 部分总共占 8 个字节，共 64 位，我们按照上图中“1 -&gt; 8”，也就是从后面往前面拼接起来：</span> ❕<span style="display:none">%%<br>0756-🏡⭐️◼️对象头 markword ?🔜MSTM📝 总共占 8 个字节即 64 位◼️⭐️-point-202302070756%%</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113182125.png"></p><h4 id="3-1-1-1-分代年龄"><a href="#3-1-1-1-分代年龄" class="headerlink" title="3.1.1.1. 分代年龄"></a>3.1.1.1. 分代年龄</h4><p>记录分代年龄一共 4 bit，所以最大为 2^4 - 1 &#x3D; 15。所以配置最大分代年龄 -XX:MaxTenuringThreshold&#x3D;n 这个 n 不能大于 15，当然也不能小于 0.等于 0 的话，就直接入老年代。<span style="background-color:#ff00ff">等于 16 的话，就是从不进入老年代</span>，这样不符合 JVM 规范，所以不能大于 15。默认是 15。</p><h2 id="3-2-32-位-CPU-最大支持-4G-内存⭐️🔴"><a href="#3-2-32-位-CPU-最大支持-4G-内存⭐️🔴" class="headerlink" title="3.2. 32 位 CPU 最大支持 4G 内存⭐️🔴"></a>3.2. 32 位 CPU 最大支持 4G 内存⭐️🔴</h2><p><strong>实际上内存是把 8 个 bit 排成 1 组， 每 1 组成为 1 个单位， 大小是 1byte(字节）， cpu 每一次只能访问 1 个 byte， 而不能单独去访问具体的 1 个小格子 (bit). 1 个 byte 字节就是内存的最小的 IO 单位.</strong><br>既然内存的最小 IO 单位是字节 byte，那么我们其实不需要为每一个格子也就是每一 bit 去分配地址了，而是按照 8 个 bit 为一组，也就是一个字节分配一个地址。</p><p>其实计算机操作系统会给内存每 1 个字节分配 1 个内存地址, cpu 只需要知道某个数据类型的地址, 就可以直接去到对应的内存位置去提取数据了。<br>我们再算一下，<span style="background-color:#ff0000">其实 32 位表示 232 个地址</span>，而每一个地址是指向的是 8bit 为一组的 byte ，所以要算到寻址的话，就要在乘以 8 ，也就是 235 个 bit，这样再换算为 GB 就是 4GB 了。 ❕<span style="display:none">%%<br>1517-🏡⭐️◼️字长与可表示内存大小关系 ?🔜MSTM📝 字长表示计算机一次性可以处理 (操作) 的数据的位数，比如 32 位，就表示 CPU 一次性可以处理 2 的 32 次方大小的数，即最大表示这么大的一个数。那么对于内存寻址的话，如果以 bit 为单位，那么就最大能表达 2 的 32 次方大小的内存，算下来是 0.5G，但如果以 8 个 bit 为一组表示一个单位，那么一个字长能最大表达内存大小就是 4G 了。其实 CPU 到内存上读取数据只要知道起始地址即可，然后每种数据类型大小预先设定好，内存存放又是连续的，所以从起点数固定大小的内容就是想要的数据了。◼️⭐️-point-202302071517%%</span></p><p>字与字长：<a href="/2023/02/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80-1%E3%80%81%E7%9B%B8%E5%85%B3%E5%90%8D%E8%AF%8D/" title="计算机基础-1、相关名词">计算机基础-1、相关名词</a></p><h2 id="3-3-压缩指针"><a href="#3-3-压缩指针" class="headerlink" title="3.3. 压缩指针"></a>3.3. 压缩指针</h2><p>这一部分用于存储对象的类型指针，该指针指向它的类元数据 instanceKlass，JVM 通过这个指针确定对象是哪个类的实例。该指针的位长度为 JVM 的一个字大小，即 32 位的 JVM 为 32 位，64 位的 JVM 为 64 位。 如果应用的对象过多，使用 64 位的指针将浪费大量内存。为了节约内存可以使用选项 +UseCompressedOops 开启指针压缩，其中，oop 即 ordinary object pointer 普通对象指针。开启该选项后，下列指针将压缩至 32 位。1.6 中某个版本之后默认开启。</p><p>压缩指针包括：<span style="background-color:#ff00ff">每个 Class 的属性指针（即静态变量）、 每个对象的属性指针（即对象变量） 、普通对象数组的每个元素指针</span></p><p>当然，也不是所有的指针都会压缩，一些特殊类型的指针 JVM 不会优化，比如<span style="background-color:#ff0000">指向 PermGen 的 Class 对象指针 (JDK8 中指向元空间的 Class 对象指针)、本地变量、堆栈元素、入参、返回值和 NULL 指针</span>等。❕<span style="display:none">%%<br>1210-🏡⭐️◼️不需要压缩的指针 ?🔜MSTM📝 离寄存器最近的指针，比如本地变量表中的指针、栈中的指针、入参返回值、NULL 指针，以及堆中 instanceMirrorKlass 持有的方法区中的 instanceKlass 的指针◼️⭐️-point-202302071210%%</span></p><h2 id="3-4-为什么压缩⭐️🔴"><a href="#3-4-为什么压缩⭐️🔴" class="headerlink" title="3.4. 为什么压缩⭐️🔴"></a>3.4. 为什么压缩⭐️🔴</h2><h3 id="3-4-1-原因"><a href="#3-4-1-原因" class="headerlink" title="3.4.1. 原因"></a>3.4.1. 原因</h3><p><strong>64 位 JVM 在支持更大堆的同时，由于对象引用变大却带来了性能问题：</strong></p><ol><li><p>增加了 GC 开销<br>64 位对象引用需要占用更多的堆空间，留给其他数据的空间将会减少，从而加快了 GC 的发生，更频繁的进行 GC。</p></li><li><p>降低 CPU 缓存命中率<br>64 位对象引用增大了，CPU 能缓存的 oop 将会更少，从而降低了 CPU 缓存的效率。</p></li></ol><h3 id="3-4-2-原理"><a href="#3-4-2-原理" class="headerlink" title="3.4.2. 原理"></a>3.4.2. 原理</h3><p>为了能够保持 32 位的性能，oop 必须保留 32 位。那么，如何用 32 位 oop 来引用更大的堆内存呢？<strong>答案是压缩指针（CompressedOops）</strong></p><p>4 字节，8 位最大表示 4GB 内存。那么 Java 是怎么做到 4 个字节表示 32GB 呢？怎有扩大了 8 倍？这就要使用到之前提到的 Java 的对齐填充机制了。</p><p>Java 的 8 字节对齐填充，就像是内存的 8bit 为一组，变为 1byte 一样。</p><p>这里的压缩指针，不是真实的操作系统内存地址，而是 Java 进行 8byte 映射之后的地址，即 8 个 byte 为一组作为一个表达单位。因此相同的指针数量下，单位表达能力扩大了 8 倍，就相当于操作系统的指针表达能力有进行的 8 倍的扩容。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230207153201.png" alt="image.png"></p><p><a href="https://blog.csdn.net/liujianyangbj/article/details/108049482">https://blog.csdn.net/liujianyangbj/article/details/108049482</a></p><h3 id="3-4-3-实现方式"><a href="#3-4-3-实现方式" class="headerlink" title="3.4.3. 实现方式"></a>3.4.3. 实现方式</h3><p>JVM 的实现方式是，不再保存所有引用，而是每隔 8 个字节保存一个引用。例如，原来保存每个引用 0、1、2…，现在只保存 0、8、16…。因此，指针压缩后，并不是所有引用都保存在堆中，而是以 8 个字节为间隔保存引用。</p><p>在实现上，堆中的引用其实还是按照 0x0、0x1、0x2…进行存储。只不过当引用被存入 64 位的寄存器时，JVM 将其左移 3 位（相当于末尾添加 3 个 0），例如 0x0、0x1、0x2…分别被转换为 0x0、0x8、0x10。而当从寄存器读出时，JVM 又可以右移 3 位，丢弃末尾的 0。（oop 在堆中是 32 位，在寄存器中是 35 位，2 的 35 次方&#x3D;32G。也就是说，使用 32 位，来达到 35 位 oop 所能引用的堆内存空间）<br><a href="https://juejin.cn/post/6844903768077647880">https://juejin.cn/post/6844903768077647880</a></p><h2 id="3-5-哪些指针需要压缩⭐️🔴"><a href="#3-5-哪些指针需要压缩⭐️🔴" class="headerlink" title="3.5. 哪些指针需要压缩⭐️🔴"></a>3.5. 哪些指针需要压缩⭐️🔴</h2><p>首先看下官网 <a href="https://wiki.openjdk.java.net/display/HotSpot/CompressedOops">《CompressedOops》</a> 给出的需要被压缩的类型：</p><blockquote><h4 id="Which-oops-are-compressed"><a href="#Which-oops-are-compressed" class="headerlink" title="Which oops are compressed?"></a>Which oops are compressed?</h4><p>In an ILP32-mode JVM, or if the UseCompressedOops flag is turned off in LP64 mode, all oops are the native machine word size.</p><p>If UseCompressedOops is true, the following oops in the heap will be compressed:</p><ul><li>the klass field of every object</li><li>every oop instance field</li><li>every element of an oop array (objArray)</li></ul></blockquote><p>我们可以看出指针压缩只有在 64 位 JVM 下才有效，在 32 位 JVM 或未开启指针压缩的 64 位 JVM 下 oop 大小都是原生的机器字大小。而具体压缩的则是<span style="background-color:#ff00ff"> klass field、oop instance field、以及每个数组中元素的 oop</span>。❕<span style="display:none">%%<br>1208-🏡⭐️◼️哪些指针需要压缩 ?🔜MSTM📝 Klass field、oop instance field、数组元素引用对象◼️⭐️-point-202302071208%%</span></p><h2 id="3-6-为什么-8-字节对齐"><a href="#3-6-为什么-8-字节对齐" class="headerlink" title="3.6. 为什么 8 字节对齐"></a>3.6. 为什么 8 字节对齐</h2><p>假如我们这里有 3 个对象 A、B、C，他们的大小分别为 8、16、8 字节（为什么假设这几个值，我们先按下不表），并且在内存中连续存储，为更好理解，我们简化为下图：<br><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7ba8d262b793426d8bc7b4336de1f5fe~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image" alt="image.png"><br>方案 1，指针用 <code> 内存位置</code> 来标记对象在内存中的位置：<br>A：00000000 00000000 00000000 00000000 （十进制表示：0）<br>B：00000000 00000000 00000000 00001000 （十进制表示：8）<br>C：00000000 00000000 00000000 00011000 （十进制表示：24）</p><p>从上面可以看出 32 位的指针，满打满算也只能存储 2^32，约 4GB 的内存地址。<br>如果是 64 位的指针，就能表示 2^64，上面说的可以理解为无限大，甚至感觉还有点浪费。</p><p>既然 64 位指针用来存储太浪费了，有什么更好的办法可以在 32 位的限制下表示更多的内存地址吗？  这时，我们发现对象 A、B、C 大小都是 8 字节的整数倍，即 8 是他们对象大小的最大公约数！</p><p>我们可以借助索引来标识。  用 8 位内存地址偏移量 代表 1 索引<br>那么 A 的位置就可以标识为 索引 0，B 为 索引 1，C 为 索引 3。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf01af24d20b48bbaba1470367b26231~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image" alt="image.png"><br>方案 2，指针用 <code>索引</code> 来标记对象在内存中的位置：<br>A：00000000 00000000 00000000 00000000 （十进制表示：0）<br>B：00000000 00000000 00000000 00000001 （十进制表示：1）<br>C：00000000 00000000 00000000 00000011 （十进制表示：3）</p><p>加入索引这一概念是为了方便理解；实际上 JVM 是通过读取时左移 3 位，存储时右移 3 位来完的。<br><span style="background-color:#ff00ff">原因 1</span><strong>也就是说原本可表示 4GB 的内存地址，因为 1 索引表示 8 个内存地址偏移量，现在可以表示最高存储 32GB 的内存地址了。</strong></p><p>上面的对象 A、B、C 我们假设的大小是 8 字节、16 字节、8 字节；共同点你可能发现了，他们都是 8 字节的倍数，其实 Java 对象的大小就必须是 8 字节的整数倍，如果没有这个条件，上面说的索引说法也不成立。<br><span style="background-color:#ff00ff">原因 2</span><br>当然除了为了支持上面这些功能外，另外还有的就是因为现在大多数计算机都是高效的 64 位处理器，一次能处理 64 位的指令，即 8 个字节的数据，<span style="background-color:#ffff00">如果数据的存放地址不做字节对齐，对正常的工作不会产生任何影响，但是性能会降低，CPU 需要多个周期来读取后将数据拼接起来才能读取到数据。</span>HotSpot VM 的自动内存管理系统也就遵循了这个要求，这样子性能更高，处理更快。</p><h1 id="4-多少字节-对象大小计算-⭐️🔴⭐️🔴"><a href="#4-多少字节-对象大小计算-⭐️🔴⭐️🔴" class="headerlink" title="4. 多少字节 (对象大小计算)⭐️🔴⭐️🔴"></a>4. 多少字节 (对象大小计算)⭐️🔴⭐️🔴</h1><h2 id="4-1-new-Object"><a href="#4-1-new-Object" class="headerlink" title="4.1. new Object()"></a>4.1. new Object()</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221125143504.png"></p><p><strong>正确答案</strong></p><p>在 JDK8 下 64 位操作系统中 new Object() 占多少字节？<br>答案：16 字节</p><p><span style="background-color:#ff00ff">8 个字节是 MarkWord  + 4 个字节是指针（jdk 8 默认开启指针压缩）+ 4 个字节是对齐填充位</span></p><h2 id="4-2-加其他属性"><a href="#4-2-加其他属性" class="headerlink" title="4.2. 加其他属性"></a>4.2. 加其他属性</h2><p><strong>int 占 4 字节，short 占 2 字节，long 占 8 字节，byte 占 1 字节，float 占 4 字节，double 占 8 字节，char 占 2 字节，boolean 占 1 字节</strong></p><p><span style="background-color:#00ff00">注意空对象的 4 个填充位可以用占用小于等于 4 的属性来填充，比如 short 类型的话占用 2 个字节，会再额外用 2 个字节补齐</span> ❕<span style="display:none">%%<br>1157-🏡⭐️◼️对象头中的对齐补充大小 ?🔜MSTM📝 是可以根据属性大小变化的，比如 new Object() 开启指针压缩是 8(MarkWord)+4(压缩过的类型指针)+4(对齐填充)，如果加了一个占 2 个字节大小的属性，那么对齐填充就是 2 个字节◼️⭐️-point-202301301157%%</span></p><p>[[20221110-如何计算一个对象的大小？ - 掘金]]</p><h1 id="5-哈希几次"><a href="#5-哈希几次" class="headerlink" title="5. 哈希几次"></a>5. 哈希几次</h1><p>[[20221112-Java GC详解 - 最全面的理解Java对象结构 - 对象指针 OOPs  HeapDump性能社区]]</p><h1 id="6-虚函数表"><a href="#6-虚函数表" class="headerlink" title="6. 虚函数表"></a>6. 虚函数表</h1><p>一个 <code>klassVtable</code> 可看成是由多个 <code>vtableEntry</code> 组成的数组，其中每个元素 <code>vtableEntry</code> 里面都包含了一个方法的地址。在进行虚分派时，JVM 会根据方法在 <code>klassVtable</code> 中的索引，找到对应的 <code>vtableEntry</code>，进而得到方法的实际地址，最后根据该地址找到方法的字节码并执行。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/2/2/17004ea47df4ace5~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image" alt="vtalbe结构"></p><p>vtalbe 结构</p><p>[[Java的对象模型——Oop-Klass模型（二） - 掘金]]</p><h1 id="7-使用-JOL-分析对象布局"><a href="#7-使用-JOL-分析对象布局" class="headerlink" title="7. 使用 JOL 分析对象布局"></a>7. 使用 JOL 分析对象布局</h1><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></pre></td><td class="code"><pre><code class="hljs xml"><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.openjdk.jol<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>jol-core<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.14<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></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></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_">NewObjectLayoutTest</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>        System.out.println(ClassLayout.parseInstance(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>()).toPrintable());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221125164247.png"></p><h1 id="8-参考与感谢"><a href="#8-参考与感谢" class="headerlink" title="8. 参考与感谢"></a>8. 参考与感谢</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-为什么要字节对齐？ - 掘金]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-为什么JVM要用到压缩指针？Java对象要求8字节的整数倍？ - 掘金]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-Java 对象头 - 简书]]</p><p><a href="https://heapdump.cn/article/2545514">https://heapdump.cn/article/2545514</a></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> timeline </tag>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-1、JMM与MESI</title>
      <link href="/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-1%E3%80%81JMM%E4%B8%8EMESI/"/>
      <url>/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-1%E3%80%81JMM%E4%B8%8EMESI/</url>
      
        <content type="html"><![CDATA[<h1 id="1-CPU-架构"><a href="#1-CPU-架构" class="headerlink" title="1. CPU 架构"></a>1. CPU 架构</h1><h2 id="1-1-CPU-主要架构"><a href="#1-1-CPU-主要架构" class="headerlink" title="1.1. CPU 主要架构"></a>1.1. CPU 主要架构</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116162723.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114092628.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114152404.svg"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221114151834.png"></p><h3 id="1-1-1-Front-End（Core-前端）"><a href="#1-1-1-Front-End（Core-前端）" class="headerlink" title="1.1.1. Front End（Core 前端）"></a>1.1.1. Front End（Core 前端）</h3><p>首先看 Front-end，Front-end 的主要目的就是从内存里提取各种各样的 X86 指令，然后对指令进行译码，融合优化等操作，把 X86 指令转化为最适合执行单元执行的微指令流传递给执行单元。Front-End 的存在就是为了让执行单元时刻保持繁忙，将 CPU 的性能完全发挥出来。</p><h4 id="1-1-1-1-乱序组件"><a href="#1-1-1-1-乱序组件" class="headerlink" title="1.1.1.1. 乱序组件"></a>1.1.1.1. 乱序组件</h4><ul><li>**Allocation Queue(IDQ): 分配队列，分配队列作为前端与执行单元的接口，是 Core 前端的最后的一个部件。分配队列的目的是将微指令进行重新整合与融合，发给执行单元进行乱序执行。分配队列又包含了 Loop Stream Detector(LSD) 循环流检测器，对循环操作进行优化与 up-Fusion(微指令融合单元)。融合是为了让后续解码单元更有效率并且节省 ROB（re-order buffer）的空间</li></ul><p><img src="https://pic1.zhimg.com/80/v2-31d86c5c0a08fb1dcce87747c3223b50_720w.webp"></p><h3 id="1-1-2-Execution-Engine-执行单元"><a href="#1-1-2-Execution-Engine-执行单元" class="headerlink" title="1.1.2. Execution Engine (执行单元)"></a>1.1.2. Execution Engine (执行单元)</h3><p>Front-End 的存在就是为了让执行单元时刻保持繁忙，那么执行单元的最主要的目的就是执行指令，运算。这也是一个 core 中最根本的单元。</p><p>当被 AQ 或者 IDQ（分配单元）优化过后的微指令来到执行单元时，首先最先传输到</p><h4 id="1-1-2-1-重排缓冲区"><a href="#1-1-2-1-重排缓冲区" class="headerlink" title="1.1.2.1. 重排缓冲区"></a>1.1.2.1. 重排缓冲区</h4><ul><li>**ROB（re-order buffer）：重新排序缓冲区。ROB 的存在 ROB 的目的<span style="background-color:#00ff00">为存储 out-of-order 的处理结果，作为 EU 的入口兼部分出口，它是乱序执行的最基本保证</span>。当指令被传如 ROB 中，微指令流会以顺序执行的方式传入到后面的 RS，在经过 ROB 时，会占用 ROB 的一个位置，这个位置是存储微指令乱序执行处理完成时候的结果，之后经过整合会顺序写回到相应的寄存器。而微指令在经过 ROB 时候会做一些优化。（消除寄存器移动，置零指令与置一指令等）。此外对于超线程中的寄存器别名技术在此经过 RAT（寄存器别名表）进行寄存器重命名。</li></ul><p><img src="https://pic3.zhimg.com/80/v2-dd56b40540282c6feb74de7b6af2e48e_720w.webp"></p><ul><li><strong>RS(Scheduler unified reservation station)：</strong>统一调度保留站。指令经过前面千辛万苦来到这里，此时微指令不在融合在一起，而是被单独的分配给下面各个执行单元，从架构图中可以看到，RS 下面挂载了八个端口，每个端口后面挂载不同的执行模块应对不同的指令需求.</li></ul><p><img src="https://pic4.zhimg.com/80/v2-58c7699ff760f8e3fed8693e5690125b_720w.webp"></p><p>从上图中可以看到，对于 8 个端口，其中 Port0,Port1,Port5,Port6 负责各种常见运算 (整数，浮点，除法，移位，AES 加密，复合整数运算……)，而 Port2,Port3 负责从下层的指令缓存提取数据，port4 负责存储数据到 L1 缓存。Port7 挂载地址生成单元（AGU address generation unit）.</p><h3 id="1-1-3-Memory-Subsystem"><a href="#1-1-3-Memory-Subsystem" class="headerlink" title="1.1.3. Memory Subsystem"></a>1.1.3. Memory Subsystem</h3><p>图中紫色的模块，在 EU（执行单元）在执行指令时候，L1 数据 Cache 负责供给执行指令期间所需要的数据。从图中可以看到 L1 数据缓存是 8- 路并行数据缓存，L1 数据缓存可以通过数据页表地址缓存从 L2 提取数据与存储数据。</p><h4 id="1-1-3-1-保存缓冲区"><a href="#1-1-3-1-保存缓冲区" class="headerlink" title="1.1.3.1. 保存缓冲区"></a>1.1.3.1. 保存缓冲区</h4><p>Store Buffer</p><p><img src="https://pic2.zhimg.com/80/v2-81f39695ca8dd21a111102357889c035_720w.webp"></p><h2 id="1-2-高速缓存"><a href="#1-2-高速缓存" class="headerlink" title="1.2. 高速缓存"></a>1.2. 高速缓存</h2><h3 id="1-2-1-为什么要有"><a href="#1-2-1-为什么要有" class="headerlink" title="1.2.1. 为什么要有"></a>1.2.1. 为什么要有</h3><h4 id="1-2-1-1-性能适配"><a href="#1-2-1-1-性能适配" class="headerlink" title="1.2.1.1. 性能适配"></a>1.2.1.1. 性能适配</h4><p>CPU 和内存访问性能的差距非常大。如今，一次内存的访问，大约需要 120 个 CPU Cycle。这也意味着，在今天，<strong>CPU 和内存的访问速度已经有了 120 倍的差距。</strong><br>为了弥补两者之间的性能差异，充分利用 CPU，现代 CPU 中引入了 <strong>高速缓存 (CPU Cache)<strong>。高速缓存分为 L1&#x2F;L2&#x2F;L3 Cache，不是一个单纯的、概念上的缓存（比如使用内存作为硬盘的缓存），</strong>而是指特定的由 SRAM 组成的物理芯片</strong>。下图是一张 Intel CPU 的放大照片。这里面大片的长方形芯片，就是这个 CPU 使用的 20MB 的 L3 Cache，可以看到现代 CPU 中大量的空间已经被 SRAM 占据。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221128124059.png"></p><h4 id="1-2-1-2-局部性原理"><a href="#1-2-1-2-局部性原理" class="headerlink" title="1.2.1.2. 局部性原理"></a>1.2.1.2. 局部性原理</h4><ul><li>时间局部性：某个内存单元在较短时间内很可能被再次访问</li><li>空间局部性：某个内存单元被访问后相邻的内存单元较短时间内很可能被访问</li></ul><p>在各类基准测试 (Benchmark) 和实际应用场景中，<strong>CPU Cache 的命中率通常能达到 95% 以上。</strong></p><h3 id="1-2-2-缓存行"><a href="#1-2-2-缓存行" class="headerlink" title="1.2.2. 缓存行"></a>1.2.2. 缓存行</h3><h4 id="1-2-2-1-是什么"><a href="#1-2-2-1-是什么" class="headerlink" title="1.2.2.1. 是什么"></a>1.2.2.1. 是什么</h4><p>之所以会有 CPU 缓存，时间局部性是其中一个重要的原因。不过，我们到底应该怎么利用处理器的空间局部性呢？比起拷贝一个单独的内存地址到 CPU 缓存里，拷贝一个 <strong>缓存行</strong> (<strong>Cache Line</strong>) 是更好的实现。<span style="background-color:#00ff00">一个缓存行是一个连续的内存段。</span></p><p>缓存行的大小取决于缓存的级别 (同样的，具体还是取决于处理器模型)。举个例子，这是我的电脑的 L1 缓存行的大小：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-string">$</span> <span class="hljs-string">sysctl</span> <span class="hljs-string">-a</span> <span class="hljs-string">|</span> <span class="hljs-attr">grep cachelinehw.cachelinesize:</span> <span class="hljs-number">64</span><br></code></pre></td></tr></table></figure><p>处理器会拷贝一段连续的 64 字节的内存段到 L1 缓存里，而不是仅仅拷贝一个单独的变量。比如 long 类型变量，1 个占用 8 个字节，那么一个缓存行就可放 8 个 long 类型的变量</p><p>现在总结一下，为了平衡 CPU 和内存的性能差异，现在 CPU 引入高速缓存：</p><ul><li>**高速缓存 (CPU Cache)**：用于平衡 CPU 和内存的性能差异，分为 L1&#x2F;L2&#x2F;L3 Cache。其中 L1&#x2F;L2 是 CPU 私有，L3 是所有 CPU 共享。</li><li>**缓存行 (Cache Line)**：高速缓存的最小单元，一次从内存中读取的数据大小。常用的 Intel 服务器 Cache Line 的大小通常是 64 字节。</li></ul><p>两种写入策略。</p><ul><li>**写直达 (Write-Through)**：每一次数据都要写入到主内存里面。</li><li>**写回 (Write-Back)**：数据写到 CPU Cache 就结束。只有当 CPU Cache 是脏数据时，才把数据写入主内存。<br>写回这个策略里，如果我们大量的操作，都能够命中缓存。那么大部分时间里，我们都不需要读写主内存，自然性能会比写直达的效果好很多。</li></ul><h4 id="1-2-2-2-伪共享"><a href="#1-2-2-2-伪共享" class="headerlink" title="1.2.2.2. 伪共享"></a>1.2.2.2. 伪共享</h4><p>[[【译】CPU 高速缓存原理和应用 - Strike Freedom]]</p><h2 id="1-3-乱序执行技术"><a href="#1-3-乱序执行技术" class="headerlink" title="1.3. 乱序执行技术"></a>1.3. 乱序执行技术</h2><p>乱序执行（Out-of-order Execution）是以乱序方式执行指令，即 CPU 允许将多条指令不按程序规定的顺序而分开发送给各相应电路单元进行处理。这样，根据各个电路单元的状态和各指令能否提前执行的具体情况分析，将能够提前执行的指令立即发送给相应电路单元予以执行，在这期间不按规定顺序执行指令；然后由 <strong>重新排列单元将各执行单元的结果按指令顺序重新排列</strong>。乱序执行的目的，就是为了 <strong>使 CPU 内部电路满负荷运转，并相应提高 CPU 运行程序的速度</strong>。</p><p>实现乱序执行的关键在于 <strong>取消传统的“取指”和“执行”两个阶段之间指令需要线性排列的限制</strong>，而使用一个 <strong>指令缓冲池</strong>(即<span style="background-color:#00ff00">重排缓冲区 ROB</span>) 来开辟一个较长的指令窗口，<strong>允许执行单元在一个较大的范围内调遣和执行已译码的程序指令流</strong>。</p><h2 id="1-4-CPU-乱序来源"><a href="#1-4-CPU-乱序来源" class="headerlink" title="1.4. CPU 乱序来源"></a>1.4. CPU 乱序来源</h2><h3 id="1-4-1-指令执行乱序"><a href="#1-4-1-指令执行乱序" class="headerlink" title="1.4.1. 指令执行乱序"></a>1.4.1. 指令执行乱序</h3><p>因为 MESI 规范要求变量值写入缓存行的前提条件是变量必须在缓存行中，这就要求有一个中间的 StoreBuffer 作为缓冲 (其实这是 StoreBuffer 需要存在的一个原因，主要原因还是为了避免 CPU 写入缓存行时等待其他 CPU 的 ack 消息而导致的性能下降)，否则会导致 CPU 效率下降。那么<br>ROB 写入到 StoreBuffer 有 2 种方式：</p><ol><li>ROB 中的变量，在 StoreBuffer 中谁先有谁先写</li><li>严格的 FIFO 方式<br>从 x86-TSO 模型的物理构件角度解释就是，写操作会按照 FIFO 的规则 进入 StoreBuffer，并且按照 FIFO 的顺序刷入共享存储</li></ol><p>so，x86 是可以保证不会出现 CPU 的指令执行乱序的</p><h3 id="1-4-2-内存写入乱序"><a href="#1-4-2-内存写入乱序" class="headerlink" title="1.4.2. 内存写入乱序"></a>1.4.2. 内存写入乱序</h3><p>然鹅，从 StoreBuffer 到 共享缓存这一步，是异步的，就需要 CPU 指令来保证了，比如 volatile 的 fence 方法中的 lock 指令，值得注意的是 lock 这个 CPU 指令也具有禁止编译优化的作用。</p><h4 id="1-4-2-1-问题根源"><a href="#1-4-2-1-问题根源" class="headerlink" title="1.4.2.1. 问题根源"></a>1.4.2.1. 问题根源</h4><p>[[后端 - MESI 缓存一致性协议引发的一些思考_个人文章 - SegmentFault 思否]]</p><h1 id="2-Java-Memory-Model"><a href="#2-Java-Memory-Model" class="headerlink" title="2. Java Memory Model"></a>2. Java Memory Model</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106110623.jpg" alt="image-20200128184358700"></p><p><strong>工作内存就是每个线程独享的线程栈</strong></p><h2 id="2-1-并发编程的-3-大特性"><a href="#2-1-并发编程的-3-大特性" class="headerlink" title="2.1. 并发编程的 3 大特性"></a>2.1. 并发编程的 3 大特性</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106111027.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106111220.png"></p><h2 id="2-2-JMM8-大原子操作"><a href="#2-2-JMM8-大原子操作" class="headerlink" title="2.2. JMM8 大原子操作"></a>2.2. JMM8 大原子操作</h2><ul><li>read(读取)：作用于主内存变量，把一个变量值从主内存传输到线程的工作内存中，以便随后的 load 动作使用;</li><li>load(载入)：作用于工作内存的变量，它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中;</li><li>use(使用)：作用于工作内存的变量，把工作内存中的一个变量值传递给执行引擎，每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作;</li><li>assign(赋值)：作用于工作内存的变量，它把一个从执行引擎接收到的值赋值给工作内存的变量，每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;</li><li>store(存储)：<span style="background-color:#00ff00">作用于工作内存的变量</span>，把工作内存中的一个变量的值传送到主内存中，以便随后的 write 的操作;</li><li>write(写入)：<span style="background-color:#00ff00">作用于主内存的变量</span>，它把 store 操作从工作内存中一个变量的值传送到主内存的变量中;</li></ul><h2 id="2-3-JMM-缓存不一致问题"><a href="#2-3-JMM-缓存不一致问题" class="headerlink" title="2.3. JMM 缓存不一致问题"></a>2.3. JMM 缓存不一致问题</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106110701.jpg" alt="image-20200128193625783"></p><h3 id="2-3-1-工作原理"><a href="#2-3-1-工作原理" class="headerlink" title="2.3.1. 工作原理"></a>2.3.1. 工作原理</h3><h4 id="2-3-1-1-总线加锁"><a href="#2-3-1-1-总线加锁" class="headerlink" title="2.3.1.1. 总线加锁"></a>2.3.1.1. 总线加锁</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230106110712.jpg" alt="image-20200321183046051"></p><ol><li>先到先得，一个线程 read 主内存数据时，用 lock 命令给总线加锁，此时 CPU 跟主内存的通信被锁定，其他 CPU 的线程都无法操作主内存，直到将运算结果 write 回主内存后再用 unlock 命令将锁释放，之后其他线程才能操作主内存中的这个资源。</li><li>从主内存中，读取锁的最小单位是缓存行，加入一个数据的长度大于一个缓存行，这时候就会出现缓存行失效问题，当缓存行失效，就会走总线加锁。</li></ol><h4 id="2-3-1-2-MESI"><a href="#2-3-1-2-MESI" class="headerlink" title="2.3.1.2. MESI"></a>2.3.1.2. MESI</h4><h1 id="3-MESI"><a href="#3-MESI" class="headerlink" title="3. MESI"></a>3. MESI</h1><p><a href="https://www.cnblogs.com/jackeason/p/11336317.html">https://www.cnblogs.com/jackeason/p/11336317.html</a></p><p>MESI 的状态机包含了 4 个状态，也是名字的由来：</p><ul><li>(M)odified: 单副本 + 脏数据（即缓存改变，未写回内存）</li><li>(E)xclusive: 单副本 + 干净数据</li><li>(S)hared: 多副本 + 干净数据</li><li>(I)nvalid: 数据未加载或缓存已失效</li></ul><h2 id="3-1-工作原理"><a href="#3-1-工作原理" class="headerlink" title="3.1. 工作原理"></a>3.1. 工作原理</h2><blockquote><p>具体 CPU 如何从主内存中加载变量进行运行的过程，在 CPU 缓存架构中有详解，这里不做过多的解释</p><p>1.假设 CPU1 率先抢到时间片，当变量 count 加载至 CPU 缓存中时，会将 count 的使用标志为（E 独占：首次加载会将变量置为独占，也就说明没有其他 CPU 进行加载）</p><p>2.CPU2 也获得时间片，把变量 count 加载缓存中，此时 count 的使用标志为（E 独占），并发送消息至总线，告知其他 CPU 读取了变量的值，各 CPU 通过时刻监听（总线嗅探机制）获得到此变量已被多个 CPU 所加载，那么此时 CPU2 就会将自身 count 的使用标志置为（S 共享），CPU1 也会将变量的使用标志也会置为（S）</p><p>3.CPU1 从缓存中加载 count 至寄存器中进行自增操作，执行完毕之后，count &#x3D; 0 -&gt; 1，此时由于 count 的值发生了变化，因此 CPU1 中变量 count 使用标志应为（M 修改），此时 CPU1 会发送消息至总线，告知其他线程已经修改了变量 count 的值，其他 CPU 嗅探到值的修改，就会将自身变量 count 的使用标志置为（I 无效）</p><p>4.CPU1 会将 M 状态的变量立刻写回至主内存中，写回完毕之后，CPU1 会将使用状态置为（E 独享），发送消息至总线，告知其他 CPU 已经写回完毕，其他 CPU 会再此从主内存中读取变量 count 的值，读取完毕之后，也会发送消息至总线，其他 CPU 嗅探到之后将自身变量 count 置为（S 共享），自身变量的使用状态也会置为（S 共享）</p></blockquote><p>MESI 失效时，缓存锁会退化到总线锁，失效的情况：</p><ol><li>当缓存行存储的数据超过最小存储单元大小时（数据长度存储跨越多个缓存行的情况），就会导致 MESI 操作缓存行无效，导致 MESI 缓存一致性协议失效；</li><li>系统不支持缓存一致性协议。</li></ol><p>缓存行大小：32B、64B、128B(因系统而定)</p><h2 id="3-2-状态变换"><a href="#3-2-状态变换" class="headerlink" title="3.2. 状态变换"></a>3.2. 状态变换</h2><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;并发研究之CPU缓存一致性协议(MESI) - 枫飘雪落 - 博客园]]</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221115172751.png"></p><table><thead><tr><th>状态</th><th>描述</th><th>监听任务</th></tr></thead><tbody><tr><td>M 修改 (Modified)</td><td>该 Cache line 有效，数据被修改了，和内存中的数据不一致，数据只存在于本 Cache 中。</td><td>缓存行必须时刻监听所有试图读该缓存行相对就主存的操作，这种操作必须在缓存将该缓存行写回主存并将状态变成 S（共享）状态之前被延迟执行。</td></tr><tr><td>E 独享、互斥 (Exclusive)</td><td>该 Cache line 有效，数据和内存中的数据一致，数据只存在于本 Cache 中。</td><td>缓存行也必须监听其它缓存读主存中该缓存行的操作，一旦有这种操作，该缓存行需要变成 S（共享）状态。</td></tr><tr><td>S 共享 (Shared)</td><td>该 Cache line 有效，数据和内存中的数据一致，数据存在于很多 Cache 中。</td><td>缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求，并将该缓存行变成无效（Invalid）。</td></tr><tr><td>I 无效 (Invalid)</td><td>该 Cache line 无效。</td><td>无</td></tr></tbody></table><p>在 MESI 协议中，<strong>每个 Cache 的 Cache 控制器不仅知道自己的读写操作，而且也监听 (snoop) 其它 Cache 的读写操作</strong>。每个 Cache line 所处的状态根据本核和其它核的读写操作在 4 个状态间进行迁移</p><p>Local Read 表示本内核读本 Cache 中的值，Local Write 表示本内核写本 Cache 中的值，Remote Read 表示其它内核读其它 Cache 中的值，Remote Write 表示其它内核写其它 Cache 中的值，箭头表示本 Cache line 状态的迁移，环形箭头表示状态不变。 当内核需要访问的数据不在本 Cache 中，而其它 Cache 有这份数据的备份时，本 Cache 既可以从内存中导入数据，也可以从其它 Cache 中导入数据，不同的处理器会有不同的选择。MESI 协议为了使自己更加通用，没有定义这些细节，只定义了状态之间的迁移，下面的描述假设本 Cache 从内存中导入数据.</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;并发原理系列一：MESI与内存屏障-今日头条]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;CPU高速缓存行与内存关系 及并发MESI 协议 - JokerJason - 博客园]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;聊聊缓存一致性协议 - Yungyu - 博客园]]</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;CPU缓存一致性协议—MESI详解 - 提拉没有米苏 - 博客园]]</p><h2 id="3-3-5-种协议消息"><a href="#3-3-5-种协议消息" class="headerlink" title="3.3. 5 种协议消息"></a>3.3. 5 种协议消息</h2><p>Mesi 协议消息<br><strong>Read</strong>：”read” 消息用来获取指定物理地址上的 cache line 数据。<br><strong>Read Response</strong>：该消息携带了 “read” 消息所请求的数据。read response 可能来自于 memory 或者是其他 CPU cache。<br><strong>Invalidate</strong>：该消息将其他 CPU cache 中指定的数据设置为失效。该消息携带物理地址，其他 CPU cache 在收到该消息后，必须进行匹配，发现在自己的 cache line 中有该地址的数据，那么就将其从 cahe line 中移除，<span style="background-color:#ff0000">并响应 Invalidate Acknowledge 回应</span>。Invalidate Acknowledge 消息用做回应 Invalidate 消息。<br><strong>Read Invalidate</strong>：该消息中带有物理地址，用来说明想要读取哪一个 cache line 中的数据。这个消息还有 Invalidate 消息的效果。其实该消息是 read + Invalidate 消息的组合，发送该消息后 cache 期望收到一个 read response 消息。<br><strong>Writeback</strong>： 该消息带有地址和数据，该消息用在 modified 状态的 cache line 被置换时发出，用来将最新的数据写回 memory 或其他下一级 cache 中。</p><h2 id="3-4-Store-Buffer⭐️🔴"><a href="#3-4-Store-Buffer⭐️🔴" class="headerlink" title="3.4. Store Buffer⭐️🔴"></a>3.4. Store Buffer⭐️🔴</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230528-1555%%</span>❕ ^pyabmn</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116152644.png"></p><p>当 cpu0 要<span style="background-color:#00ff00">写数据到本地 cache</span>的时候，<span style="background-color:#ffff00">如果不是 M 或者 E 状态，需要发送一个 invalidate 消息</span>给 cpu1，<span style="background-color:#ff0000">只有收到 cpu1 的 acknowledgement 才能写数据到 cache 中，在这个过程中 cpu0 需要等待，这大大影响了性能</span>。一种解决办法是在 cpu 和 cache 之间引入 store buffer，当发出 invalidate 之后直接把数据写入 store buffer。当收到 acknowledgement 之后可以把 store buffer 中的数据写入 cache。<br>现在的架构图是这样的： ^47f91q</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116152811.png"></p><p>Store Buffer 的确提高了 CPU 的资源利用率，不过优化了带来了新的问题。在新数据存储在 Store Buffer 里时，如果此时有一条 read 指令，若仍旧从 cache 中读取数据时，读到的是旧的数据。要解决这个问题就必须要求 CPU 读取数据时得先看 Store Buferes 里面有没有，如果有则直接读取 Store Buferes 里的值，如果没有才能读取自己缓存里面的数据，这也就是所谓的“Store Forward”。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221116152942.png"></p><h2 id="3-5-Invalidate-Queue⭐️🔴"><a href="#3-5-Invalidate-Queue⭐️🔴" class="headerlink" title="3.5. Invalidate Queue⭐️🔴"></a>3.5. Invalidate Queue⭐️🔴</h2><p>但是内存屏障的处理方法有个问题，那就是 store buffer 空间是有限的，如果 store buffer 中的空间被 smp_mb 之后的存储塞满，cpu 还是得等待 invalidate 消息返回才能继续处理。解决这种问题的思路是让 invalidate ack 能更早得返回，一种办法是提供一种放置 invalidate message 的队列，称为 invalidate queue. cpu 可以在收到 invalidate 之后马上返回 invalidate ack，而不是在把本地 cache invalidate 之后，并把 invalidate message 放置到 invalide queue，以待之后处理。</p><p>CPU 其实不需要完成 invalidate 操作就可以回送 acknowledge 消息，这样，就不会阻止发生 invalidate 请求的那个 CPU 进入无聊的等待状态。CPU 可以 buffer 这些 invalidate message（放入 Invalidate Queues），然后直接回应 acknowledge，表示自己已经收到请求，随后会慢慢处理。</p><h2 id="3-6-读写屏障⭐️🔴"><a href="#3-6-读写屏障⭐️🔴" class="headerlink" title="3.6. 读写屏障⭐️🔴"></a>3.6. 读写屏障⭐️🔴</h2><p>store buffer 和 invalidate queue 的引入导致不满足全局有序，所以需要有写屏障和读屏障。<br>读屏障用于处理 invalidate queue，写屏障用于处理 store buffer。<br> X86 架构下的读屏障指令是 lfenc，写屏障指令是 sfence，读写屏障指令是 mfence。</p><h3 id="3-6-1-读屏障⭐️🔴"><a href="#3-6-1-读屏障⭐️🔴" class="headerlink" title="3.6.1. 读屏障⭐️🔴"></a>3.6.1. 读屏障⭐️🔴</h3><p><strong>作用</strong>：所有读屏障之前发生的内存更新，对读屏障之后的 load 操作都是可见的<br><strong>cpu 实际操作：</strong> 把 <strong>失效队列</strong>（invalidate queue）里的实效指令（I）全部执行</p><h3 id="3-6-2-写屏障⭐️🔴"><a href="#3-6-2-写屏障⭐️🔴" class="headerlink" title="3.6.2. 写屏障⭐️🔴"></a>3.6.2. 写屏障⭐️🔴</h3><p><strong>作用</strong>：所有写屏障之前发生的内存更新（M）对之后的命令都是可见的<br><strong>cpu 实际操作：等到 存储缓存</strong>（store buffer）为空（所有更新已刷出），cpu 才能执行写屏障之后指令</p><h3 id="3-6-3-Full-屏障"><a href="#3-6-3-Full-屏障" class="headerlink" title="3.6.3. Full 屏障"></a>3.6.3. Full 屏障</h3><p><strong>作用</strong>：上述二者之和<br><strong>cpu 实际操作</strong>：上述二者之后</p><h2 id="3-7-cache-写策略"><a href="#3-7-cache-写策略" class="headerlink" title="3.7. cache 写策略"></a>3.7. cache 写策略</h2><p>Write-through（直写模式）在数据更新时，同时写入缓存 Cache 和后端存储。此模式的优点是操作简单；缺点是因为数据修改需要同时写入存储，数据写入速度较慢。</p><p><span style="background-color:#00ff00">Write-back（回写模式）</span>在数据更新时只写入缓存 Cache。只在数据被替换出缓存时，被修改的缓存数据才会被写到后端存储。此模式的优点是数据写入速度快，因为不需要写存储；缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况，数据将无法找回。</p><h2 id="3-8-总线嗅探"><a href="#3-8-总线嗅探" class="headerlink" title="3.8. 总线嗅探"></a>3.8. 总线嗅探</h2><p>高速缓存有缓存控制器，然后用来监视总线，如果某一个 CPU 想要修改共享缓存的数据，它会先进行广播，其他的 CPU 就会嗅探到这个事务或者广播。然后他们会判断自己本地高速缓存是否也有这样的数据副本，如果没有则不用理会。<br>总线嗅探机制一般分为两种协议，一种是<span style="background-color:#00ff00">写失效协议 (write-invalidate)</span>，一个核心写入缓存后，会广播一个失效请求，其他核心嗅探到则将自己缓存中对应的缓存行标记为失效; 另一种是写更新协议 (write-update)，写入缓存的核心除了要广播一个失效请求外，还要广播数据内容，把对应数据传输给其他 CPU。如果有则通常让他们无效 (write-invalidate) 或者更新 (write-update)。但是对于总线嗅探来说，一般都是使用 write-invalidate，让这些数据无效，因为 write-update 会产生数据拷贝，产生总线流量。</p><p>原文链接： <a href="https://blog.csdn.net/zhanglh046/article/details/115307993">https://blog.csdn.net/zhanglh046/article/details/115307993</a></p><h2 id="3-9-状态流转案例"><a href="#3-9-状态流转案例" class="headerlink" title="3.9. 状态流转案例"></a>3.9. 状态流转案例</h2><p>MESI<br>MESI 通过给每一个 Cache Line 设置一个大小为 2bit （四个状态）的状态位来保证一致性</p><p>状态描述<br>M（Modified）被修改的，该 Cache Line 被当前 CPU 核心修改了，和主存不一致，但是是最新的数据，可以直接使用<br>E（Exclusive）独占的，该 Cache Line 只有当前 CPU 核心有，而且数据和主存一样，可以直接使用<br>S（Shared）共享的，有多个 CPU 核心里有该 Cache Line，而且数据和主存一样，可以直接使用<br>I（Invalid）失效的，表示该 Cache Line 无效，需要读取数据就直接取主存中读取即可<br>例子<br>情景：假设现在存在一个 CPU 存在两个核心 core a， core b；</p><h3 id="3-9-1-读取数据"><a href="#3-9-1-读取数据" class="headerlink" title="3.9.1. 读取数据"></a>3.9.1. 读取数据</h3><p>core a 需要读取数据 x<br>core a 会先查看自己的缓存中是否有该数据：<br>如果存在，就查看 Cache Line 的状态，如果是 M，E，S 的话直接使用即可<br>如果不存在，或者 状态是 I ，就会向总线发起读请求 (read)，其他 CPU 核心就会监听该消息，并检查自己有没有该数据:</p><ul><li>如果 core b 缓存中没有该数据，core a 就直接到主存中读取数据即可</li><li>如果 core b 缓存中有该数据， 状态是 E ，说明 core b 的缓存和主存一致，那么 core b 就会把该 数据发送到总线，让 core a 去获取数据，还需要把状态改为 S</li><li>如果 core b 缓存中有该数据，状态是 S ，说明 core b 的缓存和主存一致，那么 core b 就会把该 数据发送到总线，让 core a 去获取数据</li><li>如果 core b 缓存中有该数据，状态是 M，说明 core b 的缓存和主存不一致，那么 core b 需要先把数据写到主存中，把状态改为 S，分享给 core a</li><li>如果 core b 缓存中有该数据，状态是 I，core a 就直接到主存中读取数据即可</li></ul><h3 id="3-9-2-写入数据"><a href="#3-9-2-写入数据" class="headerlink" title="3.9.2. 写入数据"></a>3.9.2. 写入数据</h3><p>core a 需要写入数据 x</p><p>core a 会先查看自己的缓存中是否有该数据：<br>如果存在，并且 Cache Line 的状态为 M ，说明该数据只有在 core a 中是最新的，那么直接修改数据即可，状态不变<br>如果存在，并且 Cache Line 的状态为 E ，说明该数据只有在 core a 中是最新的，那么直接修改数据，然后修改状态为 M<br>如果存在，并且 Cache Line 的状态为 S ，说明该数据在其他核心中存在备份，那么需要发出一个 RFO (Request For Owner) 请求，它需要拥有这行数据的权限，通知到其他的 CPU 核心， 自己对该数据进行了修改， 其他核心需要把自己对应的 Cache Line 的状态改为 I ，即无效，core a 需要修改状态为 M<br>如果不存在或者为 Cache Line 状态为 I ，就需要先读取数据到缓存中（回到上面的读取数据流程），然后修改状态为 M ，同时发消息到总线通知其他核心将该数据状态改为 I</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;看懂这篇，才能说了解并发底层技术 - 腾讯云开发者社区-腾讯云]]</p><p><strong>初始 I 状态</strong>：一开始时，缓存行没有加载任何数据，所以它处于 I 状态。<br><strong>本地写（Local Write）</strong>：如果本地处理器写数据至处于 I 状态的缓存行，则缓存行的状态变成 M。<br><strong>本地读（Local Read）</strong>：如果本地处理器读取处于 I 状态的缓存行，很明显此缓存没有数据给它。此时分两种情况：</p><ul><li>(1) 其它处理器的缓存里也没有此行数据，则从内存加载数据到此缓存行后，再将它设成 E 状态，表示只有我一家有这条数据，其它处理器都没有；</li><li>(2) 其它处理器的缓存有此行数据，则将此缓存行的状态设为 S 状态。（备注：如果处于 M 状态的缓存行，再由本地处理器写入&#x2F;读出，状态是不会改变的）<br><strong>远程读（Remote Read）</strong>：假设我们有两个处理器 c1 和 c2，如果 c2 需要读另外一个处理器 c1 的缓存行内容 (由 c1 应答 c2 的 read 请求)，c1 需要把它缓存行的内容通过内存控制器 (Memory Controller) 发送给 c2，c2 接到后将相应的缓存行状态设为 S。在设置之前，内存也得从总线上得到这份数据并保存。<br><strong>远程写（Remote Write）</strong>：其实确切地说不是远程写，而是 c2 得到 c1 的数据后，不是为了读，而是为了写。也算是本地写，只是 c1 也拥有这份数据的拷贝，这该怎么办呢？c2 将发出一个 RFO (Request For Owner) 请求，它需要拥有这行数据的权限，其它处理器的相应缓存行设为 I，除了它自已，谁不能动这行数据。这保证了数据的安全，同时处理 RFO 请求以及设置 I 的过程将给写操作带来很大的性能消耗。</li></ul><h2 id="3-10-落地实现"><a href="#3-10-落地实现" class="headerlink" title="3.10. 落地实现"></a>3.10. 落地实现</h2><p>AMD 的 Opteron 处理器使用从 MESI 中演化出的<span style="background-color:#00ff00">MOESI 协议</span>，O(Owned) 是 MESI 中 S 和 M 的一个合体，表示本 Cache line 被修改，和内存中的数据不一致，不过其它的核可以有这份数据的拷贝，状态为 S。</p><p>Intel 的 core i7 处理器使用从 MESI 中演化出的<span style="background-color:#00ff00">MESIF 协议</span>，F(Forward) 从 Share 中演化而来，一个 Cache line 如果是 Forward 状态，它可以把数据直接传给其它内核的 Cache，而 Share 则不能。</p><p><a href="https://www.cnblogs.com/cherish010/p/8602635.html">https://www.cnblogs.com/cherish010/p/8602635.html</a></p><h1 id="4-屏障和-Lock"><a href="#4-屏障和-Lock" class="headerlink" title="4. 屏障和 Lock"></a>4. 屏障和 Lock</h1><a href="/2022/11/24/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98-%E5%9F%BA%E7%A1%80-10%E3%80%81%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/" title="并发编程专题-基础-10、内存屏障">并发编程专题-基础-10、内存屏障</a><h1 id="5-总结-来龙去脉⭐️🔴⭐️🔴"><a href="#5-总结-来龙去脉⭐️🔴⭐️🔴" class="headerlink" title="5. 总结 - 来龙去脉⭐️🔴⭐️🔴"></a>5. 总结 - 来龙去脉⭐️🔴⭐️🔴</h1><ol><li><span style="background-color:#ff00ff">因为内存的速度和 CPU 匹配不上，所以在内存和 CPU 之间加了多级缓存。</span></li><li>单核 CPU 独享不会出现数据不一致的问题，但是多核情况下会有缓存一致性问题。</li><li><span style="background-color:#ff00ff">缓存一致性协议就是为了解决多组缓存导致的缓存一致性问题。</span></li><li>缓存一致性协议有两种实现方式，一个是基于目录的，一个是基于总线嗅探的。</li><li>基于目录的方式延迟高，但是占用总线流量小，适合 CPU 核数多的系统。</li><li>基于总线嗅探的方式延迟低，但是占用总线流量大，适合 CPU 核数小的系统。</li><li><span style="background-color:#ff00ff">常见的 MESI 协议就是基于总线嗅探实现的。</span></li><li><span style="background-color:#ff00ff">MESI 解决了缓存一致性问题，但是还是不能将 CPU 性能压榨到极致。</span><br>   !<a href="/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-1%E3%80%81JMM%E4%B8%8EMESI/" title="并发基础-1、JMM与MESI">并发基础-1、JMM与MESI</a></li><li><span style="background-color:#ff00ff">为了进一步压榨 CPU，所以引入了 store buffer 和 invalidate queue。</span></li><li><span style="background-color:#ff00ff">store buffer 和 invalidate queue 的引入导致不满足全局有序，所以需要有写屏障和读屏障。</span></li><li>X86 架构下的读屏障指令是 lfenc，写屏障指令是 sfence，读写屏障指令是 mfence。</li><li>lock 前缀指令直接锁缓存行，也能达到内存屏障的效果。</li><li><span style="background-color:#ff00ff">x86 架构下，volatile 的底层实现就是 lock 前缀指令。</span></li><li>JMM 是一个模型，是一个便于 Java 开发人员开发的抽象模型。</li><li>缓存性一致性协议是为了解决 CPU 多核系统下的数据一致性问题，是一个客观存在的东西，不需要去触发。</li><li><strong>JMM 和缓存一致性协议没有一毛钱关系。</strong></li><li><strong>JMM 和 MESI 没有一毛钱关系。</strong></li></ol><h1 id="6-参考与感谢"><a href="#6-参考与感谢" class="headerlink" title="6. 参考与感谢"></a>6. 参考与感谢</h1><p>[[关于缓存一致性协议、MESI、StoreBuffer、InvalidateQueue、内存屏障、Lock指令和JMM的那点事  HeapDump性能社区]]</p><p>[[MESI 协议学习笔记  三点水]]</p><p>[[内存屏障及其在 JVM 下的应用 — Blogs by ylgrgyq]]</p><p>👍 [[cpu缓存和volatile - XuMinzhe - 博客园]]</p><p>JMM 完善于 <a href="https://link.segmentfault.com/?enc=PV6GfA24/DoFWatq/r8pPA==.+q6CgZcQq5DGewDfrvSMWEW6oCoKl+qIyjXyJDFcC8tkEmDyzGwlu3gJqYXsFNvb">JSR-133</a>，现在一般会把详细说明放在 Java Language 的 Spec 上，比如 Java11 的话在：<a href="https://link.segmentfault.com/?enc=o6ed6BfL7g86k67Y56td0g==.aQTiJ2Adn2nH2UrtXmPeSi65AUs2HZJhC0m1I+txwTM23m50K7CmAhI2ha8jKR9Rq9Ve6f+hSFE/tZ8tWdTf5R2RqGj5n2nJNe/LVYDdyZI=">Chapter 17. Threads and Locks</a>。在这些说明之外，还有个特别出名的 Cookbook，叫 <a href="https://link.segmentfault.com/?enc=Cc8voHRYDmHjNAmEOTW/0g==.tduSK/KKFZo4xGurj6YzNuGR4ZrmimxxMBi4VlikUXf10T5Z44AMgnbksklMOl94">The JSR-133 Cookbook for Compiler Writers</a></p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发编程专题-基础-2、Synchronized</title>
      <link href="/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-2%E3%80%81Synchronized/"/>
      <url>/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-2%E3%80%81Synchronized/</url>
      
        <content type="html"><![CDATA[<p><span      class='ob-timelines'      data-date="1125-1716"      data-title=' 并发编程专题 - 基础 -2、Synchronized'      data-class='orange'      data-type='range'      data-end='2022-11-25'></p></span><h1 id="1-特性"><a href="#1-特性" class="headerlink" title="1. 特性"></a>1. 特性</h1><p>synchronized 是用了 JMM8 大操作的 lock 和 unlock 操作，对主内存中的变量进行了独占操作。</p><p>并发编程 3 大特性是可见性，原子性，有序性。 而 synchronized 能同时保证。而 volatile 只能保证可见性，有序性，不能保证原子性</p><h2 id="1-1-原子性"><a href="#1-1-原子性" class="headerlink" title="1.1. 原子性"></a>1.1. 原子性</h2><p>原子性指的是在一次或多次操作中，要么所有的操作都执行并且不会受其他因素干扰而中断，要么所有的操作都不执行。</p><h2 id="1-2-可见性"><a href="#1-2-可见性" class="headerlink" title="1.2. 可见性"></a>1.2. 可见性</h2><p>可见性是指一个线程对共享变量进行了修改，另一个线程可以立即读取得到修改后的最新值。</p><h2 id="1-3-有序性"><a href="#1-3-有序性" class="headerlink" title="1.3. 有序性"></a>1.3. 有序性</h2><p>有序性是指程序中代码的执行顺序，Java 在编译时和运行时会对代码进行优化，会导致程序最终的执行顺序不一定就是我们编写代码时的顺序。</p><p>例如，instance &#x3D; new Singleton() 实例化对象的语句分为三步：</p><ul><li>1、分配对象的内存空间；</li><li>2、初始化对象；</li><li>3、设置实例对象指向刚分配的内存地址；</li></ul><p>上述第二步操作需要依赖第一步，但是第三步操作不需要依赖第二步，所以执行顺序可能为：1-&gt;2-&gt;3、</p><p>1-&gt;3-&gt;2，当执行顺序为 1-&gt;3-&gt;2 时，可能实例对象还没正确初始化，我们直接拿到使用的时候可能会报错。</p><h2 id="1-4-可重入特性"><a href="#1-4-可重入特性" class="headerlink" title="1.4. 可重入特性"></a>1.4. 可重入特性</h2><p>synchronized 和 ReentrantLock 都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时，将会处于阻塞状态，但当一个线程再次请求自己持有对象锁的临界资源时，这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。</p><h2 id="1-5-原子性比较"><a href="#1-5-原子性比较" class="headerlink" title="1.5. 原子性比较"></a>1.5. 原子性比较</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230512160426.png" alt="image.png"></p><h1 id="2-深入剖析"><a href="#2-深入剖析" class="headerlink" title="2. 深入剖析"></a>2. 深入剖析</h1><h2 id="2-1-应用层面"><a href="#2-1-应用层面" class="headerlink" title="2.1. 应用层面"></a>2.1. 应用层面</h2><p>8 锁问题：<a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-9%E3%80%81Java%E5%90%84%E7%A7%8D%E9%94%81/" title="并发基础-9、Java各种锁">并发基础-9、Java各种锁</a></p><p><strong>竞态条件</strong><br>多个线程在临界区内执行，由于代码的执行序列不同而导致结果无法预测，称之为发生了竞态条件。为了避免临界区的竞态条件发生，有多种手段可以达到目的：</p><ul><li>阻塞式的解决方案：synchronized，Lock</li><li>非阻塞式的解决方案：CAS</li></ul><p>synchronized 同步块是 Java 提供的一种原子性 <code>内置锁</code>，Java 中的每个对象都可以把它当作一个同步锁来使用，这些 Java 内置的使用者看不到的锁被称为内置锁，也叫作监视器锁，目的就是保证多个线程在进入 synchronized 代码段或者方法时，将并行变串行。</p><ol><li>synchronized 修饰的实例方法，多线程并发访问时，只能有一个线程进入，获得对象内置锁，其他线程阻塞等待，但在此期间线程仍然可以访问其他方法。</li><li>synchronized 修饰的静态方法，多线程并发访问时，只能有一个线程进入，获得类锁，其他线程阻塞等待，但在此期间线程仍然可以访问其他方法。</li><li>synchronized 修饰的代码块，多线程并发访问时，只能有一个线程进入，根据括号中的对象或者是类，获得相应的对象内置锁或者是类锁</li><li>每个类都有一个类锁，类的每个对象也有一个内置锁，它们是互不干扰的，也就是说一个线程可以同时获得类锁和该类实例化对象的内置锁，当线程访问非 synchronzied 修饰的方法时，并不需要获得锁，因此不会产生阻塞。</li></ol><h3 id="2-1-1-三种锁类型"><a href="#2-1-1-三种锁类型" class="headerlink" title="2.1.1. 三种锁类型"></a>2.1.1. 三种锁类型</h3><p><span style="background-color:#00ff00">本质上都是依赖对象来锁</span></p><ul><li><strong>this 锁：当前实例锁</strong></li><li><strong>Class 锁：类对象锁</strong></li><li><strong>Object 锁：对象实例锁</strong></li></ul><h3 id="2-1-2-三种应用方式"><a href="#2-1-2-三种应用方式" class="headerlink" title="2.1.2. 三种应用方式"></a>2.1.2. 三种应用方式</h3><ul><li>修饰实例成员方法：使用 this 锁，线程想要执行被 Synchronized 关键字修饰的成员实例方法必须先获取当前实例对象的锁资源；</li><li>修饰静态成员方法：使用 class 锁，线程想要执行被 Synchronized 关键字修饰的静态方法必须先获取当前类对象的锁资源；</li><li>修饰代码块：使用 Object 锁，使用给定的对象实现锁功能，线程想要执行被 Synchronized 关键字修饰的代码块必须先获取当前给定对象的锁资源；</li></ul><p>链接：<a href="https://www.jianshu.com/p/884eb51266e4">https://www.jianshu.com/p/884eb51266e4</a></p><h2 id="2-2-字节码层面"><a href="#2-2-字节码层面" class="headerlink" title="2.2. 字节码层面"></a>2.2. 字节码层面</h2><h3 id="2-2-1-monitorenter、monitorexit"><a href="#2-2-1-monitorenter、monitorexit" class="headerlink" title="2.2.1. monitorenter、monitorexit"></a>2.2.1. monitorenter、monitorexit</h3><h4 id="2-2-1-1-修饰代码块"><a href="#2-2-1-1-修饰代码块" class="headerlink" title="2.2.1.1. 修饰代码块"></a>2.2.1.1. 修饰代码块</h4><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">class</span> <span class="hljs-title class_">SynchronizedTest</span> &#123;  <br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doSth</span><span class="hljs-params">()</span>&#123;  <br>        <span class="hljs-keyword">synchronized</span> (SynchronizedTest.class)&#123;  <br>            System.out.println(<span class="hljs-string">&quot;test Synchronized&quot;</span> );  <br>        &#125;  <br>    &#125;  <br>  <br><span class="hljs-comment">//    public synchronized void doSth()&#123;  </span><br><span class="hljs-comment">//        System.out.println(&quot;test Synchronized method&quot; );  </span><br><span class="hljs-comment">//    &#125;  </span><br>&#125;<br></code></pre></td></tr></table></figure><p><code>javap -c SynchronizedTest.class</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110075743.png"></p><p>每个对象有一个监视器锁 monitor，当 monitor 被占用时就会处于锁定状态，线程执行 monitorenter 指令时尝试获取 monitor 的所有权，过程如下：</p><p>1.如果 monitor 的进入数为 0，则该线程进入 monitor，然后将进入数设置为 1，该线程即为 monitor 的所有者</p><p>2.如果线程已经占有该 monitor，只是重新进入，则进入 monitor 的进入数加 1</p><p>3.如果其他线程已经占有该 monitor，则该线程进入阻塞状态，直到 monitor 的进入数为 0，再重新尝试获取 monitor 的所有权</p><p>执行 monitorexit 的线程必须是 objectref 所对应的 monitor 的所有者。</p><p>指令执行时，monitor 的进入数减 1，如果减 1 后进入数为 0，那线程退出 monitor，不再是这个 monitor 的所有者。其他被这个 monitor 阻塞的线程可以尝试去获取这个 monitor 的所有权。</p><blockquote><p>通过这两段描述，我们应该能很清楚的看出 Synchronized 的实现原理，Synchronized 的语义底层是通过一个 monitor 的对象来完成，其实 wait&#x2F;notify 等方法也依赖于 monitor 对象，这就是为什么只有在同步的块或者方法中才能调用 wait&#x2F;notify 等方法，否则会抛出 <code>java.lang.IllegalMonitorStateException</code> 的异常的原因</p></blockquote><h4 id="2-2-1-2-2-个-monitorexit"><a href="#2-2-1-2-2-个-monitorexit" class="headerlink" title="2.2.1.2. 2 个 monitorexit"></a>2.2.1.2. 2 个 monitorexit</h4><p>从上面的中文注释处可以看到，对于 <code>synchronized</code> 关键字而言，<code>javac</code> 在编译时，会生成对应的 <code>monitorenter</code> 和 <code>monitorexit</code> 指令分别对应 <code>synchronized</code> 同步块的进入和退出，有两个 <code>monitorexit</code> 指令的原因是：为了保证抛异常的情况下也能释放锁，所以 <code>javac</code> 为同步代码块添加了一个隐式的 try-finally，在 finally 中会调用 <code>monitorexit</code> 命令释放锁。</p><h3 id="2-2-2-ACC-SYNCHRONIZED"><a href="#2-2-2-ACC-SYNCHRONIZED" class="headerlink" title="2.2.2. ACC_SYNCHRONIZED"></a>2.2.2. ACC_SYNCHRONIZED</h3><h4 id="2-2-2-1-修饰方法"><a href="#2-2-2-1-修饰方法" class="headerlink" title="2.2.2.1. 修饰方法"></a>2.2.2.1. 修饰方法</h4><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">class</span> <span class="hljs-title class_">SynchronizedTest</span> &#123;  <br>  <br><span class="hljs-comment">//    public void doSth()&#123;  </span><br><span class="hljs-comment">//        synchronized (SynchronizedTest.class)&#123;  </span><br><span class="hljs-comment">//            System.out.println(&quot;test Synchronized&quot; );  </span><br><span class="hljs-comment">//        &#125;  </span><br><span class="hljs-comment">//    &#125;  </span><br>  <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doSth</span><span class="hljs-params">()</span>&#123;  <br>        System.out.println(<span class="hljs-string">&quot;test Synchronized method&quot;</span> );  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><p><code>javap -v SynchronizedTest.class </code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110080551.png"></p><blockquote><p>方法级别的同步是隐式的，作为方法调用的一部分。同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。</p><p>当调用一个设置了 ACC_SYNCHRONIZED 标志的方法，执行线程需要先获得 monitor 锁，然后开始执行方法，方法执行之后再释放 monitor 锁，当方法不管是正常 return 还是抛出异常都会释放对应的 monitor 锁。</p><p>在这期间，如果其他线程来请求执行方法，会因为无法获得监视器锁而被阻断住。</p><p>如果在方法执行过程中，发生了异常，并且方法内部并没有处理该异常，那么在异常被抛到方法外面之前监视器锁会被自动释放。</p></blockquote><h2 id="2-3-JDK-源码层面"><a href="#2-3-JDK-源码层面" class="headerlink" title="2.3. JDK 源码层面"></a>2.3. JDK 源码层面</h2><h3 id="2-3-1-ObjectMonitor"><a href="#2-3-1-ObjectMonitor" class="headerlink" title="2.3.1. ObjectMonitor"></a>2.3.1. ObjectMonitor</h3><p>JVM 的 monitorenter、monitorexit 这种机制底层是什么？<br><strong>ObjectMonitor</strong> 是 JVM 中的对于操作系统 <code>管程</code> 的实现。</p><blockquote><p>管程 (英语：Monitors，也称为监视器) 是一种程序结构，结构内的多个子程序（对象或模块）形成的多个工作线程互斥访问共享资源。</p></blockquote><blockquote><p>引入管程的原因<br>信号量机制的缺点：进程自备同步操作，P(S) 和 V(S) 操作大量分散在各个进程中，不易管理，易发生死锁。<br>管程特点：管程封装了同步操作，对进程隐蔽了同步细节，简化了同步功能的调用界面。</p></blockquote><p>简单地说管程就是一个概念，任何语言都可以实现。目的就是为了简化同步调用的过程。</p><h4 id="2-3-1-1-ObjectMonitor-对象"><a href="#2-3-1-1-ObjectMonitor-对象" class="headerlink" title="2.3.1.1. ObjectMonitor 对象"></a>2.3.1.1. ObjectMonitor 对象</h4><p>使用 monitor 机制的目的主要是为了互斥进入临界区，为了做到能够阻塞无法进入临界区的进程&#x2F;线程，还需要一个 monitor object 来协助，这个 monitor object 内部会有相应的数据结构，例如<span style="background-color:#00ff00">列表，来保存被阻塞的线程</span>；同时由于 monitor 机制本质上是基于 mutex 这种基本原语的，所以 monitor object 还必须维护<span style="background-color:#00ff00">一个基于 mutex 的锁</span>。 此外，为了在适当的时候能够阻塞和唤醒 进程&#x2F;线程，还需要<span style="background-color:#00ff00">引入一个条件变量</span>，这个条件变量用来决定什么时候是“适当的时候”，这个条件可以来自程序代码的逻辑，也可以是在 monitor object 的内部，总而言之，程序员对条件变量的定义有很大的自主性。不过，由于 monitor object 内部采用了数据结构来保存被阻塞的队列，因此<span style="background-color:#00ff00">它也必须对外提供两个 API 来让线程进入阻塞状态以及之后被唤醒，分别是 wait 和 notify</span>。管程机制中，monitor object 充当着维护 mutex 以及定义 wait&#x2F;signal API 来管理线程的阻塞和唤醒的角色。</p><p>JVM 中的同步是基于进入和退出 monitor object 实现的，<code>每个实例都会有个monitor object ，可以和对象一起创建，销毁。</code></p><p>通常所说的对象的内置锁，是对象头 Mark Word 中的重量级锁指针指向的 monitor 对象，该对象是在 HotSpot 底层 C++ 语言编写的 (openjdk 里面看)</p><h4 id="2-3-1-2-OM-创建时机"><a href="#2-3-1-2-OM-创建时机" class="headerlink" title="2.3.1.2. OM 创建时机"></a>2.3.1.2. OM 创建时机</h4><p>执行 monitorenter 指令时，线程会为锁对象关联一个 ObjectMonitor 对象</p><p>许多文章声称一个对象关联到一个 monitor，这个说法不够准确。如果对象已经是重量级锁了，对象头的确指向了一个 <code>monitor</code>。但对于正在膨胀的锁，会先从 <strong>线程私有</strong> 的 <code>monitor</code> 集合 <code>omFreeList</code> 中分配对象。如果 <code>omFreeList</code> 中已经没有 <code>monitor</code> 对象，再从 <strong>JVM 全局</strong> 的 <code>gFreeList</code> 中分配一批 <code>monitor</code> 到 <code>omFreeList</code> 中。</p><h4 id="2-3-1-3-数据结构"><a href="#2-3-1-3-数据结构" class="headerlink" title="2.3.1.3. 数据结构"></a>2.3.1.3. 数据结构</h4><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">ObjectMonitor() &#123;<br>   _header       = NULL;<br>   _count        = <span class="hljs-number">0</span>; <span class="hljs-comment">// 用来记录获取锁的线程数</span><br>   _waiters      = <span class="hljs-number">0</span>;<br>   _recursions   = <span class="hljs-number">0</span>; <span class="hljs-comment">// 线程的重入次数</span><br>   _object       = NULL;<br>   _owner        = NULL; <span class="hljs-comment">// 标识拥有该monitor的线程</span><br>   _WaitSet      = NULL; <span class="hljs-comment">// 处于wait状态的线程，会被加入到_WaitSet</span><br>                         <span class="hljs-comment">// 等待线程组成的双向循环链表，_WaitSet是第一个节点</span><br>   _WaitSetLock  = <span class="hljs-number">0</span> ;<br>   _Responsible  = NULL ;<br>   _succ         = NULL ;<br>   _cxq          = NULL ; <span class="hljs-comment">// 多线程竞争锁进入时的单向链表</span><br>                          <span class="hljs-comment">//（ContentionList）：当一个线程尝试获得锁时，如果该锁已经被占用，则会将该线程插入到cxq队列的队首</span><br>   FreeNext      = NULL ;<br>   _EntryList    = NULL ; <span class="hljs-comment">// 处于等待锁block状态的线程，会被加入到该列表</span><br>                          <span class="hljs-comment">// _owner从该双向循环链表中唤醒线程结点，_EntryList是第一个节点</span><br>   _SpinFreq     = <span class="hljs-number">0</span> ;<br>   _SpinClock    = <span class="hljs-number">0</span> ;<br>   OwnerIsThread = <span class="hljs-number">0</span> ;<br> &#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110140612.png"></p><h5 id="2-3-1-3-1-count"><a href="#2-3-1-3-1-count" class="headerlink" title="2.3.1.3.1. _count"></a>2.3.1.3.1. <code>_count</code></h5><h5 id="2-3-1-3-2-recursions"><a href="#2-3-1-3-2-recursions" class="headerlink" title="2.3.1.3.2. _recursions"></a>2.3.1.3.2. <code>_recursions</code></h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221111223242.png"></p><h5 id="2-3-1-3-3-3个队列"><a href="#2-3-1-3-3-3个队列" class="headerlink" title="2.3.1.3.3. 3个队列"></a>2.3.1.3.3. <code>3个队列</code></h5><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221111221930.png"></p><p><a href="https://www.cnblogs.com/kundeg/archive/2018/02/06/8422557.html">https://www.cnblogs.com/kundeg/archive/2018/02/06/8422557.html</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110141331.png"></p><p><strong>简要流程</strong></p><ul><li>想要获取 monitor 的线程,首先会进入 _EntryList 队列。</li><li>当某个线程获取到对象的 monitor 后，进入 _Owner 区域，设置为当前线程，同时计数器 _count 加 1。此时该线程就持有 mutex 锁</li><li>如果线程调用了 wait() 方法，则会进入 _WaitSet 队列。它就会释放持有的 mutex 锁，即将 _owner 赋值为 null，<code>_count自减1,进入_WaitSet队列阻塞等待</code>。</li><li>如果其他线程调用 notify() &#x2F; notifyAll() ，会唤醒 _WaitSet 中的某个线程，从 WaitSet 移动到 cxq 或 EntryList 中去 (根据配置策略决定放到哪一个中)，该线程再次尝试获取 monitor 锁，成功即进入 _Owner 区域。</li><li>同步方法执行完毕了，线程退出临界区，会将 monitor 的 owner 设为 null，并释放 mutex 锁。</li><li>新请求锁的线程将首先被加入到 ConetentionList 中，当某个拥有锁的线程（Owner 状态）调用 unlock 之后，如果发现 EntryList 为空则从 ContentionList 中移动线程到 EntryList，下面说明下 ContentionList 和 EntryList 的实现方式：</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110142733.png"></p><p><strong>ContentionList 虚拟队列</strong></p><blockquote><p>ContentionList 并不是一个真正的 Queue，而只是一个虚拟队列，原因在于 ContentionList 是由 Node 及其 next 指针逻辑构成，并不存在一个 Queue 的数据结构。ContentionList 是一个先进先出（FIFO）的队列，每次新加入 Node 时都会在队头进行，通过 CAS 改变第一个节点的的指针为新增节点，同时设置新增节点的 next 指向后续节点，而取得操作则发生在队尾。显然，该结构其实是个 Lock-Free 的队列。<br>因为只有 Owner 线程才能从队尾取元素，也即线程出列操作无争用，当然也就避免了 CAS 的 ABA 问题。</p></blockquote><p><strong>EntryList</strong></p><blockquote><p>EntryList 与 ContentionList 逻辑上同属等待队列，ContentionList 会被线程并发访问，为了降低对 ContentionList 队尾的争用，而建立 EntryList。<span style="background-color:#00ff00">Owner 线程在 unlock 时会从 ContentionList 中迁移线程到 EntryList，并会指定 EntryList 中的某个线程（一般为 Head）为 Ready（OnDeck）线程。Owner 线程并不是把锁传递给 OnDeck 线程，只是把竞争锁的权利交给 OnDeck，OnDeck 线程需要重新竞争锁。</span>这样做虽然牺牲了一定的公平性，但极大的提高了整体吞吐量，在 Hotspot 中把 OnDeck 的选择行为称之为“竞争切换”。<br>OnDeck 线程获得锁后即变为 owner 线程，无法获得锁则会依然留在 EntryList 中，考虑到公平性，在 EntryList 中的位置不发生变化（依然在队头）。如果 Owner 线程被 wait 方法阻塞，则转移到 WaitSet 队列；如果在某个时刻被 notify&#x2F;notifyAll 唤醒，则再次转移到 EntryList。</p></blockquote><p><strong>WaitSet</strong></p><blockquote><p>通过 object 获得内置锁 (objectMonitor)，通过内置锁将 Thread 封装成 OjectWaiter 对象，然后 addWaiter 将它插入以 _waitSet 为首结点的等待线程链表中去，最后释放锁。<br>通过 object 获得内置锁 (objectMonitor)，调用内置锁的 notify 方法，通过 _waitset 结点移出等待链表中的首结点，将它置于 _EntrySet 中去，等待获取锁。</p></blockquote><h4 id="2-3-1-4-工作机制"><a href="#2-3-1-4-工作机制" class="headerlink" title="2.3.1.4. 工作机制"></a>2.3.1.4. 工作机制</h4><p>见下方重量级锁章节</p><h4 id="2-3-1-5-重量级锁的重量"><a href="#2-3-1-5-重量级锁的重量" class="headerlink" title="2.3.1.5. 重量级锁的重量"></a>2.3.1.5. 重量级锁的重量</h4><ol><li>synchronzied 实现同步用到了对象的内置锁 (ObjectMonitor)，而在 ObjectMonitor 的函数调用中会涉及到 Mutex lock 等特权指令，那么这个时候就存在操作系统用户态和核心态的转换，这种切换会消耗大量的系统资源，因为用户态与内核态都有各自专用的内存空间，专用的寄存器等，用户态切换至内核态需要传递给许多变量、参数给内核，内核也需要保护好用户态在切换时的一些寄存器值、变量等，这也是为什么早期的 synchronized 效率低的原因。因此，这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 <code>重量级锁</code>。</li><li>处于 <code>ContentionList</code>、<code>EntryList</code>、<code>WaitSet</code> 中的线程都处于阻塞状态，线程的阻塞或者唤醒都需要操作系统来帮忙，Linux 内核下采用 <code>pthread_mutex_lock</code> 系统调用实现的，进程需要从用户态切换到内核态的 <code>pthread_mutex_lock</code> 系统调用，是内核态为用户态进程提供的 Linux 内核态下互斥锁 (Mutex) 的访问机制，所以使用 <code>pthread_mutex_lock</code> 系统调用时，进程需要从用户态切换到内核态，而这种切换是需要消耗很多时间的，有可能比用户执行代码的时间还要长</li></ol><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221111-httpsdocs.oracle.comcdE19253-01819-70516n919hpagindex.html]]</p><h3 id="2-3-2-对象与-monitor-关联"><a href="#2-3-2-对象与-monitor-关联" class="headerlink" title="2.3.2. 对象与 monitor 关联"></a>2.3.2. 对象与 monitor 关联</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221110142132.png"></p><h2 id="2-4-内存屏障层面"><a href="#2-4-内存屏障层面" class="headerlink" title="2.4. 内存屏障层面"></a>2.4. 内存屏障层面</h2><p>synchronized 可见性是通过内存屏障实现的，按可见性划分，内存屏障分为：</p><ul><li>Load 屏障：执行 refresh，从其他处理器的高速缓冲、主内存，加载数据到自己的高速缓存，保证数据是最新的；</li><li>Store 屏障：执行 flush 操作，自己处理器更新的变量的值，刷新到高速缓存、主内存去；</li></ul><p>获取锁时，会清空当前线程工作内存中共享变量的副本值，重新从主内存中获取变量最新的值；<br>释放锁时，会将工作内存的值重新刷新回主内存；</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-type">int</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>synchronize (<span class="hljs-built_in">this</span>)&#123;   <span class="hljs-comment">//monitorenter</span><br>    <span class="hljs-comment">// Load内存屏障</span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> a;  <span class="hljs-comment">// 读，通过load内存屏障，强制执行refresh，保证读到最新的</span><br>    a = <span class="hljs-number">10</span>; <span class="hljs-comment">// 写，释放锁时会通过Store，强制flush到高速缓存或主内存</span><br>&#125;    <span class="hljs-comment">//monitorexit</span><br><span class="hljs-comment">//Store内存屏障</span><br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221128091102.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221126161358.png"></p><p>synchronized 的有序性是依靠内存屏障实现的。</p><p>按照有序性，内存屏障可分为：</p><ul><li>Acquire 屏障：load 屏障之后，加 Acquire 屏障。它会禁止同步代码块内的读操作，和外面的读写操作发生指令重排；</li><li>Release 屏障：禁止写操作，和外面的读写操作发生指令重排；</li></ul><p>在 monitorenter 指令和 Load 屏障之后，会加一个 Acquire 屏障，这个屏障的作用是禁止同步代码块里面的读操作和外面的读写操作之间发生指令重排，在 monitorexit 指令前加一个 Release 屏障，也是禁止同步代码块里面的写操作和外面的读写操作之间发生重排序。如下：</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>synchronize (<span class="hljs-built_in">this</span>)&#123;  <span class="hljs-comment">//monitorenter</span><br>    <span class="hljs-comment">// Load内存屏障</span><br>    <span class="hljs-comment">// Acquire屏障，禁止代码块内部的读，和外面的读写发生指令重排</span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> a;<br>    a = <span class="hljs-number">10</span>;    <span class="hljs-comment">//注意：内部还是会发生指令重排</span><br>    <span class="hljs-comment">// Release屏障，禁止写，和外面的读写发生指令重排</span><br>&#125; <span class="hljs-comment">//monitorexit</span><br><span class="hljs-comment">//Store内存屏障</span><br></code></pre></td></tr></table></figure><h2 id="2-5-汇编实现层面"><a href="#2-5-汇编实现层面" class="headerlink" title="2.5. 汇编实现层面"></a>2.5. 汇编实现层面</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221127150423.png"></p><h1 id="3-重量级锁"><a href="#3-重量级锁" class="headerlink" title="3. 重量级锁"></a>3. 重量级锁</h1><h2 id="3-1-锁膨胀"><a href="#3-1-锁膨胀" class="headerlink" title="3.1. 锁膨胀"></a>3.1. 锁膨胀</h2><h3 id="3-1-1-触发条件"><a href="#3-1-1-触发条件" class="headerlink" title="3.1.1. 触发条件"></a>3.1.1. 触发条件</h3><p>轻量级锁的释放也比较简单，就是<span style="background-color:#ff00ff">将当前线程栈帧中锁记录空间中的 Mark Word 尝试 cas 替换到锁对象的对象头中</span>，如果成功表示锁释放成功。否则，锁膨胀成重量级锁，实现重量级锁的释放锁逻辑。</p><p>如果当前 mark 处于加锁状态，且 mark 中的 ptr 指针指向当前线程的栈帧，则执行同步代码，否则说明有多个线程竞争轻量级锁，轻量级锁需要膨胀升级为重量级锁</p><p>当调用一个锁对象的 <code>wait/notify/notifyall</code> 方法时，若当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。</p><h3 id="3-1-2-执行过程"><a href="#3-1-2-执行过程" class="headerlink" title="3.1.2. 执行过程"></a>3.1.2. 执行过程</h3><p>锁膨胀的过程实际上是<span style="background-color:#ffff00">获得一个 ObjectMonitor 对象监视器</span>，而真正抢占锁的逻辑，在 <code>ObjectMonitor::enter</code> 方法里面。</p><p><code>inflate</code> 其中是一个 for 循环，主要是为了处理多线程同时调用 inflate 的情况。然后会根据锁对象的状态进行不同的处理：</p><p>1.已经是重量级状态，说明膨胀已经完成，返回并继续执行 ObjectMonitor::enter 方法。<br>2.如果是轻量级锁则需要进行膨胀操作。<br>3.如果是膨胀中状态，则进行忙等待。<br>4.如果是无锁状态则需要进行膨胀操作</p><p><strong>轻量级锁锁膨胀的过程</strong><br><code>步骤 1</code>、调用 <code>omAlloc</code> 获取一个可用的 <code>ObjectMonitor</code> 对象。在 <code>omAlloc</code> 方法中会先从 <strong>线程私有</strong> 的 <code>monitor</code> 集合 <code>omFreeList</code> 中分配对象，如果 <code>omFreeList</code> 中已经没有 <code>monitor</code> 对象，则从 <strong>JVM 全局</strong> 的 <code>gFreeList</code> 中分配一批 <code>monitor</code> 到 <code>omFreeList</code> 中；</p><p><code>步骤 2</code>、通过 CAS 尝试将 Mark Word 设置为 markOopDesc:INFLATING，标识当前锁正在膨胀中。如果 CAS 失败，说明同一时刻其它线程已经将 Mark Word 设置为 markOopDesc:INFLATING，当前线程进行自旋等待膨胀完成。</p><p><code>步骤 3</code>、如果 CAS 成功，设置 monitor 的各个字段：设置 <code>monitor</code> 的 header 字段为 <code>displaced mark word</code>，owner 字段为 <code>Lock Record</code>，obj 字段为锁对象等；</p><p>📢  <span style="background-color:#ffff00"><font color=#ff0000>设置 ObjectMonitor 的 _owner 为拥有对象轻量级锁的线程的 Lock Record，而不是当前正在 inflate 的线程的 Lock Record</font></span>，用于重量级锁竞争的判断。根本原因是因为锁对象的 Mark Word 里轻量级锁信息变成了重量级锁的信息，如果这个 CAS 操作是其他线程 (比如 B) 完成的，那么 A 是不知情的，而且 B 在获取轻量级锁时也 Blocked 了，所以干脆让 A 去处理轻量级锁膨胀后的逻辑。</p><p><code>步骤 4</code>、设置锁对象头的 <code>mark word</code> 为重量级锁状态，指向第一步分配的 <code>monitor</code> 对象；</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202144631.png"></p><h2 id="3-2-monitor-竞争"><a href="#3-2-monitor-竞争" class="headerlink" title="3.2. monitor 竞争"></a>3.2. monitor 竞争</h2><p><a href="https://www.cnblogs.com/father-of-little-pig/p/16314318.html">https://www.cnblogs.com/father-of-little-pig/p/16314318.html</a><br><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a></p><p>当锁膨胀 <code>inflate</code> 执行完并返回对应的 <code>ObjectMonitor</code> 时，并不表示该线程竞争到了锁，真正的锁竞争发生在 <code>ObjectMonitor::enter</code> 方法中。</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><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></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> ATTR <span class="hljs-title">ObjectMonitor::enter</span><span class="hljs-params">(TRAPS)</span> </span>&#123;<br><br>  Thread * <span class="hljs-type">const</span> Self = THREAD ;<br>  <span class="hljs-type">void</span> * cur ;<br>  <span class="hljs-comment">// 步骤1</span><br>  <span class="hljs-comment">// owner为null，如果能CAS设置成功，则当前线程直接获得锁</span><br>  cur = Atomic::<span class="hljs-built_in">cmpxchg_ptr</span> (Self, &amp;_owner, <span class="hljs-literal">NULL</span>) ;<br>  <span class="hljs-keyword">if</span> (cur == <span class="hljs-literal">NULL</span>) &#123;<br>     ...<br>     <span class="hljs-keyword">return</span> ;<br>  &#125;<br>  <span class="hljs-comment">// 如果是重入的情况</span><br>  <span class="hljs-keyword">if</span> (cur == Self) &#123;<br>     <span class="hljs-comment">// TODO-<span class="hljs-doctag">FIXME:</span> check for integer overflow!  BUGID 6557169.</span><br>     _recursions ++ ;<br>     <span class="hljs-keyword">return</span> ;<br>  &#125;<br>  <span class="hljs-comment">// 步骤2</span><br>  <span class="hljs-comment">// 如果当前线程是之前持有轻量级锁的线程</span><br>  <span class="hljs-comment">// 上节轻量级锁膨胀将owner指向之前Lock Record的指针</span><br>  <span class="hljs-comment">// 这里利用owner判断是否第一次进入。</span><br>  <span class="hljs-keyword">if</span> (Self-&gt;<span class="hljs-built_in">is_lock_owned</span> ((address)cur)) &#123;<br>    <span class="hljs-built_in">assert</span> (_recursions == <span class="hljs-number">0</span>, <span class="hljs-string">&quot;internal state error&quot;</span>);<br>    <span class="hljs-comment">// 重入计数重置为1</span><br>    _recursions = <span class="hljs-number">1</span> ;<br>    <span class="hljs-comment">// 设置owner字段为当前线程</span><br>    _owner = Self ;<br>    OwnerIsThread = <span class="hljs-number">1</span> ;<br>    <span class="hljs-keyword">return</span> ;<br>  &#125;<br>  ...<br>  <span class="hljs-comment">// 步骤3</span><br>  <span class="hljs-comment">// 在调用系统的同步操作之前，先尝试自旋获得锁</span><br>  <span class="hljs-keyword">if</span> (Knob_SpinEarly &amp;&amp; <span class="hljs-built_in">TrySpin</span> (Self) &gt; <span class="hljs-number">0</span>) &#123;    <br>     ...<br>     <span class="hljs-comment">//自旋的过程中获得了锁，则直接返回</span><br>     Self-&gt;_Stalled = <span class="hljs-number">0</span> ;<br>     <span class="hljs-keyword">return</span> ;<br>  &#125;<br>  ...<br>  &#123; <br>    ...<br>    <span class="hljs-comment">// 步骤4</span><br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>      jt-&gt;<span class="hljs-built_in">set_suspend_equivalent</span>();<br>      <span class="hljs-comment">// 在该方法中调用系统同步操作</span><br>      <span class="hljs-built_in">EnterI</span> (THREAD) ;<br>      ...<br>    &#125;<br>    Self-&gt;<span class="hljs-built_in">set_current_pending_monitor</span>(<span class="hljs-literal">NULL</span>); <br>  &#125;<br>  ...<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202174945.png"><br><code>步骤 1</code>、<code>Atomic::cmpxchg_ptr (Self, &amp;_owner, NULL) ;</code> 通过 CAS 尝试将 owner 设置为当前线程。如果当前是无锁、返回 null，说明获取锁成功，简单操作后返回。如果是重入，则 <code>_recursions ++</code></p><p><code>步骤 2</code>、当前线程是之前持有轻量级锁的线程，则为首次进入，设置 recursions 为 1，owner 为当前线程，该线程成功获得锁并返回。</p><p><code>步骤 3</code>、在调用系统的同步操作之前，先通过 TrySpin 方法 <strong>自旋尝试</strong> 获得锁，尽可能减少同步操作带来的开销。</p><p><code>步骤 4</code>、获取锁失败，调用 <code>EnterI</code> 方法，在该方法中调用系统同步操作。</p><p><span style="background-color:#00ff00">这里注意，轻量级锁膨胀成功时，会把 owner 字段设置为<font color=#ff0000>膨胀之前轻量级锁持有线程的</font>栈中的 <code>Lock Record</code> 的指针，并在竞争时判断，具体是用在了步骤 2 的判断里。这么做的原因是，假设当前线程 A 持有锁对象的锁，线程 B 进入同步代码块，并把锁对象升级为重量级锁。但此时，线程 A 可能还在执行，并无法感知其持有锁对象的变化。因此，需要线程 B 在执行 <code>ObjectMonitor::enter</code> 时，将自己放入到阻塞等列等待。并需要线程 A 第二次进入、或者退出的时候对 monitor 进行一些操作，以此保证代码块的同步。</span></p><h2 id="3-3-monitor-等待"><a href="#3-3-monitor-等待" class="headerlink" title="3.3. monitor 等待"></a>3.3. monitor 等待</h2><p><a href="https://www.cnblogs.com/father-of-little-pig/p/16314318.html">https://www.cnblogs.com/father-of-little-pig/p/16314318.html</a><br><a href="https://www.cnblogs.com/kundeg/archive/2018/02/06/8422557.html">https://www.cnblogs.com/kundeg/archive/2018/02/06/8422557.html</a></p><p><code>ObjectMonitor</code> 竞争失败的线程，通过自旋执行 <code>ObjectMonitor::EnterI</code> 方法等待锁的释放，EnterI 方法的部分逻辑实现如下：</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><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></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 省略部分代码</span><br><span class="hljs-function"><span class="hljs-type">void</span> ATTR <span class="hljs-title">ObjectMonitor::EnterI</span> <span class="hljs-params">(TRAPS)</span> </span>&#123;<br>    Thread * Self = THREAD ;<br>    <span class="hljs-built_in">assert</span> (Self-&gt;<span class="hljs-built_in">is_Java_thread</span>(), <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>    <span class="hljs-built_in">assert</span> (((JavaThread *) Self)-&gt;<span class="hljs-built_in">thread_state</span>() == _thread_blocked   , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br><br>    <span class="hljs-comment">// Try lock 尝试获取锁</span><br>    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">TryLock</span> (Self) &gt; <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-built_in">assert</span> (_succ != Self              , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>        <span class="hljs-built_in">assert</span> (_owner == Self             , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>        <span class="hljs-built_in">assert</span> (_Responsible != Self       , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>        <span class="hljs-comment">// 如果获取成功则退出，避免 park unpark 系统调度的开销</span><br>        <span class="hljs-keyword">return</span> ;<br>    &#125;<br><br>    <span class="hljs-comment">// 自旋获取锁</span><br>    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">TrySpin</span>(Self) &gt; <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-built_in">assert</span> (_owner == Self, <span class="hljs-string">&quot;invariant&quot;</span>);<br>        <span class="hljs-built_in">assert</span> (_succ != Self, <span class="hljs-string">&quot;invariant&quot;</span>);<br>        <span class="hljs-built_in">assert</span> (_Responsible != Self, <span class="hljs-string">&quot;invariant&quot;</span>);<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// 当前线程被封装成 ObjectWaiter 对象 node, 状态设置成 ObjectWaiter::TS_CXQ</span><br>    <span class="hljs-function">ObjectWaiter <span class="hljs-title">node</span><span class="hljs-params">(Self)</span> </span>;<br>    Self-&gt;_ParkEvent-&gt;<span class="hljs-built_in">reset</span>() ;<br>    node._prev   = (ObjectWaiter *) <span class="hljs-number">0xBAD</span> ;<br>    node.TState  = ObjectWaiter::TS_CXQ ;<br><br>    <span class="hljs-comment">// 通过 CAS 把 node 节点 push 到_cxq 列表中</span><br>    ObjectWaiter * nxt ;<br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>        node._next = nxt = _cxq ;<br>        <span class="hljs-keyword">if</span> (Atomic::<span class="hljs-built_in">cmpxchg_ptr</span> (&amp;node, &amp;_cxq, nxt) == nxt) <span class="hljs-keyword">break</span> ;<br><br>        <span class="hljs-comment">// 再次 tryLock</span><br>        <span class="hljs-comment">// CAS失败的话 再尝试获得锁，这样也可以降低插入到_cxq队列的频率</span><br>        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">TryLock</span> (Self) &gt; <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-built_in">assert</span> (_succ != Self         , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>            <span class="hljs-built_in">assert</span> (_owner == Self        , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>            <span class="hljs-built_in">assert</span> (_Responsible != Self  , <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>            <span class="hljs-keyword">return</span> ;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>        <span class="hljs-comment">// 本段代码的主要思想和 AQS 中相似可以类比来看</span><br>        <span class="hljs-comment">// 再次尝试</span><br>        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">TryLock</span> (Self) &gt; <span class="hljs-number">0</span>) <span class="hljs-keyword">break</span> ;<br>        <span class="hljs-built_in">assert</span> (_owner != Self, <span class="hljs-string">&quot;invariant&quot;</span>) ;<br><br>        <span class="hljs-keyword">if</span> ((SyncFlags &amp; <span class="hljs-number">2</span>) &amp;&amp; _Responsible == <span class="hljs-literal">NULL</span>) &#123;<br>           Atomic::<span class="hljs-built_in">cmpxchg_ptr</span> (Self, &amp;_Responsible, <span class="hljs-literal">NULL</span>) ;<br>        &#125;<br><br>        <span class="hljs-comment">// 满足条件则 park self</span><br>        <span class="hljs-keyword">if</span> (_Responsible == Self || (SyncFlags &amp; <span class="hljs-number">1</span>)) &#123;<br>            <span class="hljs-built_in">TEVENT</span> (Inflated enter - park TIMED) ;<br>            Self-&gt;_ParkEvent-&gt;<span class="hljs-built_in">park</span> ((jlong) RecheckInterval) ;<br>            <span class="hljs-comment">// Increase the RecheckInterval, but clamp the value.</span><br>            RecheckInterval *= <span class="hljs-number">8</span> ;<br>            <span class="hljs-keyword">if</span> (RecheckInterval &gt; <span class="hljs-number">1000</span>) RecheckInterval = <span class="hljs-number">1000</span> ;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-built_in">TEVENT</span> (Inflated enter - park UNTIMED) ;<br>            <span class="hljs-comment">// 通过 park 将当前线程挂起，等待被唤醒</span><br>            Self-&gt;_ParkEvent-&gt;<span class="hljs-built_in">park</span>() ;<br>        &#125;<br><br>        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">TryLock</span>(Self) &gt; <span class="hljs-number">0</span>) <span class="hljs-keyword">break</span> ;<br>        <span class="hljs-comment">// 再次尝试自旋</span><br>        <span class="hljs-keyword">if</span> ((Knob_SpinAfterFutile &amp; <span class="hljs-number">1</span>) &amp;&amp; <span class="hljs-built_in">TrySpin</span>(Self) &gt; <span class="hljs-number">0</span>) <span class="hljs-keyword">break</span>;<br>    &#125;<br>    <span class="hljs-keyword">return</span> ;<br>&#125;<br></code></pre></td></tr></table></figure><p>EnterI 大致原理：一个 <code>ObjectMonitor</code> 对象包括两个同步队列（<code>_cxq</code> 和 <code>_EntryList</code>） ，以及一个等待队列 <code>_WaitSet</code>。cxq、EntryList 、WaitSet 都是由 ObjectWaiter 构成的链表结构。其中，<code>_cxq</code> 为单向链表，<code>_EntryList</code> 为双向链表。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113135319.png"></p><ol><li>当一个线程尝试获得重量级锁且没有竞争到时<span style="background-color:#ffff00">(又经过 TryLock、TrySpin 努力)</span>，该线程会被封装成一个 <code>ObjectWaiter</code> 的 node 对象 <code>通过CAS</code> 插入到 cxq 的队列的队首，状态设置成 ObjectWaiter::TS_CXQ。此过程中还会 tryLock，也能起到降低插入到 _cxq 队列的频率的作用。</li><li>在将 CXQ 挂起的 for 循环中，node 对象还 <code>通过自旋尝试获取锁</code>，如果在指定的阈值范围内没有获得锁，则通过 park 将当前线程挂起，进入<span style="background-color:#ffff00">BLOCKED</span>状态，等待被唤醒。</li><li>当线程释放锁时，会根据唤醒策略，从 cxq 或 EntryList 中挑选一个线程 <code>unpark</code> 唤醒。唤醒时会从挂起的点继续执行，通过 ObjectMonitor::TryLock 尝试获取锁。</li><li>如果线程获得锁后调用 <code>Object#wait</code> 方法，则会将线程加入到 WaitSet 中，进入<span style="background-color:#ffff00">WAITING 或 TIMED_WAITING</span>状态。</li><li>当被 <code>Object#notify</code> 唤醒后，会将线程从 WaitSet 移动到 cxq 或 EntryList 中去，进入<span style="background-color:#ffff00">BLOCKED</span>状态。</li><li>需要注意的是，当调用一个锁对象的 <code>wait</code> 或 <code>notify</code> 方法时，<span style="background-color:#00ff00">若当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。</span></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202214650.png"></p><p><strong>线程状态温习</strong></p><a href="/2022/11/12/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-7%E3%80%81Thread/" title="并发基础-7、Thread">并发基础-7、Thread</a><h2 id="3-4-monitor-释放"><a href="#3-4-monitor-释放" class="headerlink" title="3.4. monitor 释放"></a>3.4. monitor 释放</h2><p><a href="https://www.cnblogs.com/kundeg/archive/2018/02/06/8422557.html">https://www.cnblogs.com/kundeg/archive/2018/02/06/8422557.html</a><br><a href="https://juejin.cn/post/6977744259688939551#heading-10">https://juejin.cn/post/6977744259688939551#heading-10</a></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><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></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">void</span> ATTR <span class="hljs-title">ObjectMonitor::exit</span><span class="hljs-params">(<span class="hljs-type">bool</span> not_suspended, TRAPS)</span> </span>&#123;<br>   Thread * Self = THREAD ;<br>   <span class="hljs-keyword">if</span> (THREAD != _owner) &#123;<span class="hljs-comment">//如果当前锁对象中的_owner没有指向当前线程</span><br>     <span class="hljs-comment">//如果_owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程</span><br>     <span class="hljs-keyword">if</span> (THREAD-&gt;<span class="hljs-built_in">is_lock_owned</span>((address) _owner)) &#123;<br>       <span class="hljs-comment">// Transmute _owner from a BasicLock pointer to a Thread address.</span><br>       <span class="hljs-comment">// We don&#x27;t need to hold _mutex for this transition.</span><br>       <span class="hljs-comment">// Non-null to Non-null is safe as long as all readers can</span><br>       <span class="hljs-comment">// tolerate either flavor.</span><br>       <span class="hljs-built_in">assert</span> (_recursions == <span class="hljs-number">0</span>, <span class="hljs-string">&quot;invariant&quot;</span>) ;<br>       _owner = THREAD ;<br>       _recursions = <span class="hljs-number">0</span> ;<br>       OwnerIsThread = <span class="hljs-number">1</span> ;<br>     &#125; <span class="hljs-keyword">else</span> &#123;<br>       <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> we need to handle unbalanced monitor enter/exit</span><br>       <span class="hljs-comment">// in native code by throwing an exception.</span><br>       <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Throw an IllegalMonitorStateException ?</span><br>       <span class="hljs-built_in">TEVENT</span> (Exit - Throw IMSX) ;<br>       <span class="hljs-built_in">assert</span>(<span class="hljs-literal">false</span>, <span class="hljs-string">&quot;Non-balanced monitor enter/exit!&quot;</span>);<br>       <span class="hljs-keyword">if</span> (<span class="hljs-literal">false</span>) &#123;<br>          <span class="hljs-built_in">THROW</span>(vmSymbols::<span class="hljs-built_in">java_lang_IllegalMonitorStateException</span>());<br>       &#125;<br>       <span class="hljs-keyword">return</span>;<br>     &#125;<br>   &#125;<br>   <span class="hljs-comment">//如果当前，线程重入锁的次数，不为0，那么就重新走ObjectMonitor::exit，直到重入锁次数为0为止</span><br>   <span class="hljs-keyword">if</span> (_recursions != <span class="hljs-number">0</span>) &#123;<br>     _recursions--;        <span class="hljs-comment">// this is simple recursive enter</span><br>     <span class="hljs-built_in">TEVENT</span> (Inflated exit - recursive) ;<br>     <span class="hljs-keyword">return</span> ;<br>   &#125;<br>  ...<span class="hljs-comment">//此处省略很多代码</span><br>  <span class="hljs-keyword">for</span> (;;) &#123;<br>    <span class="hljs-keyword">if</span> (Knob_ExitPolicy == <span class="hljs-number">0</span>) &#123;<br>      OrderAccess::<span class="hljs-built_in">release_store</span>(&amp;_owner, (<span class="hljs-type">void</span>*)<span class="hljs-literal">NULL</span>);   <span class="hljs-comment">//释放锁</span><br>      OrderAccess::<span class="hljs-built_in">storeload</span>();                        <span class="hljs-comment">// See if we need to wake a successor</span><br>      <span class="hljs-keyword">if</span> ((<span class="hljs-built_in">intptr_t</span>(_EntryList)|<span class="hljs-built_in">intptr_t</span>(_cxq)) == <span class="hljs-number">0</span> || _succ != <span class="hljs-literal">NULL</span>) &#123;<br>        <span class="hljs-built_in">TEVENT</span>(Inflated exit - simple egress);<br>        <span class="hljs-keyword">return</span>;<br>      &#125;<br>      <span class="hljs-built_in">TEVENT</span>(Inflated exit - complex egress);<br>      <span class="hljs-comment">//省略部分代码...</span><br>    &#125;<br>    <span class="hljs-comment">//省略部分代码...</span><br>    ObjectWaiter * w = <span class="hljs-literal">NULL</span>;<br>    <span class="hljs-type">int</span> QMode = Knob_QMode;<br>    <span class="hljs-comment">//根据QMode的模式判断，</span><br>    <span class="hljs-comment">//如果QMode == 2则直接从_cxq挂起的线程中唤醒    </span><br>    <span class="hljs-keyword">if</span> (QMode == <span class="hljs-number">2</span> &amp;&amp; _cxq != <span class="hljs-literal">NULL</span>) &#123;<br>      w = _cxq;<br>      <span class="hljs-built_in">ExitEpilog</span>(Self, w);<br>      <span class="hljs-keyword">return</span>;<br>    &#125;<br>     <span class="hljs-comment">//省略部分代码... 省略的代码为根据QMode的不同，不同的唤醒机制</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>当某个持有锁的线程执行完同步代码块时，会进行锁的释放。在 HotSpot 中，通过退出 monitor 的方式实现锁的释放，并通知被阻塞的线程，具体实现位于 <code>ObjectMonitor::exit</code> 方法中。</p><ul><li>判断当前锁对象中的 owner 没有指向当前线程，如果 owner 指向的 BasicLock 在当前线程栈上，那么将 _owner 指向当前线程。</li><li>如果当前锁对象中的 _owner 指向当前线程，则判断当前线程重入锁的次数，如果不为 0，继续执行 ObjectMonitor::exit()，直到重入锁次数为 0 为止。</li><li>释放当前锁，唤醒操作。根据不同的策略（由 QMode 指定），从 cxq 或 EntryList 中获取头节点，通过 <code>ObjectMonitor::ExitEpilog</code> 方法唤醒该节点封装的线程，唤醒操作最终由 unpark 完成。</li></ul><p>根据 QMode 的不同 (默认为 0)，有不同的处理方式：</p><p>QMode &#x3D; 0：暂时什么都不做；<br>QMode &#x3D; 2 且 cxq 非空：取 cxq 队列队首的 ObjectWaiter 对象，调用 ExitEpilog 方法，该方法会唤醒 ObjectWaiter 对象的线程，然后立即返回，后面的代码不会执行了；<br>QMode &#x3D; 3 且 cxq 非空：把 cxq 队列插入到 EntryList 的尾部；<br>QMode &#x3D; 4 且 cxq 非空：把 cxq 队列插入到 EntryList 的头部；</p><p>只有 QMode&#x3D;2 的时候会提前返回，等于 0、3、4 的时继续执行：</p><p>1.如果 EntryList 的首元素非空，就取出来调用 ExitEpilog 方法，该方法会唤醒 ObjectWaiter 对象的线程，然后立即返回；<br>2.如果 EntryList 的首元素为空，就将 cxq 的所有元素放入到 EntryList 中，然后再从 EntryList 中取出来队首元素执行 ExitEpilog 方法，然后立即返回；<br>3.被唤醒的线程，继续竞争 monitor；</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221203074915.png"></p><h2 id="3-5-wait-和-notify"><a href="#3-5-wait-和-notify" class="headerlink" title="3.5. wait 和 notify"></a>3.5. wait 和 notify</h2><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230607-2118%%</span>❕ ^shroec</p><ul><li><span style="background-color:#ff00ff">wait，notify 必须是持有当前对象锁 Monitor 的线程才能调用</span> (<span style="background-color:#00ff00">对象锁代指 ObjectMonitor&#x2F;Monitor，锁对象代指 Object</span>)</li><li>上面有说到，当在 sychronized 中锁对象 Object 调用 wait 时会加入 waitSet 队列，WaitSet 的元素对象就是 <span style="background-color:#ff00ff">ObjectWaiter</span><br><strong>调用对象锁的 wait() 方法时，线程会被封装成 ObjectWaiter，最后使用 park 方法挂起，而当对象锁使用 notify() 时</strong></li><li>如果 waitSet 为空，则直接返回</li><li>waitSet 不为空从 waitSet 获取一个 ObjectWaiter，然后根据不同的 Policy 加入到 EntryList 或通过 <code>Atomic::cmpxchg_ptr</code> 指令自旋操作加入 <strong>cxq 队列</strong> 或者直接 unpark 唤醒</li><li>Object 的 notifyAll 方法则对应 <code>voidObjectMonitor::notifyAll(TRAPS)</code>，流程和 notify 类似。不过会通过 for 循环取出 WaitSet 的 ObjectWaiter 节点，再依次唤醒所有线程</li></ul><h2 id="3-6-源码分析"><a href="#3-6-源码分析" class="headerlink" title="3.6. 源码分析"></a>3.6. 源码分析</h2><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221113-从 Monitorenter 源码看 Synchronized 锁优化的过程 - 掘金]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-Java锁与线程的那些事]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221113-(三)死磕并发之深入Hotspot源码剖析Synchronized关键字实现 - 掘金]]</p><h1 id="4-锁优化"><a href="#4-锁优化" class="headerlink" title="4. 锁优化"></a>4. 锁优化</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221111-synchronized 实现原理  小米信息部技术团队]]</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-[博客大赛] 图文并茂!!讲解 JUC 重量级锁、轻量级锁、自旋、锁膨胀…._qq60751173d6bae 的技术博客 _51CTO 博客]]</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-Synchronized 偏向锁升级 - MaXianZhe - 博客园]]</p><p><a href="https://www.bilibili.com/video/av70549061?p=180">https://www.bilibili.com/video/av70549061?p=180</a></p><p>事实上，只有在 JDK1.6 之前，synchronized 的实现才会直接调用 ObjectMonitor 的 enter 和 exit，这种锁被称之为重量级锁。</p><p>Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗，引入了 <code>偏向锁</code> 和 <code>轻量级锁</code>：锁一共有 4 种状态，级别从低到高依次是：无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。</p><p>所以锁的状态总共有四种：无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争，锁可以从偏向锁升级到轻量级锁，再升级的重量级锁（但是锁的升级是单向的，也就是说只能从低到高升级，不会出现锁的降级）。JDK 1.6 中默认是开启偏向锁和轻量级锁的，我们也可以通过 -XX:-UseBiasedLocking&#x3D;false 来禁用偏向锁</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230604074215.jpg" alt="image-20200131213915151"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230604074224.jpg" alt="image-20200131214209117"></p><h2 id="4-1-偏向锁"><a href="#4-1-偏向锁" class="headerlink" title="4.1. 偏向锁"></a>4.1. 偏向锁</h2><h3 id="4-1-1-优点作用-减少轻量级锁不必要的-CAS-线程-ID-的替换⭐️🔴"><a href="#4-1-1-优点作用-减少轻量级锁不必要的-CAS-线程-ID-的替换⭐️🔴" class="headerlink" title="4.1.1. 优点作用 - 减少轻量级锁不必要的 CAS 线程 ID 的替换⭐️🔴"></a>4.1.1. 优点作用 - 减少轻量级锁不必要的 CAS 线程 ID 的替换⭐️🔴</h3><p><span style="background-color:#00ff00">轻量级锁在没有竞争时（就自己这个线程），每次重入仍然需要执行 CAS 操作。Java 6 中引入了偏向锁来做进一步优化：只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头，之后发现这个线程 ID 是自己的就表示没有竞争，不用重新 CAS. </span></p><p>如果一个线程获得了锁，那么锁就进入偏向模式，此时 Mark Word 记录该线程的 ID；当该线程再次请求锁时，无需做任何同步操作，即需要在获取锁的时候<span style="background-color:#ff00ff">检查一下 Mark Word 的锁标记位 &#x3D; 偏向锁 (01)，并且 threadID &#x3D; 该线程 ID 即可</span>，因此，省去了锁申请的操作。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230604074234.jpg" alt="image-20200131213740074"></p><h3 id="4-1-2-缺点问题"><a href="#4-1-2-缺点问题" class="headerlink" title="4.1.2. 缺点问题"></a>4.1.2. 缺点问题</h3><p><span style="background-color:#ff00ff">撤销偏向需要将持锁线程升级为轻量级锁，这个过程中所有线程需要暂停（STW）</span><br>撤销的场景很多：访问对象的 hashCode、调用 wait&#x2F;notify<br>可以主动使用 -XX:-UseBiasedLocking 禁用偏向锁</p><h3 id="4-1-3-延迟偏向"><a href="#4-1-3-延迟偏向" class="headerlink" title="4.1.3. 延迟偏向"></a>4.1.3. 延迟偏向</h3><p>偏向锁模式存在偏向锁延迟机制：HotSpot 虚拟机在启动后有个 <code>4s</code> 的延迟才会对每个新建的对象开启偏向锁模式。因为 JVM 启动时会进行一系列的复杂活动，比如装载配置，系统类初始化等等。在这个过程中会使用大量 synchronized 关键字对对象加锁，且这些锁大多数都不是偏向锁。待启动完成后再延迟打开偏向锁。<br>可以使用 <code>-XX:BiasedLockingStartupDelay=0</code> 参数关闭延迟，让其在程序启动时立刻启动。</p><h3 id="4-1-4-匿名偏向"><a href="#4-1-4-匿名偏向" class="headerlink" title="4.1.4. 匿名偏向"></a>4.1.4. 匿名偏向</h3><p><code>匿名偏向状态</code>：锁对象 mark word 标志位为 101，且存储的 <code>Thread ID</code> 为空 (源码中按 null 进行判断的，JOL 打印是 54 位都是 0) 时的状态 (即锁对象为偏向锁，且没有线程偏向于这个锁对象)。</p><p>为什么上述偏向锁逻辑没有判断 <strong>无锁状态的锁对象</strong>（001）？</p><p><strong>只有匿名偏向的对象才能进入偏向锁模式</strong>。偏向锁是延时初始化的，默认是 4000ms。初始化后会将所有加载的 Klass 的 prototype header 修改为匿名偏向样式。当创建一个对象时，会通过 Klass 的 prototype_header 来初始化该对象的对象头。简单的说，偏向锁初始化结束后，后续所有对象的对象头都为 <strong>匿名偏向</strong> 样式，在此之前创建的对象则为 <strong>无锁状态</strong>。而对于无锁状态的锁对象，如果有竞争，会直接进入到轻量级锁。这也是为什么 JVM 启动前 4 秒对象会直接进入到轻量级锁的原因。</p><h3 id="4-1-5-加锁流程⭐️🔴"><a href="#4-1-5-加锁流程⭐️🔴" class="headerlink" title="4.1.5. 加锁流程⭐️🔴"></a>4.1.5. 加锁流程⭐️🔴</h3><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><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></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-built_in">CASE</span>(_monitorenter): &#123;<br>  <span class="hljs-comment">// lockee 就是锁对象</span><br>  oop lockee = <span class="hljs-built_in">STACK_OBJECT</span>(<span class="hljs-number">-1</span>);<br>  <span class="hljs-comment">// derefing&#x27;s lockee ought to provoke implicit null check</span><br>  <span class="hljs-built_in">CHECK_NULL</span>(lockee);<br>  <span class="hljs-comment">// code 1：找到一个空闲的Lock Record</span><br>  BasicObjectLock* limit = istate-&gt;<span class="hljs-built_in">monitor_base</span>();<br>  BasicObjectLock* most_recent = (BasicObjectLock*) istate-&gt;<span class="hljs-built_in">stack_base</span>();<br>  BasicObjectLock* entry = <span class="hljs-literal">NULL</span>;<br>  <span class="hljs-keyword">while</span> (most_recent != limit ) &#123;<br>    <span class="hljs-keyword">if</span> (most_recent-&gt;<span class="hljs-built_in">obj</span>() == <span class="hljs-literal">NULL</span>) entry = most_recent;<br>    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (most_recent-&gt;<span class="hljs-built_in">obj</span>() == lockee) <span class="hljs-keyword">break</span>;<br>    most_recent++;<br>  &#125;<br>  <span class="hljs-comment">//entry不为null，代表还有空闲的Lock Record</span><br>  <span class="hljs-keyword">if</span> (entry != <span class="hljs-literal">NULL</span>) &#123;<br>    <span class="hljs-comment">// code 2：将Lock Record的obj指针指向锁对象</span><br>    entry-&gt;<span class="hljs-built_in">set_obj</span>(lockee);<br>    <span class="hljs-type">int</span> success = <span class="hljs-literal">false</span>;<br>    <span class="hljs-type">uintptr_t</span> epoch_mask_in_place = (<span class="hljs-type">uintptr_t</span>)markOopDesc::epoch_mask_in_place;<br>    <span class="hljs-comment">// markoop即对象头的mark word</span><br>    markOop mark = lockee-&gt;<span class="hljs-built_in">mark</span>();<br>    <span class="hljs-type">intptr_t</span> hash = (<span class="hljs-type">intptr_t</span>) markOopDesc::no_hash;<br>    <span class="hljs-comment">// code 3：如果锁对象的mark word的状态是偏向模式</span><br>    <span class="hljs-keyword">if</span> (mark-&gt;<span class="hljs-built_in">has_bias_pattern</span>()) &#123;<br>      <span class="hljs-type">uintptr_t</span> thread_ident;<br>      <span class="hljs-type">uintptr_t</span> anticipated_bias_locking_value;<br>      thread_ident = (<span class="hljs-type">uintptr_t</span>)istate-&gt;<span class="hljs-built_in">thread</span>();<br>     <span class="hljs-comment">// code 4：这里有几步操作，下文分析</span><br>      anticipated_bias_locking_value =<br>        (((<span class="hljs-type">uintptr_t</span>)lockee-&gt;<span class="hljs-built_in">klass</span>()-&gt;<span class="hljs-built_in">prototype_header</span>() | thread_ident) ^ (<span class="hljs-type">uintptr_t</span>)mark) &amp;<br>        ~((<span class="hljs-type">uintptr_t</span>) markOopDesc::age_mask_in_place);<br>     <span class="hljs-comment">// code 5：如果偏向的线程是自己且epoch等于class的epoch</span><br>      <span class="hljs-keyword">if</span>  (anticipated_bias_locking_value == <span class="hljs-number">0</span>) &#123;<br>        <br>      &#125;<br>       <span class="hljs-comment">// code 6：如果偏向模式关闭，则尝试撤销偏向锁</span><br>      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((anticipated_bias_locking_value &amp; markOopDesc::biased_lock_mask_in_place) != <span class="hljs-number">0</span>) &#123;<br>        <br>      &#125;<br>         <span class="hljs-comment">// code 7：如果epoch不等于class中的epoch，则尝试重偏向</span><br>      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((anticipated_bias_locking_value &amp; epoch_mask_in_place) !=<span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">// 构造一个偏向当前线程的mark word</span><br>        <br>      &#125;<br>      <span class="hljs-keyword">else</span> &#123;<br>         <span class="hljs-comment">// 走到这里说明当前要么偏向别的线程，要么是匿名偏向（即没有偏向任何线程）</span><br>        <span class="hljs-comment">// code 8：下面构建一个匿名偏向的mark word，尝试用CAS指令替换掉锁对象的mark word</span><br>        markOop header = (markOop) ((<span class="hljs-type">uintptr_t</span>) mark &amp; ((<span class="hljs-type">uintptr_t</span>)markOopDesc::biased_lock_mask_in_place |(<span class="hljs-type">uintptr_t</span>)markOopDesc::age_mask_in_place |epoch_mask_in_place));<br>        <span class="hljs-keyword">if</span> (hash != markOopDesc::no_hash) &#123;<br>          header = header-&gt;<span class="hljs-built_in">copy_set_hash</span>(hash);<br>        &#125;<br>        markOop new_header = (markOop) ((<span class="hljs-type">uintptr_t</span>) header | thread_ident);<br>        <span class="hljs-comment">// debugging hint</span><br>        <span class="hljs-built_in">DEBUG_ONLY</span>(entry-&gt;<span class="hljs-built_in">lock</span>()-&gt;<span class="hljs-built_in">set_displaced_header</span>((markOop) (<span class="hljs-type">uintptr_t</span>) <span class="hljs-number">0xdeaddead</span>);)<br>        <span class="hljs-keyword">if</span> (Atomic::<span class="hljs-built_in">cmpxchg_ptr</span>((<span class="hljs-type">void</span>*)new_header, lockee-&gt;<span class="hljs-built_in">mark_addr</span>(), header) == header) &#123;<br>           <span class="hljs-comment">// CAS修改成功</span><br>          <span class="hljs-keyword">if</span> (PrintBiasedLockingStatistics)<br>            (* BiasedLocking::<span class="hljs-built_in">anonymously_biased_lock_entry_count_addr</span>())++;<br>        &#125;<br>        <span class="hljs-keyword">else</span> &#123;<br>          <span class="hljs-comment">// 如果修改失败说明存在多线程竞争，所以进入monitorenter方法</span><br>          <span class="hljs-built_in">CALL_VM</span>(InterpreterRuntime::<span class="hljs-built_in">monitorenter</span>(THREAD, entry), handle_exception);<br>        &#125;<br>        success = <span class="hljs-literal">true</span>;<br>      &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// 如果偏向线程不是当前线程或没有开启偏向模式等原因都会导致success==false</span><br>    <span class="hljs-keyword">if</span> (!success) &#123;<br>      <span class="hljs-comment">// 轻量级锁的逻辑</span><br>      <span class="hljs-comment">//code 9: 构造一个无锁状态的Displaced Mark Word，并将Lock Record的lock指向它</span><br>      markOop displaced = lockee-&gt;<span class="hljs-built_in">mark</span>()-&gt;<span class="hljs-built_in">set_unlocked</span>();<br>      entry-&gt;<span class="hljs-built_in">lock</span>()-&gt;<span class="hljs-built_in">set_displaced_header</span>(displaced);<br>      <span class="hljs-comment">//如果指定了-XX:+UseHeavyMonitors，则call_vm=true，代表禁用偏向锁和轻量级锁</span><br>      <span class="hljs-type">bool</span> call_vm = UseHeavyMonitors;<br>      <span class="hljs-comment">// 利用CAS将对象头的mark word替换为指向Lock Record的指针</span><br>      <span class="hljs-keyword">if</span> (call_vm || Atomic::<span class="hljs-built_in">cmpxchg_ptr</span>(entry, lockee-&gt;<span class="hljs-built_in">mark_addr</span>(), displaced) != displaced) &#123;<br>        <span class="hljs-comment">// 判断是不是锁重入</span><br>        <span class="hljs-keyword">if</span> (!call_vm &amp;&amp; THREAD-&gt;<span class="hljs-built_in">is_lock_owned</span>((address) displaced-&gt;<span class="hljs-built_in">clear_lock_bits</span>())) &#123;        <span class="hljs-comment">//code 10: 如果是锁重入，则直接将Displaced Mark Word设置为null</span><br>          entry-&gt;<span class="hljs-built_in">lock</span>()-&gt;<span class="hljs-built_in">set_displaced_header</span>(<span class="hljs-literal">NULL</span>);<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>          <span class="hljs-built_in">CALL_VM</span>(InterpreterRuntime::<span class="hljs-built_in">monitorenter</span>(THREAD, entry), handle_exception);<br>        &#125;<br>      &#125;<br>    &#125;<br>    <span class="hljs-built_in">UPDATE_PC_AND_TOS_AND_CONTINUE</span>(<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-comment">// lock record不够，重新执行</span><br>    istate-&gt;<span class="hljs-built_in">set_msg</span>(more_monitors);<br>    <span class="hljs-built_in">UPDATE_PC_AND_RETURN</span>(<span class="hljs-number">0</span>); <span class="hljs-comment">// Re-execute</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>偏向锁的获取一定是从 <code>匿名偏向</code> 状态开始，然后 <code>偏向</code> 到某一个线程。</p><ol><li>从当前线程的栈中找到一个空闲的 Lock Record（即代码中的 BasicObjectLock，下文都用 Lock Record 代指），判断 Lock Record 是否空闲的依据是其 obj 字段是否为 null。注意这里是按内存地址从低往高找到最后一个可用的 Lock Record，换而言之，就是找到内存地址最高的可用 Lock Record。</li><li>获取到 Lock Record 后，首先要做的就是为其 obj 字段赋值。</li><li>判断锁对象的 mark word 是否是<span style="background-color:#ff00ff">偏向模式，即低 3 位是否为 101</span>。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202074605.png"></p><p><span style="background-color:#ff00ff">5.  <code>CAS</code> 将偏向线程 id 改为当前线程 id，如果当前是匿名偏向则能修改成功，否则进入锁升级的逻辑。</span><br><span style="background-color:#ff00ff">6. 若对象头 mark word 显示存储 thread id 为当前线程 id，表明这是一次锁重入操作，会向当前线程的栈帧中 <code>添加一条 displaced mark word 为空的 lock record 记录</code>（与轻量级锁记录重入记录原理相同）。</span><br>7. 若对象头 mark word 显示已存储其它 thread id，表明该锁对象已偏向其它线程，进入偏向锁撤销流程。等待 safepoint 检查偏向线程是否存活（遍历 jvm 线程栈检查），若偏向线程存活且还在执行同步代码块则进入升级轻量锁流程，否则将对象头 mark word 置为无锁 mark word，进入升级轻量级锁流程。</p><p><code>即不论偏向线程是否存活，只要该锁对象偏向过一个非当前线程的线程，在当前线程访问时一定会升级为轻量级锁。</code></p><p><strong>CAS 获取偏向锁的过程中存在并发冲突</strong>||<strong>已偏向其他线程</strong>||<strong>epoch 值过期</strong>||<strong>class 偏向模式关闭</strong>，都会进入到 <code>InterpreterRuntime::monitorenter</code> 方法， 在该方法中会进行偏向锁撤销和升级。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202065119.png"></p><p><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a><br><a href="http://northsea.top/?p=195#3-%E5%81%8F%E5%90%91%E9%94%81">http://northsea.top/?p=195#3-%E5%81%8F%E5%90%91%E9%94%81</a></p><h3 id="4-1-6-撤销流程"><a href="#4-1-6-撤销流程" class="headerlink" title="4.1.6. 撤销流程"></a>4.1.6. 撤销流程</h3><p><a href="https://www.cnblogs.com/FraserYu/p/15743542.html">https://www.cnblogs.com/FraserYu/p/15743542.html</a></p><p><span style="background-color:#ff00ff">撤销是指在获取偏向锁的过程因为不满足条件导致要将锁对象改为非偏向锁状态</span><br>偏向锁的 <code>撤销</code>（revoke）是一个很特殊的操作，为了执行撤销操作，需要等待 <code>全局安全点</code>，此时所有的工作线程都停止了执行。偏向锁的撤销操作并不是将对象恢复到无锁可偏向的状态，而是在偏向锁的获取过程中，发现竞争时，直接将一个被偏向的对象 <code>升级到</code> 被加了 <code>轻量级锁</code> 的状态。</p><p><a href="https://www.cnblogs.com/juniorMa/articles/13845491.html">https://www.cnblogs.com/juniorMa/articles/13845491.html</a><br><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a><br><a href="https://juejin.cn/post/6977744259688939551#heading-4">https://juejin.cn/post/6977744259688939551#heading-4</a></p><ul><li>查看偏向的线程是否存活，如果已经不存活了，则直接撤销偏向锁。JVM 维护了一个集合存放所有存活的线程，通过遍历该集合判断某个线程是否存活 [[性能调优专题-基础-1、Java-相关名词#线程集合]]。</li><li>偏向的线程是否还在同步块中，如果不在了，则撤销偏向锁。我们回顾一下偏向锁的加锁流程：每次进入同步块（即执行 <code>monitorenter</code>）的时候都会以从高往低的顺序在栈中找到第一个可用的 <code>Lock Record</code>，将其 obj 字段指向锁对象。每次解锁（即执行 <code>monitorexit</code>）的时候都会将最低的一个相关 <code>Lock Record</code> 移除掉。所以可以通过遍历线程栈中的 <code>Lock Record</code> 来判断线程是否还在同步块中。</li><li>将偏向线程所有相关 <code>Lock Record</code> 的 <code>Displaced Mark Word</code> 设置为 null，然后将最高位的 <code>Lock Record</code> 的 <code>Displaced Mark Word</code> 设置为无锁状态，最高位的 <code>Lock Record</code> 也就是第一次获得锁时的 <code>Lock Record</code>（这里的第一次是指重入获取锁时的第一次），然后将对象头指向最高位的 <code>Lock Record</code>，这里不需要用 CAS 指令，因为是在 <code>safepoint</code>。 执行完后，就升级成了轻量级锁。原偏向线程的所有 Lock Record 都已经变成轻量级锁的状态。</li></ul><p>　　总结下上面原作者的话：</p><p>　　1 如果原来的线程不存活了，锁对象变成无锁状态，方法 return。这样，ThreadB 就能进入 slow_enter 了。slow_enter 就是获取轻量级锁，获取不到才是锁膨胀。</p><p>　　2 如果原线程存在，这是要构造一个无锁状态的 mark word，放到最开始的那个 Lock Record 里。这是因为此时要进行轻量级锁升级，轻量级锁释放就会把 Lock record 里的 mark word 写回对象头里。</p><p>　　  那么原线程直接获取该轻量级锁，同时 ThreadB 还是进入 slow_enter，参与到轻量级锁的竞争</p><h3 id="4-1-7-释放流程"><a href="#4-1-7-释放流程" class="headerlink" title="4.1.7. 释放流程"></a>4.1.7. 释放流程</h3><p>锁释放是指线程执行完毕，退出同步代码块。偏向锁的释放并不是偏向锁的撤销，对象头还是偏向锁。most_recent-&gt;set_obj(NULL); 这就是释放偏向锁，其实啥也没干。还要把这条 LockRecord 删除掉 每次解锁（即执行 <code>monitorexit</code>）的时候都会将最低的一个相关 <code>Lock Record</code> 移除掉。</p><p>偏向锁的释放可参考 <a href="http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp#l1923">bytecodeInterpreter.cpp#1923</a>，这里也不贴了。偏向锁的释放只要将对应 <code>Lock Record</code> 释放就好了，但这里的释放并不会将 mark word 里面的 thread ID 去掉，这样做是为了下一次更方便的加锁。而轻量级锁则需要将 <code>Displaced Mark Word</code> 替换到对象头的 mark word 中。如果 CAS 失败或者是重量级锁则进入到 <code>InterpreterRuntime::monitorexit</code> 方法中。</p><h3 id="4-1-8-锁重入"><a href="#4-1-8-锁重入" class="headerlink" title="4.1.8. 锁重入"></a>4.1.8. 锁重入</h3><p>该线程 ID 是自己的,则表示可重入,直接可以获取 (此时在自己的线程栈中继续生成一条新的 Lock Record)</p><h3 id="4-1-9-其他撤销"><a href="#4-1-9-其他撤销" class="headerlink" title="4.1.9. 其他撤销"></a>4.1.9. 其他撤销</h3><p><a href="https://www.cnblogs.com/father-of-little-pig/p/16314318.html">https://www.cnblogs.com/father-of-little-pig/p/16314318.html</a><br>状态跟踪<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162218.png"></p><h4 id="4-1-9-1-偏向锁撤销之调用对象-HashCode"><a href="#4-1-9-1-偏向锁撤销之调用对象-HashCode" class="headerlink" title="4.1.9.1. 偏向锁撤销之调用对象 HashCode"></a>4.1.9.1. 偏向锁撤销之调用对象 HashCode</h4><p><span style="display:none">%%<br>▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230604-0833%%</span>❕ ^6jfu5x</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162016.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162033.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162047.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162101.png"></p><h5 id="4-1-9-1-1-为什么调用-hashcode-会撤销偏向锁"><a href="#4-1-9-1-1-为什么调用-hashcode-会撤销偏向锁" class="headerlink" title="4.1.9.1.1. 为什么调用 hashcode 会撤销偏向锁"></a>4.1.9.1.1. 为什么调用 hashcode 会撤销偏向锁</h5><p>[[20221112-Java GC详解 - 最全面的理解Java对象结构 - 对象指针 OOPs  HeapDump性能社区#^jy0jkr]]</p><h5 id="4-1-9-1-2-hashcode-存储位置"><a href="#4-1-9-1-2-hashcode-存储位置" class="headerlink" title="4.1.9.1.2. hashcode 存储位置"></a>4.1.9.1.2. hashcode 存储位置</h5><h4 id="4-1-9-2-偏向锁撤销之调用-wait-x2F-notify"><a href="#4-1-9-2-偏向锁撤销之调用-wait-x2F-notify" class="headerlink" title="4.1.9.2. 偏向锁撤销之调用 wait&#x2F;notify"></a>4.1.9.2. 偏向锁撤销之调用 wait&#x2F;notify</h4><p>偏向锁状态执行 obj.notify() 会升级为轻量级锁，调用 obj.wait(timeout) 会升级为重量级锁</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162326.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112162346.png"></p><h3 id="4-1-10-批量重偏向和批量撤销"><a href="#4-1-10-批量重偏向和批量撤销" class="headerlink" title="4.1.10. 批量重偏向和批量撤销"></a>4.1.10. 批量重偏向和批量撤销</h3><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;偏向锁理论太抽象，实战了解下偏向锁如何发生以及如何升级【实战篇】 - 掘金]]</p><p>撤销偏向和重偏向都是 <code>以类为单位批量</code> 进行的<br>依赖三个阈值作出判断：</p><figure class="highlight plaintext"><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 plaintext"># 批量重偏向阈值 <br>-XX:BiasedLockingBulkRebiasThreshold=20 <br># 重置计数的延迟时间 <br>-XX:BiasedLockingDecayTime=25000 <br># 批量撤销阈值 <br>-XX:BiasedLockingBulkRevokeThreshold=40<br></code></pre></td></tr></table></figure><p>简单总结，对于 <strong>一个类</strong>，按默认参数来说：<br><strong>单个偏向撤销的计数达到 20，就会进行批量重偏向。<br>距上次批量重偏向 25 秒内，计数达到 40，就会发生批量撤销。</strong><br>每隔 (&gt;&#x3D;) 25 秒，会重置在 [20, 40) 内的计数为 0，这意味着可以发生多次批量重偏向。</p><p>注意：对于一个类来说，批量撤销只能发生一次，因为批量撤销后，该类禁用了可偏向属性，后面该类的对象都是不可偏向的，包括新创建的对象。</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-[博客大赛] 图文并茂!!讲解 JUC 重量级锁、轻量级锁、自旋、锁膨胀…._qq60751173d6bae 的技术博客 _51CTO 博客]]</p><h3 id="4-1-11-源码解析"><a href="#4-1-11-源码解析" class="headerlink" title="4.1.11. 源码解析"></a>4.1.11. 源码解析</h3><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-死磕Synchronized底层实现–偏向锁-Java知音]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-Java锁与线程的那些事]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-Synchronized 偏向锁升级 - MaXianZhe - 博客园]]</p><h2 id="4-2-轻量级锁"><a href="#4-2-轻量级锁" class="headerlink" title="4.2. 轻量级锁"></a>4.2. 轻量级锁</h2><h3 id="4-2-1-概念作用-多个线程交替执行⭐️🔴"><a href="#4-2-1-概念作用-多个线程交替执行⭐️🔴" class="headerlink" title="4.2.1. 概念作用 - 多个线程交替执行⭐️🔴"></a>4.2.1. 概念作用 - 多个线程交替执行⭐️🔴</h3><p>轻量级锁是 JDK 6 之中加入的新型锁机制，它名字中的“轻量级”是相对于使用 monitor 的传统锁而言的，因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是，轻量级锁并不是用来代替重量级锁的。<br>引入轻量级锁的目的：在 <code>多线程交替执行同步块的情况下</code>，尽量避免重量级锁引起的性能消耗，但是如果多个线程在同一时刻进入临界区，会导致轻量级锁膨胀升级重量级锁，所以轻量级锁的出现并非是要替代重量级锁。</p><h3 id="4-2-2-加锁流程-轻量级锁加锁及锁膨胀⭐️🔴⭐️🔴"><a href="#4-2-2-加锁流程-轻量级锁加锁及锁膨胀⭐️🔴⭐️🔴" class="headerlink" title="4.2.2. 加锁流程 - 轻量级锁加锁及锁膨胀⭐️🔴⭐️🔴"></a>4.2.2. 加锁流程 - 轻量级锁加锁及锁膨胀⭐️🔴⭐️🔴</h3><p><a href="https://www.javazhiyin.com/24364.html">https://www.javazhiyin.com/24364.html</a><br><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a><br><a href="https://dandelioncloud.cn/article/details/1403089140002131970">https://dandelioncloud.cn/article/details/1403089140002131970</a></p><p>HotSpot 中偏向锁的具体实现可参考 <a href="http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp#l1816">bytecodeInterpreter.cpp#1816</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202101854.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112085951.jpg"></p><p><span style="background-color:#ff00ff">多个线程竞争偏向锁导致偏向锁升级为轻量级锁</span></p><ol><li>JVM 在当前线程的栈帧中创建 Lock Reocrd，<span style="background-color:#ff00ff">将内部 obj （ptr_to_obj）指针指向锁对像</span>。然后构造一个无锁状态的 Mark Word(相当于<span style="background-color:#ff00ff">复制无锁状态的 Mark Word 到锁记录中</span>)，并将 lock record 中的 Displaced Mark Word 指向它。（Displaced Mark Word，供解锁时恢复锁对象对象头）</li><li><span style="background-color:#ff00ff">线程尝试使用 CAS 将对象头中的 Mark Word 中的 ptr_to_lock_record(指向栈中锁记录的指针) 替换为 上面第一步刚在栈中创建的 Lock Reocrd 的指针。如果成功则获得锁</span></li><li>CAS 失败时判断检查已获取轻量级锁线程是否为自己（遍历栈帧查找其它 obj* 指向同个对象且 mark word 不为空的 lock record），如果是则代表当前为一次锁重入，就将当前 lock record 的 mark word （displaced mark word）置为 null(上面第一步设置的)，即当前 lock record 即作为一个计数器使用，结束。</li><li>若 CAS 失败且当前线程栈帧中不存在其它指向该锁对象的 lock record，说明存在其它线程竞争，锁膨胀至重量级锁。</li></ol><p>Mark Word 布局温习</p><a href="/2022/11/09/007-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E4%B8%93%E9%A2%98/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA-1%E3%80%81%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98/" title="对象创建-1、对象内存">对象创建-1、对象内存</a><h3 id="4-2-3-解锁流程"><a href="#4-2-3-解锁流程" class="headerlink" title="4.2.3. 解锁流程"></a>4.2.3. 解锁流程</h3><p><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a><br><a href="https://dandelioncloud.cn/article/details/1403089140002131970">https://dandelioncloud.cn/article/details/1403089140002131970</a></p><p>轻量级锁释放的入口在 <a href="http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp#l1923">bytecodeInterpreter.cpp#1923</a>。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113125337.png"></p><ol><li>遍历线程栈帧，找多所有 obj* 指向该锁对象的 lock record。</li><li>如果 lock record 的 displaced mark word 为 null，代表这是一条重入所记录，将 obj 置为 null 后 continue。</li><li>如果 lock record 的 displaced mark word 不为 null，则利用 <code> CAS 操作</code> 将锁对象对象头的 mark word 恢复成 displaced mark word，这一步成功则说明解锁成功，失败则膨胀至重量锁。</li></ol><p>轻量级锁释放时需要将 <code>Displaced Mark Word</code> <span style="background-color:#ff00ff">替换到</span>对象头的 <code>mark word</code> 中。如果 CAS 失败或者是重量级锁则进入到 <code>InterpreterRuntime::monitorexit</code> 方法中。</p><h3 id="4-2-4-优点"><a href="#4-2-4-优点" class="headerlink" title="4.2.4. 优点"></a>4.2.4. 优点</h3><p>正常交替流程<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202142955.png"></p><p>其性能提升的依据是对于绝大部分的锁在整个生命周期内都是不会存在竞争。在多线程交替执行同步块的情况下，可以避免重量级锁引起的性能消耗。</p><h3 id="4-2-5-缺点⭐️🔴"><a href="#4-2-5-缺点⭐️🔴" class="headerlink" title="4.2.5. 缺点⭐️🔴"></a>4.2.5. 缺点⭐️🔴</h3><p>在有多线程竞争的情况下轻量级锁增加了额外开销。</p><h3 id="4-2-6-锁重入"><a href="#4-2-6-锁重入" class="headerlink" title="4.2.6. 锁重入"></a>4.2.6. 锁重入</h3><p>轻量级锁在拷贝 mark word 到线程栈 Lock Record 中时，如果有重入锁，则在线程栈中继续压栈 Lock Record 记录，只不过 mark word 的值为空，等到解锁后，依次弹出，最终将 mard word 恢复到对象头中，如图所示</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112135554.png"></p><p>[[20221111-轻量级锁（锁重入的实现方式）-蒲公英云]]<br>[[Java锁与线程状态的那些事.pdf]]</p><h3 id="4-2-7-源码"><a href="#4-2-7-源码" class="headerlink" title="4.2.7. 源码"></a>4.2.7. 源码</h3><p><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a><br>[[轻量级锁加锁 &amp; 解锁过程  gorden5566]]<br>[[轻量级锁（锁重入的实现方式）-蒲公英云]]</p><h2 id="4-3-自旋锁"><a href="#4-3-自旋锁" class="headerlink" title="4.3. 自旋锁"></a>4.3. 自旋锁</h2><blockquote><p>线程的阻塞和唤醒需要 CPU 从用户态转为核心态，频繁的阻塞和唤醒对 CPU 来说是一件负担很重的工作，势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面，对象锁的锁状态只会持续很短一段时间，为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。<br>何谓自旋锁？<br>所谓自旋锁，就是让该线程等待一段时间，不会被立即挂起，看持有锁的线程是否会很快释放锁。怎么等待呢？执行一段无意义的循环即可（自旋）。<br>自旋等待不能替代阻塞，先不说对处理器数量的要求（多核，貌似现在没有单核的处理器了），虽然它可以避免线程切换带来的开销，但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁，那么自旋的效率就非常好，反之，自旋的线程就会白白消耗掉处理的资源，它不会做任何有意义的工作，典型的占着茅坑不拉屎，这样反而会带来性能上的浪费。所以说，自旋等待的时间（自旋的次数）必须要有一个限度，如果自旋超过了定义的时间仍然没有获取到锁，则应该被挂起。<br><span style="background-color:#ff00ff">自旋锁在 JDK 1.4.2 中引入，默认关闭，但是可以使用 -XX:+UseSpinning 开开启，在 JDK1.6 中默认开启。同时自旋的默认次数为 10 次，可以通过参数 -XX:PreBlockSpin 来调整；</span><br>如果通过参数 -XX:preBlockSpin 来调整自旋锁的自旋次数，会带来诸多不便。假如我将参数调整为 10，但是系统很多线程都是等你刚刚退出的时候就释放了锁（假如你多自旋一两次就可以获取锁），你是不是很尴尬。于是 JDK1.6 引入自适应的自旋锁，让虚拟机会变得越来越聪明。</p></blockquote><p><strong>适应自旋锁</strong></p><blockquote><p>JDK 1.6 引入了更加聪明的自旋锁，即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的，它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢？线程如果自旋成功了，那么下次自旋的次数会更加多，因为虚拟机认为既然上次成功了，那么此次自旋也很有可能会再次成功，那么它就会允许自旋等待持续的次数更多。反之，如果对于某个锁，很少有自旋能够成功的，那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程，以免浪费处理器资源。<br>有了自适应自旋锁，随着程序运行和性能监控信息的不断完善，虚拟机对程序锁的状况预测会越来越准确，虚拟机会变得越来越聪明。</p></blockquote><h2 id="4-4-锁消除"><a href="#4-4-锁消除" class="headerlink" title="4.4. 锁消除"></a>4.4. 锁消除</h2><blockquote><p>为了保证数据的完整性，我们在进行操作时需要对这部分操作进行同步控制，但是在有些情况下，JVM 检测到不可能存在共享数据竞争，这是 JVM 会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。<br>如果不存在竞争，为什么还需要加锁呢？所以锁消除可以节省毫无意义的请求锁的时间。变量是否逃逸，对于虚拟机来说需要使用数据流分析来确定，但是对于我们程序员来说这还不清楚么？我们会在明明知道不存在数据竞争的代码块前加上同步吗？但是有时候程序并不是我们所想的那样？我们虽然没有显示使用锁，但是我们在使用一些 JDK 的内置 API 时，如 StringBuffer、Vector、HashTable 等，这个时候会存在隐形的加锁操作。比如 StringBuffer 的 append() 方法，Vector 的 add() 方法：</p></blockquote><figure class="highlight cpp"><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 cpp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-type">void</span> <span class="hljs-title">vectorTest</span><span class="hljs-params">()</span></span>&#123;<br>     Vector&lt;String&gt; vector = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Vector</span>&lt;String&gt;();<br>     <span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> i = <span class="hljs-number">0</span> ; i &lt; <span class="hljs-number">10</span> ; i++)&#123;<br>         vector.<span class="hljs-built_in">add</span>(i + <span class="hljs-string">&quot;&quot;</span>);<br>     &#125;<br> <br>     System.out.<span class="hljs-built_in">println</span>(vector);<br> &#125;<br></code></pre></td></tr></table></figure><p>在运行这段代码时，JVM 可以明显检测到变量 vector 没有逃逸出方法 vectorTest() 之外，所以 JVM 可以大胆地将 vector 内部的加锁操作消除。</p><h2 id="4-5-锁粗化"><a href="#4-5-锁粗化" class="headerlink" title="4.5. 锁粗化"></a>4.5. 锁粗化</h2><blockquote><p>我们知道在使用同步锁的时候，需要让同步块的作用范围尽可能小—仅在共享数据的实际作用域中才进行同步，这样做的目的是为了使需要同步的操作数量尽可能缩小，如果存在锁竞争，那么等待锁的线程也能尽快拿到锁。<br>在大多数的情况下，上述观点是正确的，LZ 也一直坚持着这个观点。但是如果一系列的连续加锁解锁操作，可能会导致不必要的性能损耗，所以引入锁粗话的概念。<br>锁粗话概念比较好理解，就是将多个连续的加锁、解锁操作连接在一起，扩展成一个范围更大的锁。如上面实例：vector 每次 add 的时候都需要加锁操作，JVM 检测到对同一个对象（vector）连续加锁、解锁操作，会合并一个更大范围的加锁、解锁操作，即加锁解锁操作会移到 for 循环之外。</p></blockquote><h2 id="4-6-应用场景"><a href="#4-6-应用场景" class="headerlink" title="4.6. 应用场景"></a>4.6. 应用场景</h2><ul><li>偏向锁 : 偏向锁适合在只有一个线程访问锁的场景,在此种场景下,线程只需要执行一次 CAS 获取偏向锁,后续该线程可重入访问该锁时仅仅只需要简单的判断 Mark Word 的线程 ID 即可</li><li>轻量级锁 : 轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时间的竞争,此种场景下,线程每次获取锁只需要执行一次 CAS 即可</li><li>重量级锁 : 重量级锁适合在多线程竞争环境下访问锁,执行临界区的时间比较长,由于竞争激烈,自旋后未获取到锁的线程将会被挂起进入等待队列,等待持有锁的线程释放锁后唤醒它.此种场景下,线程每次都需要进行多次 CAS 操作,操作失败将会被放入队列里等待唤醒.</li></ul><h1 id="5-锁升级"><a href="#5-锁升级" class="headerlink" title="5. 锁升级"></a>5. 锁升级</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230604074200.jpg" alt="image-20200129142133191"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112130237.png"></p><p>无锁是锁升级前的一个中间态，必须要恢复到无锁才能进行升级，因为需要有拷贝 mark word 的过程，并且修改指针。</p><p><code>思考1：重量级锁释放之后变为无锁，此时有新的线程来调用同步块，会获取什么锁？</code></p><blockquote><p>通过实验可以得出，后面的线程会获得轻量级锁，相当于线程竞争不激烈，多个线程通过 CAS 就能轮流获取锁，并且释放。</p></blockquote><p><a href="https://img2022.cnblogs.com/blog/1765702/202205/1765702-20220529150327464-184883137.png"><img src="https://img2022.cnblogs.com/blog/1765702/202205/1765702-20220529150327464-184883137.png"></a></p><p><code>思考2：为什么有轻量级锁还需要重量级锁？</code></p><blockquote><p>因为轻量级锁时通过 CAS 自旋的方式获取锁，该方式消耗 CPU 资源的，如果锁的时间长，或者自旋线程多，CPU 会被大量消耗；而重量级锁有等待队列，所有拿不到锁的进入等待队列，不需要消耗 CPU 资源。</p></blockquote><p><code>思考3：偏向锁是否一定比轻量级锁效率高吗？</code></p><blockquote><p>不一定，在明确知道会有多线程竞争的情况下，偏向锁肯定会涉及锁撤销，需要暂停线程，回到安全点，并检查线程是否活着，故撤销需要消耗性能，这时候直接使用轻量级锁。<br>JVM 启动过程，会有很多线程竞争，所以默认情况启动时不打开偏向锁，过一段儿时间再打开。</p></blockquote><h2 id="5-1-锁记录-LockRecord"><a href="#5-1-锁记录-LockRecord" class="headerlink" title="5.1. 锁记录 ( LockRecord)"></a>5.1. 锁记录 ( LockRecord)</h2><p>线程栈的栈帧中，可以有多个 LockRecord</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112085951.jpg"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221112084915.jpg"></p><p>[[20221111-Synchronized 探秘 - 软件即世界]]</p><h2 id="5-2-锁对象-ObjectRef"><a href="#5-2-锁对象-ObjectRef" class="headerlink" title="5.2. 锁对象 (ObjectRef)"></a>5.2. 锁对象 (ObjectRef)</h2><p><a href="#2-1-1-%E4%B8%89%E7%A7%8D%E9%94%81%E7%B1%BB%E5%9E%8B">作为锁的对象</a></p><h2 id="5-3-对象锁-ObjectMonitor"><a href="#5-3-对象锁-ObjectMonitor" class="headerlink" title="5.3. 对象锁 (ObjectMonitor)"></a>5.3. 对象锁 (ObjectMonitor)</h2><p><a href="#2-3-1-ObjectMonitor">ObjectMonitor</a></p><h2 id="5-4-升级图示"><a href="#5-4-升级图示" class="headerlink" title="5.4. 升级图示"></a>5.4. 升级图示</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113123434.png"></p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221112-[博客大赛] 图文并茂!!讲解 JUC 重量级锁、轻量级锁、自旋、锁膨胀…._qq60751173d6bae 的技术博客 _51CTO 博客]]</p><h2 id="5-5-锁升级原理-流程-⭐️🔴⭐️🔴"><a href="#5-5-锁升级原理-流程-⭐️🔴⭐️🔴" class="headerlink" title="5.5. 锁升级原理 (流程)⭐️🔴⭐️🔴"></a>5.5. 锁升级原理 (流程)⭐️🔴⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221202103858.png"></p><p>Synchronized 在 jdk1.6 版本之前，是通过重量级锁的方式来实现线程之间锁的竞争。之所以称它为重量级锁，是因为它的底层底层依赖操作系统的 <code>Mutex Lock</code> 来实现互斥功能。 Mutex 是系统方法，由于权限隔离的关系，应用程序调用系统方法时需要切换到内核态来执行。这里涉及到用户态向内核态的切换，这个切换会带来性能的损耗。</p><p>在 jdk1.6 版本中，synchronized 增加了锁升级的机制，来平衡数据安全性和性能。简单来说，就是线程去访问 synchronized 同步代码块的时候，synchronized 根据线程竞争情况，会先尝试在不加重量级锁的情况下去保证线程安全性。所以引入了偏向锁和轻量级锁的机制。</p><p>偏向锁，就是直接把当前锁偏向于某个线程，简单来说就是通过 CAS 修改偏向锁标记，这种锁适合同一个线程多次去申请同一个锁资源并且没有其他线程竞争的场景。<br>轻量级锁也可以称为自旋锁，基于自适应自旋的机制，通过多次自旋重试去竞争锁。自旋锁优点在于它避免避免了用户态到内核态的切换带来的性能开销。</p><p>Synchronized 引入了锁升级的机制之后，如果有线程去竞争锁： 首先，synchronized 会尝试使用偏向锁的方式去竞争锁资源，如果能够竞争到偏向锁，表示加锁成功直接返回。</p><p>如果竞争锁失败，说明当前锁已经偏向了其他线程。需要将锁升级到轻量级锁，在轻量级锁状态下，竞争锁的线程根据自适应自旋次数去尝试抢占锁资源，如果在轻量级锁状态下还是没有竞争到锁，就只能升级到重量级锁<br>在重量级锁状态下，没有竞争到锁的线程就会被阻塞，线程状态是 Blocked。处于锁等待状态的线程需要等待获得锁的线程来触发唤醒。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230604120233.png" alt="image.png"></p><p>总的来说，Synchronized 的锁升级的设计思想，在我看来本质上是一种性能和 安全性的平衡，也就是如何在不加锁的情况下能够保证线程安全性。 这种思想在编程领域比较常见，比如 Mysql 里面的 MVCC 使用版本链的方式来 解决多个并行事务的竞争问题。</p><h1 id="6-为什么要废弃偏向锁"><a href="#6-为什么要废弃偏向锁" class="headerlink" title="6. 为什么要废弃偏向锁"></a>6. 为什么要废弃偏向锁</h1><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-你知道 Java 的偏向锁被废弃掉了吗？_杏仁技术站的博客-CSDN博客]]</p><p>在 Java15 后默认禁用偏向锁可能会导致一些 Java 应用的性能下降，所以 HotSpot 提供了显示开启偏向锁的命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"># 在 Java15 后，手动开启偏向锁在启动的时候会收到警告信息-XX:+UseBiasedLocking<br></code></pre></td></tr></table></figure><p><a href="https://developer.aliyun.com/article/916746#slide-8">https://developer.aliyun.com/article/916746#slide-8</a></p><h1 id="7-对比"><a href="#7-对比" class="headerlink" title="7. 对比"></a>7. 对比</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221113184722.png"></p><h1 id="8-实战经验"><a href="#8-实战经验" class="headerlink" title="8. 实战经验"></a>8. 实战经验</h1><h2 id="8-1-减少-synchronized-的范围"><a href="#8-1-减少-synchronized-的范围" class="headerlink" title="8.1. 减少 synchronized 的范围"></a>8.1. 减少 synchronized 的范围</h2><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">synchronized</span> (Demo01.class) &#123;<br>  System.out.println(<span class="hljs-string">&quot;aaa&quot;</span>);<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="8-2-降低-synchronized-锁的粒度"><a href="#8-2-降低-synchronized-锁的粒度" class="headerlink" title="8.2. 降低 synchronized 锁的粒度"></a>8.2. 降低 synchronized 锁的粒度</h2><p>将一个锁拆分为多个锁提高并发度</p><h2 id="8-3-读写分离"><a href="#8-3-读写分离" class="headerlink" title="8.3. 读写分离"></a>8.3. 读写分离</h2><p>读取时不加锁，写入和删除时加锁<br>ConcurrentHashMap，CopyOnWriteArrayList 和 ConyOnWriteSet</p><h2 id="8-4-常见错误"><a href="#8-4-常见错误" class="headerlink" title="8.4. 常见错误"></a>8.4. 常见错误</h2><h3 id="8-4-1-synchronized-new-Object"><a href="#8-4-1-synchronized-new-Object" class="headerlink" title="8.4.1. synchronized (new Object())"></a>8.4.1. synchronized (new Object())</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">synchronized (new Object())<br></code></pre></td></tr></table></figure><p>每次调用创建的是不同的锁，相当于无锁</p><h3 id="8-4-2-synchronized-享元"><a href="#8-4-2-synchronized-享元" class="headerlink" title="8.4.2. synchronized (享元)"></a>8.4.2. synchronized (享元)</h3><figure class="highlight plaintext"><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 plaintext">private Integer count;<br>synchronized (count)<br></code></pre></td></tr></table></figure><p>String，Boolean 在实现了都用了享元模式，即值在一定范围内，对象是同一个。所以看似是用了不同的对象，其实用的是同一个对象。会导致一个锁被多个地方使用</p><h3 id="8-4-3-正确的加锁姿势"><a href="#8-4-3-正确的加锁姿势" class="headerlink" title="8.4.3. 正确的加锁姿势"></a>8.4.3. 正确的加锁姿势</h3><figure class="highlight plaintext"><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 plaintext">// 普通对象锁<br>private final Object lock = new Object();<br>// 静态对象锁<br>private static final Object lock = new Object();<br></code></pre></td></tr></table></figure><h1 id="9-源码入口"><a href="#9-源码入口" class="headerlink" title="9. 源码入口"></a>9. 源码入口</h1><p><a href="https://github.com/farmerjohngit/myblog/issues/13">https://github.com/farmerjohngit/myblog/issues/13</a><br>偏向锁入口，肯定是要在源码中找到对 <code>monitorenter</code> 指令解析的地方。在 HotSpot 的中有两处地方对 <code>monitorenter</code> 指令进行解析：一个是在 <a href="http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp#l1816">bytecodeInterpreter.cpp#1816</a> ，另一个是在 <a href="http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/cpu/x86/vm/templateTable_x86_64.cpp#l3667">templateTable_x86_64.cpp#3667</a>。其中，<code>bytecodeInterpreter</code> 是 JVM 中的字节码解释器， <code>templateInterpreter</code> 为模板解释器。HotSpot 对运行效率有着极其执着的追求，显然会倾向于用模板解释器来实现。R 大的 <a href="https://book.douban.com/annotation/31407691/">读书笔记</a> 中有说明，HotSpot 中只用到了模板解释器，并没有用到字节码解释器。因此，本文认为 <code>montorenter</code> 的解析入口为 <a href="http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/cpu/x86/vm/templateTable_x86_64.cpp#l3667">templateTable_x86_64.cpp#3667</a>。但模板解释器 <code>templateInterpreter</code> 都是汇编代码，不易读，且实现逻辑与字节码解释器 <code>bytecodeInterpreter</code> 大体一致。因此本文的源码都以 <code>bytecodeInterpreter</code> 来说明，借此窥探 <code>synchronized</code> 的实现原理。</p><h1 id="10-面试题"><a href="#10-面试题" class="headerlink" title="10. 面试题"></a>10. 面试题</h1><h2 id="10-1-wait-和-notify-这个为什么要在-synchronized-代码块中"><a href="#10-1-wait-和-notify-这个为什么要在-synchronized-代码块中" class="headerlink" title="10.1. wait 和 notify 这个为什么要在 synchronized 代码块中"></a>10.1. wait 和 notify 这个为什么要在 synchronized 代码块中</h2><p>在多线程里面，要实现多个线程之间的通信，除了<span style="background-color:#ff00ff">管道流</span>以外，只能通过共享变量的方法来实现，也就是线程 t1 修改共享变量 s，线程 t2 获取修改后的共享变量 s，从而完成数据通信。但是多线程本身具有并行执行的特性，也就是在同一时刻，多个线程可以同时执行。在这种情况下，线程 t2 在访问共享变量 s 之前，必须要知道线程 t1 已经修改过了共享变量 s，否则就需要等待。同时，线程 t1 修改过了共享变量 S 之后，还需要通知在等待中的线程 t2。所以要在这种特性下要去实现线程之间的通信，就<span style="background-color:#ff00ff">必须要有一个竞争条件</span>控制线程在什么条件下等待，什么条件下唤醒。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230607210753.png" alt="image.png"></p><p>而 Synchronized 同步关键字就可以实现这样一个互斥条件，也就是在通过共享变量来实现多个线程通信的场景里面，参与通信的线程必须要竞争到这个共享变量的锁资源，才有资格对共享变量做修改，修改完成后就释放锁，那么其他的线程就可以再次来竞争同一个共享变量的锁来获取修改后的数据，从而完成线程之前的通信。<br>所以这也是为什么 wait&#x2F;notify 需要放在 Synchronized 同步代码块中的原因，有了 Synchronized 同步锁，就可以实现对多个通信线程之间的互斥，实现条件等待和条件唤醒。<br>另外，为了避免 wait&#x2F;notify 的错误使用，jdk 强制要求把 wait&#x2F;notify 写在同步代码块里面，否则会抛出 IllegalMonitorStateException<br>最后，基于 wait&#x2F;notify 的特性，非常适合实现生产者消费者的模型，比如说用 wait&#x2F;notify 来实现连接池就绪前的等待与就绪后的唤醒。</p><p>wait 和 notify 的实现逻辑：<a href="/2022/11/08/003-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B8%93%E9%A2%98/%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80-2%E3%80%81Synchronized/" title="并发基础-2、Synchronized">并发基础-2、Synchronized</a></p><h3 id="10-1-1-管道流"><a href="#10-1-1-管道流" class="headerlink" title="10.1.1. 管道流"></a>10.1.1. 管道流</h3><p><a href="https://www.cnblogs.com/qlqwjy/p/10118733.html">https://www.cnblogs.com/qlqwjy/p/10118733.html</a><br>在 Java 语言中提供了各种各样的输入&#x2F;输出流 Stream, 使我们能够方便地对数据进行操作，其中管道流 (pipeStream) 是一种特殊的流，用于在不同线程间直接传送数据。一个发送数据到输出管道，另一个线程从输入管道中读数据。通过使用管道，实现不同线程间的通信，而无需借助于临时文件之类的动西。</p><p>　　在 Java 的 JDK 中提供了 4 个类来使线程间可以通信:</p><p>　　(1)PipedInputStream 和 PipedOutputStream</p><p>　　(2)PipedReader 和 PipedWriter</p><h1 id="11-参考与感谢"><a href="#11-参考与感谢" class="headerlink" title="11. 参考与感谢"></a>11. 参考与感谢</h1><h2 id="11-1-黑马"><a href="#11-1-黑马" class="headerlink" title="11.1. 黑马"></a>11.1. 黑马</h2><h3 id="11-1-1-视频"><a href="#11-1-1-视频" class="headerlink" title="11.1.1. 视频"></a>11.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/av70549061?p=180">https://www.bilibili.com/video/av70549061?p=180</a></p><h2 id="11-2-其他"><a href="#11-2-其他" class="headerlink" title="11.2. 其他"></a>11.2. 其他</h2><p><a href="https://juejin.im/post/5c37377351882525ec200f9e">https://juejin.im/post/5c37377351882525ec200f9e</a><br><a href="https://www.cnblogs.com/paddix/p/5367116.html">https://www.cnblogs.com/paddix/p/5367116.html</a><br><a href="https://juejin.cn/post/6844903918653145102">https://juejin.cn/post/6844903918653145102</a><br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221109-Synchronized解析——如果你愿意一层一层剥开我的心 - 掘金]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221110-由Java 15废弃偏向锁，谈谈Java Synchronized 的锁机制 - Yano_nankai - 博客园]]</p><p><a href="https://www.bilibili.com/video/BV168411e7wr/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV168411e7wr/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.yuque.com/qieshuyuni/ws3p3g/ai3b4s">https://www.yuque.com/qieshuyuni/ws3p3g/ai3b4s</a></p><p>✅ <a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a></p><p>：[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221111-(三)死磕并发之深入Hotspot源码剖析Synchronized关键字实现 - 掘金]]</p><p><a href="https://juejin.cn/post/6995156191286394888">https://juejin.cn/post/6995156191286394888</a></p><p><a href="https://www.jianshu.com/p/46a874d52b71">https://www.jianshu.com/p/46a874d52b71</a></p><p><a href="https://www.cnblogs.com/father-of-little-pig/p/16314318.html">https://www.cnblogs.com/father-of-little-pig/p/16314318.html</a></p><p>源码 <a href="https://github.com/openjdk/jdk/blob/jdk8-b120/hotspot/src/share/vm/runtime/objectMonitor.hpp">https://github.com/openjdk/jdk/blob/jdk8-b120/hotspot/src/share/vm/runtime/objectMonitor.hpp</a><br><a href="http://60.205.225.95/?p=49">http://60.205.225.95/?p=49</a></p><h2 id="11-3-源码分析"><a href="#11-3-源码分析" class="headerlink" title="11.3. 源码分析"></a>11.3. 源码分析</h2><p><a href="https://juejin.cn/post/7104638789456232478#heading-12">https://juejin.cn/post/7104638789456232478#heading-12</a><br><a href="https://xiaomi-info.github.io/2020/03/24/synchronized/">https://xiaomi-info.github.io/2020/03/24/synchronized/</a><br><a href="https://juejin.cn/post/6977744259688939551#heading-9">https://juejin.cn/post/6977744259688939551#heading-9</a><br><a href="https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/">https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/</a></p><p>应用：[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;(二)深入理解Java并发编程之Synchronized关键字实现原理剖析 - 简书]]</p>]]></content>
      
      
      <categories>
          
          <category> 并发编程专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 并发编程专题 </tag>
            
            <tag> 关键字 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-8、HashMap版本变化</title>
      <link href="/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-8%E3%80%81HashMap%E7%89%88%E6%9C%AC%E5%8F%98%E5%8C%96/"/>
      <url>/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-8%E3%80%81HashMap%E7%89%88%E6%9C%AC%E5%8F%98%E5%8C%96/</url>
      
        <content type="html"><![CDATA[<h1 id="脑图"><a href="#脑图" class="headerlink" title="脑图"></a>脑图</h1><p><a href="https://www.processon.com/view/link/63687577f346fb2f8d429007">https://www.processon.com/view/link/63687577f346fb2f8d429007</a></p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-9、集合面试题</title>
      <link href="/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-9%E3%80%81%E9%9B%86%E5%90%88%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
      <url>/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-9%E3%80%81%E9%9B%86%E5%90%88%E9%9D%A2%E8%AF%95%E9%A2%98/</url>
      
        <content type="html"><![CDATA[<h1 id="1-ConcurrentHashMap-的-size-方法是线-程安全的吗？为什么"><a href="#1-ConcurrentHashMap-的-size-方法是线-程安全的吗？为什么" class="headerlink" title="1. ConcurrentHashMap 的 size() 方法是线 程安全的吗？为什么"></a>1. ConcurrentHashMap 的 size() 方法是线 程安全的吗？为什么</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230411152701.png" alt="image.png"></p><p>ConcurrentHashMap 的 size() 方法是非线程安全的。也就是说，当有线程调用 put 方法在添加元素的时候，其他线程在调用 size() 方法获取的元素个数和实际存储元素个数是不一致的。<br>原因是 size() 方法是一个&#x3D;&#x3D;非同步方法&#x3D;&#x3D;，<span style="background-color:#ff00ff">put() 方法和 size() 方法并没有实现同步锁</span>put() 方法的实现逻辑是：在 hash 表上添加或者修改某个元素，然后再对总的元素个数进行累加。其中，线程的安全性仅仅局限在 hash 表数组粒度的锁同步，避免同一个节点出现数据竞争带来线程安全问题。</p><p><strong>数组元素个数的累加方式用到了两个方案：</strong></p><blockquote><ol><li>当线程竞争不激烈的时候，直接用 cas 的方式对一个 long 类型的变量做原子递增。</li><li>当线程竞争比较激烈的时候，使用一个 CounterCell 数组，用分而治之的思想减少多线程竞争，从而实现元素个数的原子累加。</li></ol></blockquote><p>size() 方法的逻辑就是<span style="background-color:#ff00ff">遍历 CounterCell 数组中的每个 value 值进行累加，再加上 baseCount，汇总得到一个结果</span>。所以很明显，size() 方法得到的数据和真实数据必然是不一致的。因此从 size() 方法本身来看，它的整个计算过程是线程安全的，因为这里用到了 CAS 的方式解决了并发更新问题。但是站在 ConcurrentHashMap 全局角度来看，put() 方法和 size() 方法之间的数据是不一致的，因此也就不是线程安全的。<br>之所以不像 HashTable 那样，直接在方法级别加同步锁。在我看来有两个考虑点。直接在 size() 方法加锁，就会造成数据写入的并发冲突，对性能造成影响，当然 有些朋友会说可以加读写锁，但是同样会造成 put 方法锁的范围扩大，性能影响极大！ ConcurrentHashMap 并发集合中，对于 size() 数量的一致性需求并不大，并发 集合更多的是去保证数据存储的安全性。</p><h1 id="2-HashMap-中的-hash-方法为什么要右移-16-位异或？"><a href="#2-HashMap-中的-hash-方法为什么要右移-16-位异或？" class="headerlink" title="2. HashMap 中的 hash 方法为什么要右移 16 位异或？"></a>2. HashMap 中的 hash 方法为什么要右移 16 位异或？</h1><p>之所以要对 hashCode 无符号右移 16 位并且异或，核心目的是为了让 hash 值的散列度更高，尽可能减少 hash 表的 hash 冲突，从而提升数据查找的性能。<br>在 HashMap 的 put 方法里面，是通过 Key 的 hash 值与数组的长度取模计算得 到数组的位置。 而在绝大部分的情况下，n 的值一般都会小于 2^16 次方，也就是 65536。 所以也就意味着 i 的值 ， 始终是使用 hash 值的低 16 位与 (n-1) 进行取模运算， 这个是由与运算符&amp;的特性决定的。 这样就会造成 key 的散列度不高，导致大量的 key 集中存储在固定的几个数组位置，很显然会影响到数据查找性能。因此，为了提升 key 的 hash 值的散列度，在 hash 方法里面，做了位移运算。 首先使用 key 的 hashCode 无符号右移 16 位，意味着把 hashCode 的高位移动到了低位。 然后再用 hashCode 与右移之后的值进行异或运算，就相当于把高位和低位的特征进行组合。 从而降低了 hash 冲突的概率。</p><h2 id="2-1-二次哈希"><a href="#2-1-二次哈希" class="headerlink" title="2.1. 二次哈希"></a>2.1. 二次哈希</h2><p>二次哈希指的是将哈希码的高 16 位与低 16 位进行异或运算得到的加工过的哈希码<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410222407.png" alt="image.png"></p><h1 id="3-HashMap-死循环"><a href="#3-HashMap-死循环" class="headerlink" title="3. HashMap 死循环"></a>3. HashMap 死循环</h1><p><a href="https://www.bilibili.com/video/BV1yL4y157ta/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1yL4y157ta/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="4-ConcurrentHashMap-是如何保证线程安全的"><a href="#4-ConcurrentHashMap-是如何保证线程安全的" class="headerlink" title="4. ConcurrentHashMap 是如何保证线程安全的"></a>4. ConcurrentHashMap 是如何保证线程安全的</h1><p><a href="https://www.bilibili.com/video/BV1q541127Bk/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1q541127Bk/?spm_id_from=..search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h1 id="5-参考与感谢"><a href="#5-参考与感谢" class="headerlink" title="5. 参考与感谢"></a>5. 参考与感谢</h1><a href="/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-7%E3%80%81ConcurrentHashMap(JDK1.8)/" title="集合框架-7、ConcurrentHashMap(JDK1.8)">集合框架-7、ConcurrentHashMap(JDK1.8)</a><a href="/2023/03/31/011-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%93%E9%A2%98/%E9%9D%A2%E8%AF%95%E4%B8%93%E9%A2%98-7%E3%80%81%E5%85%AB%E8%82%A1%E6%96%87/" title="面试专题-7、八股文">面试专题-7、八股文</a>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-7、ConcurrentHashMap(JDK1.8)</title>
      <link href="/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-7%E3%80%81ConcurrentHashMap(JDK1.8)/"/>
      <url>/2022/11/06/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-7%E3%80%81ConcurrentHashMap(JDK1.8)/</url>
      
        <content type="html"><![CDATA[<h1 id="1-容器初始化"><a href="#1-容器初始化" class="headerlink" title="1. 容器初始化"></a>1. 容器初始化</h1><p><a href="https://www.bilibili.com/video/BV17i4y1x71z?t=421.7">https://www.bilibili.com/video/BV17i4y1x71z?t=421.7</a></p><h2 id="1-1-源码分析"><a href="#1-1-源码分析" class="headerlink" title="1.1. 源码分析"></a>1.1. 源码分析</h2><blockquote><p>在 jdk8 的 ConcurrentHashMap 中一共有 5 个构造方法，这四个构造方法中都没有对内部的数组做初始化， 只是对一些变量的初始值做了处理</p><p>jdk8 的 ConcurrentHashMap 的<span style="background-color:#ff00ff">数组初始化是在第一次添加元素时完成</span></p></blockquote><p>PS: 传入 32，最终大小为 64，与前面的都不同</p><blockquote><p>注意，调用这个方法，得到的初始容量和我们之前讲的 HashMap 以及 jdk7 的 ConcurrentHashMap 不同，即使你传递的是一个 2 的幂次方数，该方法计算出来的初始容量依然是比这个值<span style="background-color:#ff0000">大一点</span>的 2 的幂次方数</p></blockquote><blockquote><p>JDK1.8: 传递进来一个初始容量，ConcurrentHashMap 会基于这个值计算一个比这个值<span style="background-color:#ff0000">大一半</span>的 2 的幂次方数作为初始容量</p></blockquote><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-comment">//没有维护任何变量的操作，如果调用该方法，数组长度默认是16  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">ConcurrentHashMap</span><span class="hljs-params">()</span> &#123;  <br>&#125;<br><br><span class="hljs-comment">//传递进来一个初始容量，ConcurrentHashMap会基于这个值计算一个比这个值大一半的2的幂次方数作为初始容量  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">ConcurrentHashMap</span><span class="hljs-params">(<span class="hljs-type">int</span> initialCapacity)</span> &#123;  <br>    <span class="hljs-keyword">if</span> (initialCapacity &lt; <span class="hljs-number">0</span>)  <br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>();  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">cap</span> <span class="hljs-operator">=</span> ((initialCapacity &gt;= (MAXIMUM_CAPACITY &gt;&gt;&gt; <span class="hljs-number">1</span>)) ?  <br>               MAXIMUM_CAPACITY :  <br>               tableSizeFor(initialCapacity + (initialCapacity &gt;&gt;&gt; <span class="hljs-number">1</span>) + <span class="hljs-number">1</span>));  <br>    <span class="hljs-built_in">this</span>.sizeCtl = cap;  <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><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//调用2个参数的构造  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">ConcurrentHashMap</span><span class="hljs-params">(<span class="hljs-type">int</span> initialCapacity, <span class="hljs-type">float</span> loadFactor)</span> &#123;  <br>    <span class="hljs-built_in">this</span>(initialCapacity, loadFactor, <span class="hljs-number">1</span>);  <br>&#125;<br><br><span class="hljs-comment">//计算一个大于或者等于给定的容量值，该值是2的幂次方数作为初始容量  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">ConcurrentHashMap</span><span class="hljs-params">(<span class="hljs-type">int</span> initialCapacity,  </span><br><span class="hljs-params">                         <span class="hljs-type">float</span> loadFactor, <span class="hljs-type">int</span> concurrencyLevel)</span> &#123;  <br>    <span class="hljs-keyword">if</span> (!(loadFactor &gt; <span class="hljs-number">0.0f</span>) || initialCapacity &lt; <span class="hljs-number">0</span> || concurrencyLevel &lt;= <span class="hljs-number">0</span>)  <br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>();  <br>    <span class="hljs-keyword">if</span> (initialCapacity &lt; concurrencyLevel)   <span class="hljs-comment">// Use at least as many bins  </span><br>        initialCapacity = concurrencyLevel;   <span class="hljs-comment">// as estimated threads  </span><br>    <span class="hljs-type">long</span> <span class="hljs-variable">size</span> <span class="hljs-operator">=</span> (<span class="hljs-type">long</span>)(<span class="hljs-number">1.0</span> + (<span class="hljs-type">long</span>)initialCapacity / loadFactor);  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">cap</span> <span class="hljs-operator">=</span> (size &gt;= (<span class="hljs-type">long</span>)MAXIMUM_CAPACITY) ?  <br>        MAXIMUM_CAPACITY : tableSizeFor((<span class="hljs-type">int</span>)size);  <br>    <span class="hljs-built_in">this</span>.sizeCtl = cap;  <br>&#125;<br><br><span class="hljs-comment">//基于一个Map集合，构建一个ConcurrentHashMap  </span><br><span class="hljs-comment">//初始容量为16  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">ConcurrentHashMap</span><span class="hljs-params">(Map&lt;? extends K, ? extends V&gt; m)</span> &#123;  <br>    <span class="hljs-built_in">this</span>.sizeCtl = DEFAULT_CAPACITY;  <br>    putAll(m);  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="1-2-sizeCtl-含义解释"><a href="#1-2-sizeCtl-含义解释" class="headerlink" title="1.2. sizeCtl 含义解释"></a>1.2. <code>sizeCtl</code> 含义解释</h2><blockquote><p><strong>注意：以上这些构造方法中，都涉及到一个变量 <code>sizeCtl</code>，这个变量是一个非常重要的变量，而且具有非常丰富的含义，它的值不同，对应的含义也不一样，这里我们先对这个变量不同的值的含义做一下说明，后续源码分析过程中，进一步解释</strong></p><p><code>sizeCtl</code> 为 0，代表数组未初始化， 且数组的<span style="background-color:#ff0000">初始容量为 16</span></p><p><code>sizeCtl</code> 为正数，如果数组未初始化，那么其记录的是数组的初始容量，如果数组已经初始化，那么其记录的是数组的扩容阈值</p><p><code>sizeCtl</code> 为 -1，表示数组正在进行初始化</p><p><code>sizeCtl</code> 小于 0，并且不是 -1，表示数组正在扩容， -(1+n)，表示此时有 n 个线程正在共同完成数组的扩容操作</p></blockquote><h1 id="2-添加安全"><a href="#2-添加安全" class="headerlink" title="2. 添加安全"></a>2. 添加安全</h1><h2 id="2-1-总体概述⭐️🔴"><a href="#2-1-总体概述⭐️🔴" class="headerlink" title="2.1. 总体概述⭐️🔴"></a>2.1. 总体概述⭐️🔴</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230523073423.png" alt="image.png"></p><ol><li>如果 tab 为空，调用 initTable() 方法进行初始化，通过 <code>CAS+自旋</code> 保证线程安全</li><li>如果 tab 不为空，就判断所在的桶是否为空，如果是的话，说明是第一个元素，就调用 <code>casTabAt() </code> 方法直接新建节点添加到 Node 数组中就可以了</li><li>如果正在扩容，就帮助扩容</li><li>如果没有扩容也不为空，就把元素插入桶中，先使用 synchronized 进行加锁，这个锁的粒度就是数组的具体的一个元素，fh 是当前索引位置的 hash 值，如果大于等于 0，说明是链表，否则是红黑树。链表插入会对 binCount 加一操作，新元素插入尾部，如果 key 相同覆盖原来的值</li><li>判断 binCount 是否大于等于 TREEIFY_THRESHOLD（值为 8） ，这时候调用 treeifyBin() 方法考虑将链表转换为红黑树，真正要转为红黑树还要求数组长度大于 64</li></ol><h2 id="2-2-数组初始化，initTable-方法"><a href="#2-2-数组初始化，initTable-方法" class="headerlink" title="2.2. 数组初始化，initTable 方法"></a>2.2. 数组初始化，initTable 方法</h2><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">private</span> <span class="hljs-keyword">final</span> Node&lt;K,V&gt;[] initTable() &#123;<br>    Node&lt;K,V&gt;[] tab; <span class="hljs-type">int</span> sc;<br>    <span class="hljs-comment">//cas+自旋，保证线程安全，对数组进行初始化操作</span><br>    <span class="hljs-keyword">while</span> ((tab = table) == <span class="hljs-literal">null</span> || tab.length == <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">//如果sizeCtl的值（-1）小于0，说明此时正在初始化， 让出cpu</span><br>        <span class="hljs-keyword">if</span> ((sc = sizeCtl) &lt; <span class="hljs-number">0</span>)<br>            Thread.yield(); <span class="hljs-comment">// lost initialization race; just spin</span><br>        <span class="hljs-comment">//cas修改sizeCtl的值为-1，修改成功，进行数组初始化，失败，继续自旋</span><br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (U.compareAndSwapInt(<span class="hljs-built_in">this</span>, SIZECTL, sc, -<span class="hljs-number">1</span>)) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-keyword">if</span> ((tab = table) == <span class="hljs-literal">null</span> || tab.length == <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-comment">//sizeCtl为0，取默认长度16，否则去sizeCtl的值</span><br>                    <span class="hljs-type">int</span> <span class="hljs-variable">n</span> <span class="hljs-operator">=</span> (sc &gt; <span class="hljs-number">0</span>) ? sc : DEFAULT_CAPACITY;<br>                    <span class="hljs-meta">@SuppressWarnings(&quot;unchecked&quot;)</span><br>                    <span class="hljs-comment">//基于初始长度，构建数组对象</span><br>                    Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>&lt;?,?&gt;[n];<br>                    table = tab = nt;<br>                    <span class="hljs-comment">//计算扩容阈值，并赋值给sc</span><br>                    sc = n - (n &gt;&gt;&gt; <span class="hljs-number">2</span>);<br>                &#125;<br>            &#125; <span class="hljs-keyword">finally</span> &#123;<br>                <span class="hljs-comment">//将扩容阈值，赋值给sizeCtl</span><br>                sizeCtl = sc;<br>            &#125;<br>            <span class="hljs-keyword">break</span>;<br>        &#125;<br>    &#125;<br>    <span class="hljs-keyword">return</span> tab;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-2-1-流程图"><a href="#2-2-1-流程图" class="headerlink" title="2.2.1. 流程图"></a>2.2.1. 流程图</h3><p><a href="https://www.processon.com/view/link/6369aced0e3e74618c3a6872">https://www.processon.com/view/link/6369aced0e3e74618c3a6872</a></p><h2 id="2-3-添加元素-put-x2F-putVal-方法"><a href="#2-3-添加元素-put-x2F-putVal-方法" class="headerlink" title="2.3. 添加元素 put&#x2F;putVal 方法"></a>2.3. 添加元素 put&#x2F;putVal 方法</h2><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></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><br><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-variable">HASH_BITS</span> <span class="hljs-operator">=</span> <span class="hljs-number">0x7fffffff</span>; <span class="hljs-comment">// 保证hash为正数</span><br><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-title function_">spread</span><span class="hljs-params">(<span class="hljs-type">int</span> h)</span> &#123;  <br>    <span class="hljs-keyword">return</span> (h ^ (h &gt;&gt;&gt; <span class="hljs-number">16</span>)) &amp; HASH_BITS;  <br>&#125;<br><br><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-comment">//如果有空值或者空键，直接抛异常  </span><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-comment">//基于key计算hash值，并进行一定的扰动  </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-comment">//记录某个桶上元素的个数，如果超过8个，会转成红黑树  </span><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-comment">//如果数组还未初始化，先对数组进行初始化  </span><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-comment">//如果hash计算得到的桶位置没有元素，利用cas将元素添加  </span><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-comment">//cas+自旋（和外侧的for构成自旋循环），保证元素添加安全  </span><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-comment">//如果hash计算得到的桶位置元素的hash值为MOVED，证明正在扩容，那么协助扩容  </span><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-comment">//hash计算的桶位置元素不为空，且当前没有处于扩容操作，进行元素添加  </span><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-comment">//对当前桶进行加锁，保证线程安全，执行元素添加操作  </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-comment">//普通链表节点  </span><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-comment">//树节点，将元素添加到红黑树中  </span><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-comment">//注意下，这个判断是在同步锁外部，因为 treeifyBin内部也有同步锁，并不影响</span><br>            <span class="hljs-keyword">if</span> (binCount != <span class="hljs-number">0</span>) &#123;  <br>                <span class="hljs-comment">//链表长度大于/等于8，将链表转成红黑树  </span><br>                <span class="hljs-keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD)  <br>                    treeifyBin(tab, i);  <br>                <span class="hljs-comment">//如果是重复键，直接将旧值返回  </span><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>    <span class="hljs-comment">//添加的是新元素，维护集合长度，并判断是否要进行扩容操作  </span><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><blockquote><p><strong>通过以上源码，我们可以看到，当需要添加元素时，会针对当前元素所对应的桶位进行加锁操作，这样一方面保证元素添加时，多线程的安全，同时对某个桶位加锁不会影响其他桶位的操作，进一步提升多线程的并发效率</strong></p></blockquote><h3 id="2-3-1-图示"><a href="#2-3-1-图示" class="headerlink" title="2.3.1. 图示"></a>2.3.1. 图示</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107230727.png"></p><h3 id="2-3-2-流程图"><a href="#2-3-2-流程图" class="headerlink" title="2.3.2. 流程图"></a>2.3.2. 流程图</h3><p><a href="https://www.processon.com/view/link/636a35ec5653bb5ba36c0559">https://www.processon.com/view/link/636a35ec5653bb5ba36c0559</a></p><h1 id="3-扩容安全"><a href="#3-扩容安全" class="headerlink" title="3. 扩容安全"></a>3. 扩容安全</h1><h2 id="3-1-源码分析"><a href="#3-1-源码分析" class="headerlink" title="3.1. 源码分析"></a>3.1. 源码分析</h2><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></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_">transfer</span><span class="hljs-params">(Node&lt;K,V&gt;[] tab, Node&lt;K,V&gt;[] nextTab)</span> &#123;<br>    <span class="hljs-type">int</span> <span class="hljs-variable">n</span> <span class="hljs-operator">=</span> tab.length, stride;<br>    <span class="hljs-comment">//如果是多cpu，那么每个线程划分任务，最小任务量是16个桶位的迁移</span><br>    <span class="hljs-keyword">if</span> ((stride = (NCPU &gt; <span class="hljs-number">1</span>) ? (n &gt;&gt;&gt; <span class="hljs-number">3</span>) / NCPU : n) &lt; MIN_TRANSFER_STRIDE)<br>        stride = MIN_TRANSFER_STRIDE; <span class="hljs-comment">// subdivide range</span><br>    <span class="hljs-comment">//如果是扩容线程，此时新数组为null</span><br>    <span class="hljs-keyword">if</span> (nextTab == <span class="hljs-literal">null</span>) &#123;            <span class="hljs-comment">// initiating</span><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-meta">@SuppressWarnings(&quot;unchecked&quot;)</span><br>            <span class="hljs-comment">//两倍扩容创建新数组</span><br>            Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>&lt;?,?&gt;[n &lt;&lt; <span class="hljs-number">1</span>];<br>            nextTab = nt;<br>        &#125; <span class="hljs-keyword">catch</span> (Throwable ex) &#123;      <span class="hljs-comment">// try to cope with OOME</span><br>            sizeCtl = Integer.MAX_VALUE;<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        nextTable = nextTab;<br>        <span class="hljs-comment">//记录线程开始迁移的桶位，从后往前迁移</span><br>        transferIndex = n;<br>    &#125;<br>    <span class="hljs-comment">//记录新数组的末尾</span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">nextn</span> <span class="hljs-operator">=</span> nextTab.length;<br>    <span class="hljs-comment">//已经迁移的桶位，会用这个节点占位（这个节点的hash值为-1--MOVED）</span><br>    ForwardingNode&lt;K,V&gt; fwd = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ForwardingNode</span>&lt;K,V&gt;(nextTab);<br>    <span class="hljs-type">boolean</span> <span class="hljs-variable">advance</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br>    <span class="hljs-type">boolean</span> <span class="hljs-variable">finishing</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// to ensure sweep before committing nextTab</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>, bound = <span class="hljs-number">0</span>;;) &#123;<br>        Node&lt;K,V&gt; f; <span class="hljs-type">int</span> fh;<br>        <span class="hljs-keyword">while</span> (advance) &#123;<br>            <span class="hljs-type">int</span> nextIndex, nextBound;<br>            <span class="hljs-comment">//i记录当前正在迁移桶位的索引值</span><br>            <span class="hljs-comment">//bound记录下一次任务迁移的开始桶位</span><br>            <br>            <span class="hljs-comment">//--i &gt;= bound 成立表示当前线程分配的迁移任务还没有完成</span><br>            <span class="hljs-keyword">if</span> (--i &gt;= bound || finishing)<br>                advance = <span class="hljs-literal">false</span>;<br>            <span class="hljs-comment">//没有元素需要迁移 -- 后续会去将扩容线程数减1，并判断扩容是否完成</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((nextIndex = transferIndex) &lt;= <span class="hljs-number">0</span>) &#123;<br>                i = -<span class="hljs-number">1</span>;<br>                advance = <span class="hljs-literal">false</span>;<br>            &#125;<br>            <span class="hljs-comment">//计算下一次任务迁移的开始桶位，并将这个值赋值给transferIndex</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (U.compareAndSwapInt<br>                     (<span class="hljs-built_in">this</span>, TRANSFERINDEX, nextIndex,<br>                      nextBound = (nextIndex &gt; stride ?<br>                                   nextIndex - stride : <span class="hljs-number">0</span>))) &#123;<br>                bound = nextBound;<br>                i = nextIndex - <span class="hljs-number">1</span>;<br>                advance = <span class="hljs-literal">false</span>;<br>            &#125;<br>        &#125;<br>        <span class="hljs-comment">//如果没有更多的需要迁移的桶位，就进入该if</span><br>        <span class="hljs-keyword">if</span> (i &lt; <span class="hljs-number">0</span> || i &gt;= n || i + n &gt;= nextn) &#123;<br>            <span class="hljs-type">int</span> sc;<br>            <span class="hljs-comment">//扩容结束后，保存新数组，并重新计算扩容阈值，赋值给sizeCtl</span><br>            <span class="hljs-keyword">if</span> (finishing) &#123;<br>                nextTable = <span class="hljs-literal">null</span>;<br>                table = nextTab;<br>                sizeCtl = (n &lt;&lt; <span class="hljs-number">1</span>) - (n &gt;&gt;&gt; <span class="hljs-number">1</span>);<br>                <span class="hljs-keyword">return</span>;<br>            &#125;<br>   <span class="hljs-comment">//扩容任务线程数减1</span><br>            <span class="hljs-keyword">if</span> (U.compareAndSwapInt(<span class="hljs-built_in">this</span>, SIZECTL, sc = sizeCtl, sc - <span class="hljs-number">1</span>)) &#123;<br>                <span class="hljs-comment">//判断当前所有扩容任务线程是否都执行完成</span><br>                <span class="hljs-keyword">if</span> ((sc - <span class="hljs-number">2</span>) != resizeStamp(n) &lt;&lt; RESIZE_STAMP_SHIFT)<br>                    <span class="hljs-keyword">return</span>;<br>                <span class="hljs-comment">//所有扩容线程都执行完，标识结束</span><br>                finishing = advance = <span class="hljs-literal">true</span>;<br>                i = n; <span class="hljs-comment">// recheck before commit</span><br>            &#125;<br>        &#125;<br>        <span class="hljs-comment">//当前迁移的桶位没有元素，直接在该位置添加一个fwd节点</span><br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((f = tabAt(tab, i)) == <span class="hljs-literal">null</span>)<br>            advance = casTabAt(tab, i, <span class="hljs-literal">null</span>, fwd);<br>        <span class="hljs-comment">//当前节点已经被迁移</span><br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((fh = f.hash) == MOVED)<br>            advance = <span class="hljs-literal">true</span>; <span class="hljs-comment">// already processed</span><br>        <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">//当前节点需要迁移，加锁迁移，保证多线程安全</span><br>            <span class="hljs-comment">//此处迁移逻辑和jdk7的ConcurrentHashMap相同，不再赘述</span><br>            <span class="hljs-keyword">synchronized</span> (f) &#123;<br>                <span class="hljs-keyword">if</span> (tabAt(tab, i) == f) &#123;<br>                    Node&lt;K,V&gt; ln, hn;<br>                    <span class="hljs-keyword">if</span> (fh &gt;= <span class="hljs-number">0</span>) &#123;<br>                        <span class="hljs-type">int</span> <span class="hljs-variable">runBit</span> <span class="hljs-operator">=</span> fh &amp; n;<br>                        Node&lt;K,V&gt; lastRun = f;<br>                        <span class="hljs-keyword">for</span> (Node&lt;K,V&gt; p = f.next; p != <span class="hljs-literal">null</span>; p = p.next) &#123;<br>                            <span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> p.hash &amp; n;<br>                            <span class="hljs-keyword">if</span> (b != runBit) &#123;<br>                                runBit = b;<br>                                lastRun = p;<br>                            &#125;<br>                        &#125;<br>                        <span class="hljs-keyword">if</span> (runBit == <span class="hljs-number">0</span>) &#123;<br>                            ln = lastRun;<br>                            hn = <span class="hljs-literal">null</span>;<br>                        &#125;<br>                        <span class="hljs-keyword">else</span> &#123;<br>                            hn = lastRun;<br>                            ln = <span class="hljs-literal">null</span>;<br>                        &#125;<br>                        <span class="hljs-keyword">for</span> (Node&lt;K,V&gt; p = f; p != lastRun; p = p.next) &#123;<br>                            <span class="hljs-type">int</span> <span class="hljs-variable">ph</span> <span class="hljs-operator">=</span> p.hash; <span class="hljs-type">K</span> <span class="hljs-variable">pk</span> <span class="hljs-operator">=</span> p.key; <span class="hljs-type">V</span> <span class="hljs-variable">pv</span> <span class="hljs-operator">=</span> p.val;<br>                            <span class="hljs-keyword">if</span> ((ph &amp; n) == <span class="hljs-number">0</span>)<br>                                ln = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>&lt;K,V&gt;(ph, pk, pv, ln);<br>                            <span class="hljs-keyword">else</span><br>                                hn = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Node</span>&lt;K,V&gt;(ph, pk, pv, hn);<br>                        &#125;<br>                        setTabAt(nextTab, i, ln);<br>                        setTabAt(nextTab, i + n, hn);<br>                        setTabAt(tab, i, fwd);<br>                        advance = <span class="hljs-literal">true</span>;<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>                        TreeBin&lt;K,V&gt; t = (TreeBin&lt;K,V&gt;)f;<br>                        TreeNode&lt;K,V&gt; lo = <span class="hljs-literal">null</span>, loTail = <span class="hljs-literal">null</span>;<br>                        TreeNode&lt;K,V&gt; hi = <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> (Node&lt;K,V&gt; e = t.first; e != <span class="hljs-literal">null</span>; e = e.next) &#123;<br>                            <span class="hljs-type">int</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> e.hash;<br>                            TreeNode&lt;K,V&gt; p = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TreeNode</span>&lt;K,V&gt;<br>                                (h, e.key, e.val, <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);<br>                            <span class="hljs-keyword">if</span> ((h &amp; n) == <span class="hljs-number">0</span>) &#123;<br>                                <span class="hljs-keyword">if</span> ((p.prev = loTail) == <span class="hljs-literal">null</span>)<br>                                    lo = p;<br>                                <span class="hljs-keyword">else</span><br>                                    loTail.next = p;<br>                                loTail = p;<br>                                ++lc;<br>                            &#125;<br>                            <span class="hljs-keyword">else</span> &#123;<br>                                <span class="hljs-keyword">if</span> ((p.prev = hiTail) == <span class="hljs-literal">null</span>)<br>                                    hi = p;<br>                                <span class="hljs-keyword">else</span><br>                                    hiTail.next = p;<br>                                hiTail = p;<br>                                ++hc;<br>                            &#125;<br>                        &#125;<br>                        ln = (lc &lt;= UNTREEIFY_THRESHOLD) ? untreeify(lo) :<br>                            (hc != <span class="hljs-number">0</span>) ? <span class="hljs-keyword">new</span> <span class="hljs-title class_">TreeBin</span>&lt;K,V&gt;(lo) : t;<br>                        hn = (hc &lt;= UNTREEIFY_THRESHOLD) ? untreeify(hi) :<br>                            (lc != <span class="hljs-number">0</span>) ? <span class="hljs-keyword">new</span> <span class="hljs-title class_">TreeBin</span>&lt;K,V&gt;(hi) : t;<br>                        setTabAt(nextTab, i, ln);<br>                        setTabAt(nextTab, i + n, hn);<br>                        setTabAt(tab, i, fwd);<br>                        advance = <span class="hljs-literal">true</span>;<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-2-疑问"><a href="#3-2-疑问" class="headerlink" title="3.2. 疑问"></a>3.2. 疑问</h2><h3 id="3-2-1-fh-gt-x3D-0⭐️🔴"><a href="#3-2-1-fh-gt-x3D-0⭐️🔴" class="headerlink" title="3.2.1. fh&gt;&#x3D;0⭐️🔴"></a>3.2.1. fh&gt;&#x3D;0⭐️🔴</h3><p><span style="display:none">%%<br>▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230411-1512%%</span>❕ ^chx2qw</p><p>因为在 spread(key.hashCode()) 方法中 (h ^ (h &gt;&gt;&gt; 16)) &amp; HASH_BITS 保证了 hash 为非负数<br>然后 TreeBin 中的 static final int TREEBIN   &#x3D; -2，所以可以如此判断链表还是红黑树<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221108132913.png"></p><h2 id="3-3-图解"><a href="#3-3-图解" class="headerlink" title="3.3. 图解"></a>3.3. 图解</h2><p>图示为方便画图为 4，实际源码中为 16<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221108091832.png"></p><h1 id="4-多线程扩容"><a href="#4-多线程扩容" class="headerlink" title="4. 多线程扩容"></a>4. 多线程扩容</h1><blockquote><p>多线程协助扩容的操作会在两个地方被触发：</p><p>① 当添加元素时，发现添加的元素对用的桶位为 fwd 节点，就会先去协助扩容，然后再添加元素<br>② 当添加完元素后，判断当前元素个数达到了扩容阈值，此时发现 sizeCtl 的值小于 0，并且新数组不为空，这个时候，会去协助扩容</p></blockquote><h2 id="4-1-源码分析"><a href="#4-1-源码分析" class="headerlink" title="4.1. 源码分析"></a>4.1. 源码分析</h2><h3 id="4-1-1-元素未添加时协助扩容"><a href="#4-1-1-元素未添加时协助扩容" class="headerlink" title="4.1.1. 元素未添加时协助扩容"></a>4.1.1. 元素未添加时协助扩容</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><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">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-comment">//发现此处为fwd节点，协助扩容，扩容结束后，再循环回来添加元素</span><br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((fh = f.hash) == MOVED)<br>            tab = helpTransfer(tab, f);<br>        <br>        <span class="hljs-comment">//省略代码</span><br></code></pre></td></tr></table></figure><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"><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></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);<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 &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="hljs-number">1</span> ||<br>                sc == rs + MAX_RESIZERS || 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>                <span class="hljs-comment">//扩容，传递一个不是null的nextTab</span><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><h3 id="4-1-2-先添加元素再协助扩容"><a href="#4-1-2-先添加元素再协助扩容" class="headerlink" title="4.1.2. 先添加元素再协助扩容"></a>4.1.2. 先添加元素再协助扩容</h3><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">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>    <span class="hljs-comment">//省略代码</span><br>    <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-comment">//元素个数达到扩容阈值</span><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);<br>            <span class="hljs-comment">//sizeCtl小于0，说明正在执行扩容，那么协助扩容</span><br>            <span class="hljs-keyword">if</span> (sc &lt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-keyword">if</span> ((sc &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="hljs-number">1</span> ||<br>                    sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="hljs-literal">null</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>))<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,<br>                                         (rs &lt;&lt; RESIZE_STAMP_SHIFT) + <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><h2 id="4-2-图解"><a href="#4-2-图解" class="headerlink" title="4.2. 图解"></a>4.2. 图解</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221108185959.png"></p><h1 id="5-集合长度的累计方式"><a href="#5-集合长度的累计方式" class="headerlink" title="5. 集合长度的累计方式"></a>5. 集合长度的累计方式</h1><h2 id="5-1-addCount-方法"><a href="#5-1-addCount-方法" class="headerlink" title="5.1. addCount 方法"></a>5.1. addCount 方法</h2><h3 id="5-1-1-作用"><a href="#5-1-1-作用" class="headerlink" title="5.1.1. 作用"></a>5.1.1. 作用</h3><p>两个作用 : 添加 (统计) 容器元素个数 、 检查是否达到了阈值而执行扩容操作.</p><h3 id="5-1-2-主要逻辑"><a href="#5-1-2-主要逻辑" class="headerlink" title="5.1.2. 主要逻辑"></a>5.1.2. 主要逻辑</h3><blockquote><p><strong>① CounterCell 数组不为空，优先利用数组中的 CounterCell 记录数量</strong></p><p><strong>② 如果数组为空，尝试对 baseCount 进行累加，失败后，会执行 fullAddCount 逻辑</strong></p><p><strong>③ 如果是添加元素操作，会继续判断是否需要扩容</strong></p></blockquote><h3 id="5-1-3-源码"><a href="#5-1-3-源码" class="headerlink" title="5.1.3. 源码"></a>5.1.3. 源码</h3><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></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-comment">//当CounterCell数组不为空，则优先利用数组中的CounterCell记录数量</span><br>    <span class="hljs-comment">//或者当baseCount的累加操作失败，会利用数组中的CounterCell记录数量</span><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-comment">//标识是否有多线程竞争</span><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-comment">//当as数组为空</span><br>        <span class="hljs-comment">//或者当as长度为0</span><br>        <span class="hljs-comment">//或者当前线程对应的as数组桶位的元素为空</span><br>        <span class="hljs-comment">//或者当前线程对应的as数组桶位不为空，但是累加失败</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>            <span class="hljs-comment">//以上任何一种情况成立，都会进入该方法，传入的uncontended是false</span><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>        <span class="hljs-comment">//计算元素个数</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-comment">//当元素个数达到扩容阈值</span><br>        <span class="hljs-comment">//并且数组不为空</span><br>        <span class="hljs-comment">//并且数组长度小于限定的最大值</span><br>        <span class="hljs-comment">//满足以上所有条件，执行扩容</span><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-comment">//这个是一个很大的正数</span><br>            <span class="hljs-type">int</span> <span class="hljs-variable">rs</span> <span class="hljs-operator">=</span> resizeStamp(n);<br>            <span class="hljs-comment">//sc小于0，说明有线程正在扩容，那么会协助扩容</span><br>            <span class="hljs-keyword">if</span> (sc &lt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-comment">//扩容结束或者扩容线程数达到最大值或者扩容后的数组为null或者没有更多的桶位需要转移，结束操作</span><br>                <span class="hljs-keyword">if</span> ((sc &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="hljs-number">1</span> ||<br>                    sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="hljs-literal">null</span> ||<br>                    transferIndex &lt;= <span class="hljs-number">0</span>)<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-comment">//扩容线程加1，成功后，进行协助扩容操作</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>                    <span class="hljs-comment">//协助扩容，newTable不为null</span><br>                    transfer(tab, nt);<br>            &#125;<br>            <span class="hljs-comment">//没有其他线程在进行扩容，达到扩容阈值后，给sizeCtl赋了一个很大的负数</span><br>            <span class="hljs-comment">//1+1=2 --》 代表此时有一个线程在扩容</span><br>            <br>            <span class="hljs-comment">//rs &lt;&lt; RESIZE_STAMP_SHIFT)是一个很大的负数</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (U.compareAndSwapInt(<span class="hljs-built_in">this</span>, SIZECTL, sc,<br>                                         (rs &lt;&lt; RESIZE_STAMP_SHIFT) + <span class="hljs-number">2</span>))<br>                <span class="hljs-comment">//扩容，newTable为null</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><h2 id="5-2-fullAddCount-方法"><a href="#5-2-fullAddCount-方法" class="headerlink" title="5.2. fullAddCount 方法"></a>5.2. fullAddCount 方法</h2><blockquote><p><strong>① 当 CounterCell 数组不为空，优先对 CounterCell 数组中的 CounterCell 的 value 累加</strong></p><p><strong>② 当 CounterCell 数组为空，会去创建 CounterCell 数组，默认长度为 2，并对数组中的 CounterCell 的 value 累加</strong></p><p><strong>③ 当数组为空，并且此时有别的线程正在创建数组，那么尝试对 baseCount 做累加，成功即返回，否则自旋</strong></p></blockquote><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></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_">fullAddCount</span><span class="hljs-params">(<span class="hljs-type">long</span> x, <span class="hljs-type">boolean</span> wasUncontended)</span> &#123;<br>    <span class="hljs-type">int</span> h;<br>    <span class="hljs-comment">//获取当前线程的hash值</span><br>    <span class="hljs-keyword">if</span> ((h = ThreadLocalRandom.getProbe()) == <span class="hljs-number">0</span>) &#123;<br>        ThreadLocalRandom.localInit();      <span class="hljs-comment">// force initialization</span><br>        h = ThreadLocalRandom.getProbe();<br>        wasUncontended = <span class="hljs-literal">true</span>;<br>    &#125;<br>    <span class="hljs-comment">//标识是否有冲突，如果最后一个桶不是null，那么为true</span><br>    <span class="hljs-type">boolean</span> <span class="hljs-variable">collide</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;                <span class="hljs-comment">// True if last slot nonempty</span><br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>        CounterCell[] as; CounterCell a; <span class="hljs-type">int</span> n; <span class="hljs-type">long</span> v;<br>        <span class="hljs-comment">//数组不为空，优先对数组中CouterCell的value累加</span><br>        <span class="hljs-keyword">if</span> ((as = counterCells) != <span class="hljs-literal">null</span> &amp;&amp; (n = as.length) &gt; <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-comment">//线程对应的桶位为null</span><br>            <span class="hljs-keyword">if</span> ((a = as[(n - <span class="hljs-number">1</span>) &amp; h]) == <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-keyword">if</span> (cellsBusy == <span class="hljs-number">0</span>) &#123;            <span class="hljs-comment">// Try to attach new Cell</span><br>                    <span class="hljs-comment">//创建CounterCell对象</span><br>                    <span class="hljs-type">CounterCell</span> <span class="hljs-variable">r</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CounterCell</span>(x); <span class="hljs-comment">// Optimistic create</span><br>                    <span class="hljs-comment">//利用CAS修改cellBusy状态为1，成功则将刚才创建的CounterCell对象放入数组中</span><br>                    <span class="hljs-keyword">if</span> (cellsBusy == <span class="hljs-number">0</span> &amp;&amp;<br>                        U.compareAndSwapInt(<span class="hljs-built_in">this</span>, CELLSBUSY, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>)) &#123;<br>                        <span class="hljs-type">boolean</span> <span class="hljs-variable">created</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>                        <span class="hljs-keyword">try</span> &#123;               <span class="hljs-comment">// Recheck under lock</span><br>                            CounterCell[] rs; <span class="hljs-type">int</span> m, j;<br>                            <span class="hljs-comment">//桶位为空， 将CounterCell对象放入数组</span><br>                            <span class="hljs-keyword">if</span> ((rs = counterCells) != <span class="hljs-literal">null</span> &amp;&amp;<br>                                (m = rs.length) &gt; <span class="hljs-number">0</span> &amp;&amp;<br>                                rs[j = (m - <span class="hljs-number">1</span>) &amp; h] == <span class="hljs-literal">null</span>) &#123;<br>                                rs[j] = r;<br>                                <span class="hljs-comment">//表示放入成功</span><br>                                created = <span class="hljs-literal">true</span>;<br>                            &#125;<br>                        &#125; <span class="hljs-keyword">finally</span> &#123;<br>                            cellsBusy = <span class="hljs-number">0</span>;<br>                        &#125;<br>                        <span class="hljs-keyword">if</span> (created) <span class="hljs-comment">//成功退出循环</span><br>                            <span class="hljs-keyword">break</span>;<br>                        <span class="hljs-comment">//桶位已经被别的线程放置了已给CounterCell对象，继续循环</span><br>                        <span class="hljs-keyword">continue</span>;           <span class="hljs-comment">// Slot is now non-empty</span><br>                    &#125;<br>                &#125;<br>                collide = <span class="hljs-literal">false</span>;<br>            &#125;<br>            <span class="hljs-comment">//桶位不为空，重新计算线程hash值，然后继续循环</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!wasUncontended)       <span class="hljs-comment">// CAS already known to fail</span><br>                wasUncontended = <span class="hljs-literal">true</span>;      <span class="hljs-comment">// Continue after rehash</span><br>            <span class="hljs-comment">//重新计算了hash值后，对应的桶位依然不为空，对value累加</span><br>            <span class="hljs-comment">//成功则结束循环</span><br>            <span class="hljs-comment">//失败则继续下面判断</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-comment">//数组被别的线程改变了，或者数组长度超过了可用cpu大小，重新计算线程hash值，否则继续下一个判断</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (counterCells != as || n &gt;= NCPU)<br>                collide = <span class="hljs-literal">false</span>;            <span class="hljs-comment">// At max size or stale</span><br>            <span class="hljs-comment">//当没有冲突，修改为有冲突，并重新计算线程hash，继续循环</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!collide)<br>                collide = <span class="hljs-literal">true</span>;<br>            <span class="hljs-comment">//如果CounterCell的数组长度没有超过cpu核数，对数组进行两倍扩容</span><br>            <span class="hljs-comment">//并继续循环</span><br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cellsBusy == <span class="hljs-number">0</span> &amp;&amp;<br>                     U.compareAndSwapInt(<span class="hljs-built_in">this</span>, CELLSBUSY, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>)) &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-keyword">if</span> (counterCells == as) &#123;<span class="hljs-comment">// Expand table unless stale</span><br>                        CounterCell[] rs = <span class="hljs-keyword">new</span> <span class="hljs-title class_">CounterCell</span>[n &lt;&lt; <span class="hljs-number">1</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; n; ++i)<br>                            rs[i] = as[i];<br>                        counterCells = rs;<br>                    &#125;<br>                &#125; <span class="hljs-keyword">finally</span> &#123;<br>                    cellsBusy = <span class="hljs-number">0</span>;<br>                &#125;<br>                collide = <span class="hljs-literal">false</span>;<br>                <span class="hljs-keyword">continue</span>;                   <span class="hljs-comment">// Retry with expanded table</span><br>            &#125;<br>            h = ThreadLocalRandom.advanceProbe(h);<br>        &#125;<br>        <span class="hljs-comment">//CounterCell数组为空，并且没有线程在创建数组，修改标记，并创建数组</span><br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cellsBusy == <span class="hljs-number">0</span> &amp;&amp; counterCells == as &amp;&amp;<br>                 U.compareAndSwapInt(<span class="hljs-built_in">this</span>, CELLSBUSY, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>)) &#123;<br>            <span class="hljs-type">boolean</span> <span class="hljs-variable">init</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">try</span> &#123;                           <span class="hljs-comment">// Initialize table</span><br>                <span class="hljs-keyword">if</span> (counterCells == as) &#123;<br>                    CounterCell[] rs = <span class="hljs-keyword">new</span> <span class="hljs-title class_">CounterCell</span>[<span class="hljs-number">2</span>];<br>                    rs[h &amp; <span class="hljs-number">1</span>] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">CounterCell</span>(x);<br>                    counterCells = rs;<br>                    init = <span class="hljs-literal">true</span>;<br>                &#125;<br>            &#125; <span class="hljs-keyword">finally</span> &#123;<br>                cellsBusy = <span class="hljs-number">0</span>;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (init)<br>                <span class="hljs-keyword">break</span>;<br>        &#125;<br>        <span class="hljs-comment">//数组为空，并且有别的线程在创建数组，那么尝试对baseCount做累加，成功就退出循环，失败就继续循环</span><br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (U.compareAndSwapLong(<span class="hljs-built_in">this</span>, BASECOUNT, v = baseCount, v + x))<br>            <span class="hljs-keyword">break</span>;                          <span class="hljs-comment">// Fall back on using base</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="5-3-图解"><a href="#5-3-图解" class="headerlink" title="5.3. 图解"></a>5.3. 图解</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221108195428.png"></p><h1 id="6-集合长度获取"><a href="#6-集合长度获取" class="headerlink" title="6. 集合长度获取"></a>6. 集合长度获取</h1><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-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><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-comment">//获取baseCount的值</span><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-comment">//遍历CounterCell数组，累加每一个CounterCell的value值</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; 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><h1 id="7-参考"><a href="#7-参考" class="headerlink" title="7. 参考"></a>7. 参考</h1><h2 id="7-1-黑马"><a href="#7-1-黑马" class="headerlink" title="7.1. 黑马"></a>7.1. 黑马</h2><p><span style="display:none">%%<br>▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230411-1521%%</span>❕ ^5h9x2g</p><h3 id="7-1-1-视频"><a href="#7-1-1-视频" class="headerlink" title="7.1.1. 视频"></a>7.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV17i4y1x71z/?from=search&amp;seid=3516507855592185473&amp;spm_id_from=333.337.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV17i4y1x71z/?from=search&amp;seid=3516507855592185473&amp;spm_id_from=333.337.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="7-1-2-资料"><a href="#7-1-2-资料" class="headerlink" title="7.1.2. 资料"></a>7.1.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">001</span>-基础知识专题/<span class="hljs-number">001</span>-集合框架<br></code></pre></td></tr></table></figure><p>[[ConcurrentHashMap源码分析(二)]]</p><p>[[20221108-ConcurrentHashMap - 知乎]]</p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-6、ConcurrentHashMap(JDK1.7)</title>
      <link href="/2022/10/29/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-6%E3%80%81ConcurrentHashMap(JDK1.7)/"/>
      <url>/2022/10/29/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-6%E3%80%81ConcurrentHashMap(JDK1.7)/</url>
      
        <content type="html"><![CDATA[<h1 id="1-Unsafe-介绍"><a href="#1-Unsafe-介绍" class="headerlink" title="1. Unsafe 介绍"></a>1. Unsafe 介绍</h1><h2 id="1-1-1、Unsafe-简介"><a href="#1-1-1、Unsafe-简介" class="headerlink" title="1.1. 1、Unsafe 简介"></a>1.1. 1、Unsafe 简介</h2><p>Unsafe 类相当于是一个 java 语言中的后门类，<strong>提供了硬件级别的原子操作</strong>，所以在一些并发编程中被大量使用。jdk 已经作出说明，该类对程序员而言不是一个安全操作，在后续的 jdk 升级过程中，可能会禁用该类。所以这个类的使用是一把双刃剑，实际项目中谨慎使用，以免造成 jdk 升级不兼容问题。</p><h2 id="1-2-2、Unsafe-Api"><a href="#1-2-2、Unsafe-Api" class="headerlink" title="1.2. 2、Unsafe Api"></a>1.2. 2、Unsafe Api</h2><p>这里并不系统讲解 Unsafe 的所有功能，只介绍和接下来内容相关的操作</p><p><code>arrayBaseOffset</code>：获取数组的基础偏移量</p><p><code>arrayIndexScale</code>：获取数组中元素的偏移间隔，要获取对应所以的元素，将索引号和该值相乘，获得数组中指定角标元素的偏移量</p><p><code>getObjectVolatile</code>：获取对象上的属性值或者数组中的元素</p><p><code>getObject</code>：获取对象上的属性值或者数组中的元素，已过时</p><p><code>putOrderedObject</code>：设置对象的属性值或者数组中某个角标的元素，更高效</p><p><code>putObjectVolatile</code>：设置对象的属性值或者数组中某个角标的元素</p><p><code>putObject</code>：设置对象的属性值或者数组中某个角标的元素，已过时</p><h2 id="1-3-3、代码演示"><a href="#1-3-3、代码演示" class="headerlink" title="1.3. 3、代码演示"></a>1.3. 3、代码演示</h2><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_">Test02</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> <span class="hljs-keyword">throws</span> Exception &#123;<br>        Integer[] arr = &#123;<span class="hljs-number">2</span>,<span class="hljs-number">5</span>,<span class="hljs-number">1</span>,<span class="hljs-number">8</span>,<span class="hljs-number">10</span>&#125;;<br><br>        <span class="hljs-comment">//获取Unsafe对象</span><br>        <span class="hljs-type">Unsafe</span> <span class="hljs-variable">unsafe</span> <span class="hljs-operator">=</span> getUnsafe();<br>        <span class="hljs-comment">//获取Integer[]的基础偏移量</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">baseOffset</span> <span class="hljs-operator">=</span> unsafe.arrayBaseOffset(Integer[].class);<br>        <span class="hljs-comment">//获取Integer[]中元素的偏移间隔</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">indexScale</span> <span class="hljs-operator">=</span> unsafe.arrayIndexScale(Integer[].class);<br><br>        <span class="hljs-comment">//获取数组中索引为2的元素对象</span><br>        <span class="hljs-type">Object</span> <span class="hljs-variable">o</span> <span class="hljs-operator">=</span> unsafe.getObjectVolatile(arr, (<span class="hljs-number">2</span> * indexScale) + baseOffset);<br>        System.out.println(o); <span class="hljs-comment">//1</span><br><br>        <span class="hljs-comment">//设置数组中索引为2的元素值为100</span><br>        unsafe.putOrderedObject(arr,(<span class="hljs-number">2</span> * indexScale) + baseOffset,<span class="hljs-number">100</span>);<br><br>        System.out.println(Arrays.toString(arr));<span class="hljs-comment">//[2, 5, 100, 8, 10]</span><br>    &#125;<br><br>    <span class="hljs-comment">//反射获取Unsafe对象</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Unsafe <span class="hljs-title function_">getUnsafe</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-type">Field</span> <span class="hljs-variable">theUnsafe</span> <span class="hljs-operator">=</span> Unsafe.class.getDeclaredField(<span class="hljs-string">&quot;theUnsafe&quot;</span>);<br>        theUnsafe.setAccessible(<span class="hljs-literal">true</span>);<br>        <span class="hljs-keyword">return</span> (Unsafe) theUnsafe.get(<span class="hljs-literal">null</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107223658.png"></p><h1 id="2-容器初始化"><a href="#2-容器初始化" class="headerlink" title="2. 容器初始化"></a>2. 容器初始化</h1><h2 id="2-1-1、源码分析"><a href="#2-1-1、源码分析" class="headerlink" title="2.1. 1、源码分析"></a>2.1. 1、源码分析</h2><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></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-title function_">ConcurrentHashMap</span><span class="hljs-params">()</span> &#123;  <br>    <span class="hljs-comment">//调用本类的带参构造  </span><br>    <span class="hljs-comment">//DEFAULT_INITIAL_CAPACITY = 16  </span><br>    <span class="hljs-comment">//DEFAULT_LOAD_FACTOR = 0.75f  </span><br>    <span class="hljs-comment">//int DEFAULT_CONCURRENCY_LEVEL = 16  </span><br>    <span class="hljs-built_in">this</span>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);  <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><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 java"><span class="hljs-comment">//initialCapacity 定义ConcurrentHashMap存放元素的容量  </span><br><span class="hljs-comment">//concurrencyLevel 定义ConcurrentHashMap中Segment[]的大小  </span><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">ConcurrentHashMap</span><span class="hljs-params">(<span class="hljs-type">int</span> initialCapacity,  </span><br><span class="hljs-params">                         <span class="hljs-type">float</span> loadFactor, <span class="hljs-type">int</span> concurrencyLevel)</span> &#123;  <br>     <br>    <span class="hljs-type">int</span> <span class="hljs-variable">sshift</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">ssize</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;  <br>    <span class="hljs-comment">//计算Segment[]的大小，保证是2的幂次方数  </span><br>    <span class="hljs-keyword">while</span> (ssize &lt; concurrencyLevel) &#123;  <br>        ++sshift;  <br>        ssize &lt;&lt;= <span class="hljs-number">1</span>;  <br>    &#125;  <br>    <span class="hljs-comment">//这两个值用于后面计算Segment[]的角标  </span><br>    <span class="hljs-built_in">this</span>.segmentShift = <span class="hljs-number">32</span> - sshift;  <br>    <span class="hljs-built_in">this</span>.segmentMask = ssize - <span class="hljs-number">1</span>;  <br>      <br>    <span class="hljs-comment">//计算每个Segment中存储元素的个数  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> initialCapacity / ssize;  <br>    <span class="hljs-keyword">if</span> (c * ssize &lt; initialCapacity)  <br>        ++c;  <br>    <span class="hljs-comment">//最小Segment中存储元素的个数为2  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">cap</span> <span class="hljs-operator">=</span> MIN_SEGMENT_TABLE_CAPACITY;  <br>    <span class="hljs-comment">////矫正每个Segment中存储元素的个数，保证是2的幂次方，最小为2  </span><br>    <span class="hljs-keyword">while</span> (cap &lt; c)  <br>        cap &lt;&lt;= <span class="hljs-number">1</span>;  <br>    <span class="hljs-comment">//创建一个Segment对象，作为其他Segment对象的模板  </span><br>    Segment&lt;K,V&gt; s0 =  <br>        <span class="hljs-keyword">new</span> <span class="hljs-title class_">Segment</span>&lt;K,V&gt;(loadFactor, (<span class="hljs-type">int</span>)(cap * loadFactor),  <br>                         (HashEntry&lt;K,V&gt;[])<span class="hljs-keyword">new</span> <span class="hljs-title class_">HashEntry</span>[cap]);  <br>    Segment&lt;K,V&gt;[] ss = (Segment&lt;K,V&gt;[])<span class="hljs-keyword">new</span> <span class="hljs-title class_">Segment</span>[ssize];  <br>    <span class="hljs-comment">//利用Unsafe类，将创建的Segment对象存入0角标位置  </span><br>    UNSAFE.putOrderedObject(ss, SBASE, s0); <span class="hljs-comment">// ordered write of segments[0]  </span><br>    <span class="hljs-built_in">this</span>.segments = ss;  <br>&#125;<br></code></pre></td></tr></table></figure><p>综上：ConcurrentHashMap 中保存了一个 **默认长度为 16 的 <code>Segment[]</code>**，每个 Segment 元素中保存了一个 **默认长度为 2 的 <code>HashEntry[]</code>**，我们添加的元素，是存入对应的 Segment 中的 <code>HashEntry[]</code> 中。所以 ConcurrentHashMap 中<span style="background-color:#ff0000">默认元素的长度是 32 个，而不是 16 个</span></p><h2 id="2-2-2、两个数组"><a href="#2-2-2、两个数组" class="headerlink" title="2.2. 2、两个数组"></a>2.2. 2、两个数组</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107224218.png"></p><h3 id="2-2-1-Segment-extends-ReentrantLock⭐️🔴"><a href="#2-2-1-Segment-extends-ReentrantLock⭐️🔴" class="headerlink" title="2.2.1. Segment-extends ReentrantLock⭐️🔴"></a>2.2.1. Segment-extends ReentrantLock⭐️🔴</h3><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> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Segment</span>&lt;K,V&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ReentrantLock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Serializable</span> &#123;  <br>  ...  <br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p>我们发现<span style="background-color:#ff0000"> Segment 是继承自 ReentrantLock</span> 的，学过线程的兄弟都知道，它可以实现同步操作，从而保证多线程下的安全。因为每个 Segment 之间的锁互不影响，所以我们也将 ConcurrentHashMap 中的这种锁机制称之为 <strong>分段锁</strong>，这比 HashTable 的线程安全操作高效的多。</p></blockquote><h3 id="2-2-2-HashEntry"><a href="#2-2-2-HashEntry" class="headerlink" title="2.2.2. HashEntry"></a>2.2.2. HashEntry</h3><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-comment">//ConcurrentHashMap中真正存储数据的对象  </span><br><span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HashEntry</span>&lt;K,V&gt; &#123;  <br>    <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> hash; <span class="hljs-comment">//通过运算，得到的键的hash值  </span><br>    <span class="hljs-keyword">final</span> K key; <span class="hljs-comment">// 存入的键  </span><br>    <span class="hljs-keyword">volatile</span> V value; <span class="hljs-comment">//存入的值  </span><br>    <span class="hljs-keyword">volatile</span> HashEntry&lt;K,V&gt; next; <span class="hljs-comment">//记录下一个元素，形成单向链表  </span><br>​  <br>    HashEntry(<span class="hljs-type">int</span> hash, K key, V value, HashEntry&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>&#125;<br></code></pre></td></tr></table></figure><h1 id="3-添加安全⭐️🔴"><a href="#3-添加安全⭐️🔴" class="headerlink" title="3. 添加安全⭐️🔴"></a>3. 添加安全⭐️🔴</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230523073444.png" alt="image.png"></p><h2 id="3-1-ConcurrentHashMap-的-put-方法"><a href="#3-1-ConcurrentHashMap-的-put-方法" class="headerlink" title="3.1. ConcurrentHashMap 的 put 方法"></a>3.1. ConcurrentHashMap 的 put 方法</h2><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">public</span> V <span class="hljs-title function_">put</span><span class="hljs-params">(K key, V value)</span> &#123;  <br>    Segment&lt;K,V&gt; s;  <br>    <span class="hljs-keyword">if</span> (value == <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-comment">//基于key，计算hash值  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">hash</span> <span class="hljs-operator">=</span> hash(key);  <br>    <span class="hljs-comment">//因为一个键要计算两个数组的索引，为了避免冲突，这里取高位计算Segment[]的索引  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> (hash &gt;&gt;&gt; segmentShift) &amp; segmentMask;  <br>    <span class="hljs-comment">//判断该索引位的Segment对象是否创建，没有就创建  </span><br>    <span class="hljs-keyword">if</span> ((s = (Segment&lt;K,V&gt;)UNSAFE.getObject          <span class="hljs-comment">// nonvolatile; recheck  </span><br>         (segments, (j &lt;&lt; SSHIFT) + SBASE)) == <span class="hljs-literal">null</span>) <span class="hljs-comment">//  in ensureSegment  </span><br>        s = ensureSegment(j);  <br>    <span class="hljs-comment">//调用Segmetn的put方法实现元素添加  </span><br>    <span class="hljs-keyword">return</span> s.put(key, hash, value, <span class="hljs-literal">false</span>);  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-2-ConcurrentHashMap-的-ensureSegment-方法"><a href="#3-2-ConcurrentHashMap-的-ensureSegment-方法" class="headerlink" title="3.2. ConcurrentHashMap 的 ensureSegment 方法"></a>3.2. ConcurrentHashMap 的 ensureSegment 方法</h2><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-comment">//创建对应索引位的Segment对象，并返回  </span><br><span class="hljs-keyword">private</span> Segment&lt;K,V&gt; <span class="hljs-title function_">ensureSegment</span><span class="hljs-params">(<span class="hljs-type">int</span> k)</span> &#123;  <br>    <span class="hljs-keyword">final</span> Segment&lt;K,V&gt;[] ss = <span class="hljs-built_in">this</span>.segments;  <br>    <span class="hljs-type">long</span> <span class="hljs-variable">u</span> <span class="hljs-operator">=</span> (k &lt;&lt; SSHIFT) + SBASE; <span class="hljs-comment">// raw offset  </span><br>    Segment&lt;K,V&gt; seg;  <br>    <span class="hljs-comment">//获取，如果为null，即创建  </span><br>    <span class="hljs-keyword">if</span> ((seg = (Segment&lt;K,V&gt;)UNSAFE.getObjectVolatile(ss, u)) == <span class="hljs-literal">null</span>) &#123;  <br>        <span class="hljs-comment">//以0角标位的Segment为模板  </span><br>        Segment&lt;K,V&gt; proto = ss[<span class="hljs-number">0</span>]; <span class="hljs-comment">// use segment 0 as prototype  </span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">cap</span> <span class="hljs-operator">=</span> proto.table.length;  <br>        <span class="hljs-type">float</span> <span class="hljs-variable">lf</span> <span class="hljs-operator">=</span> proto.loadFactor;  <br>        <span class="hljs-type">int</span> <span class="hljs-variable">threshold</span> <span class="hljs-operator">=</span> (<span class="hljs-type">int</span>)(cap * lf);  <br>        HashEntry&lt;K,V&gt;[] tab = (HashEntry&lt;K,V&gt;[])<span class="hljs-keyword">new</span> <span class="hljs-title class_">HashEntry</span>[cap];  <br>        <span class="hljs-comment">//获取，如果为null，即创建  </span><br>        <span class="hljs-keyword">if</span> ((seg = (Segment&lt;K,V&gt;)UNSAFE.getObjectVolatile(ss, u))  <br>            == <span class="hljs-literal">null</span>) &#123; <span class="hljs-comment">// recheck  </span><br>            <span class="hljs-comment">//创建  </span><br>            Segment&lt;K,V&gt; s = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Segment</span>&lt;K,V&gt;(lf, threshold, tab);  <br>            <span class="hljs-comment">//自旋方式，将创建的Segment对象放到Segment[]中，确保线程安全  </span><br>            <span class="hljs-keyword">while</span> ((seg = (Segment&lt;K,V&gt;)UNSAFE.getObjectVolatile(ss, u))  <br>                   == <span class="hljs-literal">null</span>) &#123;  <br>                <span class="hljs-keyword">if</span> (UNSAFE.compareAndSwapObject(ss, u, <span class="hljs-literal">null</span>, seg = s))  <br>                    <span class="hljs-keyword">break</span>;  <br>            &#125;  <br>        &#125;  <br>    &#125;  <br>    <span class="hljs-comment">//返回  </span><br>    <span class="hljs-keyword">return</span> seg;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-3-Segment-的-put-方法"><a href="#3-3-Segment-的-put-方法" class="headerlink" title="3.3. Segment 的 put 方法"></a>3.3. Segment 的 put 方法</h2><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">final</span> V <span class="hljs-title function_">put</span><span class="hljs-params">(K key, <span class="hljs-type">int</span> hash, V value, <span class="hljs-type">boolean</span> onlyIfAbsent)</span> &#123;  <br>    <span class="hljs-comment">//尝试获取锁，获取成功，node为null，代码向下执行  </span><br>    <span class="hljs-comment">//如果有其他线程占据锁对象，那么去做别的事情，而不是一直等待，提升效率  </span><br>    <span class="hljs-comment">//scanAndLockForPut 稍后分析  </span><br>    HashEntry&lt;K,V&gt; node = tryLock() ? <span class="hljs-literal">null</span> :  <br>        scanAndLockForPut(key, hash, value);  <br>    V oldValue;  <br>    <span class="hljs-keyword">try</span> &#123;  <br>        HashEntry&lt;K,V&gt;[] tab = table;  <br>        <span class="hljs-comment">//取hash的低位，计算HashEntry[]的索引  </span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">index</span> <span class="hljs-operator">=</span> (tab.length - <span class="hljs-number">1</span>) &amp; hash;  <br>        <span class="hljs-comment">//获取索引位的元素对象  </span><br>        HashEntry&lt;K,V&gt; first = entryAt(tab, index);  <br>        <span class="hljs-keyword">for</span> (HashEntry&lt;K,V&gt; e = first;;) &#123;  <br>            <span class="hljs-comment">//获取的元素对象不为空  </span><br>            <span class="hljs-keyword">if</span> (e != <span class="hljs-literal">null</span>) &#123;  <br>                K k;  <br>                <span class="hljs-comment">//如果是重复元素，覆盖原值  </span><br>                <span class="hljs-keyword">if</span> ((k = e.key) == key ||  <br>                    (e.hash == hash &amp;&amp; key.equals(k))) &#123;  <br>                    oldValue = e.value;  <br>                    <span class="hljs-keyword">if</span> (!onlyIfAbsent) &#123;  <br>                        e.value = value;  <br>                        ++modCount;  <br>                    &#125;  <br>                    <span class="hljs-keyword">break</span>;  <br>                &#125;  <br>                <span class="hljs-comment">//如果不是重复元素，获取链表的下一个元素，继续循环遍历链表  </span><br>                e = e.next;  <br>            &#125;  <br>            <span class="hljs-keyword">else</span> &#123; <span class="hljs-comment">//如果获取到的元素为空  </span><br>                <span class="hljs-comment">//当前添加的键值对的HashEntry对象已经创建  </span><br>                <span class="hljs-keyword">if</span> (node != <span class="hljs-literal">null</span>)  <br>                    node.setNext(first); <span class="hljs-comment">//头插法关联即可  </span><br>                <span class="hljs-keyword">else</span>  <br>                    <span class="hljs-comment">//创建当前添加的键值对的HashEntry对象  </span><br>                    node = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashEntry</span>&lt;K,V&gt;(hash, key, value, first);  <br>                <span class="hljs-comment">//添加的元素数量递增  </span><br>                <span class="hljs-type">int</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> count + <span class="hljs-number">1</span>;  <br>                <span class="hljs-comment">//判断是否需要扩容  </span><br>                <span class="hljs-keyword">if</span> (c &gt; threshold &amp;&amp; tab.length &lt; MAXIMUM_CAPACITY)  <br>                    <span class="hljs-comment">//需要扩容  </span><br>                    rehash(node);  <br>                <span class="hljs-keyword">else</span>  <br>                    <span class="hljs-comment">//不需要扩容  </span><br>                    <span class="hljs-comment">//将当前添加的元素对象，存入数组角标位，完成头插法添加元素  </span><br>                    setEntryAt(tab, index, node);  <br>                ++modCount;  <br>                count = c;  <br>                oldValue = <span class="hljs-literal">null</span>;  <br>                <span class="hljs-keyword">break</span>;  <br>            &#125;  <br>        &#125;  <br>    &#125; <span class="hljs-keyword">finally</span> &#123;  <br>        <span class="hljs-comment">//释放锁  </span><br>        unlock();  <br>    &#125;  <br>    <span class="hljs-keyword">return</span> oldValue;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-4-Segment-的-scanAndLockForPut-方法"><a href="#3-4-Segment-的-scanAndLockForPut-方法" class="headerlink" title="3.4. Segment 的 scanAndLockForPut 方法"></a>3.4. Segment 的 scanAndLockForPut 方法</h2><p>该方法在线程没有获取到锁的情况下，去完成 HashEntry 对象的创建，提升效率</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">private</span> HashEntry&lt;K,V&gt; <span class="hljs-title function_">scanAndLockForPut</span><span class="hljs-params">(K key, <span class="hljs-type">int</span> hash, V value)</span> &#123;  <br>    <span class="hljs-comment">//获取头部元素  </span><br>    HashEntry&lt;K,V&gt; first = entryForHash(<span class="hljs-built_in">this</span>, hash);  <br>    HashEntry&lt;K,V&gt; e = first;  <br>    HashEntry&lt;K,V&gt; node = <span class="hljs-literal">null</span>；  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">retries</span> <span class="hljs-operator">=</span> -<span class="hljs-number">1</span>; <span class="hljs-comment">// negative while locating node  </span><br>    <span class="hljs-keyword">while</span> (!tryLock()) &#123;  <br>        <span class="hljs-comment">//获取锁失败  </span><br>        HashEntry&lt;K,V&gt; f; <span class="hljs-comment">// to recheck first below  </span><br>        <span class="hljs-keyword">if</span> (retries &lt; <span class="hljs-number">0</span>) &#123;  <br>            <span class="hljs-comment">//没有下一个节点，并且也不是重复元素，创建HashEntry对象，不再遍历  </span><br>            <span class="hljs-keyword">if</span> (e == <span class="hljs-literal">null</span>) &#123;  <br>                <span class="hljs-keyword">if</span> (node == <span class="hljs-literal">null</span>) <span class="hljs-comment">// speculatively create node  </span><br>                    node = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashEntry</span>&lt;K,V&gt;(hash, key, value, <span class="hljs-literal">null</span>);  <br>                retries = <span class="hljs-number">0</span>;  <br>            &#125;  <br>            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key.equals(e.key))  <br>                <span class="hljs-comment">//重复元素，不创建HashEntry对象，不再遍历  </span><br>                retries = <span class="hljs-number">0</span>;  <br>            <span class="hljs-keyword">else</span>  <br>                <span class="hljs-comment">//继续遍历下一个节点  </span><br>                e = e.next;  <br>        &#125;  <br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (++retries &gt; MAX_SCAN_RETRIES) &#123;  <br>            <span class="hljs-comment">//如果尝试获取锁的次数过多，直接阻塞  </span><br>            <span class="hljs-comment">//MAX_SCAN_RETRIES会根据可用cpu核数来确定  </span><br>            lock();  <br>            <span class="hljs-keyword">break</span>;  <br>        &#125;  <br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((retries &amp; <span class="hljs-number">1</span>) == <span class="hljs-number">0</span> &amp;&amp;  <br>                 (f = entryForHash(<span class="hljs-built_in">this</span>, hash)) != first) &#123;  <br>            <span class="hljs-comment">//如果期间有别的线程获取锁，重新遍历  </span><br>            e = first = f; <span class="hljs-comment">// re-traverse if entry changed  </span><br>            retries = -<span class="hljs-number">1</span>;  <br>        &#125;  <br>    &#125;  <br>    <span class="hljs-keyword">return</span> node;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-5-模拟多线程的代码流程"><a href="#3-5-模拟多线程的代码流程" class="headerlink" title="3.5. 模拟多线程的代码流程"></a>3.5. 模拟多线程的代码流程</h2><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></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> <span class="hljs-keyword">throws</span> Exception &#123;  <br>    <span class="hljs-keyword">final</span> <span class="hljs-type">ConcurrentHashMap</span> <span class="hljs-variable">chm</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>();  <br><br>    <span class="hljs-keyword">new</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>            chm.put(<span class="hljs-string">&quot;通话&quot;</span>,<span class="hljs-string">&quot;11&quot;</span>);  <br>            System.out.println(<span class="hljs-string">&quot;-----------&quot;</span>);  <br>        &#125;  <br>    &#125;.start();  <br><br>  <span class="hljs-comment">//让第一个线程先启动，进入put方法  </span><br>    Thread.sleep(<span class="hljs-number">1000</span>);  <br><br>    <span class="hljs-keyword">new</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>            chm.put(<span class="hljs-string">&quot;重地&quot;</span>,<span class="hljs-string">&quot;22&quot;</span>);  <br>            System.out.println(<span class="hljs-string">&quot;===========&quot;</span>);  <br>        &#125;  <br>    &#125;.start();  <br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-5-1-流程图"><a href="#3-5-1-流程图" class="headerlink" title="3.5.1. 流程图"></a>3.5.1. 流程图</h3><p><a href="https://www.processon.com/view/link/636a079b1efad40cd880e4fc">https://www.processon.com/view/link/636a079b1efad40cd880e4fc</a></p><h1 id="4-扩容安全"><a href="#4-扩容安全" class="headerlink" title="4. 扩容安全"></a>4. 扩容安全</h1><h2 id="4-1-源码分析"><a href="#4-1-源码分析" class="headerlink" title="4.1. 源码分析"></a>4.1. 源码分析</h2><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">rehash</span><span class="hljs-params">(HashEntry&lt;K,V&gt; node)</span> &#123;  <br>    HashEntry&lt;K,V&gt;[] oldTable = table;  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">oldCapacity</span> <span class="hljs-operator">=</span> oldTable.length;  <br>    <span class="hljs-comment">//两倍容量  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">newCapacity</span> <span class="hljs-operator">=</span> oldCapacity &lt;&lt; <span class="hljs-number">1</span>;  <br>    threshold = (<span class="hljs-type">int</span>)(newCapacity * loadFactor);  <br>    <span class="hljs-comment">//基于新容量，创建HashEntry数组  </span><br>    HashEntry&lt;K,V&gt;[] newTable =  <br>        (HashEntry&lt;K,V&gt;[]) <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashEntry</span>[newCapacity];  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">sizeMask</span> <span class="hljs-operator">=</span> newCapacity - <span class="hljs-number">1</span>;  <br>    <span class="hljs-comment">//实现数据迁移  </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; oldCapacity ; i++) &#123;  <br>        HashEntry&lt;K,V&gt; e = oldTable[i];  <br>        <span class="hljs-keyword">if</span> (e != <span class="hljs-literal">null</span>) &#123;  <br>            HashEntry&lt;K,V&gt; next = e.next;  <br>            <span class="hljs-type">int</span> <span class="hljs-variable">idx</span> <span class="hljs-operator">=</span> e.hash &amp; sizeMask;  <br>            <span class="hljs-keyword">if</span> (next == <span class="hljs-literal">null</span>)   <span class="hljs-comment">//  Single node on list  </span><br>                <span class="hljs-comment">//原位置只有一个元素，直接放到新数组即可  </span><br>                newTable[idx] = e;  <br>            <span class="hljs-keyword">else</span> &#123; <span class="hljs-comment">// Reuse consecutive sequence at same slot  </span><br>                <span class="hljs-comment">//=========图一=====================  </span><br>                HashEntry&lt;K,V&gt; lastRun = e;  <br>                <span class="hljs-type">int</span> <span class="hljs-variable">lastIdx</span> <span class="hljs-operator">=</span> idx;  <br>                <span class="hljs-keyword">for</span> (HashEntry&lt;K,V&gt; last = next;  <br>                     last != <span class="hljs-literal">null</span>;  <br>                     last = last.next) &#123;  <br>                    <span class="hljs-type">int</span> <span class="hljs-variable">k</span> <span class="hljs-operator">=</span> last.hash &amp; sizeMask;  <br>                    <span class="hljs-keyword">if</span> (k != lastIdx) &#123;  <br>                        lastIdx = k;  <br>                        lastRun = last;  <br>                    &#125;  <br>                &#125;  <br>                <span class="hljs-comment">//=========图一=====================  </span><br>                  <br>                <span class="hljs-comment">//=========图二=====================  </span><br>                newTable[lastIdx] = lastRun;  <br>                <span class="hljs-comment">//=========图二=====================  </span><br>                <span class="hljs-comment">// Clone remaining nodes  </span><br>                <span class="hljs-comment">//=========图三=====================  </span><br>                <span class="hljs-keyword">for</span> (HashEntry&lt;K,V&gt; p = e; p != lastRun; p = p.next) &#123;  <br>                    <span class="hljs-type">V</span> <span class="hljs-variable">v</span> <span class="hljs-operator">=</span> p.value;  <br>                    <span class="hljs-type">int</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> p.hash;  <br>                    <span class="hljs-type">int</span> <span class="hljs-variable">k</span> <span class="hljs-operator">=</span> h &amp; sizeMask;  <br>                    HashEntry&lt;K,V&gt; n = newTable[k];  <br>                    <span class="hljs-comment">//这里旧的HashEntry不会放到新数组  </span><br>                    <span class="hljs-comment">//而是基于原来的数据创建了一个新的HashEntry对象，放入新数组  </span><br>                    newTable[k] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashEntry</span>&lt;K,V&gt;(h, p.key, v, n);  <br>                &#125;  <br>                <span class="hljs-comment">//=========图三=====================  </span><br>            &#125;  <br>        &#125;  <br>    &#125;  <br>    <span class="hljs-comment">//采用头插法，将新元素加入到数组中  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">nodeIndex</span> <span class="hljs-operator">=</span> node.hash &amp; sizeMask; <span class="hljs-comment">// add the new node  </span><br>    node.setNext(newTable[nodeIndex]);  <br>    newTable[nodeIndex] = node;  <br>    table = newTable;  <br>&#125;<br></code></pre></td></tr></table></figure><p>既有串片段的重定向到新数组，也有复制元素到新数组</p><blockquote><p>图一<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107225548.png"></p></blockquote><blockquote><p>图二<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107225626.png"></p></blockquote><blockquote><p>图三<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107225634.png"></p></blockquote><h1 id="5-集合长度获取"><a href="#5-集合长度获取" class="headerlink" title="5. 集合长度获取"></a>5. 集合长度获取</h1><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"><br><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-comment">// Try a few times to get accurate count. On failure due to  </span><br>    <span class="hljs-comment">// continuous async changes in table, resort to locking.  </span><br>    <span class="hljs-keyword">final</span> Segment&lt;K,V&gt;[] segments = <span class="hljs-built_in">this</span>.segments;  <br>    <span class="hljs-type">int</span> size;  <br>    <span class="hljs-type">boolean</span> overflow; <span class="hljs-comment">// true if size overflows 32 bits  </span><br>    <span class="hljs-type">long</span> sum;         <span class="hljs-comment">// sum of modCounts  </span><br>    <span class="hljs-type">long</span> <span class="hljs-variable">last</span> <span class="hljs-operator">=</span> <span class="hljs-number">0L</span>;   <span class="hljs-comment">// previous sum  </span><br>    <span class="hljs-type">int</span> <span class="hljs-variable">retries</span> <span class="hljs-operator">=</span> -<span class="hljs-number">1</span>; <span class="hljs-comment">// first iteration isn&#x27;t retry  </span><br>    <span class="hljs-keyword">try</span> &#123;  <br>        <span class="hljs-keyword">for</span> (;;) &#123;  <br>            <span class="hljs-comment">//当第5次走到这个地方时，会将整个Segment[]的所有Segment对象锁住  </span><br>            <span class="hljs-keyword">if</span> (retries++ == RETRIES_BEFORE_LOCK) &#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; segments.length; ++j)  <br>                    ensureSegment(j).lock(); <span class="hljs-comment">// force creation  </span><br>            &#125;  <br>            sum = <span class="hljs-number">0L</span>;  <br>            size = <span class="hljs-number">0</span>;  <br>            overflow = <span class="hljs-literal">false</span>;  <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; segments.length; ++j) &#123;  <br>                Segment&lt;K,V&gt; seg = segmentAt(segments, j);  <br>                <span class="hljs-keyword">if</span> (seg != <span class="hljs-literal">null</span>) &#123;  <br>                    <span class="hljs-comment">//累加所有Segment的操作次数  </span><br>                    sum += seg.modCount;  <br>                    <span class="hljs-type">int</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> seg.count;  <br>                    <span class="hljs-comment">//累加所有segment中的元素个数 size+=c  </span><br>                    <span class="hljs-keyword">if</span> (c &lt; <span class="hljs-number">0</span> || (size += c) &lt; <span class="hljs-number">0</span>)  <br>                        overflow = <span class="hljs-literal">true</span>;  <br>                &#125;  <br>            &#125;  <br>            <span class="hljs-comment">//当这次累加值和上一次累加值一样，证明没有进行新的增删改操作，返回sum  </span><br>            <span class="hljs-comment">//第一次last为0，如果有元素的话，这个for循环最少循环两次的  </span><br>            <span class="hljs-keyword">if</span> (sum == last)  <br>                <span class="hljs-keyword">break</span>;  <br>            <span class="hljs-comment">//记录累加的值  </span><br>            last = sum;  <br>        &#125;  <br>    &#125; <span class="hljs-keyword">finally</span> &#123;  <br>        <span class="hljs-comment">//如果之前有锁住，解锁  </span><br>        <span class="hljs-keyword">if</span> (retries &gt; RETRIES_BEFORE_LOCK) &#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; segments.length; ++j)  <br>                segmentAt(segments, j).unlock();  <br>        &#125;  <br>    &#125;  <br>    <span class="hljs-comment">//溢出，返回int的最大值，否则返回累加的size  </span><br>    <span class="hljs-keyword">return</span> overflow ? Integer.MAX_VALUE : size;  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="6-参考"><a href="#6-参考" class="headerlink" title="6. 参考"></a>6. 参考</h1><p><a href="https://www.bilibili.com/video/BV17i4y1x71z/?from=search&amp;seid=3516507855592185473&amp;spm_id_from=333.337.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV17i4y1x71z/?from=search&amp;seid=3516507855592185473&amp;spm_id_from=333.337.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;001- 基础知识专题&#x2F;001- 集合框架&#x2F;ConcurrentHashMap</p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>001-基础知识专题--关键字和接口-1、Serializable接口</title>
      <link href="/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-1%E3%80%81Serializable%E6%8E%A5%E5%8F%A3/"/>
      <url>/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E6%8E%A5%E5%8F%A3-1%E3%80%81Serializable%E6%8E%A5%E5%8F%A3/</url>
      
        <content type="html"><![CDATA[<h2 id="1-系列化的用途"><a href="#1-系列化的用途" class="headerlink" title="1. 系列化的用途"></a>1. 系列化的用途</h2><ul><li>想把的内存中的对象状态保存到一个文件中或者数据库中时候</li><li>网络通信时需要用套接字在网络中传送对象时，如我们使用 RPC 协议进行网络通信时</li></ul><h2 id="2-如何序列化"><a href="#2-如何序列化" class="headerlink" title="2. 如何序列化"></a>2. 如何序列化</h2><p>只要一个类实现 Serializable 接口，那么这个类就可以序列化了。</p><h2 id="3-关于-serialVersionUID"><a href="#3-关于-serialVersionUID" class="headerlink" title="3. 关于 serialVersionUID"></a>3. 关于 serialVersionUID</h2><p>在反序列化的过程中则需要使用 serialVersionUID 来确定由那个类来加载这个对象，所以我们在实现 Serializable 接口的时候，一般还会要去尽量显示地定义 serialVersionUID,如：</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">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">long</span> <span class="hljs-variable">serialVersionUID</span> <span class="hljs-operator">=</span> <span class="hljs-number">1L</span>; <br></code></pre></td></tr></table></figure><p>在反序列化的过程中，如果接收方为对象加载了一个类，如果该对象的 serialVersionUID 与对应持久化时的类不同，那么反序列化的过程中将会导致 InvalidClassException 异常。例如，在之前反序列化的例子中，我们故意将 User 类的 serialVersionUID 改为 2L，如：</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">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">long</span> <span class="hljs-variable">serialVersionUID</span> <span class="hljs-operator">=</span> <span class="hljs-number">2L</span>; <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></pre></td><td class="code"><pre><code class="hljs java">java.io.InvalidClassException: cn.wudimanong.serializable.User; local <span class="hljs-keyword">class</span> <span class="hljs-title class_">incompatible</span>: stream <span class="hljs-type">classdesc</span> <span class="hljs-variable">serialVersionUID</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>, local <span class="hljs-keyword">class</span> <span class="hljs-title class_">serialVersionUID</span> = <span class="hljs-number">2</span> <br>    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:<span class="hljs-number">687</span>) <br>    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:<span class="hljs-number">1880</span>) <br>    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:<span class="hljs-number">1746</span>) <br>    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:<span class="hljs-number">2037</span>) <br>    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:<span class="hljs-number">1568</span>) <br>    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:<span class="hljs-number">428</span>) <br>    at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:<span class="hljs-number">31</span>) <br>    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:<span class="hljs-number">44</span>) <br></code></pre></td></tr></table></figure><p>如果我们在序列化中没有显示地声明 serialVersionUID，则序列化运行时将会根据该类的各个方面计算该类默认的 serialVersionUID 值。但是，Java 官方强烈建议所有要序列化的类都显示地声明 serialVersionUID 字段，因为 <code>如果高度依赖于JVM默认生成serialVersionUID</code>，可能会导致其与编译器的实现细节耦合，这样可能会导致在反序列化的过程中发生意外的 InvalidClassException 异常。因此，<code>为了保证跨不同Java编译器实现的serialVersionUID值的一致，实现Serializable接口的必须显示地声明serialVersionUID字段</code>。</p><h2 id="4-静态变量序列化"><a href="#4-静态变量序列化" class="headerlink" title="4. 静态变量序列化"></a>4. 静态变量序列化</h2><p>串行化只能保存对象的非静态成员交量，不能保存任何的成员方法和静态的成员变量，而且串行化保存的只是变量的值，对于变量的任何修饰符都不能保存。</p><p>如果把 Person 类中的 name 定义为 static 类型的话，试图重构，就不能得到原来的值，只能得到 null。说明 <code>对静态成员变量值是不保存的</code>。这其实比较容易理解，<span style="background-color:#ff0000">序列化保存的是对象的状态，静态变量属于类的状态，因此序列化并不保存静态变量。</span></p><h2 id="5-transient"><a href="#5-transient" class="headerlink" title="5. transient"></a>5. transient</h2><p>当某些变量不想被序列化，同是又不适合使用 static 关键字声明，那么此时就需要用 transient 关键字来声明该变量。</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221024-Java transient 关键字使用小记 - Alexia(minmin) - 博客园||Java transient 关键字]]</p><p>对象的序列化可以通过实现两种接口来实现，若实现的是 Serializable 接口，则所有的序列化将会自动进行，若实现的是 Externalizable 接口，则没有任何东西可以自动序列化，需要在 writeExternal 方法中进行手工指定所要序列化的变量，这与是否被 transient 修饰无关。</p><h2 id="6-序列化中的问题"><a href="#6-序列化中的问题" class="headerlink" title="6. 序列化中的问题"></a>6. 序列化中的问题</h2><ul><li>一个子类实现了 Serializable 接口，它的父类都没有实现 Serializable 接口，序列化该子类对象。要想反序列化后输出父类定义的某变量的数值，就需要让父类也实现 Serializable 接口或者父类有默认的无参的构造函数。</li><li>在父类没有实现 Serializable 接口时，虚拟机是不会序列化父对象的，而一个 Java 对象的构造必须先有父对象，才有子对象，反序列化也不例外。所以反序列化时，为了构造父对象，只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时，它的值是调用父类无参构造函数后的值，如果在父类无参构造函数中没有对变量赋值，那么父类成员变量值都是默认值，如这里的 Long 型就是 null。</li><li>根据以上特性，我们可以将不需要被序列化的字段抽取出来放到父类中，子类实现 Serializable 接口，父类不实现 Serializable 接口但提供一个空构造方法，则父类的字段数据将不被序列化。</li><li>serialVersionUID 字段地声明要尽可能使用 private 关键字修饰，这是因为该字段的声明只适用于声明的类，该字段作为成员变量被子类继承是没有用处的!</li><li>数组类是不能显示地声明 serialVersionUID 的，因为它们始终具有默认计算的值，不过数组类反序列化过程中也是放弃了匹配 serialVersionUID 值的要求。</li><li><em>当一个对象的实例变量引用其他对象，序列化该对象时也把引用对象进行序列化，那么这个引用对象必须是可序列化的，这个类才能序列化。</em></li></ul><h3 id="6-1-同一个对象多次序列化之间有属性更新，前后的序列化有什么区别？"><a href="#6-1-同一个对象多次序列化之间有属性更新，前后的序列化有什么区别？" class="headerlink" title="6.1. 同一个对象多次序列化之间有属性更新，前后的序列化有什么区别？"></a>6.1. 同一个对象多次序列化之间有属性更新，前后的序列化有什么区别？</h3><p>结论：当对象第一次序列化成功后，后续这个对象属性即使有修改，也不会对后面的序列化造成成影响。</p><p>原因：是序列化算法的原因，所有要序列化的对象都有一个序列化的编码号，当试图序列化一个对象，会检查这个对象是否已经序列化过，若从未序列化过，才会序列化为字节序列去输出。若已经序列化过，则会输出一个编码符号，不会重复序列化一个对象。如下<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221026080832.png"></p><h2 id="7-参考"><a href="#7-参考" class="headerlink" title="7. 参考"></a>7. 参考</h2><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221026-谈谈我对Serializable接口的理解 - 掘金]]</p>]]></content>
      
      
      <categories>
          
          <category> 001-基础知识专题 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 关键字和接口 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-4、LinkedList</title>
      <link href="/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-4%E3%80%81LinkedList/"/>
      <url>/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-4%E3%80%81LinkedList/</url>
      
        <content type="html"><![CDATA[<h2 id="1-源码解读"><a href="#1-源码解读" class="headerlink" title="1. 源码解读"></a>1. 源码解读</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221026140557.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221026141000.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221026141055.png"></p><h2 id="2-与-ArrayList-对比"><a href="#2-与-ArrayList-对比" class="headerlink" title="2. 与 ArrayList 对比"></a>2. 与 ArrayList 对比</h2><ol><li><p>是否保证线程安全： ArrayList 和 LinkedList 都是不同步的，也就是不保证线程安全；</p></li><li><p>底层数据结构： Arraylist 底层使⽤的是 <code>Object 数组</code>； LinkedList 底层使⽤的是 <code>双向链表</code> 数据结构（JDK1.6 之前为循环链表，JDK1.7 取消了循环。注意双向链表和双向循环链表的区别，下⾯有介绍到！）</p></li><li><p>插⼊和删除是否受元素位置的影响：<br>① ArrayList 采⽤数组存储，所以插⼊和删除元素的时间复杂度受元素位置的影响。 ⽐如：执⾏ add(E e) ⽅法的时候， ArrayList 会默认在将指定的元素追加到此列表的末尾，这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插⼊和删除元素的话（ add(int index, E element) ）时间复杂度就为 O(n-i)。因为在进⾏上述操作的时候集合中第 i 和第 i 个元素之后的 (n-i) 个元素都要执⾏向后位&#x2F;向前移⼀位的操作。<br>② LinkedList 采⽤链表存储，所以对于 add(E e) ⽅法的插⼊，删除元素时间复杂度不受元素位置的影响，近似 O(1)，如果是要在指定位置 i 插⼊和删除元素的话（ (add(int index, E element) ） 时间复杂度近似为 o(n)) 因为需要先移动到指定位置再插⼊。</p></li><li><p>是否⽀持快速随机访问： LinkedList 不⽀持⾼效的随机元素访问，⽽ ArrayList ⽀持。快速随机访问就是通过元素的序号快速获取元素对象 (对应于 get(int index) ⽅法)。</p></li><li><p>内存空间占⽤： ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间，⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ ArrayList 更多的空间</p></li></ol><p>（因为要存放直接后继和直接前驱以及数据）。</p><h2 id="3-特点"><a href="#3-特点" class="headerlink" title="3. 特点"></a>3. 特点</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018185951.png"></p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-5、HashMap</title>
      <link href="/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-5%E3%80%81HashMap/"/>
      <url>/2022/10/25/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-5%E3%80%81HashMap/</url>
      
        <content type="html"><![CDATA[<h1 id="1-数据结构（哈希表）"><a href="#1-数据结构（哈希表）" class="headerlink" title="1. 数据结构（哈希表）"></a>1. 数据结构（哈希表）</h1><p>hashtable（哈希表）</p><h2 id="1-1-是什么"><a href="#1-1-是什么" class="headerlink" title="1.1. 是什么"></a>1.1. 是什么</h2><p><a href="https://link.zhihu.com/?target=https://baike.baidu.com/item/%25E6%2595%25A3%25E5%2588%2597%25E8%25A1%25A8/10027933">散列表</a>（Hash table，也叫哈希表），是根据关键码值 (Key value) 而直接进行访问的 <a href="https://link.zhihu.com/?target=https://baike.baidu.com/item/%25E6%2595%25B0%25E6%258D%25AE%25E7%25BB%2593%25E6%259E%2584/1450">数据结构</a>。也就是说，它通过把关键码值映射到表中一个位置来访问记录，以加快查找的速度。这个映射函数叫做 <a href="https://link.zhihu.com/?target=https://baike.baidu.com/item/%25E6%2595%25A3%25E5%2588%2597%25E5%2587%25BD%25E6%2595%25B0/2366288">散列函数</a>，存放记录的 <a href="https://link.zhihu.com/?target=https://baike.baidu.com/item/%25E6%2595%25B0%25E7%25BB%2584/3794097">数组</a> 叫做 <a href="https://link.zhihu.com/?target=https://baike.baidu.com/item/%25E6%2595%25A3%25E5%2588%2597%25E8%25A1%25A8/10027933">散列表</a><br>给定表 M，存在函数 f(key)，对任意给定的关键字值 key，代入函数后若能得到包含该关键字的记录在表中的地址，则称表 M 为哈希 (Hash）表，函数 f(key) 为哈希 (Hash) 函数。</p><h2 id="1-2-特点"><a href="#1-2-特点" class="headerlink" title="1.2. 特点"></a>1.2. 特点</h2><p>hash 表 也叫散列表；特点：快 很快 神奇的快</p><p>结构：结构有多种。最流行、最容易理解：顺序表 + 链表</p><p>主结构：顺序表，每个顺序表的节点在单独引出一个链表</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027082258.png"></p><p>在无序数组中按照内容查找，效率低下，时间复杂度是 O(n)<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027082042.png"></p><p>在有序数组中按照内容查找，可以使用折半查找，时间复杂度 O(log2n)<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027082130.png"></p><p><strong>问题</strong>：按照内容查找，能否也不进行比较，而是通过计算得到地址，实现类似数组按照索引查询的高效率 O(1) 呢</p><p>有！！！哈希表来实现</p><h2 id="1-3-哈希函数"><a href="#1-3-哈希函数" class="headerlink" title="1.3. 哈希函数"></a>1.3. 哈希函数</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027115551.png"></p><h2 id="1-4-native-hashcode-方法"><a href="#1-4-native-hashcode-方法" class="headerlink" title="1.4. native hashcode 方法"></a>1.4. native hashcode 方法</h2><p>[[20221029-Java Object.hashCode()返回的是对象内存地址？ - 简书]]<br><span style="background-color:#ff00ff">OpenJDK8 默认 hashCode 的计算方法是通过和当前线程有关的一个随机数 + 三个确定值，运用 Marsaglia’s xorshift scheme 随机数算法得到的一个随机数。和对象内存地址无关。</span></p><h2 id="1-5-哈希碰撞-哈希冲突-解决方案"><a href="#1-5-哈希碰撞-哈希冲突-解决方案" class="headerlink" title="1.5. 哈希碰撞 (哈希冲突) 解决方案"></a>1.5. 哈希碰撞 (哈希冲突) 解决方案</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027115612.png"></p><h2 id="1-6-装填因子"><a href="#1-6-装填因子" class="headerlink" title="1.6. 装填因子"></a>1.6. 装填因子</h2><p>装填因子 a &#x3D; 总键值对数 &#x2F;  哈希表总长度</p><h1 id="2-版本变化"><a href="#2-版本变化" class="headerlink" title="2. 版本变化"></a>2. 版本变化</h1><p>jdk7 和 jdk8 的区别</p><ol><li><p>初始 table 容量<br>jdk7: 创建 HashMap 对象时，则初始 table 容量为 16<br>jdk8: 创建 HashMap 对象时，没有初始 table，仅仅只是初始加载因子。只有当第一次添加时才会初始 table 容量为 16.</p></li><li><p>存储类型<br>   jdk7: table 的类型为 Entry<br>   jdk8: table 的类型为 Node</p></li><li><p>存储结构<br>   jdk7: 哈希表为<code>数组 + 链表</code>，不管链表的总结点数是多少都不会变成树结构<br>   jdk8：哈希表为<code>数组 + 链表 + 红黑树</code>，当<span style="background-color:#ff0000">链表的节点数&gt;&#x3D;8 &amp;&amp;  桶的总个数（table 的容量、数组的长度）&gt;&#x3D;64 </span>时，会将链表结构变成红黑树结构</p></li></ol><h2 id="2-1-JDK7-头插法"><a href="#2-1-JDK7-头插法" class="headerlink" title="2.1. JDK7 头插法"></a>2.1. JDK7 头插法</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221028210712.png"></p><h1 id="3-遍历方式"><a href="#3-遍历方式" class="headerlink" title="3. 遍历方式"></a>3. 遍历方式</h1><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></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_">TestHashMap</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>    Map&lt;String, Integer&gt; map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();<br><br>    <span class="hljs-comment">//向集合中添加元素  key  value 进行添加  添加方法 put</span><br>    map.put(<span class="hljs-string">&quot;aa&quot;</span>, <span class="hljs-number">11</span>);<br>    map.put(<span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-number">22</span>);<br>    map.put(<span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-number">33</span>);<br>    map.put(<span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-number">33</span>);<br>    map.put(<span class="hljs-literal">null</span>, <span class="hljs-number">44</span>);<br><br>    <span class="hljs-comment">//获取存储键值个数</span><br>    System.out.println(map.size());<br><br>    <span class="hljs-comment">//是否包含指定的key</span><br>    System.out.println(map.containsKey(<span class="hljs-string">&quot;cc&quot;</span>));<br>    System.out.println(map.containsKey(<span class="hljs-string">&quot;dd&quot;</span>));<br><br>    <span class="hljs-comment">//根据key获取value</span><br>    System.out.println(map.get(<span class="hljs-string">&quot;cc&quot;</span>));<br><br>    <span class="hljs-comment">//根据key  删除键值对  返回被删除的值</span><br>    <span class="hljs-type">Integer</span> <span class="hljs-variable">cc</span> <span class="hljs-operator">=</span> map.remove(<span class="hljs-string">&quot;cc&quot;</span>);<br>    System.out.println(cc);<br><br>    <span class="hljs-comment">//遍历</span><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">         * 键遍历</span><br><span class="hljs-comment">         * 值遍历</span><br><span class="hljs-comment">         * 键值遍历</span><br><span class="hljs-comment">         * */</span><br>    <span class="hljs-comment">//键遍历  获取所有的键</span><br>    Set&lt;String&gt; strings = map.keySet();<br>    <span class="hljs-keyword">for</span> (String key : strings) &#123;<br>      System.out.println(<span class="hljs-string">&quot;key-&gt; &quot;</span> + key + <span class="hljs-string">&quot; value-&gt; &quot;</span> + map.get(key));<br>    &#125;<br><br>    <span class="hljs-comment">//值遍历</span><br>    Collection&lt;Integer&gt; values = map.values();<br>    <span class="hljs-keyword">for</span> (Integer value : values) &#123;<br>      System.out.println(value);<br>    &#125;<br><br>    <span class="hljs-comment">//键值遍历</span><br>    Set&lt;Map.Entry&lt;String, Integer&gt;&gt; entries = map.entrySet();<br>    <span class="hljs-keyword">for</span> (Map.Entry&lt;String, Integer&gt; entry : entries) &#123;<br>      System.out.println(entry.getKey() + <span class="hljs-string">&quot;- &quot;</span> + entry.getValue());<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="4-实现逻辑"><a href="#4-实现逻辑" class="headerlink" title="4. 实现逻辑"></a>4. 实现逻辑</h1><h2 id="4-1-put-放入数据⭐️🔴"><a href="#4-1-put-放入数据⭐️🔴" class="headerlink" title="4.1. put 放入数据⭐️🔴"></a>4.1. put 放入数据⭐️🔴</h2><p>流程图请看 <a href="https://www.processon.com/view/635b8a0d637689731714be29?fromnew=1">https://www.processon.com/view/635b8a0d637689731714be29?fromnew=1</a></p><p>当第一次添加时，将初始 table 容量为 16，临界值为 12<br>    每次添加调用 putVal 方法：</p><p><span style="background-color:#ff00ff">①先获取 key 的二次哈希值与 (数组长度 n)-1 进行取与运算，得出存放的位置</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172411.jpg" alt="image-20200203161818324"></p><p><span style="background-color:#ff00ff">②判断该存放位置上是否有元素，如果没有直接存放</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172420.jpg" alt="image-20200203162435730"></p><p><span style="background-color:#ff00ff">如果该存放位置上已有元素，则进行继续判断：</span></p><p><span style="background-color:#ff00ff">​如果和当前元素直接相等，则覆盖</span><br><span style="background-color:#ff00ff">​如果不相等，则继续判断是否是链表结构还是树状结构，按照对应结构的判断方式判断相等</span></p><p><span style="background-color:#ff0000">③将 size 更新，判断是否超过了临界值，如果超过了，则需要重新 resize() 进行 <strong>2 倍</strong> 扩容，并打乱原来的顺序，重新排列</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172429.jpg" alt="image-20200203164225255"></p><p>重新排列，为了减少碰撞</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172437.jpg" alt="image-20200203164516991"></p><p><span style="background-color:#ff00ff">④当一个桶中的链表的节点数&gt;&#x3D;8 &amp;&amp;  桶的总个数（table 的容量）&gt;&#x3D;64 时，会将链表结构变成红黑树结构</span></p><h3 id="4-1-1-二次哈希"><a href="#4-1-1-二次哈希" class="headerlink" title="4.1.1. 二次哈希"></a>4.1.1. 二次哈希</h3><p><span style="background-color:#ff0000">二次哈希指的是将哈希码的高 16 位与低 16 位进行异或运算得到的加工过的哈希码</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410222407.png" alt="image.png"></p><h3 id="4-1-2-区别"><a href="#4-1-2-区别" class="headerlink" title="4.1.2. 区别"></a>4.1.2. 区别</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107102931.png"></p><h2 id="4-2-hashCode-equals"><a href="#4-2-hashCode-equals" class="headerlink" title="4.2. hashCode() equals()"></a>4.2. hashCode() equals()</h2><h2 id="4-3-加载因子"><a href="#4-3-加载因子" class="headerlink" title="4.3. 加载因子"></a>4.3. 加载因子</h2><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221027-面试官：为什么 HashMap 的加载因子是0.75？-技术圈]]</p><p>加载因子为 0.75，链表长度超过 8 时，转为红黑树，原因？</p><p>均值取 0.5 时，根据泊松分布公式 <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172509.jpg" alt="image-20200319233948518">，可以得到以下结果：</p><figure class="highlight plaintext"><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 plaintext">* threshold of 0.75, although with a large variance because of<br>     * resizing granularity. Ignoring variance, the expected<br>     * occurrences of list size k are (exp(-0.5) * pow(0.5, k) /<br>     * factorial(k)). The first values are:<br>     *<br>     * 0:    0.60653066<br>     * 1:    0.30326533<br>     * 2:    0.07581633<br>     * 3:    0.01263606<br>     * 4:    0.00157952<br>     * 5:    0.00015795<br>     * 6:    0.00001316<br>     * 7:    0.00000094<br>     * 8:    0.00000006<br>     * more: less than 1 in ten million<br></code></pre></td></tr></table></figure><p>在理想情况下，使用随机哈希码，在扩容阈值（加载因子）为 0.75 的情况下，节点出现在频率在 Hash 桶（表）中遵循参数平均为 0.5 的泊松分布。忽略方差，即 X &#x3D; λt，P(λt &#x3D; k)，其中λt &#x3D; 0.5 的情况，按公式：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027185306.png"></p><p>计算结果如上述的列表所示，当一个 bin 中的链表长度达到 8 个元素的时候，概率为 0.00000006，几乎是一个不可能事件。</p><p>所以我们可以知道，其实常数 0.5 是作为参数代入泊松分布来计算的，而加载因子 0.75 是作为一个条件，当 HashMap 长度为 length&#x2F;size ≥ 0.75 时就扩容，在这个条件下，冲突后的拉链长度和概率结果为：</p><figure class="highlight text"><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 text">0:    0.60653066  <br>1:    0.30326533  <br>2:    0.07581633  <br>3:    0.01263606  <br>4:    0.00157952  <br>5:    0.00015795  <br>6:    0.00001316  <br>7:    0.00000094  <br>8:    0.00000006<br></code></pre></td></tr></table></figure><h3 id="4-3-1-0-5-的来历"><a href="#4-3-1-0-5-的来历" class="headerlink" title="4.3.1. 0.5 的来历"></a>4.3.1. 0.5 的来历</h3><ol><li><em>λ</em> 代表的就是单位时间或单位面积特定事件出现的平均次数，关于一个 key 是否发生碰撞的概率为 0.5。</li><li>网络推断</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221028085649.png"></p><p>如果观察的事件在单位时间或单位面积出现的平均次数保持不变，并且不同时段或空间区域内事件的发生是相互独立的，那么单位时间或单位面积该事件出现的实际次数 <em>X</em> 服从 泊松分布（Poisson distribution） ，记作 <em>X～P</em> （ <em>λ</em> ）。</p><p>具体地， <em>X&#x3D;k</em> 的概率可表示为：</p><p><a href="http://www.tjxzj.net/wp-content/uploads/2022/06/2022063008384625.jpg" title="泊松分布的计算例题"><img src="http://www.tjxzj.net/wp-content/uploads/2022/06/2022063008384625.jpg" alt="泊松分布的计算例题-统计学之家"></a></p><p>其中， <em>λ</em> &gt;0。</p><p>可以进一步推导得到泊松分布的均值和方差均为 <em>λ</em> ，即：</p><p><a href="http://www.tjxzj.net/wp-content/uploads/2022/06/2022063008385573.jpg" title="泊松分布的计算例题"><img src="http://www.tjxzj.net/wp-content/uploads/2022/06/2022063008385573.jpg" alt="泊松分布的计算例题-统计学之家"></a></p><p>因此， <em>λ</em> 代表的就是单位时间或单位面积特定事件出现的平均次数。</p><p>二项分布的极限是柏松分布，我们可以根据二项分布的期望 λ&#x3D;np 求出 λ（n 是实验的次数，p 是单次的概率）。如果 n 比较大，p 比较小，所以我们才说满足泊松分布的条件。</p><h3 id="4-3-2-与-8-的关系"><a href="#4-3-2-与-8-的关系" class="headerlink" title="4.3.2. 与 8 的关系"></a>4.3.2. 与 8 的关系</h3><p>时间和空间成本的权衡 &#x3D;&#x3D;&gt; 决定了扩容因子是 0.75 &#x3D;&#x3D;&gt; 决定了泊松分布的参数λ是 0.5 &#x3D;&#x3D;&gt; 计算出泊松分布结果为 8 时，概率为亿分之六 &#x3D;&#x3D;&gt; 决定了树化节点为 8.<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221028-从泊松分布谈起HashMap为什么默认扩容因子是0.75 - 知乎]]</p><h2 id="4-4-树化"><a href="#4-4-树化" class="headerlink" title="4.4. 树化"></a>4.4. 树化</h2><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-comment">// TREEIFY_THRESHOLD为8，如果tab.length&gt;=64也满足，树化时一个桶的节点数为9</span><br><span class="hljs-comment">// 因为binCount=0时已经有1个头节点，又加了一个节点，是2个，</span><br><span class="hljs-comment">// 循环到binCount=7时7&gt;=8-1成立，所以此时生成的树有7+2个节点</span><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></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><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></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>    <span class="hljs-comment">//申明tab 和 p 用于操作原数组和结点</span><br>    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p;<br>    <span class="hljs-type">int</span> n, i;<br>    <span class="hljs-comment">//如果原数组是空或者原数组的长度等于0，那么通过resize()方法进行创建初始化</span><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>        <span class="hljs-comment">//获取到创建后数组的长度n</span><br>        n = (tab = resize()).length;<br><br>    <span class="hljs-comment">//通过key的hash值和 数组长度-1 计算出存储元素结点的数组中位置（和1.7一样）</span><br>    <span class="hljs-comment">//并且，如果该位置为空时，则直接创建元素结点赋值给该位置，后继元素结点为null</span><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>        <span class="hljs-comment">//否则，说明该位置存在元素</span><br>        Node&lt;K,V&gt; e; K k;<br>        <span class="hljs-comment">//判断table[i]的元素的key是否与添加的key相同，若相同则直接用新value覆盖旧value</span><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-comment">//判断是否是红黑树的结点，如果是，那么就直接在树中添加或者更新键值对</span><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-comment">//否则，就是链表，则在链表中添加或替换</span><br>        <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">//遍历table[i]，并判断添加的key是否已经存在，和之前判断一样，hash和equals</span><br>            <span class="hljs-comment">//遍历完毕后仍无发现上述情况，则直接在链表尾部插入数据</span><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-comment">//如果遍历的下一个结点为空，那么直接插入</span><br>                <span class="hljs-comment">//该方法是尾插法（与1.7不同）</span><br>                <span class="hljs-comment">//将p的next赋值给e进行以下判断</span><br>                <span class="hljs-keyword">if</span> ((e = p.next) == <span class="hljs-literal">null</span>) &#123;<br>                    <span class="hljs-comment">//直接创建新结点连接在上一个结点的后继上</span><br>                    p.next = newNode(hash, key, value, <span class="hljs-literal">null</span>);<br><span class="hljs-comment">//如果插入结点后，链表的结点数大于等7（8-1，即大于8）时，则进行红黑树的转换</span><br><span class="hljs-comment">//注意:不仅仅是链表大于8，并且会在treeifyBin方法中判断数组是否为空或数组长度是否小于64</span><br><span class="hljs-comment">//如果小于64则进行扩容，并且不是直接转换为红黑树</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-comment">//完成后直接退出循环</span><br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>                <span class="hljs-comment">//不退出循环时，则判断两个元素的key是否相同</span><br>                <span class="hljs-comment">//若相同，则直接退出循环，进行下面替换的操作</span><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>                <span class="hljs-comment">//否则，让p指向下一个元素结点</span><br>                p = e;<br>            &#125;<br>        &#125;<br>        <span class="hljs-comment">//接着上面的第二个break，如果e不为空，直接用新value覆盖旧value并且返回旧value</span><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-comment">//添加成功后，判断实际存在的键值对数量size是否大于扩容阈值threshold（第一次时为12）</span><br>    <span class="hljs-keyword">if</span> (++size &gt; threshold)<br>        <span class="hljs-comment">//若大于，扩容</span><br>        resize();<br>    <span class="hljs-comment">//添加成功时会调用的方法（默认实现为空）</span><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><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><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230321123949.png" alt="image.png"></p><p><span style="background-color:#ff00ff">桶的个数超过 64，桶就是数组</span><br><span style="background-color:#ff00ff">链表长度超过 8</span></p><h2 id="4-5-链化"><a href="#4-5-链化" class="headerlink" title="4.5. 链化"></a>4.5. 链化</h2><p>基本思想是当红黑树中的元素减少并小于一定数量时，会切换回链表。而元素减少有两种情况：</p><ul><li>1、调用 map 的 remove 方法删除元素</li></ul><p>hashMap 的 remove 方法，会进入到 removeNode 方法，找到要删除的节点，并判断 node 类型是否为 treeNode，然后进入删除红黑树节点逻辑的 removeTreeNode 方法中，该方法有关解除红黑树结构的分支如下：</p><figure class="highlight plaintext"><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 plaintext">//判断是否要解除红黑树的条件<br>if (root == null || root.right == null ||<br>                (rl = root.left) == null || rl.left == null) &#123;<br>                tab[index] = first.untreeify(map);  // too small<br>                return;<br>            &#125;<br>复制代码<br></code></pre></td></tr></table></figure><p>可以看到，此处并没有利用到网上所说的，<code>当节点数小于UNTREEIFY_THRESHOLD时才转换，而是通过红黑树根节点及其子节点是否为空来判断</code>。而满足该条件的最大红黑树结构如下： <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5cbd9b4d36945ec88dc80869304268d~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image" alt="image.png"> 节点数为 10，大于 UNTREEIFY_THRESHOLD（6），但是根据该方法的逻辑判断，还是需要转换为链表的。</p><ul><li>2、resize 的时候，对红黑树进行了拆分</li></ul><p>resize 的时候，判断节点类型，如果是链表，则将链表拆分，如果是 TreeNode，则执行 TreeNode 的 split 方法分割红黑树，而 split 方法中将红黑树转换为链表的分支如下：</p><figure class="highlight plaintext"><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 plaintext">//在这之前的逻辑是将红黑树每个节点的hash和一个bit进行&amp;运算，<br>//根据运算结果将树划分为两棵红黑树，lc表示其中一棵树的节点数<br>if (lc &lt;= UNTREEIFY_THRESHOLD)<br>                    tab[index] = loHead.untreeify(map);<br>                else &#123;<br>                    tab[index] = loHead;<br>                    if (hiHead != null) // (else is already treeified)<br>                        loHead.treeify(tab);<br>                &#125;<br>复制代码<br></code></pre></td></tr></table></figure><p>这里才用到了 <code>UNTREEIFY_THRESHOLD</code> 的判断，当红黑树节点元素<span style="background-color:#ff00ff">小于等于 6 </span>时，才调用 untreeify 方法转换回链表</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221027-jdk1.8的hashmap真的是大于8就转换成红黑树，小于6就变成链表吗 - 掘金]]</p><h2 id="4-6-resize（rehash）"><a href="#4-6-resize（rehash）" class="headerlink" title="4.6. resize（rehash）"></a>4.6. resize（rehash）</h2><p>当 HashMap 中的元素越来越多的时候，hash 冲突的几率也就越来越高，因为数组的<br>长度是固定的。所以为了提高查询的效率，就要对 HashMap 的数组进行扩容，数组扩容<br>这个操作也会出现在 ArrayList 中，这是一个常用的操作，而在 HashMap 数组扩容之后，<br><span style="background-color:#ffff00">最消耗性能的点</span>就出现了：<span style="background-color:#00ff00">原数组中的数据必须重新计算其在新数组中的位置</span>，并放进去，<br>这就是 resize。</p><p>那么 HashMap 什么时候进行扩容呢？当 HashMap 中的元素个数超过数组大小<br>loadFactor 时，就会进行数组扩容，loadFactor 的默认值为 0.75，这是一个折中的取值。<br>也就是说，默认情况下，数组大小为 16，那么当 HashMap 中元素个数超过 <code>16*0.75</code>&#x3D;12<br>的时候，就把数组的大小扩展为 <code> 2*16</code>&#x3D;32，即扩大一倍，然后重新计算每个元素在数组中<br>的位置，而这是一个非常消耗性能的操作，所以如果我们已经预知 HashMap 中元素的个<br>数，那么<span style="background-color:#00ff00">预设元素的个数能够有效的提高 HashMap 的性能</span>。</p><p>HashMap 在进行扩容时，使用的 rehash 方式非常巧妙，因为每次扩容都是翻倍，与原来计算 <code>（n-1）&amp; hash </code> 的结果相比，只是多了一个 bit 位，所以节点<span style="background-color:#00ff00">要么就在原来</span><span style="background-color:#00ff00">的位置，要么就被分配到“原位置 + 旧容量”这个位置</span>。<br>例如，原来的容量为 32，那么应该拿 hash 跟 31（0x11111）做与操作；在扩容扩到了 64 的容量之后，应该拿 hash 跟 63（0x111111）做与操作。新容量跟原来相比只是多了一个 bit 位，假设原来的位置在 23，那么当新增的那个 bit 位的计算结果为 0 时，那么该节点还是在 23；相反，计算结果为 1 时，则该节点会被分配到 23+31 的桶上。<br>正是因为这样巧妙的 rehash 方式，保证了 rehash 之后每个桶上的节点数必定小于等于原来桶上的节点数，即保证了 rehash 之后不会出现更严重的冲突。</p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221028-HashMap常见问题_Android_la的博客-CSDN博客]]<br>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221028-史上最全HashMap面试题汇总_原野的稻草人的博客-CSDN博客_hashmap面试题]]</p><h2 id="4-7-rehash"><a href="#4-7-rehash" class="headerlink" title="4.7. rehash"></a>4.7. rehash</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221105175024.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221105175110.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221105175128.png"></p><h2 id="4-8-Fail-Fast-机制-modCount"><a href="#4-8-Fail-Fast-机制-modCount" class="headerlink" title="4.8. Fail-Fast 机制-modCount"></a>4.8. Fail-Fast 机制-modCount</h2><p><span style="display:none">%%<br>▶4.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230411-1745%%</span>❕ ^43oza9</p><p>我们知道 java.util.HashMap 不是线程安全的，因此如果在使用迭代器的过程中有其他线程修改了 map，那么将抛出 ConcurrentModificationException，这就是所谓 fail-fast策略。<br>这一策略在源码中的实现是通过 <span style="background-color:#ff00ff">modCount</span> 域，modCount 顾名思义就是修改次数，对 HashMap 内容的修改都将增加这个值，那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027173820.png"></p><p>在迭代过程中，<span style="background-color:#ff00ff">判断 modCount 跟 expectedModCount 是否相等</span>，如果不相等就表示已经有其他线程修改了 Map：<br>注意到 <span style="background-color:#ff00ff">modCount 声明为 <strong>volatile</strong></span>，保证线程之间修改的可见性。<br>这里说的 volatile 是在 JDK5 和 JDK6 的时候，也许是正确的，因为在 JDK5 和 JDK6 中变量 modCount 确实声明为 volatile。但在 JDK7 和 JDK8 中，已经没有这样声明了！！！！！<br>由所有 HashMap 类的“collection 视图方法”所返回的迭代器都是快速失败的：在迭代器创建之后，如果从结构上对映射进行修改，除非通过迭代器本身的 remove 方法，其他任何时间任何方式的修改，迭代器都将抛出ConcurrentModificationException。因此，<span style="background-color:#ff00ff">面对并发的修改，迭代器很快就会完全失败，而不冒在将来不确定的时间发生任意不确定行为的风险</span>。<br>注意，迭代器的快速失败行为不能得到保证，一般来说，<span style="background-color:#ffff00">存在非同步的并发修改时，不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此，编写依赖于此异常的程序的做法是错误的，正确做法是：迭代器的快速失败行为应该仅用于检测程序错误。</span></p><h3 id="4-8-1-其他作用"><a href="#4-8-1-其他作用" class="headerlink" title="4.8.1. 其他作用"></a>4.8.1. 其他作用</h3><p>ConcurrentHashMap 中 size 方法中用来判断是否是稳定有效的 size，最少 2 次比较，最多 4 次，4 次后加锁</p><h2 id="4-9-扩容死锁与环链行程"><a href="#4-9-扩容死锁与环链行程" class="headerlink" title="4.9. 扩容死锁与环链行程"></a>4.9. 扩容死锁与环链行程</h2><p>[[20221029-JDK1.7版本HashMap的源码分析_老dubbo的博客-CSDN博客]]<br><a href="https://blog.csdn.net/aidaowuqiong/article/details/103492264">https://blog.csdn.net/aidaowuqiong/article/details/103492264</a></p><p><a href="https://www.bilibili.com/video/BV1mT4y177YC/?vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1mT4y177YC/?vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221029102506.png"></p><h2 id="4-10-扩容优化"><a href="#4-10-扩容优化" class="headerlink" title="4.10. 扩容优化"></a>4.10. 扩容优化</h2><h3 id="4-10-1-区别"><a href="#4-10-1-区别" class="headerlink" title="4.10.1. 区别"></a>4.10.1. 区别</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221107102604.png"></p><h3 id="4-10-2-哪些位置需要换桶"><a href="#4-10-2-哪些位置需要换桶" class="headerlink" title="4.10.2. 哪些位置需要换桶"></a>4.10.2. 哪些位置需要换桶</h3><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221104-HashMap扩容时的rehash方法中(e.hash &amp; oldCap) &#x3D;&#x3D; 0详解 - CodeAntenna]]</p><h2 id="4-11-null-键-null-值"><a href="#4-11-null-键-null-值" class="headerlink" title="4.11. null 键 null 值"></a>4.11. null 键 null 值</h2><p>HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选的映射操作，并允许使用 null 值 (多个) 和 null 键 (1 个)。此类不保证映射的顺序，特别是它不保证该顺序恒久不变。</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-comment">// JDK 1.8实现：将 键key 转换成 哈希码（hash值）操作 = 使用hashCode() + 1次位运算 + 1次异或运算（2次扰动）</span><br>      <span class="hljs-comment">// 1. 取hashCode值： h = key.hashCode() </span><br>      <span class="hljs-comment">// 2. 高位参与低位的运算：h ^ (h &gt;&gt;&gt; 16)  </span><br>      <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>            <span class="hljs-comment">// a. 当key = null时，hash值 = 0，所以HashMap的key 可为null      </span><br>            <span class="hljs-comment">// 注：对比HashTable，HashTable对key直接hashCode（），若key为null时，会抛出异常，所以HashTable的key不可为null</span><br>            <span class="hljs-comment">// b. 当key ≠ null时，则通过先计算出 key的 hashCode()（记为h），然后 对哈希码进行 扰动处理： 按位 异或（^） 哈希码自身右移16位后的二进制</span><br>     &#125;<br></code></pre></td></tr></table></figure><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/image-20211010212147026.png" alt="image-20211010212147026"></p><h2 id="4-12-2-的-N-次方⭐️🔴"><a href="#4-12-2-的-N-次方⭐️🔴" class="headerlink" title="4.12. 2 的 N 次方⭐️🔴"></a>4.12. 2 的 N 次方⭐️🔴</h2><h3 id="4-12-1-原因"><a href="#4-12-1-原因" class="headerlink" title="4.12.1. 原因"></a>4.12.1. 原因</h3><p>新索引值&#x3D;二次哈希值  &amp;（当前容量 -1）</p><p>之所以底层数组的长度是 2 的整数次幂，是因为让按位与运算与取模运算结果相等</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172331.jpg" alt="image-20200129171632793"></p><p>原因分析：</p><ol><li><p>提高 hash 效率：因为这里的哈希算法是用的除留取余法，即要做取模运算，为了提高效率，转变为按位与运算，而要想结果相等，就要求容量值为 2 的整数次幂，否则除留取余无法转变成按位与运算。<br>  运算相比于取模运算速度大幅度提升 (按照 Bruce Eckel 给出的数据，大约可以提升 5～8 倍）。</p></li><li><p>充分利用空间：<span style="background-color:#ff00ff">如果不是 2 的 n 次方，有些位置可能为 0，与任何数做与运算都是 0，导致无法获取到数组的某些角标位置，造成空间浪费</span>。</p></li><li><p>减少 hash 碰撞：为 2 的 n 次方时，2n-1 得到的二进制数的每个位上的值都为 1，这使得在低位上&amp;时，得到的和原 hash 的低位相同，加之 hash(int h) 方法对 key 的 hashCode 的进一步优化，加入了高位计算，就使得只有相同的 hash 值的两个值才会被放到数组中的同一个位置上形成链表。所以说，当数组长度为 2 的 n 次幂的时候，不同的 key 算得得 index 相同的几率较小，那么数据在数组上分布就比较均匀，也就是说碰撞的几率小，相对的，查询的时候就不用遍历某个位置上的链表，这样查询效率也就较高了。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221029090521.png"></p></li><li><p>扩容判断优化：1.8 中的高低位也用到了 n-1 低位都为 1 的特性，即上文所说，每次扩容都是翻倍，与原来计算 <code>（n-1）&amp; hash </code> 的结果相比，只是多了一个 bit 位，所以节点<span style="background-color:#00ff00">要么就在原来的位置，要么就被分配到“原位置 + 旧容量”这个位置</span>。</p><h4 id="4-12-2-源码"><a href="#4-12-2-源码" class="headerlink" title="4.12.2. 源码"></a>4.12.2. 源码</h4><p> <img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221027140336.png"></p></li></ol><h1 id="5-红黑树"><a href="#5-红黑树" class="headerlink" title="5. 红黑树"></a>5. 红黑树</h1><p>红黑树是平衡二叉树的一种实现方式，另一种实现方式是最初的 AVL 树。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172345.jpg" alt="image-20200129174140779"></p><p><strong>TreeSet TreeMap 中里都是红黑树</strong></p><h1 id="6-hash-索引与-B-树索引的区别"><a href="#6-hash-索引与-B-树索引的区别" class="headerlink" title="6. hash 索引与 B 树索引的区别"></a>6. hash 索引与 B 树索引的区别</h1><p>Hash 索引结构的特殊性，其检索效率非常高，索引的检索可以一次定位，不像 B-Tree 索引需要从根节点到枝节点，最后才能访问到页节点这样多次的 IO 访问，所以 Hash 索引的查询效率要远高于 B-Tree 索引。</p><p>（1）Hash 索引仅仅能满足”&#x3D;”,”IN” 和”&lt;&#x3D;&gt;” 查询，<span style="background-color:#ffff00">不能使用范围查询</span>。 <br>由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值，所以它只能用于等值的过滤，不能用于基于范围的过滤，因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系，并不能保证和 Hash 运算前完全一样。</p><p>（2）Hash 索引<span style="background-color:#ffff00">无法</span>被用来避免数据的<span style="background-color:#ffff00">排序</span>操作。 <br>由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值，而且 Hash 值的大小关系并不一定和 Hash 运算前的键值完全一样，所以数据库无法利用索引的数据来避免任何排序运算；</p><p>（3）Hash 索引<span style="background-color:#ffff00">不能利用部分索引键查询</span>。 <br>对于组合索引，Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值，而不是单独计算 Hash 值，所以通过组合索引的前面一个或几个索引键进行查询的时候，Hash 索引也无法被利用。</p><p>（4）Hash 索引在<span style="background-color:#ffff00">任何时候都不能避免表扫描</span>。 <br>前面已经知道，Hash 索引是将索引键通过 Hash 运算之后，将 Hash 运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中，由于不同索引键存在相同 Hash 值，所以即使取满足某个 Hash 键值的数据的记录条数，也无法从 Hash 索引中直接完成查询，还是要通过访问表中的实际数据进行相应的比较，并得到相应的结果。</p><p>（5）Hash 索引遇到<span style="background-color:#ffff00">大量 Hash 值相等的情况后性能并不一定就会比 B-Tree 索引高</span>。 <br>对于选择性比较低的索引键，如果创建 Hash 索引，那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦，会浪费多次表数据的访问，而造成整体性能低下。</p><h1 id="7-各种比较"><a href="#7-各种比较" class="headerlink" title="7. 各种比较"></a>7. 各种比较</h1><pre><code>                                   底层结构      版本 线程安全（同步）允许null键null值</code></pre><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221203194906.png"></p><p>底层结构：哈希表<br>    jdk7: 数组 + 链表<br>    jdk8: 数组 + 链表 + 红黑树</p><p>源码分析：<br>    jdk8： HashMap 中维护了 Node 类型的数组 table，当 HashMap 创建对象时，只是对 loadFactor 初始化为 0.75；table 还是保持默认值 null</p><blockquote><pre><code>当第一次添加时，将初始table容量为16，临界值为12每次添加调用putVal方法：    ①先获取key的二次哈希值并进行取与运算，得出存放的位置    ②判断该存放位置上是否有元素，如果没有直接存放     如果该存放位置上已有元素，则进行继续判断：                            如果和当前元素直接相等，则覆盖                            如果不相等，则继续判断是否是链表结构还是树状结构，按照对应结构的判断方式判断相等    ③将size更新，判断是否超过了临界值，如果超过了，则需要重新resize()进行2倍扩容，并打乱原来的顺序，重新排列    ④&lt;span style=&quot;background-color:#ff00ff&quot;&gt;当一个桶中的链表的节点数&gt;=8 &amp;&amp;  桶的总个数（table的容量）&gt;=64时，会将链表结构变成红黑树结构&lt;/span&gt;</code></pre></blockquote><p>jdk7 和 jdk8 的区别<br>    1.jdk7: 创建 HashMap 对象时，则初始 table 容量为 16<br>      jdk8: 创建 HashMap 对象时，没有初始 table，仅仅只是初始加载因子。只有当第一次添加时才会初始 table 容量为 16.</p><pre><code>2.jdk7:table的类型为Entry  jdk8:table的类型为Node3.jdk7:哈希表为数组+链表，不管链表的总结点数是多少都不会变成树结构  jdk8：哈希表为数组+链表+红黑树，当链表的节点数&gt;=8 &amp;&amp;  桶的总个数（table的容量）&gt;=64时，会将链表结构变成红黑树结构</code></pre><p>应用层面：</p><pre><code>    要求添加元素的key重写hashCode和equals方法</code></pre><h1 id="8-泊松分布"><a href="#8-泊松分布" class="headerlink" title="8. 泊松分布"></a>8. 泊松分布</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20230410172359.jpg" alt="image-20200129175339730"></p><h1 id="9-特点"><a href="#9-特点" class="headerlink" title="9. 特点"></a>9. 特点</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018185915.png"></p><h1 id="10-防止哈希碰撞的措施⭐️🔴"><a href="#10-防止哈希碰撞的措施⭐️🔴" class="headerlink" title="10. 防止哈希碰撞的措施⭐️🔴"></a>10. 防止哈希碰撞的措施⭐️🔴</h1><pre><code> * JDK 1.7 做了9次扰动处理 = 4次位运算 + 5次异或运算 * JDK 1.8 简化了扰动函数 = 只做了2次扰动 = 1次位运算 + 1次异或运算</code></pre><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">   /**<br>     * 分析1：hash(key)<br>     * 作用：计算传入数据的哈希码（哈希值、Hash值）<br>     * 该函数在JDK 1.7 和 1.8 中的实现不同，但原理一样 = 扰动函数 = 使得根据key生成的哈希码（hash值）分布更加均匀、更具备随机性，避免出现hash值冲突（即指不同key但生成同1个hash值）<br>     * JDK 1.7 做了9次扰动处理 = 4次位运算 + 5次异或运算<br>     * JDK 1.8 简化了扰动函数 = 只做了2次扰动 = 1次位运算 + 1次异或运算<br>     */<br><br>      // JDK 1.7实现：将 键key 转换成 哈希码（hash值）操作  = 使用hashCode() + 4次位运算 + 5次异或运算（9次扰动）<br>      static final int hash(int h) &#123;<br>        h ^= k.hashCode(); <br>        h ^= (h &gt;&gt;&gt; 20) ^ (h &gt;&gt;&gt; 12);<br>        return h ^ (h &gt;&gt;&gt; 7) ^ (h &gt;&gt;&gt; 4);<br>     &#125;<br><br>      // JDK 1.8实现：将 键key 转换成 哈希码（hash值）操作 = 使用hashCode() + 1次位运算 + 1次异或运算（2次扰动）<br>      // 1. 取hashCode值： h = key.hashCode() <br>      // 2. 高位参与低位的运算：h ^ (h &gt;&gt;&gt; 16)  <br>      static final int hash(Object key) &#123;<br>           int h;<br>            return (key == null) ? 0 : (h = key.hashCode()) ^ (h &gt;&gt;&gt; 16);<br>            // a. 当key = null时，hash值 = 0，所以HashMap的key 可为null      <br>            // 注：对比HashTable，HashTable对key直接hashCode（），若key为null时，会抛出异常，所以HashTable的key不可为null<br>            // b. 当key ≠ null时，则通过先计算出 key的 hashCode()（记为h），然后 对哈希码进行 扰动处理： 按位 异或（^） 哈希码自身右移16位后的二进制<br>     &#125;<br><br>   /**<br>     * 计算存储位置的函数分析：indexFor(hash, table.length)<br>     * 注：该函数仅存在于JDK 1.7 ，JDK 1.8中实际上无该函数（直接用1条语句判断写出），但原理相同<br>     * 为了方便讲解，故提前到此讲解<br>     */<br>     static int indexFor(int h, int length) &#123;  <br>          return h &amp; (length-1); <br>          // 将对哈希码扰动处理后的结果 与运算(&amp;) （数组长度-1），最终得到存储在数组table的位置（即数组下标、索引）<br>          &#125;<br>复制代码<br></code></pre></td></tr></table></figure><h1 id="11-参考"><a href="#11-参考" class="headerlink" title="11. 参考"></a>11. 参考</h1><h2 id="11-1-黑马"><a href="#11-1-黑马" class="headerlink" title="11.1. 黑马"></a>11.1. 黑马</h2><h3 id="11-1-1-视频"><a href="#11-1-1-视频" class="headerlink" title="11.1.1. 视频"></a>11.1.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1A741127DP/?p=2&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1A741127DP/?p=2&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="11-1-2-资料"><a href="#11-1-2-资料" class="headerlink" title="11.1.2. 资料"></a>11.1.2. 资料</h3><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">/Users/taylor/Nutstore Files/Obsidian_data/pages/<span class="hljs-number">002</span>-schdule/<span class="hljs-number">001</span>-Arch/<span class="hljs-number">001</span>-Subject/<span class="hljs-number">001</span>-基础知识专题/<span class="hljs-number">001</span>-集合框架/黑马-HashMap<br></code></pre></td></tr></table></figure><h2 id="11-2-old"><a href="#11-2-old" class="headerlink" title="11.2. old"></a>11.2. old</h2><p>Java 并发编程全套视频教程（JMM+AQS+synchronized+hashmap）<br><a href="https://www.bilibili.com/video/BV17c411h7Vw/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV17c411h7Vw/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://juejin.im/post/5aa5d8d26fb9a028d2079264">https://juejin.im/post/5aa5d8d26fb9a028d2079264</a></p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221028-Java源码分析：HashMap 1.8 相对于1.7 到底更新了什么？ - 掘金]]</p><p><a href="https://www.bilibili.com/video/BV15Q4y1e7q6?p=6&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV15Q4y1e7q6?p=6&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20221104-Java 8系列之重新认识HashMap - 美团技术团队]]</p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-1、集合关系及概念</title>
      <link href="/2022/10/23/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-1%E3%80%81%E9%9B%86%E5%90%88%E5%85%B3%E7%B3%BB%E5%8F%8A%E6%A6%82%E5%BF%B5/"/>
      <url>/2022/10/23/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-1%E3%80%81%E9%9B%86%E5%90%88%E5%85%B3%E7%B3%BB%E5%8F%8A%E6%A6%82%E5%BF%B5/</url>
      
        <content type="html"><![CDATA[<h1 id="1-集合类继承关系"><a href="#1-集合类继承关系" class="headerlink" title="1. 集合类继承关系"></a>1. 集合类继承关系</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220923202528.jpg" alt="image-20200203133907641"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220923150555.jpg" alt="image-20200203133953108"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221024082344.png"></p><h1 id="2-初始容量-加载因子"><a href="#2-初始容量-加载因子" class="headerlink" title="2. 初始容量 加载因子"></a>2. 初始容量 加载因子</h1><table><thead><tr><th></th><th>初始容量</th><th>加载因子</th><th></th><th></th><th></th></tr></thead><tbody><tr><td>ArrayList</td><td>0</td><td>1.5X</td><td></td><td></td><td></td></tr><tr><td>LinkedList</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>Vector</td><td></td><td>2X</td><td></td><td></td><td></td></tr><tr><td>HashMap</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>CurrentHashMap</td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table><h1 id="3-List，Set，Map-三者的区别"><a href="#3-List，Set，Map-三者的区别" class="headerlink" title="3. List，Set，Map 三者的区别"></a>3. List，Set，Map 三者的区别</h1><p>Java 容器分为 Collection 和 Map 两大类，Collection 集合的子接口有 Set、List、Queue 三种子接口。我们比较常用的是 Set、List，Map 接口不是 collection 的子接口。</p><p>Collection 集合主要有 List 和 Set 两大接口<br>    1. List：一个有序（元素存入集合的顺序和取出的顺序一致）容器，元素可以重复，可以插入多个 null 元素，元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。<br>    2. Set：一个无序（存入和取出顺序有可能不一致）容器，不可以存储重复元素，只允许存入一<br>         个 null 元素，必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及<br>         TreeSet。</p><p>Map 是一个键值对集合，存储键、值和之间的映射。 Key 无序，唯一；value 不要求有序，允许重复。Map 没有继承于 Collection 接口，从 Map 集合中检索元素时，只要给出键对象，就会返回对应的值对象。可以存 null 键、null 值<br>Map 的常用实现类：HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap(不可以存 null 键、null 值)</p><h1 id="4-遍历-List"><a href="#4-遍历-List" class="headerlink" title="4. 遍历 List"></a>4. 遍历 List</h1><p> 有哪些不同的方式？每种方法的实现原理是什么？Java 中 List 遍历的最佳实践是什么？</p><p>遍历方式有以下几种：</p><ol><li><p>for 循环遍历，基于计数器。在集合外部维护一个计数器，然后依次读取每一个位置的元素，<br>当读取到最后一个元素后停止。</p></li><li><p>迭代器遍历，Iterator。Iterator 是面向对象的一个设计模式，目的是屏蔽不同数据集合的特<br>点，统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。</p></li><li><p>foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现，使用时不需要显式声明<br>Iterator 或计数器。优点是代码简洁，不易出错；缺点是只能做简单的遍历，不能在遍历过程中操作数据集合，例如删除、替换。</p></li></ol><p>最佳实践：<br>Java Collections 框架中提供了一个 RandomAccess 接口，用来标记 List 实现是否支持 Random Access。</p><p>如果一个数据集合实现了该接口，就意味着它支持 Random Access，按位置读取元素的平均<br>时间复杂度为 O(1)，如 ArrayList。</p><p>如果没有实现该接口，表示不支持 Random Access，如 LinkedList。<br>推荐的做法就是，支持 Random Access 的列表可用 for 循环遍历，否则建议用 Iterator 或 foreach 遍历。</p><h1 id="5-elementData-加上-transient"><a href="#5-elementData-加上-transient" class="headerlink" title="5. elementData 加上 transient"></a>5. elementData 加上 transient</h1><p>ArrayList 中的数组定义如下：<br>private transient Object[] elementData;<br>再看一下 ArrayList 的定义：</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">class</span> <span class="hljs-title class_">ArrayList</span>&lt;E&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractList</span>&lt;E&gt;<br><br><span class="hljs-keyword">implements</span> <span class="hljs-title class_">List</span>&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable<br></code></pre></td></tr></table></figure><p>可以看到 ArrayList 实现了 Serializable 接口，这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化，重写了 <code>writeObject</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></pre></td><td class="code"><pre><code class="hljs java"><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">writeObject</span><span class="hljs-params">(java.io.ObjectOutputStream s)</span> <span class="hljs-keyword">throws</span><br><br>java.io.IOException&#123;<br><br>*<span class="hljs-comment">// Write out element count, and any hidden stuff*</span><br><br><span class="hljs-type">int</span> <span class="hljs-variable">expectedModCount</span> <span class="hljs-operator">=</span> modCount;<br><br>s.defaultWriteObject();<br><br>*<span class="hljs-comment">// Write out array length*</span><br><br>s.writeInt(elementData.length);<br><br>*<span class="hljs-comment">// Write out all elements in the proper order.*</span><br><br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i&lt;size; i++)<br><br>s.writeObject(elementData[i]);<br><br><span class="hljs-keyword">if</span> (modCount != expectedModCount) &#123;<br><br><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentModificationException</span>();<br>&#125;<br></code></pre></td></tr></table></figure><p>每次序列化时，先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素，然后遍历 elementData，<code>只序列化已存入的元素</code>，这样既加快了序列化的速度，又减小了序列化之后的文件大小。<code>即size为0时，不会走序列化流程</code></p><h1 id="6-List-和-Set-的区别"><a href="#6-List-和-Set-的区别" class="headerlink" title="6. List 和 Set 的区别"></a>6. List 和 Set 的区别</h1><p>List , Set 都是继承自 Collection 接口</p><p>List 特点：一个有序（元素存入集合的顺序和取出的顺序一致）容器，元素可以重复，可以插入多个 null 元素，元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。<br>Set 特点：一个无序（存入和取出顺序有可能不一致）容器，不可以存储重复元素，只允许存入一个 null 元素，必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及<br>TreeSet。</p><p> List 支持 for 循环，也就是通过下标来遍历，也可以用迭代器，但是 set 只能用迭代，因为他无<br>序，无法用下标来取得想要的值。</p><p>Set：检索元素效率低下，删除和插入效率高，插入和删除不会引起元素位置改变。<br>List：和数组类似，List 可以动态增长，查找元素效率高，插入删除元素效率低，因为会引起<br>其他元素位置改变</p><h1 id="7-Array-和-List-之间转换"><a href="#7-Array-和-List-之间转换" class="headerlink" title="7. Array 和 List 之间转换"></a>7. Array 和 List 之间转换</h1><p>Array 转 List： Arrays. asList(array)<br>List 转 Array：List 的 toArray() 方法</p><p>注意避坑，详情请见 <a href="/2022/10/22/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/Java%E5%9F%BA%E7%A1%80-%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-2%E3%80%81%E9%9B%86%E5%90%88%E7%B1%BB%E7%9A%84%E5%9D%91/" title="Java基础-集合框架-2、集合类的坑">Java基础-集合框架-2、集合类的坑</a></p><h1 id="8-comparable-和-comparator-的区别"><a href="#8-comparable-和-comparator-的区别" class="headerlink" title="8. comparable 和 comparator 的区别"></a>8. comparable 和 comparator 的区别</h1><p>comparable 接口实际上是出自 java.lang 包，它有一个 compareTo(Object obj) 方法用来排序<br>comparator 接口实际上是出自 java.util 包，它有一个 compare(Object obj1, Object obj2) 方法用来排序<br>一般我们需要对一个集合进行简单自定义排序时，我们就要重写 compareTo 方法或 compare 方法，<br>当我们需要对某一个集合进行复合自定义排序时，比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话，我们可以使用内部比较器 (重写 compareTo 方法) 或者使用多个 Comparator 类 (重写 compare 方法) 来实现歌名排序和歌星名排序，复合 comparator 时可以使用两个参数版的 <code>Collections.sort(List&lt;T&gt; list, Comparator&lt;? super T&gt; c)</code>。</p><h2 id="8-1-compareTo-方法"><a href="#8-1-compareTo-方法" class="headerlink" title="8.1. compareTo 方法"></a>8.1. compareTo 方法</h2><p>实体类实现 comparable 接口，重写 compareTo 方法，来构成内部比较器<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221024113732.png"></p><h2 id="8-2-compare-方法"><a href="#8-2-compare-方法" class="headerlink" title="8.2. compare 方法"></a>8.2. compare 方法</h2><p>比较器类实现 comparator 接口，重写 compare 方法，来构成外部比较器，使用多态的思想，比较方式更加灵活</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221024113203.png"></p><h2 id="8-3-Collections-sort"><a href="#8-3-Collections-sort" class="headerlink" title="8.3. Collections.sort"></a>8.3. Collections.sort</h2><p>Collections 是一个工具类，sort 是其中的静态方法，是用来对 List 类型进行排序的，它有两种参数形式：</p><pre><code>public static &lt;T extends Comparable&lt;? super T&gt;&gt; void sort(List&lt;T&gt; list) &#123;    list.sort(null);&#125;public static &lt;T&gt; void sort(List&lt;T&gt; list, Comparator&lt;? super T&gt; c) &#123;    list.sort(c);&#125;</code></pre><p>1.对于 String 或 Integer 这些已经实现 Comparable 接口的类来说，可以直接使用 Collections.sort 方法传入 list 参数来实现默认方式（正序）排序；</p><p>2.如果不想使用默认方式（正序）排序，可以通过 Collections.sort 传入第二个参数类型为 Comparator 来自定义排序规则；</p><p>3.对于自定义类型 (如本例子中的 Emp)，如果想使用 Collections.sort 的方式一进行排序，可以通过实现 Comparable 接口的 compareTo 方法来进行，如果不实现，则参考第 2 点；</p><p>4.jdk1.8 的 Comparator 接口有很多新增方法，其中有个 <a href="http://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#reversed--">reversed()</a> 方法比较实用，是用来切换正序和逆序的</p><h1 id="9-迭代器-Iterator"><a href="#9-迭代器-Iterator" class="headerlink" title="9. 迭代器 Iterator"></a>9. 迭代器 Iterator</h1><h2 id="9-1-迭代器原理"><a href="#9-1-迭代器原理" class="headerlink" title="9.1. 迭代器原理"></a>9.1. 迭代器原理</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018084742.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018085135.png"></p><p>Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration，迭代器允许调用者在迭代过程中移除元素。因为所有 Collection 接继承了 Iterator 迭代器</p><p>增强 for 循环底层也是用了迭代器</p><h2 id="9-2-Iterator-怎么使用？有什么特点？"><a href="#9-2-Iterator-怎么使用？有什么特点？" class="headerlink" title="9.2. Iterator 怎么使用？有什么特点？"></a>9.2. Iterator 怎么使用？有什么特点？</h2><p>Iterator 使用代码如下：</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-keyword">interface</span> <span class="hljs-title class_">Collection</span>&lt;E&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Iterable</span>&lt;E&gt; &#123;  <br>    <span class="hljs-comment">// Query Operations  </span><br>  <br>    <span class="hljs-comment">/**     * Returns the number of elements in this collection.  If this collection     * contains more than &lt;tt&gt;Integer.MAX_VALUE&lt;/tt&gt; elements, returns     * &lt;tt&gt;Integer.MAX_VALUE&lt;/tt&gt;.  </span><br><span class="hljs-comment">     *     *<span class="hljs-doctag">@return</span> the number of elements in this collection  </span><br><span class="hljs-comment">     */</span>    <span class="hljs-type">int</span> <span class="hljs-title function_">size</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><p>Iterator 的特点是只能单向遍历，但是更加安全，因为它可以确保，在当前遍历的集合元素被更改的时候，就会抛出 ConcurrentModificationException 异常。</p><h2 id="9-3-iterator-Iterator-Iterable-关系"><a href="#9-3-iterator-Iterator-Iterable-关系" class="headerlink" title="9.3. iterator() Iterator Iterable 关系"></a>9.3. iterator() Iterator Iterable 关系</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221026182934.png"></p><h2 id="9-4-ListIterator"><a href="#9-4-ListIterator" class="headerlink" title="9.4. ListIterator"></a>9.4. ListIterator</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018085812.png"></p><h1 id="10-如何边遍历边移除-Collection-中的元素？"><a href="#10-如何边遍历边移除-Collection-中的元素？" class="headerlink" title="10. 如何边遍历边移除 Collection 中的元素？"></a>10. 如何边遍历边移除 Collection 中的元素？</h1><p>边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法，如下：<br>一种最常见的错误代码如下：运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integer i : list)) 语句时，会自动生成一个 iterator 来遍历该 list，但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。</p><h1 id="11-Iterator-和-ListIterator-有什么区别？"><a href="#11-Iterator-和-ListIterator-有什么区别？" class="headerlink" title="11. Iterator 和 ListIterator 有什么区别？"></a>11. Iterator 和 ListIterator 有什么区别？</h1><p>Iterator 可以遍历 Set 和 List 集合，而 ListIterator 只能遍历 List。<br>Iterator 只能单向遍历，而 ListIterator 可以双向遍历（向前&#x2F;后遍历）。<br>ListIterator 实现 Iterator 接口，然后添加了一些额外的功能，比如添加一个元素、替换一个元<br>素、获取前面或后面元素的索引位置。</p><h1 id="12-RandomAccess-接⼝"><a href="#12-RandomAccess-接⼝" class="headerlink" title="12. RandomAccess 接⼝"></a>12. RandomAccess 接⼝</h1><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">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">RandomAccess</span> &#123;<br>&#125;<br></code></pre></td></tr></table></figure><p>查看源码我们发现实际上 RandomAccess 接⼝中什么都没有定义。所以，在我看来<br>RandomAccess 接⼝不过是⼀个标识罢了。标识什么？ 标识实现这个接⼝的类具有随机访问功能。<br>在 binarySearch（) ⽅法中，它要判断传⼊的 list 是否 RamdomAccess 的实例，如果是，调<br>⽤ indexedBinarySearch() ⽅法，如果不是，那么调⽤ iteratorBinarySearch() ⽅法</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"><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;T&gt; <span class="hljs-type">int</span> <span class="hljs-title function_">binarySearch</span><span class="hljs-params">(List&lt;? extends Comparable&lt;? <span class="hljs-built_in">super</span> Tef list, T key)</span><br><br>&#123;<br><br><span class="hljs-keyword">if</span> (list <span class="hljs-keyword">instanceof</span> RandomAccess || list.size() &lt; BINARYSEARCH_THRESHOLD)<br><br>  <span class="hljs-keyword">return</span> Collections.indexedBinarySearch(list, key);<br><br><span class="hljs-keyword">else</span><br><br>  <span class="hljs-keyword">return</span> Collections.iteratorBinarySearch(list, key);<br><br>&#125;<br></code></pre></td></tr></table></figure><p>private static final int BINARYSEARCH_THRESHOLD   &#x3D; 5000;</p><p>ArrayList 实现了 RandomAccess 接⼝， ⽽ LinkedList 没有实现。因为 ArrayList 底层是数组，⽽ LinkedList 底层是链表。数组天然⽀持随机访问，时间复杂度为 O(1)，所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素，时间复杂度为 O(n)，所以不⽀持快速随机访问。<br>ArrayList 实现了 RandomAccess 接⼝，就表明了他具有快速随机访问功能。 RandomAccess 接⼝只是标识，并不是说 ArrayList 实现 RandomAccess 接⼝才具有快速随机访问功能的！</p><h1 id="13-Serializable-接口"><a href="#13-Serializable-接口" class="headerlink" title="13. Serializable 接口"></a>13. Serializable 接口</h1><p>[[001-基础知识专题-关键字和接口-1、Serializable接口]]</p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-3、ArrayList</title>
      <link href="/2022/10/22/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-3%E3%80%81ArrayList/"/>
      <url>/2022/10/22/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-3%E3%80%81ArrayList/</url>
      
        <content type="html"><![CDATA[<h1 id="1-特点"><a href="#1-特点" class="headerlink" title="1. 特点"></a>1. 特点</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018190126.png"></p><h1 id="2-内存结构图"><a href="#2-内存结构图" class="headerlink" title="2. 内存结构图"></a>2. 内存结构图</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221023190718.png"></p><h1 id="3-底层原理"><a href="#3-底层原理" class="headerlink" title="3. 底层原理"></a>3. 底层原理</h1><h1 id="4-扩容方法"><a href="#4-扩容方法" class="headerlink" title="4. 扩容方法"></a>4. 扩容方法</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220923161313.png" alt="image-20220923161313175"></p><p>这里需要注意一点，因为扩容操作涉及内存申请和数据搬移，是比较耗时的。所以，如果事先能确定需要存储的数据大小，最好 <strong>在创建 ArrayList 的时候事先指定数据大小</strong>。</p><p>比如我们要从数据库中取出 10000 条数据放入 ArrayList。我们看下面这几行代码，你会发现，相比之下，事先指定数据大小可以省掉很多次内存申请和数据搬移操作。</p><h2 id="4-1-jdk-区别"><a href="#4-1-jdk-区别" class="headerlink" title="4.1. jdk 区别"></a>4.1. jdk 区别</h2><p>jdk8：ArrayList 中维护了 Object[] elementData，初始容量为 0. 第一次添加时，将初始 elementData 的容量为 10 再次添加时，如果容量足够，则不用扩容直接将新元素赋值到第一个空位上 如果容量不够，会扩容 1.5 倍</p><p>jdk7:ArrayList 中维护了 Object[] elementData，初始容量为 10. 添加时，如果容量足够，则不用扩容直接将新元素赋值到第一个空位上 如果容量不够，会扩容 1.5 倍</p><p>jdk7 和 jdk8： 区别：jdk7 相当于饿汉式，创建对象时，则初始容量为 10 jdk8 相当于懒汉式，创建对象时，并没有初始容量为 10，而在添加时才去初始容量为 10</p><h1 id="5-system-arraycopy"><a href="#5-system-arraycopy" class="headerlink" title="5. system.arraycopy"></a>5. system.arraycopy</h1><h1 id="6-Fail-Fast⭐️🔴"><a href="#6-Fail-Fast⭐️🔴" class="headerlink" title="6. Fail-Fast⭐️🔴"></a>6. Fail-Fast⭐️🔴</h1><p>ArrayList 也采用了快速失败的机制，通过记录 <code>modCount</code> 参数来实现。在面对并发的修改时，迭代器很快就会完全失败，而不是冒着在将来某个不确定时间发生任意不确定行为的风险。</p><p><code>modCount</code> 字段，表示集合结构性修改的次数。所谓结构性修改，指的是影响 List 大小的修改，所以 add 操作必然会改变 <code>modCount</code> 的值。</p><p>而在需要迭代时，获取到的迭代器会先读取 <code>modCount</code> 到自己的属性 <code>expectedModCount</code> 中，在迭代操作时，先执行 <code>checkForComodification</code> 方法确定是否有并发修改，如果有直接抛出并发修改异常，防止数据错乱。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221023154143.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221023154509.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018190922.gif"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221018191119.gif"></p><h1 id="7-Fast-Safe⭐️🔴"><a href="#7-Fast-Safe⭐️🔴" class="headerlink" title="7. Fast-Safe⭐️🔴"></a>7. Fast-Safe⭐️🔴</h1><p>java.util 包下面的所有的集合类都是快速失败的，而 java.util.concurrent 包下面的所有的类都是安全失败的。快速失败的迭代器会抛出 ConcurrentModificationException 异常，而安全失败的迭代器永远不会抛出这样的异常。</p><h1 id="8-与数组比较"><a href="#8-与数组比较" class="headerlink" title="8. 与数组比较"></a>8. 与数组比较</h1><ol><li><p>Java ArrayList 无法存储基本类型，比如 int、long，需要封装为 Integer、Long 类，而 Autoboxing、Unboxing 则有一定的性能消耗，所以如果特别关注性能，或者希望使用基本类型，就可以选用数组。</p></li><li><p>如果数据大小事先已知，并且对数据的操作非常简单，用不到 ArrayList 提供的大部分方法，也可以直接使用数组。</p></li><li><p>还有一个是我个人的喜好，当要表示多维数组时，用数组往往会更加直观。比如 <code>Object[][] array</code>；而用容器的话则需要这样定义：<code>ArrayList&lt;ArrayList &gt; array</code>。</p></li></ol><p>我总结一下，对于业务开发，直接使用容器就足够了，省时省力。毕竟损耗一丢丢性能，完全不会影响到系统整体的性能。但如果你是做一些非常底层的开发，比如开发网络框架，性能的优化需要做到极致，这个时候数组就会优于容器，成为首选。</p><h1 id="9-与-Vector-比较"><a href="#9-与-Vector-比较" class="headerlink" title="9. 与 Vector 比较"></a>9. 与 Vector 比较</h1><ol><li>ArrayList 创建时的大小为 0；当加入第一个元素时，进行第一次扩容时，默认容量大小 10。</li><li>ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容。</li><li>Vector 创建时的默认大小为 10。</li><li>Vector 每次扩容都以当前数组大小的 2 倍去扩容。当指定了 capacityIncrement 之后，每次扩容仅在原先基础上增加 capacityIncrement 个单位空间。</li><li>ArrayList 和 Vector 的 add、get、size 方法的复杂度都为 O(1)，remove 方法的复杂度为 O(n)。</li><li>ArrayList 是非线程安全的，Vector 是线程安全的。</li></ol><h1 id="10-最佳实践"><a href="#10-最佳实践" class="headerlink" title="10. 最佳实践"></a>10. 最佳实践</h1><p> 假如元素的大小是固定的，而且能事先知道，我们就应该用 Array 而不是 ArrayList。<br> 有些集合类允许指定初始容量。因此，如果我们能估计出存储的元素的数目，我们可以设置初始容量来避免重新计算 hash 值或者是扩容。<br> 为了类型安全，可读性和健壮性的原因总是要使用泛型。同时，使用泛型还可以避免运行时的 ClassCastException。<br> 使用 JDK 提供的不变类 (immutable class) 作为 Map 的键可以避免为我们自己的类实现 hashCode() 和 equals() 方法。<br> 编程的时候接口优于实现。<br> 底层的集合实际上是空的情况下，返回长度是 0 的集合或者是数组，不要返回 null。</p><h1 id="11-参考"><a href="#11-参考" class="headerlink" title="11. 参考"></a>11. 参考</h1><h2 id="11-1-尚硅谷"><a href="#11-1-尚硅谷" class="headerlink" title="11.1. 尚硅谷"></a>11.1. 尚硅谷</h2><p><a href="https://blog.csdn.net/wz249863091/article/details/52853360">https://blog.csdn.net/wz249863091/article/details/52853360</a></p><p>尚硅谷大数据共 50 阶段 - 有 ML&#x2F;尚硅谷 - 第 04 阶段《集合》&#x2F;day18&#x2F;video&#x2F;09 ArrayList 的底层和源码分析.mp4</p><h2 id="11-2-黑马"><a href="#11-2-黑马" class="headerlink" title="11.2. 黑马"></a>11.2. 黑马</h2><h3 id="11-2-1-视频"><a href="#11-2-1-视频" class="headerlink" title="11.2.1. 视频"></a>11.2.1. 视频</h3><p><a href="https://www.bilibili.com/video/BV1zr4y117JM?p=34&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1zr4y117JM?p=34&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h3 id="11-2-2-资料"><a href="#11-2-2-资料" class="headerlink" title="11.2.2. 资料"></a>11.2.2. 资料</h3>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-集合框架-2、集合类的坑</title>
      <link href="/2022/10/22/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/Java%E5%9F%BA%E7%A1%80-%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-2%E3%80%81%E9%9B%86%E5%90%88%E7%B1%BB%E7%9A%84%E5%9D%91/"/>
      <url>/2022/10/22/012-%E7%BB%8F%E9%AA%8C%E4%B8%93%E9%A2%98/Java%E5%9F%BA%E7%A1%80-%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-2%E3%80%81%E9%9B%86%E5%90%88%E7%B1%BB%E7%9A%84%E5%9D%91/</url>
      
        <content type="html"><![CDATA[<h2 id="1-Arrays-asList"><a href="#1-Arrays-asList" class="headerlink" title="1. Arrays.asList"></a>1. Arrays.asList</h2><h3 id="1-1-基本类型数组"><a href="#1-1-基本类型数组" class="headerlink" title="1.1. 基本类型数组"></a>1.1. 基本类型数组</h3><p>在如下代码中，我们初始化三个数字的 int[]数组，然后使用 Arrays.asList 把数组转换为 List：</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-type">int</span>[] arr = &#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>&#125;;<br><br><span class="hljs-type">List</span> <span class="hljs-variable">list</span> <span class="hljs-operator">=</span> Arrays.asList(arr);<br><br>log.info(<span class="hljs-string">&quot;list:&#123;&#125; size:&#123;&#125; class:&#123;&#125;&quot;</span>, list, list.size(), list.get(<span class="hljs-number">0</span>).getClass());<br></code></pre></td></tr></table></figure><p>但，这样初始化的 List 并不是我们期望的包含 3 个数字的 List。通过日志可以发现，这个 List 包含的其实是一个 int 数组，整个 List 的元素个数是 1，元素类型是整数数组。</p><p>其原因是，只能是把 int 装箱为 Integer，不可能把 int 数组装箱为 Integer 数组。我们知道，Arrays.asList 方法传入的是一个泛型 T 类型可变参数，最终 int 数组整体作为了一个对象成为了泛型类型 T：</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">static</span> &lt;T&gt; List&lt;T&gt; <span class="hljs-title function_">asList</span><span class="hljs-params">(T... a)</span> &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(a);<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="1-1-1-解决方法1"><a href="#1-1-1-解决方法1" class="headerlink" title="1.1.1. 解决方法1"></a>1.1.1. 解决方法1</h4><p>如果使用 Java8 以上版本可以使用 Arrays.stream 方法来转换，否则可以把 int 数组声明为包装类型 Integer 数组：</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-type">int</span>[] arr1 = &#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>&#125;;<br><br><span class="hljs-type">List</span> <span class="hljs-variable">list1</span> <span class="hljs-operator">=</span> Arrays.stream(arr1).boxed().collect(Collectors.toList());<br><br>log.info(<span class="hljs-string">&quot;list:&#123;&#125; size:&#123;&#125; class:&#123;&#125;&quot;</span>, list1, list1.size(), list1.get(<span class="hljs-number">0</span>).getClass());<br></code></pre></td></tr></table></figure><h4 id="1-1-2-解决方法2"><a href="#1-1-2-解决方法2" class="headerlink" title="1.1.2. 解决方法2"></a>1.1.2. 解决方法2</h4><p>把 int 数组声明为包装类型 Integer 数组</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">Integer[] arr2 = &#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>&#125;;<br><br><span class="hljs-type">List</span> <span class="hljs-variable">list2</span> <span class="hljs-operator">=</span> Arrays.asList(arr2);<br><br>log.info(<span class="hljs-string">&quot;list:&#123;&#125; size:&#123;&#125; class:&#123;&#125;&quot;</span>, list2, list2.size(), list2.get(<span class="hljs-number">0</span>).getClass());<br></code></pre></td></tr></table></figure><h3 id="1-2-增删操作"><a href="#1-2-增删操作" class="headerlink" title="1.2. 增删操作"></a>1.2. 增删操作</h3><p>Arrays.asList 返回的 List 不支持增删操作。Arrays.asList 返回的 List 并不是我们期望的 java.util.ArrayList，而是 Arrays 的内部类 ArrayList。ArrayList 内部类继承自 AbstractList 类，并没有覆写父类的 add 方法，而父类中 add 方法的实现，就是抛出 UnsupportedOperationException。相关源码如下所示：</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;T&gt; List&lt;T&gt; <span class="hljs-title function_">asList</span><span class="hljs-params">(T... a)</span> &#123;<br><br><span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(a);<br><br>&#125;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ArrayList</span>&lt;E&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractList</span>&lt;E&gt;<br><br><span class="hljs-keyword">implements</span> <span class="hljs-title class_">RandomAccess</span>, java.io.Serializable<br><br>&#123;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> E[] a;<br><br>ArrayList(E[] array) &#123;<br><br>a = Objects.requireNonNull(array);<br><br>&#125;<br><br>...<br><br><span class="hljs-meta">@Override</span><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><br><span class="hljs-type">E</span> <span class="hljs-variable">oldValue</span> <span class="hljs-operator">=</span> a[index];<br><br>a[index] = element;<br><br><span class="hljs-keyword">return</span> oldValue;<br><br>&#125;<br><br>...<br><br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractList</span>&lt;E&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractCollection</span>&lt;E&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title class_">List</span>&lt;E&gt; &#123;<br><br>...<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span> index, E element)</span> &#123;<br><br><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UnsupportedOperationException</span>();<br><br>&#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="1-3-数组共享"><a href="#1-3-数组共享" class="headerlink" title="1.3. 数组共享"></a>1.3. 数组共享</h3><p>对原始数组的修改会影响到我们获得的那个 List。看一下 ArrayList 的实现，可以发现 ArrayList 其实是<span style="background-color:#00ff00">直接使用了原始的数组</span>。所以，我们要特别小心，把通过 Arrays.asList 获得的 List 交给其他方法处理，很容易因为<span style="background-color:#ffff00">共享了数组</span>，相互修改产生 Bug。</p><h4 id="1-3-1-解决方法"><a href="#1-3-1-解决方法" class="headerlink" title="1.3.1. 解决方法"></a>1.3.1. 解决方法</h4><p>需要增删和防止数组共享的方式比较简单，重新 new 一个 ArrayList 初始化 Arrays.asList 返回的 List 即可：</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></pre></td><td class="code"><pre><code class="hljs java">String[] arr = &#123;<span class="hljs-string">&quot;1&quot;</span>, <span class="hljs-string">&quot;2&quot;</span>, <span class="hljs-string">&quot;3&quot;</span>&#125;;<br><br><span class="hljs-type">List</span> <span class="hljs-variable">list</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>(Arrays.asList(arr));<br><br>arr[<span class="hljs-number">1</span>] = <span class="hljs-string">&quot;4&quot;</span>;<br><br><span class="hljs-keyword">try</span> &#123;<br><br>list.add(<span class="hljs-string">&quot;5&quot;</span>);<br><br>&#125; <span class="hljs-keyword">catch</span> (Exception ex) &#123;<br><br>ex.printStackTrace();<br><br>&#125;<br><br>log.info(<span class="hljs-string">&quot;arr:&#123;&#125; list:&#123;&#125;&quot;</span>, Arrays.toString(arr), list);<br></code></pre></td></tr></table></figure><h2 id="2-List-subList导致OOM"><a href="#2-List-subList导致OOM" class="headerlink" title="2. List.subList导致OOM"></a>2. List.subList导致OOM</h2><p>业务开发时常常要对 List 做切片处理，即取出其中部分元素构成一个新的 List，我们通常会想到使用 List.subList 方法。但，和 Arrays.asList 的问题类似，List.subList 返回的子 List 不是一个普通的 ArrayList。这个子 List 可以认为是原始 List 的视图，会和原始 List 相互影响。如果不注意，很可能会因此产生 OOM 问题。接下来，我们就一起分析下其中的坑。</p><p>如下代码所示，定义一个名为 data 的静态 List 来存放 Integer 的 List，也就是说 data 的成员本身是包含了多个数字的 List。循环 1000 次，每次都从一个具有 10 万个 Integer 的 List 中，使用 subList 方法获得一个只包含一个数字的子 List，并把这个子 List 加入 data 变量：</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">private</span> <span class="hljs-keyword">static</span> List&lt;List&lt;Integer&gt;&gt; data = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">oom</span><span class="hljs-params">()</span> &#123;<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; <span class="hljs-number">1000</span>; i++) &#123;<br><br>List&lt;Integer&gt; rawList = IntStream.rangeClosed(<span class="hljs-number">1</span>, <span class="hljs-number">100000</span>).boxed().collect(Collectors.toList());<br><br>data.add(rawList.subList(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>));<br><br>&#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>你可能会觉得，这个 data 变量里面最终保存的只是 1000 个具有 1 个元素的 List，不会占用很大空间，但程序运行不久就出现了 OOM</p><p>出现 OOM 的原因是，循环中的 1000 个具有 10 万个元素的 List 始终得不到回收，因为它始终被 subList 方法返回的 List 强引用。那么，返回的子 List 为什么会强引用原始的 List，它们又有什么关系呢？我们再继续做实验观察一下这个子 List 的特性。</p><p>首先初始化一个包含数字 1 到 10 的 ArrayList，然后通过调用 subList 方法取出 2、3、4；随后删除这个 SubList 中的元素数字 3，并打印原始的 ArrayList；最后为原始的 ArrayList 增加一个元素数字 0，遍历 SubList 输出所有元素：</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">List&lt;Integer&gt; list = IntStream.rangeClosed(<span class="hljs-number">1</span>, <span class="hljs-number">10</span>).boxed().collect(Collectors.toList());<br><br>List&lt;Integer&gt; subList = list.subList(<span class="hljs-number">1</span>, <span class="hljs-number">4</span>);<br><br>System.out.println(subList);<br><br>subList.remove(<span class="hljs-number">1</span>);<br><br>System.out.println(list);<br><br>list.add(<span class="hljs-number">0</span>);<br><br><span class="hljs-keyword">try</span> &#123;<br><br>subList.forEach(System.out::println);<br><br>&#125; <span class="hljs-keyword">catch</span> (Exception ex) &#123;<br><br>ex.printStackTrace();<br><br>&#125;<br></code></pre></td></tr></table></figure><p>代码运行后得到如下输出：</p><figure class="highlight plaintext"><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 plaintext">[2, 3, 4]<br><br>[1, 2, 4, 5, 6, 7, 8, 9, 10]<br><br>java.util.ConcurrentModificationException<br><br>at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)<br><br>at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)<br><br>at java.util.AbstractList.listIterator(AbstractList.java:299)<br><br>at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)<br><br>at java.lang.Iterable.forEach(Iterable.java:74)<br></code></pre></td></tr></table></figure><p>可以看到两个现象：</p><p>原始 List 中数字 3 被删除了，说明删除子 List 中的元素影响到了原始 List；</p><p>尝试为原始 List 增加数字 0 之后再遍历子 List，会出现 ConcurrentModificationException。</p><p>我们分析下 ArrayList 的源码，看看为什么会是这样。</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">class</span> <span class="hljs-title class_">ArrayList</span>&lt;E&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractList</span>&lt;E&gt;<br><span class="hljs-keyword">implements</span> <span class="hljs-title class_">List</span>&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable<br>&#123;<br><span class="hljs-keyword">protected</span> <span class="hljs-keyword">transient</span> <span class="hljs-type">int</span> <span class="hljs-variable">modCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>...<br><span class="hljs-keyword">public</span> List&lt;E&gt; <span class="hljs-title function_">subList</span><span class="hljs-params">(<span class="hljs-type">int</span> fromIndex, <span class="hljs-type">int</span> toIndex)</span> &#123;  <br>    subListRangeCheck(fromIndex, toIndex, size);  <br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SubList</span>(<span class="hljs-built_in">this</span>, <span class="hljs-number">0</span>, fromIndex, toIndex);  <br>&#125;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SubList</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractList</span>&lt;E&gt; <span class="hljs-keyword">implements</span> <span class="hljs-title class_">RandomAccess</span> &#123;  <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AbstractList&lt;E&gt; parent;  <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> parentOffset;  <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> offset;  <br>    <span class="hljs-type">int</span> size;  <br>  <br>    SubList(AbstractList&lt;E&gt; parent,  <br>            <span class="hljs-type">int</span> offset, <span class="hljs-type">int</span> fromIndex, <span class="hljs-type">int</span> toIndex) &#123;  <br>        <span class="hljs-built_in">this</span>.parent = parent;  <br>        <span class="hljs-built_in">this</span>.parentOffset = fromIndex;  <br>        <span class="hljs-built_in">this</span>.offset = offset + fromIndex;  <br>        <span class="hljs-built_in">this</span>.size = toIndex - fromIndex;  <br>        <span class="hljs-built_in">this</span>.modCount = ArrayList.<span class="hljs-built_in">this</span>.modCount;  <br>    &#125;<br>&#125;<br><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkForComodification</span><span class="hljs-params">()</span> &#123;  <br>    <span class="hljs-keyword">if</span> (ArrayList.<span class="hljs-built_in">this</span>.modCount != <span class="hljs-built_in">this</span>.modCount)  <br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentModificationException</span>();  <br>&#125;<br>...<br>&#125;<br></code></pre></td></tr></table></figure><p>第一，ArrayList 维护了一个叫作 modCount 的字段，表示集合结构性修改的次数。所谓结构性修改，指的是影响 List 大小的修改，所以 add 操作必然会改变 modCount 的值。</p><p>第二，分析 subList 方法可以看到，获得的 List 其实是内部类 SubList，并不是普通的ArrayList，在初始化的时候传入了 this。</p><p>第三，分析整段代码可以发现，这个 SubList 中的 parent 字段就是原始的 List。SubList 初始化的时候，并没有把原始 List 中的元素复制到独立的变量中保存。我们可以认为 SubList 是原始 List 的视图，并不是独立的 List。双方对元素的修改会相互影响，而且 SubList 强引用了原始的 List，所以大量保存这样的 SubList 会导致 OOM。</p><p>第四，分析checkForComodification方法可以发现，遍历 SubList 的时候会先获得迭代器，比较原始 ArrayList modCount 的值和 SubList 当前 modCount 的值。获得了 SubList 后，我们为原始 List 新增了一个元素修改了其 modCount，所以判等失败抛出 ConcurrentModificationException 异常。</p><h3 id="2-1-解决方法"><a href="#2-1-解决方法" class="headerlink" title="2.1. 解决方法"></a>2.1. 解决方法</h3><ol><li>不直接使用 subList 方法返回的 SubList，而是重新使用 new ArrayList，在构造方法传入 SubList，来构建一个独立的 ArrayList；</li></ol><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">List&lt;Integer&gt; subList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(list.subList(<span class="hljs-number">1</span>, <span class="hljs-number">4</span>));<br></code></pre></td></tr></table></figure><ol><li>对于 Java 8 使用 Stream 的 skip 和 limit API 来跳过流中的元素，以及限制流中元素的个数，同样可以达到 SubList 切片的目的。</li></ol><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">List&lt;Integer&gt; subList = list.stream().skip(<span class="hljs-number">1</span>).limit(<span class="hljs-number">3</span>).collect(Collectors.toList());<br></code></pre></td></tr></table></figure><h2 id="3-让合适的数据结构做合适的事情"><a href="#3-让合适的数据结构做合适的事情" class="headerlink" title="3. 让合适的数据结构做合适的事情"></a>3. 让合适的数据结构做合适的事情</h2><h3 id="3-1-使用数据结构不考虑平衡时间和空间"><a href="#3-1-使用数据结构不考虑平衡时间和空间" class="headerlink" title="3.1. 使用数据结构不考虑平衡时间和空间"></a>3.1. 使用数据结构不考虑平衡时间和空间</h3><p>要对大 List 进行单值搜索的话，可以考虑使用 HashMap，其中 Key 是要搜索的值，Value 是原始对象，会比使用 ArrayList 有非常明显的性能优势。</p><p>即使我们要搜索的不是单值而是条件区间，也可以尝试使用 HashMap 来进行“搜索性能优化”。如果你的条件区间是固定的话，可以提前把 HashMap 按照条件区间进行分组，Key 就是不同的区间。</p><p>的确，如果业务代码中有频繁的大 ArrayList 搜索，使用 HashMap 性能会好很多。类似，如果要对大 ArrayList 进行去重操作，也不建议使用 contains 方法，而是可以考虑使用 HashSet 进行去重。</p><p>关于“含金量”，我们使用 ObjectSizeCalculator 工具打印 ArrayList 和 HashMap 的内存占用，可以看到 ArrayList 占用内存 21M，而 HashMap 占用的内存达到了 72M，是 List 的三倍多。进一步使用 MAT 工具分析堆可以再次证明，ArrayList 在内存占用上性价比很高，77% 是实际的数据（如第 1 个图所示，16000000&#x2F;20861992），而 HashMap 的“含金量”只有 22%（如第 2 个图所示，16000000&#x2F;72386640）。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221021073727.png"></p><p>所以，在应用内存吃紧的情况下，我们需要考虑是否值得使用更多的内存消耗来换取更高的性能。这里我们看到的是平衡的艺术，空间换时间，还是时间换空间，只考虑任何一个方面都是不对的。</p><h3 id="3-2-过于迷信教科书的大-O-时间复杂度"><a href="#3-2-过于迷信教科书的大-O-时间复杂度" class="headerlink" title="3.2. 过于迷信教科书的大 O 时间复杂度"></a>3.2. 过于迷信教科书的大 O 时间复杂度</h3><pre><code>翻看 LinkedList 源码发现，插入操作的时间复杂度是 O(1) 的前提是，你已经有了那个要插入节点的指针。但，在实现的时候，我们需要先通过循环获取到那个节点的 Node，然后再执行插入操作。前者也是有开销的，不可能只考虑插入操作本身的代价：所以，对于插入操作，LinkedList 的时间复杂度其实也是 O(n)。继续做更多实验的话你会发现，在各种常用场景下，LinkedList 几乎都不能在性能上胜出 ArrayList。讽刺的是，LinkedList 的作者约书亚 · 布洛克（Josh Bloch），在其推特上回复别人时说，虽然 LinkedList 是我写的但我从来不用，有谁会真的用吗？</code></pre><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221023163620.png"></p><h2 id="4-参考"><a href="#4-参考" class="headerlink" title="4. 参考"></a>4. 参考</h2><p>极客时间</p>]]></content>
      
      
      <categories>
          
          <category> 集合框架 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java基础 </tag>
            
            <tag> 集合框架 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据结构与算法-3、二叉树遍历</title>
      <link href="/2022/10/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%AE%97%E6%B3%95-3%E3%80%81%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86/"/>
      <url>/2022/10/14/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%AE%97%E6%B3%95-3%E3%80%81%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86/</url>
      
        <content type="html"><![CDATA[<h1 id="1-递归"><a href="#1-递归" class="headerlink" title="1. 递归"></a>1. 递归</h1><h2 id="1-1-时空复杂度"><a href="#1-1-时空复杂度" class="headerlink" title="1.1. 时空复杂度"></a>1.1. 时空复杂度</h2><p>时间复杂度：O(N)  N：节点个数<br>空间复杂度：O(h)   h：树的高度</p><h2 id="1-2-递归序"><a href="#1-2-递归序" class="headerlink" title="1.2. 递归序"></a>1.2. 递归序</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221015195641.png"></p><p><span style="background-color:#00ff00">1-2-2-2-1-3-3-3-1</span></p><h2 id="1-3-前中后序"><a href="#1-3-前中后序" class="headerlink" title="1.3. 前中后序"></a>1.3. 前中后序</h2><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> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">process</span><span class="hljs-params">(Node root)</span> &#123;  <br>   <span class="hljs-keyword">if</span> (root == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span>;  <br>   &#125;  <br>   <span class="hljs-comment">// 1 前序  </span><br>   System.out.print(root.value + <span class="hljs-string">&quot; &quot;</span>);  <br>   process(root.left);  <br>   <span class="hljs-comment">// 2 中序  </span><br>   <span class="hljs-comment">//System.out.print(root.value + &quot; &quot;);</span><br>   process(root.right);  <br>   <span class="hljs-comment">// 3 后序 </span><br>   <span class="hljs-comment">//System.out.print(root.value + &quot; &quot;); </span><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="1-4-层序遍历"><a href="#1-4-层序遍历" class="headerlink" title="1.4. 层序遍历"></a>1.4. 层序遍历</h2><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></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_">levelOrder</span><span class="hljs-params">(TreeNode root, <span class="hljs-type">int</span> index, List result)</span> &#123;  <br>    <span class="hljs-keyword">if</span> (root == <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span>;  <br>  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">length</span> <span class="hljs-operator">=</span> result.size();  <br>    <span class="hljs-keyword">if</span> (length &lt;= index) &#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;= index - length; j++) &#123;  <br>            result.add(length + j, <span class="hljs-literal">null</span>);  <br>        &#125;  <br>    &#125;  <br>  <br>    result.set(index, root.val);  <br>    levelOrder(root.left, <span class="hljs-number">2</span>*index, result);  <br>    levelOrder(root.right, <span class="hljs-number">2</span>*index + <span class="hljs-number">1</span>, result);  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="2-迭代"><a href="#2-迭代" class="headerlink" title="2. 迭代"></a>2. 迭代</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221015195725.png"></p><h2 id="2-1-时空复杂度"><a href="#2-1-时空复杂度" class="headerlink" title="2.1. 时空复杂度"></a>2.1. 时空复杂度</h2><p>时间复杂度：O(N)  N：节点个数<br>空间复杂度：O(h)   h：树的高度</p><h2 id="2-2-先序遍历"><a href="#2-2-先序遍历" class="headerlink" title="2.2. 先序遍历"></a>2.2. 先序遍历</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221015200404.png"></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">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">pre</span><span class="hljs-params">(Node head)</span> &#123;  <br>   System.out.print(<span class="hljs-string">&quot;pre-order: &quot;</span>);  <br>   <span class="hljs-keyword">if</span> (head != <span class="hljs-literal">null</span>) &#123;  <br>      Stack&lt;Node&gt; stack = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Stack</span>&lt;Node&gt;();  <br>      stack.add(head);  <br>      <span class="hljs-keyword">while</span> (!stack.isEmpty()) &#123;  <br>         head = stack.pop();  <br>         System.out.print(head.value + <span class="hljs-string">&quot; &quot;</span>);  <br>         <span class="hljs-keyword">if</span> (head.right != <span class="hljs-literal">null</span>) &#123;  <br>            stack.push(head.right);  <br>         &#125;  <br>         <span class="hljs-keyword">if</span> (head.left != <span class="hljs-literal">null</span>) &#123;  <br>            stack.push(head.left);  <br>         &#125;  <br>      &#125;  <br>   &#125;  <br>   System.out.println();  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="2-3-中序遍历"><a href="#2-3-中序遍历" class="headerlink" title="2.3. 中序遍历"></a>2.3. 中序遍历</h2><h3 id="2-3-1-算法逻辑"><a href="#2-3-1-算法逻辑" class="headerlink" title="2.3.1. 算法逻辑"></a>2.3.1. 算法逻辑</h3><p>将整条左边界压入栈后开始弹出，并在弹出的每个节点的右孩子上尝试执行步骤1，即右孩子不为空就尝试将其左边界全部压入栈中</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221015223718.png"></p><p>从左到右，将【左头】逐渐压入栈中，最终出栈的顺序就为【左头右】<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221016144942.png"></p><h3 id="2-3-2-代码实现"><a href="#2-3-2-代码实现" class="headerlink" title="2.3.2. 代码实现"></a>2.3.2. 代码实现</h3><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_">in</span><span class="hljs-params">(Node cur)</span> &#123;  <br>   System.out.print(<span class="hljs-string">&quot;in-order: &quot;</span>);  <br>   <span class="hljs-keyword">if</span> (cur != <span class="hljs-literal">null</span>) &#123;  <br>      Stack&lt;Node&gt; stack = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Stack</span>&lt;Node&gt;();  <br>      <span class="hljs-keyword">while</span> (!stack.isEmpty() || cur != <span class="hljs-literal">null</span>) &#123;  <br>         <span class="hljs-keyword">if</span> (cur != <span class="hljs-literal">null</span>) &#123;  <br>            stack.push(cur);  <br>            cur = cur.left;  <br>         &#125; <span class="hljs-keyword">else</span> &#123;  <br>            cur = stack.pop();  <br>            System.out.print(cur.value + <span class="hljs-string">&quot; &quot;</span>);  <br>            cur = cur.right;  <br>         &#125;  <br>      &#125;  <br>   &#125;  <br>   System.out.println();  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="2-4-后序遍历"><a href="#2-4-后序遍历" class="headerlink" title="2.4. 后序遍历"></a>2.4. 后序遍历</h2><h3 id="2-4-1-方法一"><a href="#2-4-1-方法一" class="headerlink" title="2.4.1. 方法一"></a>2.4.1. 方法一</h3><p>先序遍历改为 <span style="background-color:#ffff00">头右左</span>，然后再利用另外一个栈<span style="background-color:#ffff00">倒序输出</span></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">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">pos1</span><span class="hljs-params">(Node head)</span> &#123;  <br>   System.out.print(<span class="hljs-string">&quot;pos-order: &quot;</span>);  <br>   <span class="hljs-keyword">if</span> (head != <span class="hljs-literal">null</span>) &#123;  <br>      Stack&lt;Node&gt; s1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Stack</span>&lt;Node&gt;();  <br>      Stack&lt;Node&gt; s2 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Stack</span>&lt;Node&gt;();  <br>      s1.push(head);  <br>      <span class="hljs-keyword">while</span> (!s1.isEmpty()) &#123;  <br>         head = s1.pop(); <span class="hljs-comment">// 头 右 左  </span><br>         s2.push(head);  <br>         <span class="hljs-keyword">if</span> (head.left != <span class="hljs-literal">null</span>) &#123;  <br>            s1.push(head.left);  <br>         &#125;  <br>         <span class="hljs-keyword">if</span> (head.right != <span class="hljs-literal">null</span>) &#123;  <br>            s1.push(head.right);  <br>         &#125;  <br>      &#125;  <br>      <span class="hljs-comment">// 左 右 头  </span><br>      <span class="hljs-keyword">while</span> (!s2.isEmpty()) &#123;  <br>         System.out.print(s2.pop().value + <span class="hljs-string">&quot; &quot;</span>);  <br>      &#125;  <br>   &#125;  <br>   System.out.println();  <br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-4-2-方法二"><a href="#2-4-2-方法二" class="headerlink" title="2.4.2. 方法二"></a>2.4.2. 方法二</h3><p>todo</p><h2 id="2-5-层序遍历"><a href="#2-5-层序遍历" class="headerlink" title="2.5. 层序遍历"></a>2.5. 层序遍历</h2><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_">level</span><span class="hljs-params">(Node head)</span> &#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span>;  <br>   &#125;  <br>   Queue&lt;Node&gt; queue = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedList</span>&lt;&gt;();  <br>   queue.add(head);  <br>   <span class="hljs-keyword">while</span> (!queue.isEmpty()) &#123;  <br>      <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> queue.poll();  <br>      System.out.println(cur.value);  <br>      <span class="hljs-keyword">if</span> (cur.left != <span class="hljs-literal">null</span>) &#123;  <br>         queue.add(cur.left);  <br>      &#125;  <br>      <span class="hljs-keyword">if</span> (cur.right != <span class="hljs-literal">null</span>) &#123;  <br>         queue.add(cur.right);  <br>      &#125;  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h1 id="3-Morris遍历"><a href="#3-Morris遍历" class="headerlink" title="3. Morris遍历"></a>3. Morris遍历</h1><h2 id="3-1-学习视频"><a href="#3-1-学习视频" class="headerlink" title="3.1. 学习视频"></a>3.1. 学习视频</h2><p>要想透彻理解还得看左神<br><a href="https://www.bilibili.com/video/BV1qd4y1B7XC?p=61&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204#t=2812.655011">46:52</a></p><h2 id="3-2-时空复杂度"><a href="#3-2-时空复杂度" class="headerlink" title="3.2. 时空复杂度"></a>3.2. 时空复杂度</h2><p>时间复杂度：O(N)  N：节点个数<br>空间复杂度：O(1) </p><h2 id="3-3-算法本质"><a href="#3-3-算法本质" class="headerlink" title="3.3. 算法本质"></a>3.3. 算法本质</h2><p><a href="https://www.bilibili.com/video/BV1Bg411F7oo/?spm_id_from=333.999.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Bg411F7oo/?spm_id_from=333.999.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV1Bg411F7oo/?spm_id_from=333.999.0.0&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204#t=304.500878">05:04</a></p><ol><li>回到<span style="background-color:#ffff00">上面节点(包括但不限于父节点)</span>的方法：使用栈结构，递归遍历方法是使用系统的方法调用栈；而迭代遍历方法则是需要我们自己维护一个栈。</li><li>所以不管是递归还是迭代，都是用了栈的数据结构来回到上面节点。从而导致算法的空间复杂度为O(N)。</li><li>Morris算法为了降低算法的空间复杂度为O(1)，摒弃栈结构，而是使用子节点的右指针，确切的说是人为改造最右孩子的右指针，指向上面节点cur，来回到上面节点</li></ol><h2 id="3-4-Morris序"><a href="#3-4-Morris序" class="headerlink" title="3.4. Morris序"></a>3.4. Morris序</h2><p>在Morris算法中，cur依次遍历节点的顺序，称为Morris序<br>Morris的前中后序也是对Morris序的加工</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221015092329.png"></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">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">morris</span><span class="hljs-params">(Node head)</span> &#123;<br><span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-keyword">return</span>;<br>&#125;<br><span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> head;<br><span class="hljs-type">Node</span> <span class="hljs-variable">mostRight</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br><span class="hljs-keyword">while</span> (cur != <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-comment">// 先默认头节点左孩子为最右孩子</span><br><span class="hljs-comment">// 判断cur是否有左树</span><br>mostRight = cur.left;<br><span class="hljs-comment">// 第一层大逻辑，判断是否有左孩子,如果有走if分支，如果没有cur向右移动</span><br><span class="hljs-keyword">if</span> (mostRight != <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-comment">// 获取真实的最右孩子</span><br><span class="hljs-keyword">while</span> (mostRight.right != <span class="hljs-literal">null</span> &amp;&amp; mostRight.right != cur)&#123;<br>mostRight = mostRight.right;<br>&#125;<br><span class="hljs-comment">// 从while出来 mostRight就是cur左树上的最右孩子</span><br><br><span class="hljs-comment">// 最右孩子的右指针有2种情况</span><br><span class="hljs-comment">// 第一种情况，指向空，说明是第一次走到，要将右指针指向cur</span><br><span class="hljs-comment">// cur往左移动，继续处理它的左孩子的最右孩子的右指针</span><br><span class="hljs-keyword">if</span> (mostRight.right == <span class="hljs-literal">null</span>) &#123;<br>mostRight.right = cur;<br>cur = cur.left; <span class="hljs-comment">// 向左移动 遍历以左孩子为根的子树</span><br><span class="hljs-keyword">continue</span>;<br>&#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-comment">// 第二种情况，指向cur，说明是第二次走到，要将指针复原为指向空的状态，同时向右移动，也就是说左边的已经处理完，再处理右边的子树</span><br>mostRight.right = <span class="hljs-literal">null</span>;<br>&#125;<br>&#125;<br>cur = cur.right;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-5-Morris先序"><a href="#3-5-Morris先序" class="headerlink" title="3.5. Morris先序"></a>3.5. Morris先序</h2><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">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">morrisPre</span><span class="hljs-params">(Node head)</span>&#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span>;  <br>   &#125;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> head;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">mostRight</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;  <br>  <br>   <span class="hljs-keyword">while</span> (cur != <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-comment">// 打印morris序  </span><br>      <span class="hljs-comment">// System.out.print(cur.value + &quot; &quot;);      mostRight = cur.left;  </span><br>      <span class="hljs-comment">// 第一层大逻辑，判断是否有左孩子  </span><br>      <span class="hljs-keyword">if</span> (mostRight != <span class="hljs-literal">null</span>) &#123;  <br>         <span class="hljs-comment">// 有左孩子，左孩子有右孩子，一直往右出溜，找到最右孩子  </span><br>         <span class="hljs-keyword">while</span> (mostRight.right != <span class="hljs-literal">null</span> &amp;&amp; mostRight.right != cur) &#123;  <br>            mostRight = mostRight.right;  <br>         &#125;  <br>         <span class="hljs-comment">// 最右孩子的右指针为空，第一次走到最右孩子  </span><br>         <span class="hljs-comment">// 将最右孩子的右指针人为设置为cur         // cur往左移动，继续处理左孩子的最右孩子的右指针         if (mostRight.right == null) &#123;  </span><br>            <span class="hljs-comment">// ①能来到自己2次的节点，第一次来到的时候打印  </span><br>            System.out.print(cur.value + <span class="hljs-string">&quot; &quot;</span>);  <br>            mostRight.right = cur;  <br>            cur = cur.left;  <br>            <span class="hljs-keyword">continue</span>;  <br>         &#125; <span class="hljs-keyword">else</span> &#123;  <br>            <span class="hljs-comment">// 最右孩子的右指针已经指向了cur，将指针复原  </span><br>            <span class="hljs-comment">// cur往右出溜，也就是说左边的已经处理完，再处理右边的子树            mostRight.right = null;  </span><br>         &#125;  <br>      &#125; <span class="hljs-keyword">else</span> &#123;  <br>         <span class="hljs-comment">// ②没有左孩子，就是说只会来到自己一次的节点，直接打印  </span><br>         System.out.print(cur.value + <span class="hljs-string">&quot; &quot;</span>);  <br>      &#125;  <br>      <span class="hljs-comment">// 没有左孩子，往右走  </span><br>      cur = cur.right;  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-6-Morris中序"><a href="#3-6-Morris中序" class="headerlink" title="3.6. Morris中序"></a>3.6. Morris中序</h2><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">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">morrisIn</span><span class="hljs-params">(Node head)</span>&#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span>;  <br>   &#125;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> head;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">mostRight</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;  <br>  <br>   <span class="hljs-keyword">while</span> (cur != <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-comment">// 打印morris序   </span><br>      <span class="hljs-comment">// System.out.print(cur.value + &quot; &quot;);  </span><br>      mostRight = cur.left;  <br>      <span class="hljs-comment">// 第一层大逻辑，判断是否有左孩子  </span><br>      <span class="hljs-keyword">if</span> (mostRight != <span class="hljs-literal">null</span>) &#123;  <br>         <span class="hljs-comment">// 有左孩子，左孩子有右孩子，一直往右出溜，找到最右孩子  </span><br>         <span class="hljs-keyword">while</span> (mostRight.right != <span class="hljs-literal">null</span> &amp;&amp; mostRight.right != cur) &#123;  <br>            mostRight = mostRight.right;  <br>         &#125;  <br>         <span class="hljs-comment">// 最右孩子的右指针为空，第一次走到最右孩子  </span><br>         <span class="hljs-comment">// 将最右孩子的右指针人为设置为cur         // cur往左移动，继续处理左孩子的最右孩子的右指针         if (mostRight.right == null) &#123;  </span><br>            mostRight.right = cur;  <br>            cur = cur.left;  <br>            <span class="hljs-keyword">continue</span>;  <br>         &#125; <span class="hljs-keyword">else</span> &#123;  <br>            <span class="hljs-comment">// 最右孩子的右指针已经指向了cur，将指针复原  </span><br>            <span class="hljs-comment">// cur往右出溜，也就是说左边的已经处理完，再处理右边的子树            mostRight.right = null;  </span><br>         &#125;  <br>      &#125;  <br>      <span class="hljs-comment">// 没有左孩子的节点只会遍历一次，直接打印  </span><br>      <span class="hljs-comment">// 有左孩子的节点会遍历2次，第二次的时候把右指针复原后会走到这里来，打印      System.out.print(cur.value + &quot; &quot;);  </span><br>      <span class="hljs-comment">// 没有左孩子，往右走  </span><br>      cur = cur.right;  <br>   &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-7-Morris后序"><a href="#3-7-Morris后序" class="headerlink" title="3.7. Morris后序"></a>3.7. Morris后序</h2><h3 id="3-7-1-实现逻辑"><a href="#3-7-1-实现逻辑" class="headerlink" title="3.7.1. 实现逻辑"></a>3.7.1. 实现逻辑</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221016121418.png"></p><p>从morris序中可以看出，我们在有左子树的节点(能第二次遍历到)，第二次出现时处理逻辑：<br><span style="background-color:#00ff00">逆序打印该节点的右边界(右边界指某个节点的左孩子及这个左孩子的所有右孩子)</span>，比如第二次遍历到2时，它的右边界是4；第二次遍历到1时，它的右边界逆序是5,2；第二次遍历到3时，它的右边界是6，即：4-5-2-6，然后再加上整棵树的右边界的逆序，即7-3-1<br>整个加起来就是 4-5-2-6-7-3-1</p><h3 id="3-7-2-实现代码"><a href="#3-7-2-实现代码" class="headerlink" title="3.7.2. 实现代码"></a>3.7.2. 实现代码</h3><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">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">morrisPos</span><span class="hljs-params">(Node head)</span> &#123;  <br>   <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span>) &#123;  <br>      <span class="hljs-keyword">return</span>;  <br>   &#125;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> head;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">mostRight</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;  <br>   <span class="hljs-keyword">while</span> (cur != <span class="hljs-literal">null</span>) &#123;  <br>      mostRight = cur.left;  <br>      <span class="hljs-keyword">if</span> (mostRight != <span class="hljs-literal">null</span>) &#123;  <br>         <span class="hljs-keyword">while</span> (mostRight.right != <span class="hljs-literal">null</span> &amp;&amp; mostRight.right != cur) &#123;  <br>            mostRight = mostRight.right;  <br>         &#125;  <br>         <span class="hljs-keyword">if</span> (mostRight.right == <span class="hljs-literal">null</span>) &#123;  <br>            mostRight.right = cur;  <br>            cur = cur.left;  <br>            <span class="hljs-keyword">continue</span>;  <br>         &#125; <span class="hljs-keyword">else</span> &#123;  <br>            mostRight.right = <span class="hljs-literal">null</span>;  <br>            printEdge(cur.left);  <br>         &#125;  <br>      &#125;  <br>      cur = cur.right;  <br>   &#125;  <br>   printEdge(head);  <br>   System.out.println();  <br>&#125;<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_">printEdge</span><span class="hljs-params">(Node head)</span> &#123;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">tail</span> <span class="hljs-operator">=</span> reverseEdge(head);  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">cur</span> <span class="hljs-operator">=</span> tail;  <br>   <span class="hljs-keyword">while</span> (cur != <span class="hljs-literal">null</span>) &#123;  <br>      System.out.print(cur.value + <span class="hljs-string">&quot; &quot;</span>);  <br>      cur = cur.right;  <br>   &#125;  <br>   reverseEdge(tail);  <br>&#125;  <br>  <br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Node <span class="hljs-title function_">reverseEdge</span><span class="hljs-params">(Node from)</span> &#123;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">pre</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;  <br>   <span class="hljs-type">Node</span> <span class="hljs-variable">next</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;  <br>   <span class="hljs-keyword">while</span> (from != <span class="hljs-literal">null</span>) &#123;  <br>      next = from.right;  <br>      from.right = pre;  <br>      pre = from;  <br>      from = next;  <br>   &#125;  <br>   <span class="hljs-keyword">return</span> pre;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-8-相关扩展"><a href="#3-8-相关扩展" class="headerlink" title="3.8. 相关扩展"></a>3.8. 相关扩展</h2><h1 id="4-参考"><a href="#4-参考" class="headerlink" title="4. 参考"></a>4. 参考</h1><h2 id="4-1-爱学习的陈同学"><a href="#4-1-爱学习的陈同学" class="headerlink" title="4.1. 爱学习的陈同学"></a>4.1. 爱学习的陈同学</h2><p><a href="https://www.bilibili.com/video/BV1dY4y1T7v2/?p=40&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1dY4y1T7v2/?p=40&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="4-2-黑马"><a href="#4-2-黑马" class="headerlink" title="4.2. 黑马"></a>4.2. 黑马</h2><p><a href="https://www.bilibili.com/video/BV1iJ411E7xW?p=92&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1iJ411E7xW?p=92&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="4-3-✅-4-3-左程云"><a href="#4-3-✅-4-3-左程云" class="headerlink" title="4.3. ✅  4.3. 左程云"></a>4.3. ✅  4.3. 左程云</h2><p><span style="background-color:#ffff00">要想透彻理解 还是得看左神</span><br><a href="https://www.bilibili.com/video/BV1P34y1S7QJ/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1P34y1S7QJ/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV1Bg411F7oo/?spm_id_from=333.999.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1Bg411F7oo/?spm_id_from=333.999.0.0&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>&#x2F;Users&#x2F;taylor&#x2F;Nutstore Files&#x2F;Obsidian_data&#x2F;pages&#x2F;002-schdule&#x2F;001-Arch&#x2F;001-Subject&#x2F;013-DemoCode&#x2F;algorithmbasic2020</p><p><a href="https://github.com/algorithmzuo">https://github.com/algorithmzuo</a></p>]]></content>
      
      
      <categories>
          
          <category> 数据结构与算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 二叉树 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-数据结构-5、跳表</title>
      <link href="/2022/10/01/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-5%E3%80%81%E8%B7%B3%E8%A1%A8(SkipList)/"/>
      <url>/2022/10/01/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-5%E3%80%81%E8%B7%B3%E8%A1%A8(SkipList)/</url>
      
        <content type="html"><![CDATA[<h1 id="1-动态数据结构"><a href="#1-动态数据结构" class="headerlink" title="1. 动态数据结构"></a>1. 动态数据结构</h1><p>所谓动态查找就是查找的过程中存在元素的删除和插入，这样就对实现查找的数据结构有一定的挑战，因为在每次删除和插入时都要调整数据结构，来保持秩序。</p><p>可以作为查找数据结构的包括：</p><ul><li>线性结构：数组、链表</li><li>非线性结构：平衡树</li></ul><p>链表虽然通过增加指针域提升了自由度，但是却导致数据的查询效率恶化。特别是当链表长度很长的时候，对数据的查询还得从头依次查询，这样的效率会更低。跳表的产生<span style="background-color:#00ff00">就是为了解决链表过长的问题</span>，通过<span style="background-color:#ff00ff">增加链表的多级索引来加快</span>原始链表的查询效率。这样的方式可以让查询的时间复杂度从O(n)提升至O(logn)。</p><h1 id="2-什么是跳表"><a href="#2-什么是跳表" class="headerlink" title="2. 什么是跳表"></a>2. 什么是跳表</h1><p>跳跃表(简称跳表)由美国计算机科学家William Pugh发明于1989年。他在论文《Skip lists: a probabilistic alternative to balanced trees》中详细介绍了跳表的数据结构和插入删除等操作。</p><blockquote><p>跳表(SkipList，全称跳跃表)是用于有序元素序列快速搜索查找的一个数据结构，跳表是一个随机化的数据结构，实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引，通过索引来实现快速查找。跳表不仅能提高搜索性能，同时也可以提高插入和删除操作的性能。它在性能上和红黑树，AVL树不相上下，但是跳表的原理非常简单，实现也比红黑树简单很多。</p></blockquote><p>在这里你可以看到一些关键词：链表(<strong>有序链表</strong>)、索引、二分查找。</p><p>对于一个单链表来讲，即便链表中存储的数据是有序的，如果我们要想在其中查找某个数据，也只能从头到尾遍历链表。这样查找效率就会很低，时间复杂度会很高，是 O(n)。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221003203212.png"></p><p>那怎么来提高查找效率呢？如果像图中那样，对链表建立一级“索引”，查找起来是不是就会更快一些呢？每两个结点提取一个结点到上一级，我们把抽出来的那一级叫作<strong>索引</strong>或<strong>索引层</strong>。你可以看我画的图。图中的 down 表示 down 指针，指向下一级结点。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221003204104.png"></p><p>如果我们现在要查找某个结点，比如 16。我们可以先在索引层遍历，当遍历到索引层中值为 13 的结点时，我们发现下一个结点是 17，那要查找的结点 16 肯定就在这两个结点之间。然后我们通过索引层结点的 down 指针，下降到原始链表这一层，继续遍历。这个时候，我们只需要再遍历 2 个结点，就可以找到值等于 16 的这个结点了。这样，原来如果要查找 16，需要遍历 10 个结点，现在只需要遍历 7 个结点。</p><p>从这个例子里，我们看出，<strong>加来一层索引之后，查找一个结点需要遍历的结点个数减少了，也就是说查找效率提高了</strong>。</p><h1 id="3-查询效率提升"><a href="#3-查询效率提升" class="headerlink" title="3. 查询效率提升"></a>3. 查询效率提升</h1><p>在一个单链表中查询某个数据的时间复杂度是 O(n)。那在一个具有多级索引的跳表中，查询某个数据的时间复杂度是多少呢？</p><p>这个时间复杂度的分析方法比较难想到。我把问题分解一下，先来看这样一个问题，如果链表里有 n 个结点，会有多少级索引呢？</p><p>按照我们刚才讲的，每两个结点会抽出一个结点作为上一级索引的结点，那第一级索引的结点个数大约就是 n&#x2F;2，第二级索引的结点个数大约就是 n&#x2F;4，第三级索引的结点个数大约就是 n&#x2F;8，依次类推，也就是说，**第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1&#x2F;2，那第 k 级索引结点的个数就是 n&#x2F;(2 的k 次方)。</p><p>假设索引有 h 级，最高级的索引有 2 个结点。通过上面的公式，我们可以得到 n&#x2F;(2h)&#x3D;2，从而求得 h&#x3D;log2n-1。如果包含原始链表这一层，整个跳表的高度就是 log2n。我们在跳表中查询某个数据的时候，如果每一层都要遍历 m 个结点，那在跳表中查询一个数据的时间复杂度就是 <code>O(m*logn)</code>。</p><p>那这个 m 的值是多少呢？按照前面这种索引结构，我们每一级索引都最多只需要遍历 3 个结点，也就是说 m&#x3D;3，为什么是 3 呢？我来解释一下。</p><p>假设我们要查找的数据是 x，在第 k 级索引中，我们遍历到 y 结点之后，发现 x 大于 y，小于后面的结点 z，所以我们通过 y 的 down 指针，从第 k 级索引下降到第 k-1 级索引。在第 k-1 级索引中，y 和 z 之间只有 3 个结点（包含 y 和 z），所以，我们在 K-1 级索引中最多只需要遍历 3 个结点，依次类推，每一级索引都最多只需要遍历 3 个结点。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221003210825.png"></p><p>通过上面的分析，我们得到 m&#x3D;3，所以<span style="background-color:#00ff00">在跳表中查询任意数据的时间复杂度就是 O(logn)</span>。这个查找的时间复杂度跟二分查找是一样的。换句话说，我们其实是基于单链表实现了二分查找，是不是很神奇？不过，天下没有免费的午餐，这种查询效率的提升，前提是建立了很多级索引，也就是空间换时间的设计思路。</p><h1 id="4-内存消耗"><a href="#4-内存消耗" class="headerlink" title="4. 内存消耗"></a>4. 内存消耗</h1><p>比起单纯的单链表，跳表需要存储多级索引，肯定要消耗更多的存储空间。那到底需要消耗多少额外的存储空间呢？我们来分析一下跳表的空间复杂度。</p><p>跳表的空间复杂度分析并不难，我在前面说了，假设原始链表大小为 n，那第一级索引大约有 n&#x2F;2 个结点，第二级索引大约有 n&#x2F;4 个结点，以此类推，每上升一级就减少一半，直到剩下 2 个结点。如果我们把每层索引的结点数写出来，就是一个等比数列。这几级索引的结点总和就是 n&#x2F;2+n&#x2F;4+n&#x2F;8…+8+4+2&#x3D;n-2。所以，跳表的空间复杂度是 <span style="background-color:#00ff00">O(n)</span>。</p><p>实际上，在软件开发中，我们不必太在意索引占用的额外空间。在讲数据结构和算法时，我们习惯性地把要处理的数据看成整数，但是在实际的软件开发中，原始链表中存储的有可能是很大的对象，而索引结点只需要存储关键值和几个指针，并不需要存储对象，所以当对象比索引结点大很多时，那索引占用的额外空间就可以忽略了。</p><h1 id="5-高效的插入和删除"><a href="#5-高效的插入和删除" class="headerlink" title="5. 高效的插入和删除"></a>5. 高效的插入和删除</h1><p>跳表这个动态数据结构，不仅支持查找操作，还支持动态的插入、删除操作，而且插入、删除操作的时间复杂度也是 O(logn)。为了保证原始链表中数据的有序性，我们需要先找到要插入的位置，这个查找操作就会比较耗时。</p><p>对于纯粹的单链表，需要遍历每个结点，来找到插入的位置。但是，对于跳表来说，我们讲过查找某个结点的的时间复杂度是 O(logn)，所以这里查找某个数据应该插入的位置，方法也是类似的，时间复杂度也是 O(logn)。</p><h1 id="6-动态更新索引"><a href="#6-动态更新索引" class="headerlink" title="6. 动态更新索引"></a>6. 动态更新索引</h1><p>当我们不停地往跳表中插入数据时，如果我们不更新索引，就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下，跳表还会退化成单链表。<br>作为一种动态数据结构，我们需要某种手段来维护索引与原始链表大小之间的平衡，也就是说，如果链表中结点多了，索引结点就相应地增加一些，避免复杂度退化，以及查找、插入、删除操作性能下降。</p><p>如果你了解红黑树、AVL 树这样平衡二叉树，你就知道它们是通过左右旋的方式保持左右子树的大小平衡（如果不了解也没关系，我们后面会讲），而跳表是通过<font color=#ff0000><span style="background-color:#00ff00">随机函数</span></font>来维护前面提到的“平衡性”。</p><h1 id="7-在Redis中的应用"><a href="#7-在Redis中的应用" class="headerlink" title="7. 在Redis中的应用"></a>7. 在Redis中的应用</h1><p>ZSet结构同时包含一个字典和一个跳跃表，跳跃表<span style="background-color:#00ff00">按score从小到大保存所有集合元素</span>。字典<span style="background-color:#ffff00">保存着从member到score的映射</span>。这两种结构通过指针共享相同元素的member和score，不会浪费额外内存。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">zset</span> &#123;</span> <br>    dict *dict; <br>    zskiplist *zsl; <br>&#125; zset; <br></code></pre></td></tr></table></figure><p>ZSet中的字典和跳表布局：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221003100411.png"></p><h1 id="8-参考"><a href="#8-参考" class="headerlink" title="8. 参考"></a>8. 参考</h1><p><a href="https://www.51cto.com/article/669167.html">https://www.51cto.com/article/669167.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 跳表 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据结构与算法-2、排序算法大总结</title>
      <link href="/2022/09/30/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%AE%97%E6%B3%95-2%E3%80%81%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E5%A4%A7%E6%80%BB%E7%BB%93/"/>
      <url>/2022/09/30/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%AE%97%E6%B3%95-2%E3%80%81%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E5%A4%A7%E6%80%BB%E7%BB%93/</url>
      
        <content type="html"><![CDATA[<h1 id="1-算法分类"><a href="#1-算法分类" class="headerlink" title="1. 算法分类"></a>1. 算法分类</h1><p><img src="https://tva1.sinaimg.cn/large/006tNbRwly1gbhyjsntg3j30za0f0790.jpg" alt="image-20200202124614464"></p><p>内部排序：整个排序过程不需要借助于外部存储器（如磁盘等），所有排序操作都在内存中完成。<br>外部排序：参与排序的数据非常多，数据量非常大，计算机无法把整个排序过程放在内存中完成，必须借助于外部存储器（如磁盘）。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。</p><h1 id="2-算法比较"><a href="#2-算法比较" class="headerlink" title="2. 算法比较"></a>2. 算法比较</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001173824.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221021105501.png"></p><h1 id="3-冒泡排序-bubble-sort"><a href="#3-冒泡排序-bubble-sort" class="headerlink" title="3. 冒泡排序(bubble sort)"></a>3. 冒泡排序(bubble sort)</h1><h2 id="3-1-算法思想"><a href="#3-1-算法思想" class="headerlink" title="3.1. 算法思想"></a>3.1. 算法思想</h2><p><span style="background-color:#00ff00">从左到右，两两比较，每轮确定一个数，共N-1轮，每一轮比较和交换次数都是N-1到1的递减数列</span></p><ol><li>比较相邻的元素。如果第一个比第二个大，就交换他们两个。</li><li>对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对。这步做完后，最后的元素会是最大的数。</li><li>针对所有的元素重复以上的步骤，除了最后一个。</li><li>持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928162313.gif"></p><h2 id="3-2-代码实现"><a href="#3-2-代码实现" class="headerlink" title="3.2. 代码实现"></a>3.2. 代码实现</h2><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">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">bubbleSort</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr)</span> &#123;  <br>    <span class="hljs-type">int</span> <span class="hljs-variable">temp</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;  <br>    <span class="hljs-type">boolean</span> <span class="hljs-variable">flag</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</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; arr.length - <span class="hljs-number">1</span>; i++) &#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; arr.length - i - <span class="hljs-number">1</span>; j++) &#123;  <br>            <span class="hljs-keyword">if</span> (arr[j] &gt; arr[j + <span class="hljs-number">1</span>]) &#123;  <br>                flag = <span class="hljs-literal">true</span>;  <br>                temp = arr[j];  <br>                arr[j] = arr[j + <span class="hljs-number">1</span>];  <br>                arr[j + <span class="hljs-number">1</span>] = temp;  <br>            &#125;  <br>        &#125;  <br>        System.out.println(<span class="hljs-string">&quot;第&quot;</span>+(i+<span class="hljs-number">1</span>)+<span class="hljs-string">&quot;趟排序后的数组&quot;</span>);  <br>        System.out.println(Arrays.toString(arr));  <br>        <span class="hljs-keyword">if</span> (!flag) &#123;  <br>            <span class="hljs-keyword">break</span>;  <br>        &#125; <span class="hljs-keyword">else</span> &#123;  <br>            flag = <span class="hljs-literal">false</span>;  <br>        &#125;  <br>    &#125;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="3-3-时间复杂度"><a href="#3-3-时间复杂度" class="headerlink" title="3.3. 时间复杂度"></a>3.3. 时间复杂度</h2><p>冒泡排序使用了双层for循环，其中内层循环的循环体是真正完成排序的代码，所以，<br>我们分析冒泡排序的时间复杂度，主要分析一下内层循环体的执行次数即可。<br>在最坏情况下，也就是假如要排序的元素为{6,5,4,3,2,1}逆序，那么：<br><span style="background-color:#ffff00">元素比较的次数为：</span><br><span style="background-color:#ffff00"> (N-1)+(N-2)+(N-3)+…+2+1&#x3D;((N-1)+1)<em>(N-1)&#x2F;2&#x3D;N^2&#x2F;2-N&#x2F;2;</span><br><span style="background-color:#ffff00">元素交换的次数为：</span><br><span style="background-color:#ffff00"> (N-1)+(N-2)+(N-3)+…+2+1&#x3D;((N-1)+1)</em>(N-1)&#x2F;2&#x3D;N^2&#x2F;2-N&#x2F;2;</span><br><span style="background-color:#ffff00">总执行次数为：</span><br><span style="background-color:#ffff00"> (N^2&#x2F;2-N&#x2F;2)+(N^2&#x2F;2-N&#x2F;2)&#x3D;N^2-N;</span><br>按照大O推导法则，保留函数中的最高阶项那么最终冒泡排序的时间复杂度为O(N^2).</p><h1 id="4-选择排序-selection-sort"><a href="#4-选择排序-selection-sort" class="headerlink" title="4. 选择排序(selection sort)"></a>4. 选择排序(selection sort)</h1><h2 id="4-1-算法思想"><a href="#4-1-算法思想" class="headerlink" title="4.1. 算法思想"></a>4.1. 算法思想</h2><p><span style="background-color:#00ff00">从左到右，拿第1个数分别与N-1个数比较(拿第2个数分别与N-2个数…)，共循环N-1次</span></p><ol><li>在未排序序列中找到最小（大）元素，存放到排序序列的起始位置</li><li>从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾</li><li>以此类推，直到所有元素均排序完毕<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928181156.gif"></li></ol><h2 id="4-2-代码实现"><a href="#4-2-代码实现" class="headerlink" title="4.2. 代码实现"></a>4.2. 代码实现</h2><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-comment">//选择排序时间复杂度是 O(n^2)  </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; arr.length - <span class="hljs-number">1</span>; i++) &#123;  <br>   <span class="hljs-type">int</span> <span class="hljs-variable">minIndex</span> <span class="hljs-operator">=</span> i;  <br>   <span class="hljs-type">int</span> <span class="hljs-variable">min</span> <span class="hljs-operator">=</span> arr[i];  <br>   <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> i + <span class="hljs-number">1</span>; j &lt; arr.length; j++) &#123;  <br>      <span class="hljs-keyword">if</span> (min &gt; arr[j]) &#123; <span class="hljs-comment">// 说明假定的最小值，并不是最小  </span><br>         min = arr[j]; <span class="hljs-comment">// 重置min  </span><br>         minIndex = j; <span class="hljs-comment">// 重置minIndex  </span><br>      &#125;  <br>   &#125;  <br>   <span class="hljs-comment">// 将最小值，放在arr[0], 即交换  </span><br>   <span class="hljs-keyword">if</span> (minIndex != i) &#123;  <br>      arr[minIndex] = arr[i];  <br>      arr[i] = min;  <br>   &#125;  <br>   <span class="hljs-comment">//System.out.println(&quot;第&quot;+(i+1)+&quot;轮后~~&quot;);  </span><br>   <span class="hljs-comment">//System.out.println(Arrays.toString(arr));// 1, 34, 119, 101</span><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="4-3-时间复杂度"><a href="#4-3-时间复杂度" class="headerlink" title="4.3. 时间复杂度"></a>4.3. 时间复杂度</h2><p>选择排序使用了双层for循环，其中外层循环完成了数据交换，内层循环完成了数据比较，所以我们分别统计数据<br>交换次数和数据比较次数：<br><span style="background-color:#ffff00">数据比较次数：</span><br><span style="background-color:#ffff00"> (N-1)+(N-2)+(N-3)+…+2+1&#x3D;((N-1)+1)*(N-1)&#x2F;2&#x3D;N^2&#x2F;2-N&#x2F;2;</span></p><p><span style="background-color:#ffff00">数据交换次数：N-1</span></p><p><span style="background-color:#ffff00">时间复杂度：N^2&#x2F;2-N&#x2F;2+（N-1）&#x3D;N^2&#x2F;2+N&#x2F;2-1;</span></p><p>根据大O推导法则，保留最高阶项，去除常数因子，时间复杂度为O(N^2);</p><p>因为选择排序过程中，交换次数比两两比较交换要少，所以时间上要比冒泡快一点</p><h1 id="5-插入排序-insertion-sort"><a href="#5-插入排序-insertion-sort" class="headerlink" title="5. 插入排序(insertion sort)"></a>5. 插入排序(insertion sort)</h1><h2 id="5-1-算法思想"><a href="#5-1-算法思想" class="headerlink" title="5.1. 算法思想"></a>5.1. 算法思想</h2><p><span style="background-color:#00ff00">从第2个数开始，从右向左与前面i个数比较、交换</span></p><ol><li>把所有的元素分为两组，已经排序的和未排序的；从第一个元素开始，该元素可以认为已经被排序</li><li>取出下一个元素，在已经排序的元素序列中从后向前扫描</li><li>如果该元素（已排序）大于新元素，将该元素移到下一位置</li><li>重复步骤3，直到找到已排序的元素小于或者等于新元素的位置</li><li>将新元素插入到该位置后</li><li>重复步骤2~5<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928192447.gif"></li></ol><h2 id="5-2-代码实现"><a href="#5-2-代码实现" class="headerlink" title="5.2. 代码实现"></a>5.2. 代码实现</h2><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">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(Comparable[] a)</span>&#123;  <br>    <span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> i=<span class="hljs-number">1</span>;i&lt;a.length;i++)&#123;  <br>        <span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> j=i;j&gt;<span class="hljs-number">0</span>;j--)&#123;  <br>            <span class="hljs-comment">//比较索引j处的值和索引j-1处的值，如果索引j-1处的值比索引j处的值大，则交换数据，如果不大，那么就找到合适的位置了，退出循环即可；  </span><br>            <span class="hljs-keyword">if</span> (greater(a[j-<span class="hljs-number">1</span>],a[j]))&#123;  <br>                exch(a,j-<span class="hljs-number">1</span>,j);  <br>            &#125;<span class="hljs-keyword">else</span>&#123;  <br>                <span class="hljs-keyword">break</span>;  <br>            &#125;  <br>        &#125;  <br>    &#125;  <br>&#125;  <br>  <br><span class="hljs-comment">/*  </span><br><span class="hljs-comment">    比较v元素是否大于w元素 */</span><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span>  <span class="hljs-type">boolean</span> <span class="hljs-title function_">greater</span><span class="hljs-params">(Comparable v,Comparable w)</span>&#123;  <br>    <span class="hljs-keyword">return</span> v.compareTo(w)&gt;<span class="hljs-number">0</span>;  <br>&#125;  <br>  <br><span class="hljs-comment">/*  </span><br><span class="hljs-comment">数组元素i和j交换位置  </span><br><span class="hljs-comment"> */</span><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exch</span><span class="hljs-params">(Comparable[] a,<span class="hljs-type">int</span> i,<span class="hljs-type">int</span> j)</span>&#123;  <br>    Comparable temp;  <br>    temp = a[i];  <br>    a[i]=a[j];  <br>    a[j]=temp;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="5-3-时间复杂度"><a href="#5-3-时间复杂度" class="headerlink" title="5.3. 时间复杂度"></a>5.3. 时间复杂度</h2><p>插入排序使用了双层for循环，其中内层循环的循环体是真正完成排序的代码，所以，我们分析插入排序的时间复<br>杂度，主要分析一下内层循环体的执行次数即可。<br>最坏情况，也就是待排序的数组元素为{12,10,6,5,4,3,2,1}，那么：<br><span style="background-color:#ffff00">比较的次数为：</span><br><span style="background-color:#ffff00">(N-1)+(N-2)+(N-3)+…+2+1&#x3D;((N-1)+1)<em>(N-1)&#x2F;2&#x3D;N^2&#x2F;2-N&#x2F;2;</span><br><span style="background-color:#ffff00">交换的次数为：</span><br><span style="background-color:#ffff00">(N-1)+(N-2)+(N-3)+…+2+1&#x3D;((N-1)+1)</em>(N-1)&#x2F;2&#x3D;N^2&#x2F;2-N&#x2F;2;</span><br><span style="background-color:#ffff00">总执行次数为：</span><br><span style="background-color:#ffff00">(N^2&#x2F;2-N&#x2F;2)+(N^2&#x2F;2-N&#x2F;2)&#x3D;N^2-N;</span><br>按照大O推导法则，保留函数中的最高阶项那么最终插入排序的时间复杂度为O(N^2).</p><h1 id="6-希尔排序-shell-sort"><a href="#6-希尔排序-shell-sort" class="headerlink" title="6. 希尔排序(shell sort)"></a>6. 希尔排序(shell sort)</h1><h2 id="6-1-算法思想"><a href="#6-1-算法思想" class="headerlink" title="6.1. 算法思想"></a>6.1. 算法思想</h2><p> 1.选定一个增长量gap，按照增长量gap作为数据分组的依据，对数据进行分组；<br> 2.对分好组的每一组数据完成<span style="background-color:#ffff00">插入排序</span>；<br> 3.减小增长量，最小减为1，重复第二步操作。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930225329.gif"></p><p>增长量gap的确定：增长量h的值每一固定的规则，我们这里采用以下规则：</p><figure class="highlight plaintext"><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 plaintext">int gap=1<br><br>while(gap&lt;5)&#123;<br><br>  gap=2gap+1；//3,7<br><br>&#125;<br><br>//循环结束后我们就可以确定h的最大值；<br><br>h的减小规则为：<br><br>gap=gap/2<br></code></pre></td></tr></table></figure><h2 id="6-2-代码实现"><a href="#6-2-代码实现" class="headerlink" title="6.2. 代码实现"></a>6.2. 代码实现</h2><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">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(Comparable[] a)</span>&#123;<br><span class="hljs-comment">//1.根据数组a的长度，确定增长量h的初始值；</span><br><span class="hljs-type">int</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-keyword">while</span>(h&lt;a.length/<span class="hljs-number">2</span>)&#123;<br>h=<span class="hljs-number">2</span>*h+<span class="hljs-number">1</span>;<br>&#125;<br><span class="hljs-comment">//2.希尔排序</span><br><span class="hljs-keyword">while</span>(h&gt;=<span class="hljs-number">1</span>)&#123;<br><span class="hljs-comment">//排序</span><br><span class="hljs-comment">//2.1.找到待插入的元素</span><br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=h;i&lt;a.length;i++)&#123;<br><span class="hljs-comment">//2.2把待插入的元素插入到有序数列中</span><br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j=i;j&gt;=h;j-=h)&#123;<br><br><span class="hljs-comment">//待插入的元素是a[j],比较a[j]和a[j-h]</span><br><span class="hljs-keyword">if</span> (greater(a[j-h],a[j]))&#123;<br><span class="hljs-comment">//交换元素</span><br>exch(a,j-h,j);<br>&#125;<span class="hljs-keyword">else</span>&#123;<br><span class="hljs-comment">//待插入元素已经找到了合适的位置，结束循环；</span><br><span class="hljs-keyword">break</span>;<br>&#125;<br>&#125;<br><br>&#125;<br><span class="hljs-comment">//减小h的值</span><br>h= h/<span class="hljs-number">2</span>;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="6-3-时间复杂度"><a href="#6-3-时间复杂度" class="headerlink" title="6.3. 时间复杂度"></a>6.3. 时间复杂度</h2><p>todo</p><h1 id="7-归并排序-merge-sort"><a href="#7-归并排序-merge-sort" class="headerlink" title="7. 归并排序(merge sort)"></a>7. 归并排序(merge sort)</h1><h2 id="7-1-算法思想"><a href="#7-1-算法思想" class="headerlink" title="7.1. 算法思想"></a>7.1. 算法思想</h2><p>归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用<span style="background-color:#00ff00">分治法（Divide and Conquer）</span>的一个非常典型的应用。将已有序的子序列合并，得到完全有序的序列；即先使每个子序列有序，再使子序列段间有序。若将两个有序表合并成一个有序表，称为2-路归并。</p><ol><li>尽可能的一组数据拆分成两个元素相等的子组，并对每一个子组继续拆分，直到拆分后的每个子组的元素个数是1为止。</li><li>将相邻的两个子组进行合并成一个有序的大组；</li><li>不断的重复步骤2，直到最终只有一个组为止。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930231114.gif"></li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221002082934.png"></p><h2 id="7-2-代码实现"><a href="#7-2-代码实现" class="headerlink" title="7.2. 代码实现"></a>7.2. 代码实现</h2><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">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">mergeSort</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr)</span> &#123;<br><span class="hljs-keyword">if</span> (arr == <span class="hljs-literal">null</span> || arr.length &lt; <span class="hljs-number">2</span>) &#123;<br><span class="hljs-keyword">return</span>;<br>&#125;<br>mergeSort(arr, <span class="hljs-number">0</span>, arr.length - <span class="hljs-number">1</span>);<br>&#125;<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_">mergeSort</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r)</span> &#123;<br><span class="hljs-keyword">if</span> (l == r) &#123;<br><span class="hljs-keyword">return</span>;<br>&#125;<br><span class="hljs-type">int</span> <span class="hljs-variable">mid</span> <span class="hljs-operator">=</span> l + ((r - l) &gt;&gt; <span class="hljs-number">1</span>);<br>mergeSort(arr, l, mid);<br>mergeSort(arr, mid + <span class="hljs-number">1</span>, r);<br>merge(arr, l, mid, r);<br>&#125;<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_">merge</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> l, <span class="hljs-type">int</span> m, <span class="hljs-type">int</span> r)</span> &#123;<br><span class="hljs-type">int</span>[] help = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[r - l + <span class="hljs-number">1</span>];<br><span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">p1</span> <span class="hljs-operator">=</span> l;<br><span class="hljs-type">int</span> <span class="hljs-variable">p2</span> <span class="hljs-operator">=</span> m + <span class="hljs-number">1</span>;<br><span class="hljs-keyword">while</span> (p1 &lt;= m &amp;&amp; p2 &lt;= r) &#123;<br>help[i++] = arr[p1] &lt; arr[p2] ? arr[p1++] : arr[p2++];<br>&#125;<br><span class="hljs-keyword">while</span> (p1 &lt;= m) &#123;<br>help[i++] = arr[p1++];<br>&#125;<br><span class="hljs-keyword">while</span> (p2 &lt;= r) &#123;<br>help[i++] = arr[p2++];<br>&#125;<br><span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; help.length; i++) &#123;<br>arr[l + i] = help[i];<br>&#125;<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><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></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_">Merge</span> &#123;<br>    <span class="hljs-comment">//归并所需要的辅助数组</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Comparable[] assist;<br><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">       比较v元素是否小于w元素</span><br><span class="hljs-comment">    */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">less</span><span class="hljs-params">(Comparable v, Comparable w)</span> &#123;<br>        <span class="hljs-keyword">return</span> v.compareTo(w)&lt;<span class="hljs-number">0</span>;<br>    &#125;<br><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">    数组元素i和j交换位置</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exch</span><span class="hljs-params">(Comparable[] a, <span class="hljs-type">int</span> i, <span class="hljs-type">int</span> j)</span> &#123;<br>        <span class="hljs-type">Comparable</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> a[i];<br>        a[i] = a[j];<br>        a[j] = t;<br>    &#125;<br><br><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">           对数组a中的元素进行排序</span><br><span class="hljs-comment">        */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(Comparable[] a)</span> &#123;<br>        <span class="hljs-comment">//1.初始化辅助数组assist；</span><br>        assist = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Comparable</span>[a.length];<br>        <span class="hljs-comment">//2.定义一个lo变量，和hi变量，分别记录数组中最小的索引和最大的索引；</span><br>        <span class="hljs-type">int</span> lo=<span class="hljs-number">0</span>;<br>        <span class="hljs-type">int</span> hi=a.length-<span class="hljs-number">1</span>;<br>        <span class="hljs-comment">//3.调用sort重载方法完成数组a中，从索引lo到索引hi的元素的排序</span><br>        sort(a,lo,hi);<br>    &#125;<br><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">    对数组a中从lo到hi的元素进行排序</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(Comparable[] a, <span class="hljs-type">int</span> lo, <span class="hljs-type">int</span> hi)</span> &#123;<br>        <span class="hljs-comment">//做安全性校验；</span><br>        <span class="hljs-keyword">if</span> (hi&lt;=lo)&#123;<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br><br>        <span class="hljs-comment">//对lo到hi之间的数据进行分为两个组</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">mid</span> <span class="hljs-operator">=</span> lo+(hi-lo)/<span class="hljs-number">2</span>;<span class="hljs-comment">//   5,9  mid=7</span><br><br>        <span class="hljs-comment">//分别对每一组数据进行排序</span><br>        sort(a,lo,mid);<br>        sort(a,mid+<span class="hljs-number">1</span>,hi);<br><br>        <span class="hljs-comment">//再把两个组中的数据进行归并</span><br>        merge(a,lo,mid,hi);<br>    &#125;<br><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">    对数组中，从lo到mid为一组，从mid+1到hi为一组，对这两组数据进行归并</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">merge</span><span class="hljs-params">(Comparable[] a, <span class="hljs-type">int</span> lo, <span class="hljs-type">int</span> mid, <span class="hljs-type">int</span> hi)</span> &#123;<br>        <span class="hljs-comment">//定义三个指针</span><br>        <span class="hljs-type">int</span> i=lo;<br>        <span class="hljs-type">int</span> p1=lo;<br>        <span class="hljs-type">int</span> p2=mid+<span class="hljs-number">1</span>;<br><br>        <span class="hljs-comment">//遍历，移动p1指针和p2指针，比较对应索引处的值，找出小的那个，放到辅助数组的对应索引处</span><br>        <span class="hljs-keyword">while</span>(p1&lt;=mid &amp;&amp; p2&lt;=hi)&#123;<br>            <span class="hljs-comment">//比较对应索引处的值</span><br>            <span class="hljs-keyword">if</span> (less(a[p1],a[p2]))&#123;<br>                assist[i++] = a[p1++];<br>            &#125;<span class="hljs-keyword">else</span>&#123;<br>                assist[i++]=a[p2++];<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-comment">//遍历，如果p1的指针没有走完，那么顺序移动p1指针，把对应的元素放到辅助数组的对应索引处</span><br>        <span class="hljs-keyword">while</span>(p1&lt;=mid)&#123;<br>            assist[i++]=a[p1++];<br>        &#125;<br>        <span class="hljs-comment">//遍历，如果p2的指针没有走完，那么顺序移动p2指针，把对应的元素放到辅助数组的对应索引处</span><br>        <span class="hljs-keyword">while</span>(p2&lt;=hi)&#123;<br>            assist[i++]=a[p2++];<br>        &#125;<br>        <span class="hljs-comment">//把辅助数组中的元素拷贝到原数组中</span><br>        <span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> index=lo;index&lt;=hi;index++)&#123;<br>            a[index]=assist[index];<br>        &#125;<br><br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>参考<br><a href="https://www.bilibili.com/video/BV1XV4y1H7Yn?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1XV4y1H7Yn?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV1iJ411E7xW/?p=26&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1iJ411E7xW/?p=26&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="7-3-复杂度分析"><a href="#7-3-复杂度分析" class="headerlink" title="7.3. 复杂度分析"></a>7.3. 复杂度分析</h2><ul><li><p><strong>时间复杂度：O(nlogn)</strong></p><figure class="highlight plaintext"><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 plaintext">用树状图来描述归并，如果一个数组有8个元素，那么它将每次除以2找最小的子数组，共拆log8次，值为3，所以树共有3层,那么自顶向下第k层有2^k个子数组，每个数组的长度为2^(3-k)，归并最多需要2^(3-k)次比较。因此每层的比较次数为 2^k * 2^(3-k)=2^3,那么3层总共为 3*2^3。<br><br>假设元素的个数为n，那么使用归并排序拆分的次数为log2(n),所以共log2(n)层，那么使用log2(n)替换上面3*2^3中的3这个层数，最终得出的归并排序的时间复杂度为：log2(n)* 2^(log2(n))=log2(n)*n,根据大O推导法则，忽略底数，最终归并排序的时间复杂度为O(nlogn);<br></code></pre></td></tr></table></figure></li><li><p><strong>空间复杂度：O(N)</strong> ：归并排序需要一个与原数组相同长度的数组做辅助来排序</p></li><li><p>稳定性：归并排序是稳定的排序算法，<code>temp[i++] = arr[p1] &lt;= arr[p2] ? arr[p1++] : arr[p2++];</code>这行代码可以保证当左右两部分的值相等的时候，先复制左边的值，这样可以保证值相等的时候两个元素的相对位置不变。</p></li></ul><h1 id="8-快速排序-quick-sort"><a href="#8-快速排序-quick-sort" class="headerlink" title="8. 快速排序(quick sort)"></a>8. 快速排序(quick sort)</h1><h2 id="8-1-算法思想"><a href="#8-1-算法思想" class="headerlink" title="8.1. 算法思想"></a>8.1. 算法思想</h2><p>快速排序是<span style="background-color:#00ff00">对冒泡排序的一种改进</span>。它的基本思想是：通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都比另外一部分的所有数据都要小，然后再按此方法对这两部分数据分别进行快速排序，整个排序过程可以递归进行，以此达到整个数据变成有序序列。</p><p>算法步骤</p><p>1.找一个基准值，用两个指针分别指向数组的头部和尾部；</p><p>2.先从尾部向头部开始搜索一个比基准值小的元素，搜索到即停止，并记录指针的位置；</p><p>3.再从头部向尾部开始搜索一个比基准值大的元素，搜索到即停止，并记录指针的位置；</p><p>4.交换当前左边指针位置和右边指针位置的元素；</p><p>5.重复2,3,4步骤，直到左边指针的值大于右边指针的值停止。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001091309.gif"></p><h2 id="8-2-代码实现"><a href="#8-2-代码实现" class="headerlink" title="8.2. 代码实现"></a>8.2. 代码实现</h2><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></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_">Quick</span> &#123;<br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">      比较v元素是否小于w元素</span><br><span class="hljs-comment">   */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">less</span><span class="hljs-params">(Comparable v, Comparable w)</span> &#123;<br>        <span class="hljs-keyword">return</span> v.compareTo(w) &lt; <span class="hljs-number">0</span>;<br>    &#125;<br><br><br><br>    <span class="hljs-comment">/*</span><br><span class="hljs-comment">   数组元素i和j交换位置</span><br><span class="hljs-comment">    */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exch</span><span class="hljs-params">(Comparable[] a, <span class="hljs-type">int</span> i, <span class="hljs-type">int</span> j)</span> &#123;<br>        <span class="hljs-type">Comparable</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> a[i];<br>        a[i] = a[j];<br>        a[j] = t;<br>    &#125;<br><br>    <span class="hljs-comment">//对数组内的元素进行排序</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(Comparable[] a)</span> &#123;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">lo</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">hi</span> <span class="hljs-operator">=</span> a.length-<span class="hljs-number">1</span>;<br>        sort(a,lo,hi);<br>    &#125;<br><br>    <span class="hljs-comment">//对数组a中从索引lo到索引hi之间的元素进行排序</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sort</span><span class="hljs-params">(Comparable[] a, <span class="hljs-type">int</span> lo, <span class="hljs-type">int</span> hi)</span> &#123;<br>        <span class="hljs-comment">//安全性校验</span><br>        <span class="hljs-keyword">if</span> (hi&lt;=lo)&#123;<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br><br>        <span class="hljs-comment">//需要对数组中lo索引到hi索引处的元素进行分组（左子组和右子组）；</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">partition</span> <span class="hljs-operator">=</span> partition(a, lo, hi);<span class="hljs-comment">//返回的是分组的分界值所在的索引，分界值位置变换后的索引</span><br><br>        <span class="hljs-comment">//让左子组有序</span><br>        sort(a,lo,partition-<span class="hljs-number">1</span>);<br><br>        <span class="hljs-comment">//让右子组有序</span><br>        sort(a,partition+<span class="hljs-number">1</span>,hi);<br>    &#125;<br><br>    <span class="hljs-comment">//对数组a中，从索引 lo到索引 hi之间的元素进行分组，并返回分组界限对应的索引</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">partition</span><span class="hljs-params">(Comparable[] a, <span class="hljs-type">int</span> lo, <span class="hljs-type">int</span> hi)</span> &#123;<br>       <span class="hljs-comment">//确定分界值</span><br>        <span class="hljs-type">Comparable</span> <span class="hljs-variable">key</span> <span class="hljs-operator">=</span> a[lo];<br>        <span class="hljs-comment">//定义两个指针，分别指向待切分元素的最小索引处和最大索引处的下一个位置</span><br>        <span class="hljs-type">int</span> left=lo;<br>        <span class="hljs-type">int</span> right=hi+<span class="hljs-number">1</span>;<br><br>        <span class="hljs-comment">//切分</span><br>        <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>)&#123;<br>            <span class="hljs-comment">//先从右往左扫描，移动right指针，找到一个比分界值小的元素，停止</span><br>            <span class="hljs-keyword">while</span>(less(key,a[--right]))&#123;<br>                <span class="hljs-keyword">if</span> (right==lo)&#123;<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>            &#125;<br><br>            <span class="hljs-comment">//再从左往右扫描，移动left指针，找到一个比分界值大的元素，停止</span><br>            <span class="hljs-keyword">while</span>(less(a[++left],key))&#123;<br>                <span class="hljs-keyword">if</span> (left==hi)&#123;<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>            &#125;<br>            <span class="hljs-comment">//判断 left&gt;=right,如果是，则证明元素扫描完毕，结束循环，如果不是，则交换元素即可</span><br>            <span class="hljs-keyword">if</span> (left&gt;=right)&#123;<br>                <span class="hljs-keyword">break</span>;<br>            &#125;<span class="hljs-keyword">else</span>&#123;<br>                exch(a,left,right);<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-comment">//交换分界值</span><br>        exch(a,lo,right);<br><br>       <span class="hljs-keyword">return</span> right;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="8-3-复杂度分析"><a href="#8-3-复杂度分析" class="headerlink" title="8.3. 复杂度分析"></a>8.3. 复杂度分析</h2><p>快速排序的一次切分从两头开始交替搜索，直到left和right重合，因此，<span style="background-color:#ffff00">每一次切分</span>的时间复杂度为O(n),但整个快速排序的时间复杂度和切分的次数相关。<br>最优情况：每一次切分选择的基准数字刚好将当前序列等分。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001123103.png"><br>如果我们把数组的切分看做是一个树，那么上图就是它的最优情况的图示，共切分了logn次，所以，最优情况下快速排序的时间复杂度为O(nlogn);<br>最坏情况：每一次切分选择的基准数字是当前序列中最大数或者最小数，这使得每次切分都会有一个子组，那么总共就得切分n次，所以，最坏情况下，快速排序的时间复杂度为O(n^2);</p><p>空间复杂度与空间复杂度的分析情况类似，而空间用来记录划分点，最好情况是接近二分，所以是O(logn)。最坏情况是接近遍历所有，所以是O(n)，最终通过概率统计得出O(logn)。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001123148.png"></p><h2 id="8-4-快排VS归并"><a href="#8-4-快排VS归并" class="headerlink" title="8.4. 快排VS归并"></a>8.4. 快排VS归并</h2><p>与归并排序类似，快速排序是另外一种分治的排序算法，它将一个数组分成两个子数组，将两部分独立的排序。快速排序和归并排序是互补的：归并排序将数组分成两个子数组<span style="background-color:#ffff00">分别排序</span>，并将有序的子数组<span style="background-color:#ffff00">归并</span>从而将整个数组排序，而快速排序的方式则是当两个数组都有序时，整个数组自然就有序了。在归并排序中，一个数组被等分为两半，归并调用发生在处理整个数组之前，也就是先mergeSort再merge。在快速排序中，切分数组的位置取决于数组的内容，递归调用发生在处理整个数组之后，也就是先分区后递归排序。</p><h2 id="8-5-参考"><a href="#8-5-参考" class="headerlink" title="8.5. 参考"></a>8.5. 参考</h2><p><a href="https://www.bilibili.com/video/BV1iJ411E7xW?p=35&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1iJ411E7xW?p=35&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.bilibili.com/video/BV13g41157hK?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13g41157hK?p=4&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV13g41157hK?p=4&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204#t=7599.29804">02:06:39</a></p><h1 id="9-堆排序-heap-sort"><a href="#9-堆排序-heap-sort" class="headerlink" title="9. 堆排序(heap sort)"></a>9. 堆排序(heap sort)</h1><p>堆排序是利用堆这种数据结构而设计的，是一种选择排序算法。它的最好、最坏、平均时间复杂度均为O(nlogn)。</p><p>堆的性质：完全二叉树</p><h2 id="9-1-大根堆"><a href="#9-1-大根堆" class="headerlink" title="9.1. 大根堆"></a>9.1. 大根堆</h2><p>每个节点的值都大于或等于其左右孩子的值，以x为头的整棵树的最大值为x。左右孩子的大小关系无要求。<br>节点i的左孩子：<code>2*i+1</code><br>节点i的右孩子：<code>2*i+2</code><br>节点i的父节点：<code>(i-1)/2</code></p><h3 id="9-1-1-heapinsert"><a href="#9-1-1-heapinsert" class="headerlink" title="9.1.1. heapinsert"></a>9.1.1. heapinsert</h3><p>如何保持大根堆特性<br><span style="background-color:#00ff00">每加入一个新节点，就与自己的父节点进行比较，如果大于父节点则交换</span>，交换完之后再次与新父节点比较，依次循环，直到小于等于自己的父节点或者变成了根节点为止。</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-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">heapInsert</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> index)</span> &#123;<br><span class="hljs-keyword">while</span> (arr[index] &gt; arr[(index - <span class="hljs-number">1</span>) / <span class="hljs-number">2</span>]) &#123;<br>swap(arr, index, (index - <span class="hljs-number">1</span>) /<span class="hljs-number">2</span>);<br>index = (index - <span class="hljs-number">1</span>)/<span class="hljs-number">2</span> ;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="9-1-2-heapify"><a href="#9-1-2-heapify" class="headerlink" title="9.1.2. heapify"></a>9.1.2. heapify</h3><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 某个数在index，能否向下移动</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">heapify</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> index, <span class="hljs-type">int</span> size)</span> &#123;<br><span class="hljs-type">int</span> <span class="hljs-variable">left</span> <span class="hljs-operator">=</span> index * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>; <span class="hljs-comment">//左孩子下标</span><br><span class="hljs-keyword">while</span> (left &lt; size) &#123; <span class="hljs-comment">//下方还有孩子</span><br><span class="hljs-comment">//有右孩子 &amp;&amp; 右孩子大于左孩子 --&gt; 右孩子胜出，返回右孩子下标，否则返回左孩子下标。即左右两个孩子谁大返回谁的下标 </span><br><span class="hljs-type">int</span> <span class="hljs-variable">largest</span> <span class="hljs-operator">=</span> left + <span class="hljs-number">1</span> &lt; size &amp;&amp; arr[left + <span class="hljs-number">1</span>] &gt; arr[left] ? left + <span class="hljs-number">1</span> : left;<br><span class="hljs-comment">//父亲和较大孩子之间谁的值大 返回谁的下标给largest</span><br>largest = arr[largest] &gt; arr[index] ? largest : index;<br><br><span class="hljs-comment">//表示此时index位置的值就是最大值，不需要下移</span><br><span class="hljs-keyword">if</span> (largest == index) &#123;<br><span class="hljs-keyword">break</span>;<br>&#125;<br><br><span class="hljs-comment">//较大的孩子和父交换</span><br>swap(arr, largest, index);<br><span class="hljs-comment">//位置下移</span><br>index = largest;<br><span class="hljs-comment">//左孩子变化，然后进入下一次while循环</span><br>left = index * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="9-2-算法思想"><a href="#9-2-算法思想" class="headerlink" title="9.2. 算法思想"></a>9.2. 算法思想</h2><blockquote><p>首先heapify，将数组变成堆<br>堆的首尾位置元素互换，就会将最大值放到堆的最后<br>堆size–，将最大值移除<br>再次heapify，周而复始</p></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001142458.gif"></p><h2 id="9-3-代码实现"><a href="#9-3-代码实现" class="headerlink" title="9.3. 代码实现"></a>9.3. 代码实现</h2><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></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_">heapSort</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr)</span> &#123;<br><span class="hljs-keyword">if</span> (arr == <span class="hljs-literal">null</span> || arr.length &lt; <span class="hljs-number">2</span>) &#123;<br><span class="hljs-keyword">return</span>;<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; arr.length; i++) &#123;<br>heapInsert(arr, i);<br>&#125;<br><span class="hljs-type">int</span> <span class="hljs-variable">size</span> <span class="hljs-operator">=</span> arr.length;<br>swap(arr, <span class="hljs-number">0</span>, --size);<br><span class="hljs-keyword">while</span> (size &gt; <span class="hljs-number">0</span>) &#123;<br>heapify(arr, <span class="hljs-number">0</span>, size);<br>swap(arr, <span class="hljs-number">0</span>, --size);<br>&#125;<br>&#125;<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_">heapInsert</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> index)</span> &#123;<br><span class="hljs-keyword">while</span> (arr[index] &gt; arr[(index - <span class="hljs-number">1</span>) / <span class="hljs-number">2</span>]) &#123;<br>swap(arr, index, (index - <span class="hljs-number">1</span>) /<span class="hljs-number">2</span>);<br>index = (index - <span class="hljs-number">1</span>)/<span class="hljs-number">2</span> ;<br>&#125;<br>&#125;<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_">heapify</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> index, <span class="hljs-type">int</span> size)</span> &#123;<br><span class="hljs-type">int</span> <span class="hljs-variable">left</span> <span class="hljs-operator">=</span> index * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>;<br><span class="hljs-keyword">while</span> (left &lt; size) &#123;<br><span class="hljs-type">int</span> <span class="hljs-variable">largest</span> <span class="hljs-operator">=</span> left + <span class="hljs-number">1</span> &lt; size &amp;&amp; arr[left + <span class="hljs-number">1</span>] &gt; arr[left] ? left + <span class="hljs-number">1</span> : left;<br>largest = arr[largest] &gt; arr[index] ? largest : index;<br><span class="hljs-keyword">if</span> (largest == index) &#123;<br><span class="hljs-keyword">break</span>;<br>&#125;<br>swap(arr, largest, index);<br>index = largest;<br>left = index * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>;<br>&#125;<br>&#125;<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_">swap</span><span class="hljs-params">(<span class="hljs-type">int</span>[] arr, <span class="hljs-type">int</span> i, <span class="hljs-type">int</span> j)</span> &#123;<br><span class="hljs-type">int</span> <span class="hljs-variable">tmp</span> <span class="hljs-operator">=</span> arr[i];<br>arr[i] = arr[j];<br>arr[j] = tmp;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="9-4-复杂度分析"><a href="#9-4-复杂度分析" class="headerlink" title="9.4. 复杂度分析"></a>9.4. 复杂度分析</h2><p>时间复杂度：O(nlogn)<br>空间复杂度：O(1)</p><h2 id="9-5-稳定性"><a href="#9-5-稳定性" class="headerlink" title="9.5. 稳定性"></a>9.5. 稳定性</h2><p>不稳定</p><h2 id="9-6-参考"><a href="#9-6-参考" class="headerlink" title="9.6. 参考"></a>9.6. 参考</h2><p><a href="https://www.bilibili.com/video/BV13g41157hK/?p=5&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13g41157hK/?p=5&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV13g41157hK/?p=5&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204#t=519.946362">08:39</a></p><h1 id="10-桶排序-bucket-sort"><a href="#10-桶排序-bucket-sort" class="headerlink" title="10. 桶排序(bucket sort)"></a>10. 桶排序(bucket sort)</h1><h2 id="10-1-算法思想"><a href="#10-1-算法思想" class="headerlink" title="10.1. 算法思想"></a>10.1. 算法思想</h2><ol><li>设置一个定量的数组当作空桶子</li><li>寻访序列，并且把项目一个一个放到对应的桶子去。</li><li>对每个不是空的桶子进行排序。</li><li>从不是空的桶子里把项目再放回原来的序列中。</li></ol><p>不同场景，设计不同的桶</p><p><strong>按位数</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001163603.gif"></p><p><strong>按范围</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001163636.gif"></p><h1 id="11-基数排序-radix-sort"><a href="#11-基数排序-radix-sort" class="headerlink" title="11. 基数排序(radix sort)"></a>11. 基数排序(radix sort)</h1><h2 id="11-1-算法思想"><a href="#11-1-算法思想" class="headerlink" title="11.1. 算法思想"></a>11.1. 算法思想</h2><p>一种多关键字的排序算法，可用<span style="background-color:#00ff00">桶排序</span>实现。</p><ol><li>取得数组中的最大数，并取得位数；</li><li>arr为原始数组，从最低位开始取每个位组成radix数组；</li><li>对radix进行计数排序（利用计数排序适用于小范围数的特点）<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001163803.gif"></li></ol><h1 id="12-计数排序-count-sort"><a href="#12-计数排序-count-sort" class="headerlink" title="12. 计数排序(count sort)"></a>12. 计数排序(count sort)</h1><h2 id="12-1-算法思想"><a href="#12-1-算法思想" class="headerlink" title="12.1. 算法思想"></a>12.1. 算法思想</h2><ul><li>花O(n)的时间扫描一下整个序列 A，获取最小值 min 和最大值 max</li><li>开辟一块新的空间创建新的数组 B，长度为 ( max - min + 1)</li><li>数组 B 中 index 的元素记录的值是 A 中某元素出现的次数</li><li>最后输出目标整数序列，具体的逻辑是遍历数组 B，输出相应元素以及对应的个数<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221001163949.gif"></li></ul><h1 id="13-总结"><a href="#13-总结" class="headerlink" title="13. 总结"></a>13. 总结</h1><p><img src="https://tva1.sinaimg.cn/large/006tNbRwly1gbi1vknvtnj32060s8hdu.jpg" alt="image-20200202144120469"></p><h1 id="Java-提供的默认排序算法"><a href="#Java-提供的默认排序算法" class="headerlink" title="Java 提供的默认排序算法"></a>Java 提供的默认排序算法</h1><p>Java提供的默认排序算法，具体是什么排序方式以及设计思路？这个问题本身就是有点陷阱的意味，因为需要区分是 Arrays.sort() 还是 Collections.sort() （底层是调用 Arrays.sort()）；什么数据类型；多大的数据集（太小的数据集，复杂排序是没必要的，Java 会直接进行二分插入排序）等。</p><ul><li><p>对于原始数据类型，目前使用的是所谓双轴快速排序（Dual-Pivot QuickSort），是一种改进的快速排序算法，早期版本是相对传统的快速排序，你可以阅读<a href="http://hg.openjdk.java.net/jdk/jdk/file/26ac622a4cab/src/java.base/share/classes/java/util/DualPivotQuicksort.java">源码</a>。</p></li><li><p>而对于对象数据类型，目前则是使用<a href="http://hg.openjdk.java.net/jdk/jdk/file/26ac622a4cab/src/java.base/share/classes/java/util/TimSort.java">TimSort</a>，思想上也是一种归并和二分插入排序（binarySort）结合的优化排序算法。TimSort 并不是 Java 的独创，简单说它的思路是查找数据集中已经排好序的分区（这里叫 run），然后合并这些分区来达到排序的目的。</p></li></ul><p>另外，Java 8 引入了并行排序算法（直接使用 parallelSort 方法），这是为了充分利用现代多核处理器的计算能力，底层实现基于 fork-join 框架（专栏后面会对 fork-join 进行相对详细的介绍），当处理的数据集比较小的时候，差距不明显，甚至还表现差一点；但是，当数据集增长到数万或百万以上时，提高就非常大了，具体还是取决于处理器和系统环境。</p><p>排序算法仍然在不断改进，最近双轴快速排序实现的作者提交了一个更进一步的改进，历时多年的研究，目前正在审核和验证阶段。根据作者的性能测试对比，相比于基于归并排序的实现，新改进可以提高随机数据排序速度提高 10%～20%，甚至在其他特征的数据集上也有几倍的提高，有兴趣的话你可以参考具体代码和介绍：<br><a href="http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-January/051000.html">http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-January/051000.html</a> 。</p><h1 id="14-参考"><a href="#14-参考" class="headerlink" title="14. 参考"></a>14. 参考</h1><p><a href="https://mp.weixin.qq.com/s/ekGdneZrMa23ALxt5mvKpQ">https://mp.weixin.qq.com/s/ekGdneZrMa23ALxt5mvKpQ</a><br><a href="https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg">https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg</a><br><a href="https://www.bilibili.com/video/BV1iJ411E7xW?p=23&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1iJ411E7xW?p=23&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV1B4411H76f?p=76&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1B4411H76f?p=76&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a><br><a href="https://www.bilibili.com/video/BV13g41157hK?p=6&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV13g41157hK?p=6&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p>]]></content>
      
      
      <categories>
          
          <category> 数据结构与算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 排序算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-数据结构-2、链表</title>
      <link href="/2022/09/28/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-2%E3%80%81%E9%93%BE%E8%A1%A8/"/>
      <url>/2022/09/28/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-2%E3%80%81%E9%93%BE%E8%A1%A8/</url>
      
        <content type="html"><![CDATA[<h1 id="1-什么是链表"><a href="#1-什么是链表" class="headerlink" title="1. 什么是链表"></a>1. 什么是链表</h1><p>链表(Linked list)，相较于数组，除了数据域，还增加了<span style="background-color:#00ff00">指针域</span>用于构建链式的存储数据。链表中每一个节点都包含此节点的数据和指向下一节点地址的指针。由于是通过指针进行下一个数据元素的查找和访问，使得链表的自由度更高。</p><p>这表现在对节点进行增加和删除时，只需要对上一节点的指针地址进行修改，而无需变动其它的节点。不过事物皆有两极，指针带来高自由度的同时，自然会牺牲数据查找的效率和多余空间的使用。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929072139.png"></p><h1 id="2-内存模型"><a href="#2-内存模型" class="headerlink" title="2. 内存模型"></a>2. 内存模型</h1><p>数组需要一块<strong>连续的内存空间</strong>来存储，对内存的要求比较高。如果我们申请一个 100MB 大小的数组，当内存中没有连续的、足够大的存储空间时，即便内存的剩余总可用空间大于 100MB，仍然会申请失败。</p><p>而链表恰恰相反，它并不需要一块连续的内存空间，它通过“指针”将一组<strong>零散的内存块</strong>串联起来使用，所以如果我们申请的是 100MB 大小的链表，根本不会有问题。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929073323.png"></p><h1 id="3-链表的分类"><a href="#3-链表的分类" class="headerlink" title="3. 链表的分类"></a>3. 链表的分类</h1><p>链表通过指针将一组零散的内存块串联在一起。其中，我们把内存块称为链表的“<strong>结点</strong>”。为了将所有的结点串起来，每个链表的结点除了存储数据之外，还需要记录链上的下一个结点的地址。如图所示，我们把这个记录下个结点地址的指针叫作<strong>后继指针 next</strong>。</p><h2 id="3-1-单链表"><a href="#3-1-单链表" class="headerlink" title="3.1. 单链表"></a>3.1. 单链表</h2><p>应该可以发现，其中有两个结点是比较特殊的，它们分别是第一个结点和最后一个结点。我们习惯性地把第一个结点叫作<strong>头结点</strong>，把最后一个结点叫作<strong>尾结点</strong>。其中，头结点用来记录链表的基地址。有了它，我们就可以遍历得到整条链表。而尾结点特殊的地方是：指针不是指向下一个结点，而是指向一个<strong>空地址 NULL</strong>，表示这是链表上最后一个结点。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929073601.png"></p><h2 id="3-2-双向链表"><a href="#3-2-双向链表" class="headerlink" title="3.2. 双向链表"></a>3.2. 双向链表</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929073617.png"></p><p>从我画的图中可以看出来，双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以，如果存储同样多的数据，双向链表要比单链表占用更多的内存空间。虽然两个指针比较浪费存储空间，但可以支持双向遍历，这样也带来了双向链表操作的灵活性。</p><p>从结构上来看，<span style="background-color:#00ff00">双向链表可以支持 O(1) 时间复杂度的情况下找到前驱结点</span>，正是这样的特点，也使双向链表在某些情况下的插入、删除等操作都要比单链表简单、高效。</p><h3 id="3-2-1-双向链表高效分析"><a href="#3-2-1-双向链表高效分析" class="headerlink" title="3.2.1. 双向链表高效分析"></a>3.2.1. 双向链表高效分析</h3><h4 id="3-2-1-1-插入删除操作"><a href="#3-2-1-1-插入删除操作" class="headerlink" title="3.2.1.1. 插入删除操作"></a>3.2.1.1. 插入删除操作</h4><p>在实际的软件开发中，从链表中删除一个数据无外乎这两种情况：</p><ul><li>删除结点中“值等于某个给定值”的结点；</li><li>删除给定指针指向的结点。</li></ul><p>对于第一种情况，不管是单链表还是双向链表，为了查找到值等于给定值的结点，<span style="background-color:#ffff00">都需要从头结点开始一个一个依次遍历对比，直到找到值等于给定值的结点</span>，然后再通过我前面讲的指针操作将其删除。</p><p>尽管单纯的删除操作时间复杂度是 O(1)，但遍历查找的时间是主要的耗时点，对应的时间复杂度为 O(n)。根据时间复杂度分析中的加法法则，删除值等于给定值的结点对应的链表操作的总时间复杂度为 O(n)。</p><p>对于第二种情况，我们已经找到了要删除的结点，但是<font color=#ff0000><span style="background-color:#ffff00">删除某个结点 q 需要知道其前驱结点</span></font>，而单链表并不支持直接获取前驱结点，所以，为了找到前驱结点，<span style="background-color:#ffff00">单链表还是要从头结点开始遍历链表</span>，直到 p-&gt;next&#x3D;q，说明 p 是 q 的前驱结点。</p><p>但是对于双向链表来说，这种情况就比较有优势了。因为双向链表中的结点已经保存了前驱结点的指针，不需要像单链表那样遍历。所以，针对第二种情况，<span style="background-color:#00ff00">单链表删除操作需要 O(n) 的时间复杂度，而双向链表只需要在 O(1) 的时间复杂度内就搞定了</span>！同理，如果我们希望在链表的某个指定结点前面插入一个结点，双向链表比单链表有很大的优势。双向链表可以在 O(1) 时间复杂度搞定，而单向链表需要 O(n) 的时间复杂度。</p><h4 id="3-2-1-2-查询操作"><a href="#3-2-1-2-查询操作" class="headerlink" title="3.2.1.2. 查询操作"></a>3.2.1.2. 查询操作</h4><p>除了插入、删除操作有优势之外，对于一个<span style="background-color:#ffff00">有序链表</span>，双向链表的按值查询的效率也要比单链表高一些。因为，我们可以记录上次查找的位置 p，每次查询时，<span style="background-color:#00ff00">根据要查找的值与 p 的大小关系，决定是往前还是往后查找，所以平均只需要查找一半的数据</span>。</p><p>现在，你有没有觉得双向链表要比单链表更加高效呢？这就是为什么在实际的软件开发中，双向链表尽管比较费内存，但还是比单链表的应用更加广泛的原因。如果你熟悉 Java 语言，你肯定用过 LinkedHashMap 这个容器。如果你深入研究 LinkedHashMap 的实现原理，就会发现其中就用到了双向链表这种数据结构。</p><h4 id="3-2-1-3-底层思想"><a href="#3-2-1-3-底层思想" class="headerlink" title="3.2.1.3. 底层思想"></a>3.2.1.3. 底层思想</h4><p>双向链表因为要记录前后节点地址，消耗了更多的内存空间，但查询、插入、删除的效率却提高了不少。这就是典型的<strong>用空间换时间</strong>的设计思想。<br>当内存空间充足的时候，如果我们更加追求代码的执行速度，我们就可以选择空间复杂度相对较高、但时间复杂度相对很低的算法或者数据结构。相反，如果内存比较紧缺，比如代码跑在手机或者单片机上，这个时候，就要反过来用时间换空间的设计思路。</p><h2 id="3-3-循环链表"><a href="#3-3-循环链表" class="headerlink" title="3.3. 循环链表"></a>3.3. 循环链表</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929073612.png"><br><strong>循环链表是一种特殊的单链表</strong>。实际上，循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道，单链表的尾结点指针指向空地址，表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中，你应该可以看出来，它像一个环一样首尾相连，所以叫作“循环”链表。<br>和单链表相比，<strong>循环链表</strong>的优点是从链尾到链头比较方便。当要处理的数据具有环型结构特点时，就特别适合采用循环链表。比如著名的<a href="https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98">约瑟夫问题</a>。尽管用单链表也可以实现，但是用循环链表实现的话，代码就会简洁很多。</p><h1 id="4-时间复杂度"><a href="#4-时间复杂度" class="headerlink" title="4. 时间复杂度"></a>4. 时间复杂度</h1><p>在进行数组的插入、删除操作时，<span style="background-color:#ffff00">为了保持内存数据的连续性</span><span style="background-color:#ffff00">，需要做大量的数据搬移</span>，所以时间复杂度是 O(n)。而在链表中插入或者删除一个数据，我们并不需要为了保持内存的连续性而搬移结点，因为链表的存储空间本身就不是连续的。所以，在链表中插入和删除一个数据是非常快速的。<br>从图中我们可以看出，针对链表的插入和删除操作，我们只需要考虑相邻结点的指针改变，所以对应的时间复杂度是 O(1)。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929073726.png"></p><p>但是，有利就有弊。链表要想随机访问第 k 个元素，就没有数组那么高效了。因为链表中的数据并非连续存储的，所以无法像数组那样，根据首地址和下标，通过寻址公式就能直接计算出对应的内存地址，而是需要根据指针一个结点一个结点地依次遍历，直到找到相应的结点。</p><p>你可以把链表想象成一个队伍，队伍中的每个人都只知道自己后面的人是谁，所以当我们希望知道排在第 k 位的人是谁的时候，我们就需要从第一个人开始，一个一个地往下数。所以，<span style="background-color:#ffff00">链表随机访问的性能没有数组好，需要 O(n) 的时间复杂度</span>。</p><h1 id="5-链表VS数组"><a href="#5-链表VS数组" class="headerlink" title="5. 链表VS数组"></a>5. 链表VS数组</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929075241.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929075324.png"></p><p>数组简单易用，在实现上使用的是连续的内存空间，<span style="background-color:#00ff00">可以借助 CPU 的缓存机制，预读数组中的数据，所以访问效率更高</span>。而链表在内存中并不是连续存储，所以对 CPU 缓存不友好，没办法有效预读。</p><p>数组的缺点是大小固定，一经声明就要占用整块连续内存空间。如果声明的数组过大，系统可能没有足够的连续内存空间分配给它，导致“内存不足（out of memory）”。如果声明的数组过小，则可能出现不够用的情况。这时只能再申请一个更大的内存空间，把原数组拷贝进去，非常费时。链表本身没有大小的限制，天然地支持动态扩容，我觉得这也是它与数组最大的区别。</p><p>除此之外，如果你的代码对内存的使用非常苛刻，那数组就更适合你。因为链表中的每个结点都需要消耗额外的存储空间去存储一份指向下一个结点的指针，所以内存消耗会翻倍。而且，对链表进行频繁的插入、删除操作，还会导致频繁的内存申请和释放，容易造成内存碎片，如果是 Java 语言，就有可能会导致频繁的 GC（Garbage Collection，垃圾回收）。</p><h1 id="6-实现LRU"><a href="#6-实现LRU" class="headerlink" title="6. 实现LRU"></a>6. 实现LRU</h1><a href="/2022/09/28/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-4%E3%80%81%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0LRU%E7%BC%93%E5%AD%98/" title="数据结构-4、链表实现LRU缓存">数据结构-4、链表实现LRU缓存</a><h1 id="7-参考"><a href="#7-参考" class="headerlink" title="7. 参考"></a>7. 参考</h1><p>极客时间<br><a href="https://mp.weixin.qq.com/s/ekGdneZrMa23ALxt5mvKpQ">https://mp.weixin.qq.com/s/ekGdneZrMa23ALxt5mvKpQ</a></p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 数据结构 </tag>
            
            <tag> 链表 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-数据结构-4、链表实现LRU缓存</title>
      <link href="/2022/09/28/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-4%E3%80%81%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0LRU%E7%BC%93%E5%AD%98/"/>
      <url>/2022/09/28/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-4%E3%80%81%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0LRU%E7%BC%93%E5%AD%98/</url>
      
        <content type="html"><![CDATA[]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 数据结构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Leetcode-1、链表-反转链表</title>
      <link href="/2022/09/28/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/Leetcode-1%E3%80%81%E9%93%BE%E8%A1%A8-%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8/"/>
      <url>/2022/09/28/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/Leetcode-1%E3%80%81%E9%93%BE%E8%A1%A8-%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8/</url>
      
        <content type="html"><![CDATA[<h1 id="1-迭代方法"><a href="#1-迭代方法" class="headerlink" title="1. 迭代方法"></a>1. 迭代方法</h1><h2 id="1-1-代码实现"><a href="#1-1-代码实现" class="headerlink" title="1.1. 代码实现"></a>1.1. 代码实现</h2><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">static</span> ListNode <span class="hljs-title function_">iterate</span><span class="hljs-params">(ListNode head)</span> &#123;  <br>    <span class="hljs-type">ListNode</span> <span class="hljs-variable">prev</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>, curr, next;  <br>    curr = head;  <br>    <span class="hljs-keyword">while</span> (curr != <span class="hljs-literal">null</span>) &#123;  <br>        next = curr.next;  <br>        curr.next = prev;  <br>        prev = curr;  <br>        curr = next;  <br>    &#125;  <br>    <span class="hljs-keyword">return</span> prev;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="1-2-理解要点"><a href="#1-2-理解要点" class="headerlink" title="1.2. 理解要点"></a>1.2. 理解要点</h2><h3 id="1-2-1-初始化变量"><a href="#1-2-1-初始化变量" class="headerlink" title="1.2.1. 初始化变量"></a>1.2.1. 初始化变量</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929225502.png"></p><h3 id="1-2-2-移动指针"><a href="#1-2-2-移动指针" class="headerlink" title="1.2.2. 移动指针"></a>1.2.2. 移动指针</h3><p>处理下一个节点：<br>prev移动到curr位置：<code>prev = curr</code><br>curr移动到下一个节点位置：<code>curr = next</code><br>然后继续，暂存指针<code>next = curr.next</code>，改变指针<code>curr.next = prev</code></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929230851.gif"></p><h1 id="2-递归方法"><a href="#2-递归方法" class="headerlink" title="2. 递归方法"></a>2. 递归方法</h1><h2 id="2-1-代码实现"><a href="#2-1-代码实现" class="headerlink" title="2.1. 代码实现"></a>2.1. 代码实现</h2><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ListNode <span class="hljs-title function_">recursion</span><span class="hljs-params">(ListNode head)</span> &#123;  <br>    <span class="hljs-keyword">if</span> (head == <span class="hljs-literal">null</span> || head.next == <span class="hljs-literal">null</span>) &#123;  <br>        <span class="hljs-keyword">return</span> head;  <br>    &#125;  <br>    <span class="hljs-type">ListNode</span> <span class="hljs-variable">newHead</span> <span class="hljs-operator">=</span> recursion(head.next);  <br>    head.next.next = head;  <br>    head.next = <span class="hljs-literal">null</span>;  <br>    <span class="hljs-keyword">return</span> newHead;  <br>&#125;<br></code></pre></td></tr></table></figure><h2 id="2-2-理解要点"><a href="#2-2-理解要点" class="headerlink" title="2.2. 理解要点"></a>2.2. 理解要点</h2><ol><li><span style="background-color:#00ff00">newHead只是为了得到反转后的头节点。</span>当递归第<font color=#ff0000>⑤</font>次，来到最后一个节点时，return到该节点并赋值给newHead，后面递归出栈后不再参与计算。</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929201731.png"></p><ol start="2"><li>在递归开始<span style="background-color:#00ff00">归</span>的时候，被调用方法<font color=#ff0000>⑤</font>出栈后，回到调用方执行点入口，当时recursion的参数（head.next）中的head为节点4，在执行了<code>head.next.next = head</code>后，节点5的next指向了节点4；在执行了<code>head.next = null</code>后，节点4的next指向了null</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220929202029.png"></p><p>走完37、38行，就把节点4反转成功了，如下图所示。依次类推，一直倒到递归入口处，返回反转后的newHead，即节点5<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221010194021.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221010194112.png"></p><h2 id="2-3-单个节点反转"><a href="#2-3-单个节点反转" class="headerlink" title="2.3. 单个节点反转"></a>2.3. 单个节点反转</h2><p>示意图<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221010192323.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221010192642.gif"></p><h1 id="3-参考"><a href="#3-参考" class="headerlink" title="3. 参考"></a>3. 参考</h1><h2 id="3-1-抖码课堂"><a href="#3-1-抖码课堂" class="headerlink" title="3.1. 抖码课堂"></a>3.1. 抖码课堂</h2><p><a href="https://www.bilibili.com/video/BV1qL411M7iB/?p=39&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1qL411M7iB/?p=39&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><h2 id="3-2-吴师兄学算法"><a href="#3-2-吴师兄学算法" class="headerlink" title="3.2. 吴师兄学算法"></a>3.2. 吴师兄学算法</h2><p><a href="https://blog.algomooc.com/024.html#%E4%BA%8C%E3%80%81%E9%A2%98%E7%9B%AE%E8%A7%A3%E6%9E%90">https://blog.algomooc.com/024.html#%E4%BA%8C%E3%80%81%E9%A2%98%E7%9B%AE%E8%A7%A3%E6%9E%90</a></p>]]></content>
      
      
      <categories>
          
          <category> Leetcode </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Leetcode-链表 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-数据结构-1、数组</title>
      <link href="/2022/09/27/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-1%E3%80%81%E6%95%B0%E7%BB%84/"/>
      <url>/2022/09/27/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-1%E3%80%81%E6%95%B0%E7%BB%84/</url>
      
        <content type="html"><![CDATA[<h1 id="1-什么是数组"><a href="#1-什么是数组" class="headerlink" title="1. 什么是数组"></a>1. 什么是数组</h1><p><strong>数组（Array）是一种线性表数据结构。它用一组连续的内存空间，来存储一组具有相同类型的数据。</strong></p><h2 id="1-1-线性表"><a href="#1-1-线性表" class="headerlink" title="1.1. 线性表"></a>1.1. 线性表</h2><p>顾名思义，线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组，链表、队列、栈等也是线性表结构。<br>另外，在计算机内存中以 <strong>数组</strong> 的形式保存的线性表，称为顺序表。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928090434.png"></p><p>而与它相对立的概念是 <strong>非线性表</strong>，比如二叉树、堆、图等。之所以叫非线性，是因为，在非线性表中，数据之间并不是简单的前后关系。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928090514.png"></p><h2 id="1-2-连续-相同"><a href="#1-2-连续-相同" class="headerlink" title="1.2. 连续 - 相同"></a>1.2. 连续 - 相同</h2><p><strong>连续的内存空间和相同类型的数据</strong></p><h2 id="1-3-数组的使用"><a href="#1-3-数组的使用" class="headerlink" title="1.3. 数组的使用"></a>1.3. 数组的使用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928092128.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928092242.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928092329.png"></p><h1 id="2-随机访问"><a href="#2-随机访问" class="headerlink" title="2. 随机访问"></a>2. 随机访问</h1><p>我们拿一个长度为 10 的 int 类型的数组 int[] a &#x3D; new int[10] 来举例。在我画的这个图中，计算机给数组 a[10]，分配了一块连续内存空间 1000～1039，其中，内存块的首地址为 base_address &#x3D; 1000。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928090716.png"><br>我们知道，计算机会给每个内存单元分配一个地址，计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时，它会首先通过下面的寻址公式，计算出该元素存储的内存地址：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">a[i]_address = base_address + i * data_type_size<br></code></pre></td></tr></table></figure><p>其中 data_type_size 表示数组中每个元素的大小。我们举的这个例子里，数组中存储的是 int 类型数据，所以 data_type_size 就为 4 个字节。</p><p>这里我要特别纠正一个“错误”。我在面试的时候，常常会问数组和链表的区别，很多人都回答说，“链表适合插入、删除，时间复杂度 O(1)；数组适合查找，查找时间复杂度为 O(1)”。</p><p>实际上，这种表述是不准确的。数组是适合查找操作，但是查找的时间复杂度并不为 O(1)。即便是排好序的数组，你用二分查找，时间复杂度也是 O(logn)。所以，<span style="background-color:#00ff00">正确的表述应该是，数组支持随机访问，根据下标随机访问的时间复杂度为 O(1)</span>。</p><h1 id="3-低效的插入和删除"><a href="#3-低效的插入和删除" class="headerlink" title="3. 低效的插入和删除"></a>3. 低效的插入和删除</h1><p>假设数组的长度为 n，现在，如果我们需要将一个数据插入到数组中的第 k 个位置。为了把第 k 个位置腾出来，给新来的数据，我们需要将第 k～n 这部分的元素都顺序地往后挪一位。那插入操作的时间复杂度是多少呢？你可以自己先试着分析一下。</p><p>如果在数组的末尾插入元素，那就不需要移动数据了，这时的时间复杂度为 O(1)。但如果在数组的开头插入元素，那所有的数据都需要依次往后移动一位，所以最坏时间复杂度是 O(n)。 因为我们在每个位置插入元素的概率是一样的，所以平均情况时间复杂度为 (1+2+…n)&#x2F;n&#x3D;O(n)。</p><p>如果数组中的数据是有序的，我们在某个位置插入一个新的元素时，就必须按照刚才的方法搬移 k 之后的数据。但是，<span style="background-color:#ffff00">如果数组中存储的数据并没有任何规律，数组只是被当作一个存储数据的集合。在这种情况下，</span>如果要将某个数组插入到第 k 个位置，为了避免大规模的数据搬移，我们还有一个简单的办法就是，直接将第 k 位的数据搬移到数组元素的最后，把新的元素直接放入第 k 个位置。</p><p>为了更好地理解，我们举一个例子。假设数组 a[10] 中存储了如下 5 个元素：a，b，c，d，e。</p><p>我们现在需要将元素 x 插入到第 3 个位置。我们只需要将 c 放入到 a[5]，将 a[2] 赋值为 x 即可。最后，数组中的元素如下： a，b，x，d，e，c。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928091016.png"></p><p>利用这种处理技巧，在特定场景下，在第 k 个位置插入一个元素的时间复杂度就会降为 O(1)。这个处理思想在快排中也会用到，我会在排序那一节具体来讲，这里就说到这儿。</p><p>跟插入数据类似，如果我们要删除第 k 个位置的数据，为了内存的连续性，也需要搬移数据，不然中间就会出现空洞，内存就不连续了。</p><p>和插入类似，如果删除数组末尾的数据，则最好情况时间复杂度为 O(1)；如果删除开头的数据，则最坏情况时间复杂度为 O(n)；平均情况时间复杂度也为 O(n)。</p><p>实际上，在某些特殊场景下，<span style="background-color:#ffff00">我们并不一定非得追求数组中数据的连续性</span>。如果我们将多次删除操作集中在一起执行，删除的效率是不是会提高很多呢？</p><p>我们继续来看例子。数组 a[10] 中存储了 8 个元素：a，b，c，d，e，f，g，h。现在，我们要依次删除 a，b，c 三个元素。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220928091126.png"><br>为了避免 d，e，f，g，h 这几个数据会被搬移三次，我们可以先记录下已经删除的数据。每次的删除操作并不是真正地搬移数据，只是记录数据已经被删除。当数组没有更多空间存储数据时，我们再触发执行一次真正的删除操作，这样就大大减少了删除操作导致的数据搬移。</p><p>如果你了解 JVM，你会发现，这不就是 JVM 标记清除垃圾回收算法的核心思想吗？</p><h1 id="4-容器和数组的选择"><a href="#4-容器和数组的选择" class="headerlink" title="4. 容器和数组的选择"></a>4. 容器和数组的选择</h1><p>ArrayList 最大的优势就是 <strong>可以将很多数组操作的细节封装起来</strong>。比如前面提到的数组插入、删除数据时需要搬移其他数据等。另外，它还有一个优势，就是 <strong>支持动态扩容</strong>。</p><p>数组本身在定义的时候需要预先指定大小，因为需要分配连续的内存空间。如果我们申请了大小为 10 的数组，当第 11 个数据需要存储到数组中时，我们就需要重新分配一块更大的空间，将原来的数据复制过去，然后再将新的数据插入。</p><p>如果使用 ArrayList，我们就完全不需要关心底层的扩容逻辑，ArrayList 已经帮我们实现好了。每次存储空间不够的时候，它都会将空间自动扩容为 1.5 倍大小。</p><p>不过，这里需要注意一点，<span style="background-color:#00ff00">因为扩容操作涉及内存申请和数据搬移，是比较耗时的。所以，如果事先能确定需要存储的数据大小，最好 <strong>在创建 ArrayList 的时候事先指定数据大小</strong>。</span></p><p>比如我们要从数据库中取出 10000 条数据放入 ArrayList。我们看下面这几行代码，你会发现，相比之下，事先指定数据大小可以省掉很多次内存申请和数据搬移操作。</p><figure class="highlight plaintext"><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 plaintext">ArrayList&lt;User&gt; users = new ArrayList(10000);<br>for (int i = 0; i &lt; 10000; ++i) &#123;<br>  users.add(xxx);<br>&#125;<br></code></pre></td></tr></table></figure><p>作为高级语言编程者，是不是数组就无用武之地了呢？当然不是，有些时候，用数组会更合适些，我总结了几点自己的经验。</p><ol><li>Java ArrayList 无法存储基本类型，比如 int、long，需要封装为 Integer、Long 类，而 Autoboxing、Unboxing 则有一定的性能消耗，所以如果特别关注性能，或者希望使用基本类型，就可以选用数组。</li><li>如果数据大小事先已知，并且对数据的操作非常简单，用不到 ArrayList 提供的大部分方法，也可以直接使用数组。</li><li>还有一个是我个人的喜好，当要表示多维数组时，用数组往往会更加直观。比如 <code>Object[][] array</code>；而用容器的话则需要这样定义：<code>ArrayList&lt;ArrayList &gt; array</code>。</li></ol><p>我总结一下，对于业务开发，直接使用容器就足够了，省时省力。毕竟损耗一丢丢性能，完全不会影响到系统整体的性能。但如果你是做一些非常底层的开发，比如开发网络框架，性能的优化需要做到极致，这个时候数组就会优于容器，成为首选。</p><h1 id="5-下标从-0-开始"><a href="#5-下标从-0-开始" class="headerlink" title="5. 下标从 0 开始"></a>5. 下标从 0 开始</h1><p>现在我们来思考开篇的问题：为什么大多数编程语言中，数组要从 0 开始编号，而不是从 1 开始呢？</p><p>从数组存储的内存模型上来看，“下标”最确切的定义应该是“偏移（offset）”。前面也讲到，如果用 a 来表示数组的首地址，<code>a[0]</code> 就是偏移为 0 的位置，也就是首地址，<code>a[k]</code> 就表示偏移 k 个 type_size 的位置，所以计算 <code>a[k]</code> 的内存地址只需要用这个公式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">a[k]_address = base_address + k * type_size<br></code></pre></td></tr></table></figure><p>但是，如果数组从 1 开始计数，那我们计算数组元素 <code>a[k]</code> 的内存地址就会变为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">a[k]_address = base_address + (k-1)*type_size<br></code></pre></td></tr></table></figure><p>对比两个公式，我们不难发现，从 1 开始编号，每次随机访问数组元素都多了一次减法运算，对于 CPU 来说，就是多了一次减法指令。</p><p>数组作为非常基础的数据结构，通过下标随机访问数组元素又是其非常基础的编程操作，效率的优化就要尽可能做到极致。所以为了减少一次减法操作，数组选择了从 0 开始编号，而不是从 1 开始。</p><p>不过我认为，上面解释得再多其实都算不上压倒性的证明，说数组起始编号非 0 开始不可。所以我觉得最主要的原因可能是历史原因。</p><p>C 语言设计者用 0 开始计数数组下标，之后的 Java、JavaScript 等高级语言都效仿了 C 语言，或者说，为了在一定程度上减少 C 语言程序员学习 Java 的学习成本，因此继续沿用了从 0 开始计数的习惯。实际上，很多语言中数组也并不是从 0 开始计数的，比如 Matlab。甚至还有一些语言支持负数下标，比如 Python。</p><h1 id="6-参考"><a href="#6-参考" class="headerlink" title="6. 参考"></a>6. 参考</h1><p>极客时间</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 数据结构 </tag>
            
            <tag> 数组 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java基础-数据结构-3、数组实现循环队列</title>
      <link href="/2022/09/26/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-3%E3%80%81%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97/"/>
      <url>/2022/09/26/001-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-3%E3%80%81%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97/</url>
      
        <content type="html"><![CDATA[<h1 id="1-队列"><a href="#1-队列" class="headerlink" title="1. 队列"></a>1. 队列</h1><p>队列：是一个 <strong>有序列表</strong>，可以用 <strong>数组</strong> 或 <strong>链表</strong> 实现。<br>特点：遵循 <strong>先入先出</strong> 原则。队尾入队，队头出队。即：先存入的数据，先取出。</p><h1 id="2-假溢出"><a href="#2-假溢出" class="headerlink" title="2. 假溢出"></a>2. 假溢出</h1><p>系统作为队列用的存储区还没有满,但队列却发生了溢出,我们把这种现象称为”假溢出”。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220927171630.png"><br>因为队列遵从从<span style="background-color:#ffff00">队尾</span>存入数据，从<span style="background-color:#ffff00">队头</span>取数据，所以红框部分的空间就不能继续存入新的数据，此时队列有多余的空间，却不能存入值，这种现象就叫做假溢出现象。数组中元素已出队位置需要循环利用。</p><h1 id="3-如何循环"><a href="#3-如何循环" class="headerlink" title="3. 如何循环"></a>3. 如何循环</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220927171456.png"></p><p>rear 指向队列尾的实际位置的<span style="background-color:#00ff00">后一个位置</span>。<code>并且rear和front的初始值均为0</code><br><strong>而且默认希望空出一个空间不能存放数据，是因为如果不空闲空间，那么队列满和队列为空的条件会发生表意冲突(如上图中图a和图d1，rear和front在初始和队满时都会同时处在一个位置)，为了区分，rear指向最后一个元素的后一个位置，利用取模巧妙表达队满时的位置状态。因此队列满的条件应该如下：</strong><br>队列满： <code>(rear + 1) % maxSize == front</code><br>队列空： <code>rear == front</code><br>队列中有效数据的个数是：<code>( rear + maxSize - front ) % maxSize</code></p><p>该算法取巧的地方在于 <strong>rear 的位置</strong>，注意看上图，rear 所在的位置 **<span style="background-color:#00ff00">永远是空的</span>**，实现环形队列的算法也有多种，这里空出来一个位置，是这里算法的核心</p><h1 id="4-代码实现"><a href="#4-代码实现" class="headerlink" title="4. 代码实现"></a>4. 代码实现</h1><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.Scanner;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 数组拟环形队列</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CircleQueueDemo</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">CircleQueue</span> <span class="hljs-variable">queue</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CircleQueue</span>(<span class="hljs-number">3</span>);<br><br>        <span class="hljs-comment">// 为了测试方便，写一个控制台输入的小程序</span><br>        <span class="hljs-type">Scanner</span> <span class="hljs-variable">scanner</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Scanner</span>(System.in);<br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">loop</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br>        <span class="hljs-type">char</span> <span class="hljs-variable">key</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27; &#x27;</span>; <span class="hljs-comment">// 接受用户输入指令</span><br>        System.out.println(<span class="hljs-string">&quot;s(show): 显示队列&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;e(exit): 退出程序&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;a(add): 添加数据到队列&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;g(get): 从队列取出数据&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;h(head): 查看队列头的数据&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;t(tail): 查看队列尾的数据&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;p(isEmpty): 队列是否为空&quot;</span>);<br>        <span class="hljs-keyword">while</span> (loop) &#123;<br>            key = scanner.next().charAt(<span class="hljs-number">0</span>);<br>            <span class="hljs-keyword">switch</span> (key) &#123;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;s&#x27;</span>:<br>                    queue.show();<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;e&#x27;</span>:<br>                    loop = <span class="hljs-literal">false</span>;<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;a&#x27;</span>:<br>                    System.out.println(<span class="hljs-string">&quot;请输入要添加到队列的整数：&quot;</span>);<br>                    <span class="hljs-type">int</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> scanner.nextInt();<br>                    queue.add(value);<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;g&#x27;</span>:<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        <span class="hljs-type">int</span> <span class="hljs-variable">res</span> <span class="hljs-operator">=</span> queue.get();<br>                        System.out.printf(<span class="hljs-string">&quot;取出的数据是：%d\n&quot;</span>, res);<br>                    &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>                        System.out.println(e.getMessage());<br>                    &#125;<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;h&#x27;</span>:<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        <span class="hljs-type">int</span> <span class="hljs-variable">res</span> <span class="hljs-operator">=</span> queue.head();<br>                        System.out.printf(<span class="hljs-string">&quot;队首数据：%d\n&quot;</span>, res);<br>                    &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>                        System.out.println(e.getMessage());<br>                    &#125;<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;t&#x27;</span>:<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        <span class="hljs-type">int</span> <span class="hljs-variable">res</span> <span class="hljs-operator">=</span> queue.tail();<br>                        System.out.printf(<span class="hljs-string">&quot;队尾数据：%d\n&quot;</span>, res);<br>                    &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>                        System.out.println(e.getMessage());<br>                    &#125;<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;p&#x27;</span>:<br>                    System.out.printf(<span class="hljs-string">&quot;队列是否为空：%s&quot;</span>, queue.isEmpty());<br>                    <span class="hljs-keyword">break</span>;<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CircleQueue</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> maxSize; <span class="hljs-comment">// 队列最大容量</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> front; <span class="hljs-comment">// 队列头,指向 队头 的元素</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> rear; <span class="hljs-comment">// 队列尾，指向 队尾 的下一个元素</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> arr[]; <span class="hljs-comment">// 用于存储数据，模拟队列</span><br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CircleQueue</span><span class="hljs-params">(<span class="hljs-type">int</span> arrMaxSize)</span> &#123;<br>        maxSize = arrMaxSize + <span class="hljs-number">1</span>;<br>        arr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[maxSize];<br>        front = <span class="hljs-number">0</span>;<br>        rear = <span class="hljs-number">0</span>;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 取出队列数据</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">get</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (isEmpty()) &#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;队列空&quot;</span>);<br>        &#125;<br>        <span class="hljs-comment">// front 指向的是队首的位置</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> arr[front];<br>        <span class="hljs-comment">// 需要向后移动，但是由于是环形，同样需要使用取模的方式来计算</span><br>        front = (front + <span class="hljs-number">1</span>) % maxSize;<br>        <span class="hljs-keyword">return</span> value;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 往队列存储数据</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">add</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span> &#123;<br>        <span class="hljs-keyword">if</span> (isFull()) &#123;<br>            System.out.println(<span class="hljs-string">&quot;队列已满&quot;</span>);<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        arr[rear] = n;<br>        <span class="hljs-comment">// rear 指向的是下一个位置</span><br>        <span class="hljs-comment">// 由于是环形队列,需要使用取模的形式来唤醒他的下一个位置</span><br>        rear = (rear + <span class="hljs-number">1</span>) % maxSize;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 显示队列中的数据</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (isEmpty()) &#123;<br>            System.out.println(<span class="hljs-string">&quot;队列为空&quot;</span>);<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        <span class="hljs-comment">// 打印的时候，需要从队首开始打印</span><br>        <span class="hljs-comment">// 打印的次数则是：有效的元素个数</span><br>        <span class="hljs-comment">// 获取数据的下标：由于是环形的，需要使用取模的方式来获取</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> front; i &lt; front + size(); i++) &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">index</span> <span class="hljs-operator">=</span> i % maxSize;<br>            System.out.printf(<span class="hljs-string">&quot;arr[%d] = %d \n&quot;</span>, index, arr[index]);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 查看队列的头部数据，注意：不是取出数据，只是查看</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">head</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (isEmpty()) &#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;队列空&quot;</span>);<br>        &#125;<br>        <span class="hljs-keyword">return</span> arr[front];<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 查看队尾数据</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">tail</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">if</span> (isEmpty()) &#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;队列空&quot;</span>);<br>        &#125;<br>        <span class="hljs-comment">// rear - 1 是队尾数据，但是如果是环形收尾相接的时候</span><br>        <span class="hljs-comment">// 那么 0 -1 就是 -1 了，负数时，则是数组的最后一个元素</span><br>        <span class="hljs-keyword">return</span> rear - <span class="hljs-number">1</span> &lt; <span class="hljs-number">0</span> ? arr[maxSize - <span class="hljs-number">1</span>] : arr[rear - <span class="hljs-number">1</span>];<br>    &#125;<br><br>    <span class="hljs-comment">// 队列是否已满</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isFull</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> (rear + <span class="hljs-number">1</span>) % maxSize == front;<br>    &#125;<br><br>    <span class="hljs-comment">// 队列是否为空</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEmpty</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> rear == front;<br>    &#125;<br><br>    <span class="hljs-comment">// 有效个数</span><br>    <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-keyword">return</span> (rear + maxSize - front) % maxSize;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="5-size大小计算"><a href="#5-size大小计算" class="headerlink" title="5. size大小计算"></a>5. size大小计算</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221010182732.png"></p><p>原图地址：<a href="https://www.processon.com/view/6332980c07912955b209c0d4?fromnew=1">https://www.processon.com/view/6332980c07912955b209c0d4?fromnew=1</a></p><h1 id="6-参考"><a href="#6-参考" class="headerlink" title="6. 参考"></a>6. 参考</h1><p><a href="https://www.cnblogs.com/yxm2020/p/12753564.html">https://www.cnblogs.com/yxm2020/p/12753564.html</a><br><a href="https://zq99299.github.io/dsalg-tutorial/dsalg-java-hsp/03/02.html#%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0">https://zq99299.github.io/dsalg-tutorial/dsalg-java-hsp/03/02.html#%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0</a></p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 循环队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据结构与算法-1、查找算法-BF-RK-BM-KMP</title>
      <link href="/2022/09/24/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%AE%97%E6%B3%95-1%E3%80%81%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95-BF-RK-BM-KMP/"/>
      <url>/2022/09/24/009-%E5%86%85%E5%8A%9F%E5%BF%83%E6%B3%95%E4%B8%93%E9%A2%98/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%AE%97%E6%B3%95-1%E3%80%81%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95-BF-RK-BM-KMP/</url>
      
        <content type="html"><![CDATA[<h1 id="1-BF-Brute-Force"><a href="#1-BF-Brute-Force" class="headerlink" title="1. BF(Brute-Force)"></a>1. BF(Brute-Force)</h1><h2 id="1-1-算法思想"><a href="#1-1-算法思想" class="headerlink" title="1.1. 算法思想"></a>1.1. 算法思想</h2><p>BF 算法中的 BF 是 Brute Force 的缩写，中文叫作暴力匹配算法，也叫朴素匹配算法。从名字可以看出，这种算法的字符串匹配方式很“暴力”，当然也就会比较简单、好懂，但相应的性能也不高。　</p><p>作为最简单、最暴力的字符串匹配算法，BF 算法的思想可以用一句话来概括，那就是，<strong>我们在主串中，检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串，看有没有跟模式串匹配的</strong>。我举一个例子给你看看，你应该可以理解得更清楚。</p><h2 id="1-2-代码实现"><a href="#1-2-代码实现" class="headerlink" title="1.2. 代码实现"></a>1.2. 代码实现</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925082930.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925083359.png"></p><p><span style="background-color:#ffff00">i-j+2可以理解为i-(j-1)+1，(j-1)表示失配前主串移动了几步，主串要回溯这一部分，然后再加1步。</span>比如上图主串从i&#x3D;3开始，子串从j&#x3D;1开始，走到了j&#x3D;5的时候发生了失配，主串要回溯(j-1)即(5-1)步之后，再从后面1步开始比对。而子串回溯到开头。</p><h2 id="1-3-时间复杂度"><a href="#1-3-时间复杂度" class="headerlink" title="1.3. 时间复杂度"></a>1.3. 时间复杂度</h2><p>从上面的算法思想和例子，我们可以看出，在极端情况下，比如主串是“aaaaa…aaaaaa”（省略号表示有很多重复的字符 a），模式串是“aaaaab”。我们每次都比对 m 个字符，要比对 n-m+1 次，所以，这种算法的最坏情况时间复杂度是 <code>O(n*m)</code>。</p><h1 id="2-RK算法"><a href="#2-RK算法" class="headerlink" title="2. RK算法"></a>2. RK算法</h1><h2 id="2-1-算法思想"><a href="#2-1-算法思想" class="headerlink" title="2.1. 算法思想"></a>2.1. 算法思想</h2><p>RK 算法的全称叫 Rabin-Karp 算法，是由它的两位发明者 Rabin 和 Karp 的名字来命名的。它其实就是刚刚讲的 BF 算法的升级版。通过哈希算法对主串中的 <code>n-m+1</code> 个<strong>子串</strong>分别求<strong>哈希值</strong>，然后逐个与<code>模式串的哈希值</code>比较大小，来降低 <code>BF</code> 算法的时间复杂度。</p><h2 id="2-2-时间复杂度"><a href="#2-2-时间复杂度" class="headerlink" title="2.2. 时间复杂度"></a>2.2. 时间复杂度</h2><p>整个 <code>RK</code> 算法包含两部分，计算 <code>子串哈希值</code> 和 <code>模式串</code> 哈希值与 <code>子串</code> 哈希值之间的<code>比较</code>。<br>对于 <code>模式串哈希值</code> 与每个 <code>子串哈希值</code> 之间的比较的时间复杂度是 O(1)，通过哈希算法计算子串的哈希值的时候，需要遍历子串中的每个字符。<br>总共需要比较 n-m+1 个子串的哈希值，所以，这部分的时间复杂度也是 O(n)。所以，RK 算法整体的时间复杂度就是<code> O(n)</code>。</p><blockquote><p>如果设计的 <code>Hash</code> 算法存在冲突，则在 <code>hash</code> 值相同时比较比一下子串和模式串本身，类似于 hashcode 和 equals 方法</p></blockquote><p>哈希算法的冲突概率要相对控制得低一些，如果存在大量冲突，就会导致 RK 算法的时间复杂度退化，效率下降。极端情况下，如果存在大量的冲突，每次都要再对比子串和模式串本身，那时间复杂度就会退化成 <code>O(n*m)</code>。但也不要太悲观，一般情况下，冲突不会很多，RK 算法的效率还是比 BF 算法高的。</p><h1 id="3-BM-算法"><a href="#3-BM-算法" class="headerlink" title="3. BM 算法"></a>3. BM 算法</h1><h2 id="3-1-算法思想"><a href="#3-1-算法思想" class="headerlink" title="3.1. 算法思想"></a>3.1. 算法思想</h2><p>我们把模式串和主串的匹配过程，看作模式串在主串中不停地往后滑动。当遇到不匹配的字符时，BF 算法和 RK 算法的做法是，模式串往后滑动一位，然后从模式串的第一个字符开始重新匹配。<br>而 <code>BM</code> 算法，全称是 <code>Boyer-Moore</code> 算法，其核心思想是：在模式串中某个字符与主串不能匹配的时候，将模式串往后 <code>多滑动几位</code>，以此来减少不必要的字符比较，提高匹配的效率。</p><p>前面两节讲的算法，在匹配的过程中，我们都是按模式串的下标从小到大的顺序，依次与主串中的字符进行匹配的。这种匹配顺序比较符合我们的思维习惯，而 BM 算法的匹配顺序比较特别，它是按照模式串下标从大到小的顺序，<span style="background-color:#00ff00">倒着匹配的</span>。</p><h3 id="3-1-1-坏字符规则"><a href="#3-1-1-坏字符规则" class="headerlink" title="3.1.1. 坏字符规则"></a>3.1.1. 坏字符规则</h3><p>按模式串 <code>倒序匹配</code> 过程中，把匹配失败时 <code>主串中</code> 的字符，叫作<strong>坏字符</strong>。然后在<code>模式串中查找坏字符</code>，若找到匹配字符，则将模式串中的 <code>匹配字符</code> 和 <code>坏字符</code> 对齐，<strong>否则</strong>直接将模式串滑动到 <code>坏字符之后的一位</code>，再重复进行上述过程。</p><p><strong>模式串中没有坏字符</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120526.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120539.png"></p><p><strong>模式串中有1个坏字符</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120615.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120623.png"></p><p>如果 <code>坏字符</code> 在 <code>模式串</code> 里<code>多处出现</code>，选择<span style="background-color:#00ff00"> 最靠后</span> 的那个，因为这样不会让模式串 <code>滑动过多</code>，导致本来可能匹配的情况被滑动略过。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120634.png"></p><p>注意：单纯采用<code>坏字符</code>的策略，计算出来的移动位数有可能是负数，因此 <code>BM</code> 算法还需要使用<code>好后缀规则</code>来避免这种情况。因此，在该算法中可以省略坏字符规则，却不能省略好后缀规则。</p><h4 id="3-1-1-1-移动位数"><a href="#3-1-1-1-移动位数" class="headerlink" title="3.1.1.1. 移动位数"></a>3.1.1.1. 移动位数</h4><p>当发生不匹配的时候，我们把坏字符对应的模式串中的字符下标记作 si。如果坏字符在模式串中存在，我们把这个坏字符在模式串中的下标记作 xi。如果不存在，我们把 xi 记作 -1。那模式串往后移动的位数就等于 si-xi。（注意，我这里说的下标，都是字符在模式串的下标）<br>利用坏字符规则，BM 算法在最好情况下的时间复杂度非常低，是 O(n&#x2F;m)。比如，主串是 aaabaaabaaabaaab，模式串是 aaaa。每次比对，模式串都可以直接后移四位，所以，匹配具有类似特点的模式串和主串的时候，BM 算法非常高效。</p><h3 id="3-1-2-好后缀规则"><a href="#3-1-2-好后缀规则" class="headerlink" title="3.1.2. 好后缀规则"></a>3.1.2. 好后缀规则</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120903.png"></p><p>这里如果我们按照坏字符进行移动是不合理的，这时我们可以使用好后缀规则，那么什么是好后缀呢？</p><p>BM 算法是从右往左进行比较，发现坏字符的时候此时 cac 已经匹配成功，在红色阴影处发现坏字符。此时已经匹配成功的 cac 则为我们的好后缀，此时我们拿它在模式串中查找，如果找到了另一个和好后缀相匹配的串，那我们就将另一个和<strong>好后缀相匹配</strong>的串 ，滑到和好后缀对齐的位置。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220930120957.png"></p><p> 值得注意的是防止过度滑动，滑动时 我们不仅要看好后缀在模式串中，是否有另一个匹配的子串，我们还要考察好后缀的后缀子串，是否存在跟模式串的前缀子串匹配的。</p><h1 id="4-KMP算法"><a href="#4-KMP算法" class="headerlink" title="4. KMP算法"></a>4. KMP算法</h1><h2 id="4-1-Next数组的作用"><a href="#4-1-Next数组的作用" class="headerlink" title="4.1. Next数组的作用"></a>4.1. Next数组的作用</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220924193813.png"></p><p><span style="background-color:#ffff00">最长相等前后缀的妙处：我们为了减少复杂度，要尽最大可能减少回溯，也就是尽最大可能让模式串往右移动，想办法<font color=#ff0000>跳过那些明显不能匹配成功的位置</font>。往右移动的越多，遍历的次数越少。但是，如果移动多了，会错过匹配。<font color=#ff0000>又为了防止错过匹配</font>，那么需要得到最长相等前后缀的信息，来指导模式串回溯到哪个位置。</span></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925084038.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925084317.png"><br><span style="background-color:#ffff00">如果是BF，还需要比较b和c这2种不可能的情况</span></p><h2 id="4-2-Next数组的求法"><a href="#4-2-Next数组的求法" class="headerlink" title="4.2. Next数组的求法"></a>4.2. Next数组的求法</h2><h3 id="4-2-1-手动计算"><a href="#4-2-1-手动计算" class="headerlink" title="4.2.1. 手动计算"></a>4.2.1. 手动计算</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220924194336.png"></p><ol><li>固定前2位分别为0和1，相当于递归的初始化数据部分</li><li>j&#x3D;3时，前2位的最大相等前后缀长度为0，0+1&#x3D;1</li><li>j&#x3D;4时，前3位的最大相等前后缀长度为1，1+1&#x3D;2</li><li>j&#x3D;5时，前4位的最大相等前后缀长度为1，1+1&#x3D;2</li><li>j&#x3D;6时，前5位的最大相等前后缀长度为2，2+1&#x3D;3</li><li>j&#x3D;7时，前6位的最大相等前后缀长度为0，0+1&#x3D;1</li><li>j&#x3D;8时，前7位的最大相等前后缀长度为1，1+1&#x3D;2</li><li>j&#x3D;9时，前8位的最大相等前后缀长度为2，2+1&#x3D;3</li></ol><figure class="highlight plaintext"><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 plaintext">next[5] = 2，有2层含义：<br>1. P[0~5-1]这一段字符串的最大相等前后缀的长度为2-1<br>2. 如果在j=5的时候，模式串与主串发生失配时，模式串j回溯后，从j=2开始匹配<br></code></pre></td></tr></table></figure><h3 id="4-2-2-递归计算"><a href="#4-2-2-递归计算" class="headerlink" title="4.2.2. 递归计算"></a>4.2.2. 递归计算</h3><h4 id="4-2-2-1-理解方式1"><a href="#4-2-2-1-理解方式1" class="headerlink" title="4.2.2.1. 理解方式1"></a>4.2.2.1. 理解方式1</h4><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925085011.png"></p><p>因为我们的next[]的定义就是**<font color=#ff0000><span style="background-color:#00ff00">当与主串发生失配时，模式串回溯的位置，取最大相同前后缀长度的后一位</span></font>**</p><p>例如此处已知<span style="background-color:#00ff00"> j&#x3D;5,k&#x3D;2</span>，接下来要怎么做？</p><figure class="highlight plaintext"><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 plaintext">1. 字串再加1位，求next[6]，我们希望新加上的第6位c与第3位相同，那么就可以直接得出 k=k+1 了<br>2. 所以，就相当于把4、5、6位的“abc”当做主串，把1、2、3位的“aba”当做子串，拿“aba”去“abc”里匹配，这里就是递归的体现之处。abc是第j个字符，aba是第k个字符，请看下图<br>3. 接下来我们秉承KMP思想的精髓，模式串aba要回溯，当前位置是k，回溯位置是next[k]，所以有 k = next[k]<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925123833.png"></p><h4 id="4-2-2-2-理解方式2"><a href="#4-2-2-2-理解方式2" class="headerlink" title="4.2.2.2. 理解方式2"></a>4.2.2.2. 理解方式2</h4><p>首先说一句：快速构建next数组，是KMP算法的精髓所在，核心思想是“<strong>P自己与自己做匹配</strong>”。  </p><p>为什么这样说呢？<font color=#ff0000><span style="background-color:#ffff00">因为失配前，主串和模式串是重合的，也就是相等的</span></font><br>　　<br>回顾next数组的完整定义：  </p><ul><li>定义 “k-前缀” 为一个字符串的前 k 个字符； “k-后缀” 为一个字符串的后 k 个字符。k 必须小于字符串长度。</li><li>next[x] 定义为： P[0]~P[x] 这一段字符串，使得<strong>k-前缀恰等于k-后缀</strong>的最大的k.</li></ul><p>　　这个定义中，不知不觉地就包含了一个匹配——前缀和后缀相等。接下来，我们考虑采用递推的方式求出next数组。如果next[0], next[1], … next[x-1]均已知，那么如何求出 next[x] 呢？  </p><p>　　我们来分情况讨论。首先，已经知道了 next[x-1]（以下记为now），如果 P[x] 与 P[now] 一样，那最长相等前后缀的长度就可以扩展一位，很明显 next[x] &#x3D; now + 1. 图示如下。<br>　　<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925081258.png"></p><p>刚刚解决了 P[x] &#x3D; P[now] 的情况。那如果 P[x] 与 P[now] 不一样，又该怎么办？  </p><p><img src="https://picx.zhimg.com/50/v2-ce1d46a1e3603b07a13789b6ece6022f_720w.jpg?source=1940ef5c"></p><p>如图。长度为 now 的子串 A 和子串 B 是 P[0]~P[x-1] 中最长的公共前后缀。可惜子串 A 右边的字符和 子串B 右边的那个字符不相等，next[x]不能直接变为 now+1 了。因此，我们应该<strong>缩短这个now</strong>，把它改成小一点的值，再来试试 P[x] 是否等于 P[now].  </p><p>步骤为：先求P[0<del>x-1]这串字符串的最大相同前后缀长度，也就是next[now-1]。然后回溯到next[now-1]的位置，即 now &#x3D; next[now-1]，再判断此时的P[now]是否等于P[x]，如果相等，则now&#x3D;now+1，否则继续回溯，重复上述过程。<br>　　<br>为什么缩短now，now该缩小到多少呢？<br>显然，我们不想让now缩小太多。因此我们决定，在保持“P[0]</del>P[x-1]的[now-前缀]仍然等于[now-后缀]”的前提下，让这个新的now尽可能大一点。 又因为P[0]~P[x-1] 的公共[前后缀]，前缀一定落在子串A里面、后缀一定落在子串B里面。换句话讲：接下来now应该改成：使得 <strong>A的[k-前缀]等于</strong>B的[k-后缀] 的最大的k.<br>此时我们应该已经注意到了一个非常强的性质——<strong>子串A和子串B是相同的</strong>！那么B的后缀也就等于A的后缀！因此，使得A的k-前缀等于B的k-后缀的最大的k，其实就是求子串A的最长相同前后缀的长度 —— next[now-1]！  也就是 now &#x3D; next[now-1]</p><p><img src="https://picx.zhimg.com/50/v2-c5ff4faaab9c3e13690deb86d8d17d71_720w.jpg?source=1940ef5c"></p><p>来看上面的例子。当P[now]与P[x]不相等的时候，我们需要缩小now——把now变成next[now-1]，直到P[now]&#x3D;P[x]为止。P[now]&#x3D;P[x]时，就可以直接向右扩展了。</p><h4 id="4-2-2-3-理解方式3"><a href="#4-2-2-3-理解方式3" class="headerlink" title="4.2.2.3. 理解方式3"></a>4.2.2.3. 理解方式3</h4><p>实质同理解2</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122008.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122044.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122108.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122140.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122159.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122222.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925122237.png"></p><h2 id="4-3-KMP问题"><a href="#4-3-KMP问题" class="headerlink" title="4.3. KMP问题"></a>4.3. KMP问题</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925132618.png"></p><p><strong>第一部分是构建 next 数组</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925132655.png"></p><p><strong>第二部分借助 next 数组匹配</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220925132829.png"></p><h2 id="4-4-时间复杂度"><a href="#4-4-时间复杂度" class="headerlink" title="4.4. 时间复杂度"></a>4.4. 时间复杂度</h2><p>KMP 算法包含两部分，第一部分是构建 next 数组，第二部分才是借助 next 数组匹配。所以，关于时间复杂度，我们要分别从这两部分来分析。</p><p>构建 next 数组，j 从 1 开始一直增加到 m，而 k 并不是每次 for 循环都会增加，所以，k 累积增加的值肯定小于 m。而 while 循环里 k&#x3D;next[k]，实际上是在减小 k 的值，k 累积都没有增加超过 m，所以 while 循环里面 k&#x3D;next[k] 总的执行次数也不可能超过 m。因此，next 数组计算的时间复杂度是 O(m)。</p><p>我们再来分析第二部分的时间复杂度。分析的方法是类似的。</p><p>i 从 0 循环增长到 n-1，j 的增长量不可能超过 i，所以肯定小于 n。而 while 循环中的那条语句 j&#x3D;next[j]+1，不会让 j 增长的，那有没有可能让 j 不变呢？也没有可能。因为 next[j] 的值肯定小于 j，所以 while 循环中的这条语句实际上也是在让 j 的值减少。而 j 总共增长的量都不会超过 n，那减少的量也不可能超过 n，所以 while 循环中的这条语句总的执行次数也不会超过 n，所以这部分的时间复杂度是 O(n)。</p><p>所以，综合两部分的时间复杂度，KMP 算法的时间复杂度就是 O(m+n)。</p><h1 id="5-参考"><a href="#5-参考" class="headerlink" title="5. 参考"></a>5. 参考</h1><p><a href="https://www.bilibili.com/video/BV1AY4y157yL/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1AY4y157yL/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.bilibili.com/video/BV1dY4y1T7v2?p=12&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1dY4y1T7v2?p=12&amp;spm_id_from=pageDriver&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>[[极客时间-46-数据结构与算法之美&#x2F;03-基础篇 (38讲)&#x2F;34丨字符串匹配基础（下）：如何借助BM算法轻松理解KMP算法？.pdf]]</p><p><a href="https://www.bilibili.com/video/BV1M5411j7Xx/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1M5411j7Xx/?spm_id_from=333.788&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p>✅✅<a href="https://www.bilibili.com/video/BV1j34y1E7dp/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204">https://www.bilibili.com/video/BV1j34y1E7dp/?spm_id_from=333.337.search-card.all.click&amp;vd_source=c5b2d0d7bc377c0c35dbc251d95cf204</a></p><p><a href="https://www.zhihu.com/question/21923021/answer/1032665486">https://www.zhihu.com/question/21923021/answer/1032665486</a> </p><p><a href="https://blog.csdn.net/honestjiang/article/details/108044250">https://blog.csdn.net/honestjiang/article/details/108044250</a></p><p><a href="https://www.zhihu.com/question/21923021">https://www.zhihu.com/question/21923021</a></p><p>复杂度分析：<a href="https://www.cnblogs.com/zzuuoo666/p/9028287.html#3.4%20KMP%E7%9A%84%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%88%86%E6%9E%90">https://www.cnblogs.com/zzuuoo666/p/9028287.html#3.4%20KMP%E7%9A%84%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%88%86%E6%9E%90</a></p>]]></content>
      
      
      <categories>
          
          <category> 数据结构与算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 查找算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--13、Git比对各区差异-git diff</title>
      <link href="/2022/09/21/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--13%E3%80%81Git%E6%AF%94%E5%AF%B9%E5%90%84%E5%8C%BA%E5%B7%AE%E5%BC%82-git-diff/"/>
      <url>/2022/09/21/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--13%E3%80%81Git%E6%AF%94%E5%AF%B9%E5%90%84%E5%8C%BA%E5%B7%AE%E5%BC%82-git-diff/</url>
      
        <content type="html"><![CDATA[<h2 id="1、git-diff-命令说明"><a href="#1、git-diff-命令说明" class="headerlink" title="1、git diff 命令说明"></a>1、git diff 命令说明</h2><p>在<code>commit</code>操作之前，我们通常要确定一下自己在什么地方更改了代码，看看有没有误操作代码，这个时候<code>git status</code>命令的显示就比较简单了，仅仅是列出了修改过的文件，如果要查看具体修改了什么地方，就可以使用<code>git diff</code>命令。</p><p>比较有用的选项：<code>--stat</code>：显示有多少行发生变化，简洁的展示差异。</p><h2 id="2、比较工作区与暂存区中文件的差别"><a href="#2、比较工作区与暂存区中文件的差别" class="headerlink" title="2、比较工作区与暂存区中文件的差别"></a>2、比较工作区与暂存区中文件的差别</h2><p>查看工作区与暂存区内容的区别，使用无选项的<code>git diff</code>命令。</p><p><code>git diff file_name</code>：获取指定文件的修改。</p><p><strong>（1）首先在工作目录中创建一个<code>hello.html</code>文件，并添加到暂存区。</strong></p><figure class="highlight plaintext"><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 plaintext">cd ~ &amp;&amp; rm -rf ~/gitDiffTest<br><br>git init gitDiffTest &amp;&amp; cd gitDiffTest<br><br>echo &quot;hello git diff&quot; &gt;&gt; hello.html<br><br>git add hello.html<br><br></code></pre></td></tr></table></figure><p><strong>（2）向<code>hello.html</code>文件添加一行新的内容，之后查看工作区与暂存区<code>hello.html</code>文件的区别。</strong></p><figure class="highlight plaintext"><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 plaintext">echo &quot;hello git diff again&quot; &gt;&gt; hello.html<br><br>git diff hello.html<br><br>diff --git a/hello.html b/hello.html<br><br>index 2435b37..bf8e610 100644<br><br>--- a/hello.html<br><br>+++ b/hello.html<br><br>@@ -1 +1,2 @@<br><br> hello git diff<br><br>+hello git diff again<br></code></pre></td></tr></table></figure><p>说明：</p><ul><li><code>diff --git a/hello.html b/hello.html</code>：表示进行比较的是<code>hello.html</code>文件的<code>a</code>版本(即变动前)和<code>b</code>版本(即变动后)。</li><li><code>index 2435b37..bf8e610 100644</code>：表示两个版本的<code>hash</code>索引值，前边表示暂存区文件的索引，后边代表工作区中文件的索引。</li><li><code>100644</code>：表示文件模式，<code>100</code>代表普通文件，<code>644</code>代表文件具有的权限（同Linux文件权限）。</li><li><code>--- a/hello.html</code>和<code>+++ b/hello.html</code>：表示进行比较的两个文件，<code>---</code>表示变动前的版本，<code>+++</code>表示变动后的版本。</li><li><code>@@ -1 +1,2 @@</code>：表示代码变动的位置，用两个<code>@</code>作为起首和结束。<br>以<code>+1,2</code>说明：分成三个部分：<br><code>+</code>表示变动后文件，1表示第一行，2表示连续2行。（也就是从第一行开始，有连续两行的内容。我个人的理解就是表示文件有几行内容。）</li><li>最后一部分为文件变动的具体内容，每一行最前面的标志位：<br><code>-</code>代表第一个文件删除的行，用红色表示。<br><code>+</code>表示第二个文件新增的行，用绿色表示。<br>无标志表示该行无变动。</li></ul><p>这里在简单说明一下<code>--stat</code>选项的作用，如下：</p><figure class="highlight plaintext"><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 plaintext">git diff --stat hello.html<br>hello.html | 1 +<br>1 file changed, 1 insertion(+)<br></code></pre></td></tr></table></figure><p><strong>（3）将修改后的<code>hello.html</code>文件添加到暂存区中，再次来查看该文件。</strong></p><figure class="highlight plaintext"><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 plaintext">git add .<br>git diff hello.html<br></code></pre></td></tr></table></figure><p>没有任何输出，这就说明此时，工作区中<code>hello.html</code>文件的内容，与暂存区中<code>hello.html</code>文件的内容没有区别。</p><h2 id="3、比较暂存区与本地库中文件的差别"><a href="#3、比较暂存区与本地库中文件的差别" class="headerlink" title="3、比较暂存区与本地库中文件的差别"></a>3、比较暂存区与本地库中文件的差别</h2><p>查看暂存区与本地库中文件内容的区别，使用带<code>--cached</code>选项的<code>git diff</code>命令。</p><p>使用命令：<code>git diff --cached file_name</code></p><p>提交到版本库之前</p><figure class="highlight plaintext"><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 plaintext">diff --git a/hello.html b/hello.html<br><br>new file mode 100644<br><br>index 0000000..bf8e610<br><br>--- /dev/null<br><br>+++ b/hello.html<br><br>@@ -0,0 +1,2 @@<br><br>+hello git diff<br><br>+hello git diff again<br></code></pre></td></tr></table></figure><p>说明本地版本库当前为空</p><p><strong>（1）接上面练习，把<code>hello.html</code>文件提交到本地版本库中。</strong></p><figure class="highlight plaintext"><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 plaintext">git commit -m &#x27;add hello.html file&#x27;<br>git diff --cached hello.html<br></code></pre></td></tr></table></figure><p>此时为空，表示本地版本库已经与暂存区一致</p><p><strong>（2）修改<code>hello.html</code>文件，然后添加到暂存区。</strong></p><figure class="highlight plaintext"><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 plaintext">echo &quot;hello again more&quot; &gt;&gt; hello.html<br>git add .<br></code></pre></td></tr></table></figure><p><strong>（3）比较暂存区和本地版本库中<code>hello.html</code>文件的区别。</strong></p><figure class="highlight plaintext"><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 plaintext">git diff --cached hello.html<br><br>diff --git a/hello.html b/hello.html<br><br>index bf8e610..f3d807d 100644<br><br>--- a/hello.html<br><br>+++ b/hello.html<br><br>@@ -1,2 +1,3 @@<br><br> hello git diff<br><br> hello git diff again<br><br>+hello again more<br></code></pre></td></tr></table></figure><p>从上面文件中可以看出，暂存区中的<code>hello.html</code>文件比本地版本库中的<code>hello.html</code>文件，多出一行<code>hello again more</code>内容。（解读方式同上。）</p><h2 id="4、总结git-diff命令常见用法"><a href="#4、总结git-diff命令常见用法" class="headerlink" title="4、总结git diff命令常见用法"></a>4、总结git diff命令常见用法</h2><ol><li>比较工作区与暂存区：<br><code>git diff</code>命令，不加参数即默认比较工作区与暂存区。</li><li>比较暂存区与最新本地版本库（本地库中最近一次<code>commit</code>的内容）：<br><code>git diff --cached</code>命令或者<code>git diff --staged</code>命令（1.6.1版本以上）。</li><li>比较工作区与最新本地版本库：<br><code>git diff HEAD</code>命令，如果<code>HEAD</code>指向的是<code>master</code>分支，那么<code>HEAD</code>还可以换成<code>master</code>。</li><li>比较工作区与指定<code>commit</code>提交的差异：<br><code>git diff commit-id</code>命令。</li><li>比较暂存区与指定<code>commit</code>提交的差异：<br><code>git diff --cached commit-id</code> 命令。</li><li>比较两个<code>commit</code>提交之间的差异：<br><code>git diff [&lt;commit-id&gt;] [&lt;commit-id&gt;]</code>命令。</li><li>使用<code>git diff</code>命令打补丁，这个用法以后会详解，知道有这么回事就行。</li></ol><blockquote><p>提示：以上就不详细说明了，看前面举例两个例子，其他同理。</p></blockquote><h2 id="5、总结"><a href="#5、总结" class="headerlink" title="5、总结"></a>5、总结</h2><p>以现在学到的知识点，<code>git diff</code>命令能解决我们两个问题：</p><ul><li>查看当前做的哪些更新还没有暂存？<br>需要查看细节的时候，使用<code>git diff</code>命令。</li><li>查看有哪些更新已经暂存起来，准备好了下次提交？<br>需要查看细节的时候，使用<code>git diff --cached</code>命令或者<code>git diff --staged</code>命令。</li></ul><h1 id="6、参考"><a href="#6、参考" class="headerlink" title="6、参考"></a>6、参考</h1><p><a href="https://www.cnblogs.com/liuyuelinfighting/p/16227889.html">https://www.cnblogs.com/liuyuelinfighting/p/16227889.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--14、Git移除大文件</title>
      <link href="/2022/09/21/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--14%E3%80%81Git%E7%A7%BB%E9%99%A4%E5%A4%A7%E6%96%87%E4%BB%B6/"/>
      <url>/2022/09/21/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--14%E3%80%81Git%E7%A7%BB%E9%99%A4%E5%A4%A7%E6%96%87%E4%BB%B6/</url>
      
        <content type="html"><![CDATA[<p>Git 有很多很棒的功能，但是其中一个特性会导致问题，<code>git clone</code> 会下载整个项目的历史，包括每一个文件的每一个版本。 如果所有的东西都是源代码那么这很好，因为 Git 被高度优化来有效地存储这种数据。 然而，如果某个人在之前向项目添加了一个大小特别大的文件，即使你将这个文件从项目中移除了，每次克隆还是都要强制的下载这个大文件。 之所以会产生这个问题，是因为这个文件在历史中是存在的，它会永远在那里。</p><p>当你迁移 Subversion 或 Perforce 仓库到 Git 的时候，这会是一个严重的问题。 因为这些版本控制系统并不下载所有的历史文件，所以这种文件所带来的问题比较少。 如果你从其他的版本控制系统迁移到 Git 时发现仓库比预期的大得多，那么你就需要找到并移除这些大文件。</p><p><strong>警告：这个操作对提交历史的修改是破坏性的。</strong> 它会从你必须修改或移除一个大文件引用最早的树对象开始重写每一次提交。 如果你在导入仓库后，在任何人开始基于这些提交工作前执行这个操作，那么将不会有任何问题——<font color=#ff0000><span style="background-color:#ffff00">否则， 你必须通知所有的贡献者他们需要将他们的成果变基到你的新提交上</span></font>。</p><p>为了演示，我们将添加一个大文件到测试仓库中，并在下一次提交中删除它，现在我们需要找到它，并将它从仓库中永久删除。 首先，添加一个大文件到仓库中：</p><figure class="highlight plaintext"><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 plaintext">cd ~ &amp;&amp; rm -rf ~/gitBigObjTest<br><br>git init gitBigObjTest &amp;&amp; cd gitBigObjTest<br><br>curl https://mirrors.edge.kernel.org/pub/software/scm/git/git-1.8.5.1.tar.gz &gt; git.tgz<br><br>git add git.tgz<br><br>git commit -m &#x27;add git tarball&#x27;<br><br>[master（根提交） 49af15f] add git tarball<br> 1 file changed, 0 insertions(+), 0 deletions(-)<br> create mode 100644 git.tgz<br></code></pre></td></tr></table></figure><p>哎呀——其实这个项目并不需要这个巨大的压缩文件。 现在我们将它移除：</p><figure class="highlight plaintext"><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 plaintext">git rm git.tgz<br><br>git commit -m &#x27;oops - removed large tarball&#x27;<br><br>[master 9448928] oops - removed large tarball<br> 1 file changed, 0 insertions(+), 0 deletions(-)<br> delete mode 100644 git.tgz<br></code></pre></td></tr></table></figure><p>现在，我们执行 <code>gc</code> 来查看数据库占用了多少空间：</p><figure class="highlight plaintext"><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 plaintext">git gc<br><br>枚举对象中: 5, 完成.<br>对象计数中: 100% (5/5), 完成.<br>使用 16 个线程进行压缩<br>压缩对象中: 100% (3/3), 完成.<br>写入对象中: 100% (5/5), 完成.<br>总共 5（差异 0），复用 0（差异 0），包复用 0<br></code></pre></td></tr></table></figure><p>你也可以执行 <code>count-objects</code> 命令来快速的查看占用空间大小：</p><figure class="highlight plaintext"><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 plaintext">git count-objects -v<br><br>count: 0<br>size: 0<br>in-pack: 5<br>packs: 1<br>size-pack: 4645<br>prune-packable: 0<br>garbage: 0<br>size-garbage: 0<br></code></pre></td></tr></table></figure><p><code>size-pack</code> 的数值指的是你的包文件以 KB 为单位计算的大小，所以你大约占用了 5MB 的空间。 在最后一次提交前，使用了不到 2KB ——<span style="background-color:#ffff00">显然，从之前的提交中移除文件并不能从历史中移除它。</span> 每一次有人克隆这个仓库时，他们将必须克隆所有的 5MB 来获得这个微型项目，只因为你意外地添加了一个大文件。 现在来让我们彻底的移除这个文件。</p><h1 id="查找大文件"><a href="#查找大文件" class="headerlink" title="查找大文件"></a>查找大文件</h1><p>首先你必须找到它。 在本例中，你已经知道是哪个文件了。 但是假设你不知道；该如何找出哪个文件或哪些文件占用了如此多的空间？ 如果你执行 <code>git gc</code> 命令，所有的对象将被放入一个包文件中，你可以通过运行 <code>git verify-pack</code> 命令， 然后对输出内容的第三列（即文件大小）进行排序，从而找出这个大文件。 你也可以将这个命令的执行结果通过管道传送给 <code>tail</code> 命令，因为你只需要找到列在最后的几个大对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git rev-list --objects --all | grep &quot;$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk &#x27;&#123;print$1&#125;&#x27;)&quot;<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">33385338edecc08e6c1b3dd3b754a31b2c9f3598<br>bf227d7632026b291b8815d1fa795a1e87dfa2ce<br>abd739d6b0e81d09e81308b2e4e9fc66da405eff <br>f09c45e90367235df753e99d1917891b9572dd9e git.tgz<br></code></pre></td></tr></table></figure><p>你可以看到这个大对象出现在返回结果的最底部：占用 5MB 空间。 为了找出具体是哪个文件，可以使用 <code>rev-list</code> 命令，我们在 <a href="https://git-scm.com/book/zh/v2/ch00/_enforcing_commit_message_format">指定特殊的提交信息格式</a> 中曾提到过。 如果你传递 <code>--objects</code> 参数给 <code>rev-list</code> 命令，它就会列出所有提交的 SHA-1、数据对象的 SHA-1 和与它们相关联的文件路径。 可以使用以下命令来找出你的数据对象的名字：</p><h1 id="定位问题提交"><a href="#定位问题提交" class="headerlink" title="定位问题提交"></a>定位问题提交</h1><p>现在，你只需要从过去所有的树中移除这个文件。 使用以下命令可以轻松地查看哪些提交对这个文件产生改动：</p><figure class="highlight plaintext"><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 plaintext">git log --oneline --branches -- git.tgz<br>9448928 (HEAD -&gt; master) oops - removed large tarball<br>49af15f add git tarball<br></code></pre></td></tr></table></figure><h1 id="移除文件"><a href="#移除文件" class="headerlink" title="移除文件"></a>移除文件</h1><p>现在，你必须重写 <code>49af</code> 提交之后的所有提交来从 Git 历史中完全移除这个文件。 为了执行这个操作，我们要使用 <code>filter-branch</code> 命令，这个命令在 <a href="https://git-scm.com/book/zh/v2/ch00/_rewriting_history">重写历史</a> 中也使用过：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">git filter-branch --index-filter \<br><br>  &#x27;git rm --ignore-unmatch --cached git.tgz&#x27; -- --all         <br><br>WARNING: git-filter-branch has a glut of gotchas generating mangled history<br><br>rewrites.  Hit Ctrl-C before proceeding to abort, then use an<br><br>alternative filtering tool such as &#x27;git filter-repo&#x27;<br><br>(https://github.com/newren/git-filter-repo/) instead.  See the<br><br>filter-branch manual page for more details; to squelch this warning,<br><br>set FILTER_BRANCH_SQUELCH_WARNING=1.<br><br>Proceeding with filter-branch...<br><br>  <br>Rewrite bf227d7632026b291b8815d1fa795a1e87dfa2ce (1/2) (0 seconds passed, remaining 0 predicted)    rm &#x27;git.tgz&#x27;<br><br>Rewrite 33385338edecc08e6c1b3dd3b754a31b2c9f3598 (2/2) (0 seconds passed, remaining 0 predicted)    <br><br>Ref &#x27;refs/heads/master&#x27; was rewritten<br></code></pre></td></tr></table></figure><p><code>--index-filter</code> 选项类似于在 <a href="https://git-scm.com/book/zh/v2/ch00/_rewriting_history">重写历史</a> 中提到的的 <code>--tree-filter</code> 选项， 不过这个选项并不会让命令将修改在硬盘上检出的文件，而只是修改在暂存区或索引中的文件。</p><p>你必须使用 <code>git rm --cached</code> 命令来移除文件，而不是通过类似 <code>rm file</code> 的命令——因为你需要从索引中移除它，而不是磁盘中。 还有一个原因是速度—— Git 在运行过滤器时，并不会检出每个修订版本到磁盘中，所以这个过程会非常快。 如果愿意的话，你也可以通过 <code>--tree-filter</code> 选项来完成同样的任务。 <code>git rm</code> 命令的 <code>--ignore-unmatch</code> 选项告诉命令：如果尝试删除的模式不存在时，不提示错误。 </p><p>你的历史中将不再包含对那个文件的引用。 不过，你的引用日志和你在 <code>.git/refs/original</code> 通过 <code>filter-branch</code> 选项添加的新引用中还存有对这个文件的引用，所以你必须移除它们然后重新打包数据库。 在重新打包前需要移除任何包含指向那些旧提交的指针的文件：</p><blockquote><p>通过上面的例子，可以看出命令<strong>git filter-branch</strong>通过针对不同的过滤器提供可执行脚本，从不同的角度对Git版本库进行重构。该命令的用法：</p></blockquote><figure class="highlight plaintext"><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 plaintext">git filter-branch [--env-filter &lt;command&gt;] [--tree-filter &lt;command&gt;]<br>        [--index-filter &lt;command&gt;] [--parent-filter &lt;command&gt;]<br>        [--msg-filter &lt;command&gt;] [--commit-filter &lt;command&gt;]<br>        [--tag-name-filter &lt;command&gt;] [--subdirectory-filter &lt;directory&gt;]<br>        [--prune-empty]<br>        [--original &lt;namespace&gt;] [-d &lt;directory&gt;] [-f | --force]<br>        [--] [&lt;rev-list options&gt;...]<br></code></pre></td></tr></table></figure><p>这条命令异常复杂，但是大部分参数是用于提供不同的接口，因此还是比较好理解的。</p><ul><li>该命令最后的<code>&lt;rev-list&gt;</code>参数提供要修改的版本范围，如果省略则相当于HEAD指向的当前分支。也可以使用–all来指代所有引用，但是要在–all和前面的参数间使用分隔符–。</li></ul><h1 id="彻底删除历史"><a href="#彻底删除历史" class="headerlink" title="彻底删除历史"></a>彻底删除历史</h1><ul><li>运行<strong>git filter-branch</strong>命令改写分支之后，被改写的分支会在refs&#x2F;original中对原始引用做备份。对于在refs&#x2F;original中已有备份的情况下，该命令拒绝执行，除非使用-f或–force参数。</li><li>其他需要接以<command>的参数都为<strong>git filter-branch</strong>提供相应的接口进行过虑，在下面会针对各个过滤器进行介绍。</li></ul><figure class="highlight plaintext"><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 plaintext">$ rm -Rf .git/refs/original<br>$ rm -Rf .git/logs/<br>$ git gc<br>枚举对象中: 3, 完成.<br>对象计数中: 100% (3/3), 完成.<br>使用 16 个线程进行压缩<br>压缩对象中: 100% (2/2), 完成.<br>写入对象中: 100% (3/3), 完成.<br>总共 3（差异 1），复用 1（差异 0），包复用 0<br></code></pre></td></tr></table></figure><p>让我们看看你省了多少空间。</p><figure class="highlight plaintext"><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 plaintext">$ git count-objects -v<br>count: 4<br>size: 4656<br>in-pack: 3<br>packs: 1<br>size-pack: 1<br>prune-packable: 0<br>garbage: 0<br>size-garbage: 0<br></code></pre></td></tr></table></figure><p>打包的仓库大小下降到了 8K，比 5MB 好很多。 可以从 size 的值看出，这个大文件还在你的松散对象中，并没有消失；但是它不会在推送或接下来的克隆中出现，这才是最重要的。 如果真的想要删除它，可以通过有 <code>--expire</code> 选项的 <code>git prune</code> 命令来完全地移除那个对象：</p><figure class="highlight plaintext"><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 plaintext">$ git prune --expire now<br>$ git count-objects -v<br>count: 0<br>size: 0<br>in-pack: 3<br>packs: 1<br>size-pack: 1<br>prune-packable: 0<br>garbage: 0<br>size-garbage: 0<br></code></pre></td></tr></table></figure><h1 id="让远程仓库变小"><a href="#让远程仓库变小" class="headerlink" title="让远程仓库变小"></a>让远程仓库变小</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs undefined">git push origin --force --all<br></code></pre></td></tr></table></figure><blockquote><p>因为不是 fast forward，所以需要指定 <code>--force</code> 参数。</p></blockquote><p>这里的 <code>--all</code> 会将所有分支都推送到 <code>origin</code> 上。当然你也可以只推送 <code>master</code> 分支： <code>git push origin master --force</code>。但是！如果其它远程分支有那些大文件提交的话，仍然没有瘦身！</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.worldhello.net/gotgit/06-migrate/050-git-to-git.html">https://www.worldhello.net/gotgit/06-migrate/050-git-to-git.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git原理--4、Git对象和存储原理-Commit对象</title>
      <link href="/2022/09/20/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--4%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Commit%E5%AF%B9%E8%B1%A1/"/>
      <url>/2022/09/20/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--4%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Commit%E5%AF%B9%E8%B1%A1/</url>
      
        <content type="html"><![CDATA[<h1 id="1-Commit对象介绍"><a href="#1-Commit对象介绍" class="headerlink" title="1. Commit对象介绍"></a>1. Commit对象介绍</h1><p>现在来介绍最后一种Git对象<code>commit</code>对象，也叫提交对象。</p><p>提交对象可以理解为是对树对象的一层封装，提交信息包括基于当前暂存区中索引文件生成的<code>tree</code>对象，还有包含了提交时间，提交者信息，作者信息，以及提交备注等内容，更重要的是里面还包含了父提交的ID(第一次提交没有父提交ID)，由此就可以形成Git提交的有向无环图。（是链式的关系，把所有<code>commit</code>对象关联起来）</p><p>即：<code>commit</code>对象通常指向一个 <code>tree</code> 对象，并且封装了文件的提交时间，提交者信息，作者信息，提交备注，以及父提交引用等数据。</p><p>下面是<code>commit</code>对象的存储结构：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220921184855.png"></p><h1 id="2-Commit对象说明"><a href="#2-Commit对象说明" class="headerlink" title="2. Commit对象说明"></a>2. Commit对象说明</h1><p>我们通过练习来说明<code>commit</code>对象，接着用前面<code>Tree</code>对象的本地版本库。</p><p><strong>（1）创建一个<code>commit</code>对象</strong></p><p>我们可以通过调用<code>commit-tree</code>命令创建一个提交对象，为此需要指定一个树对象的<code>SHA-1</code>值，以及该提交的父提交对象。</p><blockquote><p>说明：使用<code>commit-tree</code>命令来创建提交对象，一般都需要和父提交进行关联，如果是第一次将暂存区的文件索引数据提交到本地版本库，那么该提交操作就不需要指定父提交对象。</p></blockquote><p><strong>1）我们可以先查看一下此时Git本地库中的对象，如下</strong>：<br>演示代码请看上一节<a href="/2022/09/15/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--3%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Tree%E5%AF%B9%E8%B1%A1/" title="Git原理--3、Git对象和存储原理-Tree对象">Git原理--3、Git对象和存储原理-Tree对象</a></p><figure class="highlight plaintext"><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 plaintext">.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653 <br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547 <br>.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5 <br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 <br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 <br>.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 <br></code></pre></td></tr></table></figure><p><strong>2）我们通过第一个树对象，创建一个<code>commit</code>对象</strong></p><figure class="highlight shell"><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 shell">➜  gitObject git:(master) ✗ echo &#x27;first commit&#x27; | git commit-tree d8329f<br><br>8683a3799e49f9fd1553391d56a816fa11953fec<br><br>➜  gitObject git:(master) ✗ git cat-file -p 8683<br><br>tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>author luoweile &lt;luoweile2008@126.com&gt; 1663760954 +0800<br><br>committer luoweile &lt;luoweile2008@126.com&gt; 1663760954 +0800<br><br>  <br><br>first commit<br></code></pre></td></tr></table></figure><p>说明：</p><ul><li><code>tree</code>：表示该<code>commit</code>对象所指向的<code>tree</code>对象的索引</li><li><code>author</code>：表示该文件的作者。</li><li><code>committer</code>：表示该文件的提交者。</li><li><code>first commit</code>：这段文本是提交备注。（备注与前面留空一行）</li><li>因为是第一次进行<code>commit</code>提交操作，所以没有父提交信息。</li><li><code>1618190880 +0800</code>：表示时间，一个时间戳。</li></ul><blockquote><p>即：<code>commit</code>对象的格式很简单：指明了该时间点项目快照的顶层树对象、作者&#x2F;提交者信息（从 Git 设置的 <code>user.name</code>和 <code>user.email</code>中获得)，以及当前时间戳、留空一行，最后是提交注释。</p></blockquote><blockquote><p>提示：<code>git commit-tree</code>命令不但生成了提交对象，而且会将对应的快照（树对象）提交到本地库中。</p></blockquote><blockquote><p>因为加了时间戳，所以每次执行，commit对象的SHA-1是不一样的</p></blockquote><p><strong>（2）创建第二个<code>commit</code>对象</strong></p><p>根据第二个<code>tree</code>对象和第一个<code>commit</code>对象，来创建第二个<code>commit</code>对象。</p><p>通过<code>-p</code>选项指定父提交对象。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject ➜  gitObject git:(master) ✗ echo &#x27;second commit&#x27; | git commit-tree 163b45f0a09 -p 8683<br><br>4564e68bd6d9fd6ce818ddbc8189aebb5ff07352<br><br>➜  gitObject git:(master) ✗ git cat-file -p 4564<br><br>tree 163b45f0a0925b0655da232ea8a4188ccec615f5<br><br>parent 8683a3799e49f9fd1553391d56a816fa11953fec<br><br>author luoweile &lt;luoweile2008@126.com&gt; 1663761236 +0800<br><br>committer luoweile &lt;luoweile2008@126.com&gt; 1663761236 +0800<br><br>  <br><br>second commit<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ echo &#x27;third commit&#x27; | git commit-tree 01ab -p 4564 <br><br>7fa009de98135bfdbceadd8c68f9caa056215d01<br><br>➜  gitObject git:(master) ✗ git cat-file -p 7fa0<br><br>tree 01ab2a43b1eb150bcf00f375800727df240cf653<br><br>parent 4564e68bd6d9fd6ce818ddbc8189aebb5ff07352<br><br>author luoweile &lt;luoweile2008@126.com&gt; 1663761401 +0800<br><br>committer luoweile &lt;luoweile2008@126.com&gt; 1663761401 +0800<br><br>  <br><br>third commit<br></code></pre></td></tr></table></figure><blockquote><p><strong>提交对象的格式很简单：</strong></p><p>它先指定一个顶层树对象，代表当前项目快照；</p><p>然后是可能存在的父提交；</p><p>之后是作者&#x2F;提交者信息（依据你的 <code>user.name</code> 和 <code>user.email</code> 配置来设定，外加一个时间戳）；</p><p>留空一行，最后是提交注释。</p></blockquote><h1 id="3-本地库中对象之间的关系"><a href="#3-本地库中对象之间的关系" class="headerlink" title="3. 本地库中对象之间的关系"></a>3. 本地库中对象之间的关系</h1><p>我们可以查看一下此时Git本地库中的对象</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547<br><br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5<br><br>.git/objects/45/64e68bd6d9fd6ce818ddbc8189aebb5ff07352<br><br>.git/objects/86/83a3799e49f9fd1553391d56a816fa11953fec<br><br>.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653<br><br>.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>.git/objects/7f/a009de98135bfdbceadd8c68f9caa056215d01<br></code></pre></td></tr></table></figure><p>可以从上面看到，此时的本地版本库中共有9个对象，三个<code>blob</code>对象，三个<code>tree</code>对象，三个<code>commit</code>对象。</p><p>他们之间的关系如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220921201910.png"></p><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h1><ol><li>提交是我们经常使用的Git动作，每次提交操作都指向一个树对象，同时会产生一个<code>commit</code>对象。<br>即：一个<code>commit</code>对象包含了一个<code>tree</code>对象，这个<code>tree</code>对象记录了在那个时间点，项目包含了什么文件夹和什么文件。</li><li>一个提交对象可以有一个或者多个父提交。</li><li>每次<code>commit</code>操作都会基于当前索引文件index新建<code>tree</code>对象。那么当前索引文件，是在上次提交的基础上更新来的，所以每次提交产生的<code>commit</code>对象，与其他的<code>commit</code>对象，都有前后关系或者称为父子关系。</li><li>对于我们来说，不需要直接访问<code>blob</code>对象和<code>tree</code>对象，我们直接访问<code>commit</code>对象就可以了。<br>即：<code>commit</code>对象对应的<code>tree</code>对象下面，又包含了小的<code>tree</code>对象和<code>blob</code>对象，子的<code>tree</code>对象一层层展开，最后叶子节点就是一个个<code>blob</code>对象，也就是一个个文件。</li></ol><blockquote><p>到这里，我们就能够清楚的了解，什么叫一个Git版本。<code>tree</code>对象才是一次项目版本的快照，提交对象是对<code>tree</code>对象的一次封装。</p><p>即：</p><ul><li>项目的快照就是一个树对象。</li><li>项目的版本就是一个提交对象。</li></ul><p>而且Git的每一个版本，存储的不是增量，而存储的是当前项目的快照。同时<code>objects</code>目录中相当于存放了项目的所有历史记录，回滚就相当的方便了，找到对应的<code>commit</code>对象的hash就可以了。</p></blockquote><h1 id="5-练习"><a href="#5-练习" class="headerlink" title="5. 练习"></a>5. 练习</h1><p>请问下图中包含多少个<code>tree</code>对象和<code>blob</code>对象？<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220921201938.png"></p><p>一共包含两个<code>tree</code>对象，一个<code>blob</code>对象，一个<code>commit</code>对象。</p><p>说明：</p><ul><li>一个<code>commit</code>对象一定对应一个<code>tree</code>对象（这个<code>tree</code>对象应该是一个完整项目仓库的快照）</li><li><code>doc</code>目录下有一个<code>blob</code>对象，也就是<code>readme</code>文件。</li></ul><h1 id="6-命令总结"><a href="#6-命令总结" class="headerlink" title="6. 命令总结"></a>6. 命令总结</h1><p>Git底层命令：</p><ul><li><code>git commit-tree</code>：生成一个<code>commit</code>对象。</li><li><code>git cat-file -t 键</code>：查看Git对象的类型。</li><li><code>git cat-file -p 键</code>：查看Git对象的内容。</li></ul><h1 id="7-参考"><a href="#7-参考" class="headerlink" title="7. 参考"></a>7. 参考</h1><p><a href="https://www.cnblogs.com/liuyuelinfighting/p/16198051.html">https://www.cnblogs.com/liuyuelinfighting/p/16198051.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git原理 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git原理--5、Git对象的总结</title>
      <link href="/2022/09/20/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--5%E3%80%81Git%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%80%BB%E7%BB%93/"/>
      <url>/2022/09/20/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--5%E3%80%81Git%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%80%BB%E7%BB%93/</url>
      
        <content type="html"><![CDATA[<blockquote><p>提示：前面三篇文章已经分别的对<code>blob</code>对象、<code>tree</code>对象、<code>commit</code>对象进行了详细的说明，这篇文章我们总结一下，Git对象在基础操作流程中的生成的时机。</p></blockquote><h1 id="1-Git操作最基本的流程"><a href="#1-Git操作最基本的流程" class="headerlink" title="1. Git操作最基本的流程"></a>1. Git操作最基本的流程</h1><p><strong>1）创建工作目录对工作目录进行修改</strong>。</p><p><strong>2）执行<code>git add ./</code>命令添加文件到暂存区。</strong></p><p>相当于执行了如下两个底层命令：</p><ul><li><code>git hash-object -w 文件名</code>（修改了多少个工作目录中的文件此命令就要被执行多少次）</li><li><code>git update-index</code></li></ul><p><strong>说明：<code>git add</code>命令做了什么事情？</strong></p><p>表面上是将工作目录中的文件添加到暂存区中，其实真正的流程是：</p><ul><li>先将工作目录中的文件，生成<code>blob</code>对象存储到本地版本库中，<br>一个文件生成一个<code>blob</code>对象，一个文件执行一次<code>git hash-object -w 文件路径</code>命令。</li><li>再通过<code>git update-index</code>命令，把本地版本库中<code>blob</code>对象，生成文件的索引（快照），存储到暂存区中。</li></ul><blockquote><p>所以说Git是绝对安全的，只要你对文件做过的修改，哪怕没有提交到本地版本库，只是提交到暂存区，Git也会帮你记录下来。</p></blockquote><p><strong>3）执行<code>git commit -m &quot;注释内容&quot;</code>命令，把暂存区的快照提交到本地版本库。</strong></p><p>相当于执行了如下两个底层命令：</p><ul><li><code>git write-tree</code>：生成<code>tree</code>对象。</li><li><code>git commit-tree</code>：生成<code>commit</code>对象。</li></ul><p><strong>说明：<code>git commit</code>命令做了什么事情？</strong></p><p>表面上是将暂存区的文件索引提交到了本地版本库中，其实真正的流程是：</p><ul><li>先通过<code>git write-tree</code>命令，把暂存区中的索引信息，生成一个<code>tree</code>对象存储到本地版本库中。</li><li>然后通过<code>git commit-tree</code>命令，把上面生成的树对象进行封存，生成一个<code>commit</code>对象，存储到本地版本库中。</li></ul><p><strong>重点提示：一个<code>commit</code>对象肯定会对应一个<code>tree</code>对象（单方向1对1的关系），一个<code>commit</code>对象是不会对应两个<code>tree</code>对象的。（如上说明）</strong></p><h1 id="2-工作目录中文件的状态"><a href="#2-工作目录中文件的状态" class="headerlink" title="2. 工作目录中文件的状态"></a>2. 工作目录中文件的状态</h1><p>工作目录下面的所有文件都不外乎这两种状态：<strong>已跟踪</strong>状态或<strong>未跟踪</strong>状态。</p><p><strong>已跟踪</strong>的文件是指本来就被纳入版本控制管理的文件，在之前的快照中有它们的记录，工作一段时间后，它们的状态会分为<strong>已提交</strong>状态，<strong>已修改</strong>状态或者<strong>已暂存</strong>状态，这三种。</p><p>然后所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照，也不在当前的暂存区域。</p><p>使用Git时的文件状态变化周期如下图所示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220921212400.png"></p><blockquote><p>提示：初次克隆某个仓库到本地时，工作目录中的所有文件都属于已跟踪文件，且状态为已提交；在编辑过某些文件之后，Git将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域，直到最后一次性提交所有这些暂存起来的文件。</p></blockquote><h1 id="3-Git效率说明"><a href="#3-Git效率说明" class="headerlink" title="3. Git效率说明"></a>3. Git效率说明</h1><p>我们经历了一次完整的Git提交过程，现在我们来思考一下Git提供的这三种对象带来的高效率：</p><ul><li>首先Git会对所有的文件内容使用zlib进行压缩，这使得即使仓库中存储了非常多的内容，而<code>.git</code>也不会很大，</li><li>然后就是速度，考虑这样的情况，当我们修改了一个文件的时候，Git会去计算这个文件的<code>SHA-1</code>散列值。<br>如果该散列值所得到的路径已经存在，那就说明，这个文件并没有被真正修改（也可以是改了然后又改了回来），这时就不会在本地版本库中存储新的对象。也就是说<code>blob</code>对象跟文件名一点关系都没有，两个不同名字的文件，只要他们的内容相同，在Git的眼里他就是一个<code>blob</code>对象，且只有一份。<br>如果我们真正的修改了一个文件，那么Git会计算这个文件的散列值，然后将这个文件压缩存储在<code>objects</code>目录中。<br>这样设计的可以大大的节约存储的空间，也提升了Git的存储速度。</li><li>如果我们需要进行一次提交操作，是先对原来的文件进行更改，然后需要创建一个相应的树结构，来记录这些文件的变化。也就是每一次提交都创建一个顶层树对象来表示这次提交快照。<br>Git会对比前一个提交的顶层树对象，然后将没有改变的树对象或数据对象直接复制到新创建的这个顶层树对象中，将改变的树对象或数据对象，进行覆盖，最后再提交到本地版本库。</li></ul><blockquote><p>所以说决定你仓库大小的并不是完全在于每个文件的大小，而是你修改提交的次数，修改的次数越多，产生的树对象、数据对象和提交对象也就越多。</p></blockquote><h1 id="4-参考"><a href="#4-参考" class="headerlink" title="4. 参考"></a>4. 参考</h1><p><a href="https://www.cnblogs.com/liuyuelinfighting/p/16202023.html">https://www.cnblogs.com/liuyuelinfighting/p/16202023.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git原理 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--10、git-merge用法解析-用法大全</title>
      <link href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--10%E3%80%81git-merge%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90-%E7%94%A8%E6%B3%95%E5%A4%A7%E5%85%A8/"/>
      <url>/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--10%E3%80%81git-merge%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90-%E7%94%A8%E6%B3%95%E5%A4%A7%E5%85%A8/</url>
      
        <content type="html"><![CDATA[<p><code>git merge</code> 应该是开发者最常用的 git 指令之一，<br>默认情况下你直接使用 <code>git merge</code> 命令，没有附加任何选项命令的话，那么应该是交给 git 来判断使用哪种 merge 模式，实际上 git 默认执行的指令是 <code>git merge -ff</code> 指令（默认值）</p><h1 id="1-Fast-forward"><a href="#1-Fast-forward" class="headerlink" title="1. Fast-forward"></a>1. Fast-forward</h1><h2 id="1-1-ff"><a href="#1-1-ff" class="headerlink" title="1.1. ff"></a>1.1. ff</h2><h3 id="1-1-1-触发场景"><a href="#1-1-1-触发场景" class="headerlink" title="1.1.1. 触发场景"></a>1.1.1. 触发场景</h3><p>当dev是master最新commit(HEAD)的孩子，默认触发此模式</p><figure class="highlight plaintext"><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 plaintext">cd ~<br>rm -rf mergedemo<br>mkdir mergedemo;cd mergedemo<br>git init .<br>echo &quot;hello A&quot; &gt; file.txt<br>git add .<br>git commit -m &quot;A&quot;<br>echo &quot;hello B&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;B&quot;<br>echo &quot;hello M1&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;M1&quot;<br>echo &quot;hello M2&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;M2&quot;<br>git checkout -b dev<br>echo &quot;hello D1&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;D1&quot;<br>echo &quot;hello D2&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;D2&quot;<br>echo &quot;hello D3&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;D3&quot;<br>git log --oneline<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">git checkout master<br>gitk<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917191338.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917182938.png"></p><figure class="highlight plaintext"><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 plaintext">git checkout dev<br>gitk<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917191409.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917182738.png"></p><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(master) git merge dev      <br><br>更新 8b3d646..cca25ff<br>Fast-forward<br> file.txt | 3 +++<br> 1 file changed, 3 insertions(+)<br></code></pre></td></tr></table></figure><p>git merge dev 会默认执行 fast-forward模式<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917183059.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917192336.png"></p><p><code>Fast-forward</code> 是指 Master 合并 Feature 时候发现 Master 当前节点一直和 Feature 的根节点相同，没有发生改变，那么 Master 快速移动头指针到 Feature 的位置，所以 <strong>Fast-forward 并不会发生真正的合并</strong>，只是通过移动指针造成合并的假象，这也体现 git 设计的巧妙之处。合并后的分支指针上图所示</p><p>通常功能分支（feature556） 合并 master 后会被删除，通过下图可以看到，通过 <code>Fast-forward</code> 模式产生的合并可以产生<strong>干净并且线性的历史记录</strong>：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917192304.png"></p><h3 id="1-1-2-优缺点"><a href="#1-1-2-优缺点" class="headerlink" title="1.1.2. 优缺点"></a>1.1.2. 优缺点</h3><ul><li>优点：直接修改 HEAD 指针指向，不会创造一个新的 commit 节点，所以合并速度非常快，且产生<strong>干净并且线性的历史记录</strong></li><li>缺点：删除分支或指针向前走会丢失分支信息 (log中体现不出原来的分支操作)</li></ul><h2 id="1-2-no-ff"><a href="#1-2-no-ff" class="headerlink" title="1.2. no-ff"></a>1.2. no-ff</h2><h3 id="1-2-1-触发场景"><a href="#1-2-1-触发场景" class="headerlink" title="1.2.1. 触发场景"></a>1.2.1. 触发场景</h3><p>dev非master最新commit(HEAD)的孩子</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917191506.png"></p><figure class="highlight plaintext"><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 plaintext">cd ~<br>rm -rf mergedemo<br>mkdir mergedemo;cd mergedemo<br>git init .<br>echo &quot;hello A&quot; &gt; file.txt<br>git add .<br>git commit -m &quot;A&quot;<br>echo &quot;hello B&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;B&quot;<br>git checkout -b dev<br>echo &quot;hello D1&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;D1&quot;<br>echo &quot;hello D2&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;D2&quot;<br>echo &quot;hello D3&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;D3&quot;<br>git checkout master<br>echo &quot;hello M1&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;M1&quot;<br>echo &quot;hello M2&quot; &gt;&gt; file.txt<br>git add .<br>git commit -m &quot;M2&quot;<br>git log<br></code></pre></td></tr></table></figure><p>master分支的git log</p><figure class="highlight plaintext"><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 plaintext">1d190f3 (HEAD -&gt; master) M2<br>70a4baa M1<br>87652d3 B<br>2610c63 A<br></code></pre></td></tr></table></figure><p>dev分支的git log</p><figure class="highlight plaintext"><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 plaintext">cb082ff (HEAD -&gt; dev) D3<br>63ee19a D2<br>f43408e D1<br>87652d3 B<br>2610c63 A<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git merge dev<br></code></pre></td></tr></table></figure><p>master 已经没办法通过移动头指针来完成 <code>Fast-forward</code>，所以在 master 合并 dev 的时候就不得不做出真正的合并，真正的合并会让 git 多做很多工作，具体合并的动作如下：</p><ul><li>找出 master 和 dev 的公共祖先，节点 B，已经各自的最新提交M2, D3 三个节点的版本 （如果有冲突需要处理，我们这里的示例代码就需要手动合并，然后需要git add，git commit，因此又多出来一个commit历史，这里我备注的是“master merge dev”）</li><li>创建新的节点 D4，并且将三个版本的差异合并到 D4，并且创建 commit</li><li>将 master 和 HEAD 指针移动到 D4</li></ul><p>此时生产了一个merge commit (D4’)，这个merge commit不包含任何代码改动，而包含在dev分支上的几个commit列表(D1, D2和D3)。查看git的提交历史(git log)可以看到所有的这些提交历史记录。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917193919.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917182113.png"></p><h3 id="1-2-2-优缺点"><a href="#1-2-2-优缺点" class="headerlink" title="1.2.2. 优缺点"></a>1.2.2. 优缺点</h3><ul><li>优点：保留commit历史</li><li>缺点：产生一个新的merge commit</li></ul><h2 id="1-3-ff-only"><a href="#1-3-ff-only" class="headerlink" title="1.3. ff-only"></a>1.3. ff-only</h2><p>只会按照 <code>Fast-forward</code> 模式进行合并，如果不符合条件（并非当前分支的直接后代），则会拒绝合并请求并且退出</p><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(master) git merge dev --ff-only<br><br>fatal: 无法快进，终止。<br></code></pre></td></tr></table></figure><h2 id="1-4-手动设置合并模式"><a href="#1-4-手动设置合并模式" class="headerlink" title="1.4. 手动设置合并模式"></a>1.4. 手动设置合并模式</h2><p>先简单介绍一下 <code>git merge</code> 的三个合并参数模式：</p><ul><li>–ff 自动合并模式：当合并的分支为当前分支的后代的，那么会自动执行 <code>--ff (Fast-forward)</code> 模式，如果不匹配则执行 <code>--no-ff（non-Fast-forward）</code> 合并模式</li><li>–no-ff 非 Fast-forward 模式：在任何情况下都会创建新的 commit 进行多方合并（即使被合并的分支为自己的直接后代）</li><li>–ff-only Fast-forward 模式：只会按照 <code>Fast-forward</code> 模式进行合并，如果不符合条件（并非当前分支的直接后代），则会拒绝合并请求并且退出</li></ul><figure class="highlight plaintext"><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 plaintext">git config --global --add merge.ff false<br>git config branch.[branch name].mergeoptions &quot;--no-ff&quot;<br></code></pre></td></tr></table></figure><h2 id="1-5-适用场景"><a href="#1-5-适用场景" class="headerlink" title="1.5. 适用场景"></a>1.5. 适用场景</h2><p>三种 merge 模式没有好坏和优劣之分，只有根据你团队的需求和实际情况选择合适的合并模式才是最优解，那么应该怎么选择呢？ 我给出以下推荐：</p><ul><li>如果你是小型团队，并且追求干净线性 git 历史记录，那么我推荐使用 <code>git merge --ff-only</code> 方式保持主线模式开发是一种不错的选择</li><li>如果你团队不大不小，并且也不追求线性的 git 历史记录，要体现相对真实的 merge 记录，那么默认的 <code>git --ff</code> 比较合适</li><li>如果你是大型团队，并且要严格监控每个功能分支的合并情况，那么使用 <code>--no-ff</code> 禁用 <code>Fast-forward</code> 是一个不错的选择</li></ul><h1 id="2-squash-merge"><a href="#2-squash-merge" class="headerlink" title="2. squash merge"></a>2. squash merge</h1><p><code>--squash</code>选项的含义是：本地文件内容与不使用该选项的合并结果相同，但是不提交、不移动<code>HEAD</code>，因此需要一条额外的<code>commit</code>命令。其效果相当于将another分支上的多个<code>commit</code>合并成一个，放在当前分支上，原来的<code>commit</code>历史则没有拿过来。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221019120320.png"></p><p><a href="https://www.processon.com/view/6325a4f01efad46b0ab39c8d?fromnew=1">https://www.processon.com/view/6325a4f01efad46b0ab39c8d?fromnew=1</a></p><h2 id="2-1-适用场景"><a href="#2-1-适用场景" class="headerlink" title="2.1. 适用场景"></a>2.1. 适用场景</h2><h3 id="2-1-1-简化提交历史"><a href="#2-1-1-简化提交历史" class="headerlink" title="2.1.1. 简化提交历史"></a>2.1.1. 简化提交历史</h3><p>在dev分支上执行的是开发工作，有一些很小的提交，或者是纠正前面的错误的提交，对于这类提交对整个工程来说不需要单独显示出来一次提交，不然导致项目的提交历史过于复杂；所以基于这种原因，我们可以把dev上的所有提交都合并成一个提交；然后提交到主干。</p><blockquote><p>判断是否使用<code>--squash</code>选项最根本的标准是，待合并分支上的历史是否有意义<br>在某些情况下，我们应该优先选择使用<code>--squash</code>选项，如下：</p></blockquote><p>联想发散一下，同一个分支上压缩commit，请见<a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--1%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-reset/" title="Git进阶--1、Git-后悔药-回退撤销-reset">Git进阶--1、Git-后悔药-回退撤销-reset</a></p><h3 id="2-1-2-github-squash选项"><a href="#2-1-2-github-squash选项" class="headerlink" title="2.1.2. github squash选项"></a>2.1.2. github squash选项</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917203242.png"></p><h2 id="2-2-注意事项"><a href="#2-2-注意事项" class="headerlink" title="2.2. 注意事项"></a>2.2. 注意事项</h2><h3 id="2-2-1-分支必须是直接孩子"><a href="#2-2-1-分支必须是直接孩子" class="headerlink" title="2.2.1. 分支必须是直接孩子"></a>2.2.1. 分支必须是直接孩子</h3><figure class="highlight plaintext"><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 plaintext">5eb656a (HEAD -&gt; master) M2<br>3c31c9c M1<br>782742b B<br>685ce99 A<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917221636.png"><br>需要commit一下</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917203018.png"></p><h3 id="2-2-2-合并提交归属问题"><a href="#2-2-2-合并提交归属问题" class="headerlink" title="2.2.2. 合并提交归属问题"></a>2.2.2. 合并提交归属问题</h3><p>在这个例子中，我们把D1，D2和D3的改动合并成了一个D。</p><p>注意，squash merge并不会替你产生提交，它只是把所有的改动合并，然后放在本地文件，需要你再次手动执行git commit操作；此时又要注意了，因为你要你手动commit，也就是说这个commit是你产生的，不是有原来dev分支上的开发人员产生的，<span style="background-color:#ffff00">提交者身份发生了变化</span>。也可以这么理解，就是你把dev分支上的所有代码改动一次性porting到master分支上而已。</p><blockquote><p>由于squash merge会变更提交者作者信息，这是一个很大的问题，后期问题追溯不好处理(当然也可以由分支dev的所有者来执行squash merge操作，以解决部分问题)，rebase merge可以保留提交的作者信息，同时可以合并commit历史，完美的解决了上面的问题。</p></blockquote><a href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--11%E3%80%81git-rebase%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90%E4%B8%8Emerge%E5%8C%BA%E5%88%AB/" title="Git进阶--11、git-rebase用法解析与merge区别">Git进阶--11、git-rebase用法解析与merge区别</a><h1 id="3-参考"><a href="#3-参考" class="headerlink" title="3. 参考"></a>3. 参考</h1><p><a href="https://static.kancloud.cn/apachecn/git-doc-zh/1945501">https://static.kancloud.cn/apachecn/git-doc-zh/1945501</a><br><a href="https://www.jianshu.com/p/ff1877c5864e">https://www.jianshu.com/p/ff1877c5864e</a><br><a href="https://www.cnblogs.com/xiao2shiqi/p/14715119.html">https://www.cnblogs.com/xiao2shiqi/p/14715119.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--11、git-rebase用法解析与merge区别</title>
      <link href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--11%E3%80%81git-rebase%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90%E4%B8%8Emerge%E5%8C%BA%E5%88%AB/"/>
      <url>/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--11%E3%80%81git-rebase%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90%E4%B8%8Emerge%E5%8C%BA%E5%88%AB/</url>
      
        <content type="html"><![CDATA[<h1 id="1-语法"><a href="#1-语法" class="headerlink" title="1. 语法"></a>1. 语法</h1><figure class="highlight plaintext"><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 plaintext">git rebase [-i | --interactive] [&lt;options&gt;] [--exec &lt;cmd&gt;] [--onto &lt;newbase&gt;]<br>[&lt;upstream&gt; [&lt;branch&gt;]]<br>git rebase [-i | --interactive] [&lt;options&gt;] [--exec &lt;cmd&gt;] [--onto &lt;newbase&gt;]<br>--root [&lt;branch&gt;]<br>git rebase --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch<br></code></pre></td></tr></table></figure><h2 id="1-1-适用场景"><a href="#1-1-适用场景" class="headerlink" title="1.1. 适用场景"></a>1.1. 适用场景</h2><h3 id="1-1-1-合并时精简提交历史"><a href="#1-1-1-合并时精简提交历史" class="headerlink" title="1.1.1. 合并时精简提交历史"></a>1.1.1. 合并时精简提交历史</h3><p>squash merge也可以精简提交历史，详情请见<a href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--10%E3%80%81git-merge%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90-%E7%94%A8%E6%B3%95%E5%A4%A7%E5%85%A8/" title="Git进阶--10、git-merge用法解析-用法大全">Git进阶--10、git-merge用法解析-用法大全</a></p><blockquote><p>由于squash merge会变更提交者作者信息，这是一个很大的问题，后期问题追溯不好处理(当然也可以由分支dev的所有者来执行squash merge操作，以解决部分问题)，rebase merge可以保留提交的作者信息，同时可以合并commit历史，完美的解决了上面的问题。</p></blockquote><h3 id="1-1-2-合并完之后精简提交历史"><a href="#1-1-2-合并完之后精简提交历史" class="headerlink" title="1.1.2. 合并完之后精简提交历史"></a>1.1.2. 合并完之后精简提交历史</h3><blockquote><p>如果从另一个分支中merge时，没有使用–squash选项时，导致合并过来好多没有必要的commit，这时就可以使用git rebase来调整commit的数量。</p></blockquote><h3 id="1-1-3-更改提交历史"><a href="#1-1-3-更改提交历史" class="headerlink" title="1.1.3. 更改提交历史"></a>1.1.3. 更改提交历史</h3><h3 id="1-1-4-防止git-pull产生merge-commit"><a href="#1-1-4-防止git-pull产生merge-commit" class="headerlink" title="1.1.4. 防止git pull产生merge commit"></a>1.1.4. 防止git pull产生merge commit</h3><ul><li>方法一: 在执行git pull的时候加上–rebase参数。这参数的意思就是在合并代码之前，先执行变基操作，成功后在进行真正的merge操作。(如果有冲突需要手动解决)</li><li>方法二：在你的git bash里执行<code>git config --global pull.rebase true</code>。这个配置就是告诉git在每次pull前先进行rebase操作。这种方法和方法1原理一样，只不过方法1是每次pull前都要手动操作。</li></ul><h2 id="1-2-使用方法"><a href="#1-2-使用方法" class="headerlink" title="1.2. 使用方法"></a>1.2. 使用方法</h2><p>rebase之前</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917210358.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917214406.png"></p><p>rebase merge分两步完成： </p><p>最终效果如<a href="#1-2-3-%E6%9C%80%E7%BB%88%E6%95%88%E6%9E%9C">1.2.3 最终效果</a>所示，看起来dev分支是从M2拉出来的，而不是从B拉出来的。</p><h3 id="1-2-1-第一步执行rebase操作"><a href="#1-2-1-第一步执行rebase操作" class="headerlink" title="1.2.1. 第一步执行rebase操作"></a>1.2.1. 第一步执行rebase操作</h3><p>可以使用-i参数手动调整commit历史，是否合并如何合并。例如下rebase -i命令会弹出文本编辑框：</p><figure class="highlight plaintext"><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 plaintext">git checkout dev<br>git rebase -i master<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">pick 6e17179 D1<br><br>f bb0bac0 D2<br><br>pick c9d8149 D3<br><br>  <br><br># 变基 7ba16fe..c9d8149 到 7ba16fe（3 个提交）<br><br>#<br><br># 命令:<br><br># p, pick &lt;提交&gt; = 使用提交<br><br># r, reword &lt;提交&gt; = 使用提交，但编辑提交说明<br><br># e, edit &lt;提交&gt; = 使用提交，但停止以便在 shell 中修补提交<br><br># s, squash &lt;提交&gt; = 使用提交，但挤压到前一个提交<br><br># f, fixup [-C | -c] &lt;提交&gt; = 类似于 &quot;squash&quot;，但只保留前一个提交<br><br>#                    的提交说明，除非使用了 -C 参数，此情况下则只<br><br>#                    保留本提交说明。使用 -c 和 -C 类似，但会打开<br><br>#                    编辑器修改提交说明<br><br># x, exec &lt;命令&gt; = 使用 shell 运行命令（此行剩余部分）<br><br># b, break = 在此处停止（使用 &#x27;git rebase --continue&#x27; 继续变基）<br><br># d, drop &lt;提交&gt; = 删除提交<br><br># l, label &lt;label&gt; = 为当前 HEAD 打上标记<br><br># t, reset &lt;label&gt; = 重置 HEAD 到该标记<br><br># m, merge [-C &lt;commit&gt; | -c &lt;commit&gt;] &lt;label&gt; [# &lt;oneline&gt;]<br><br># .       创建一个合并提交，并使用原始的合并提交说明（如果没有指定<br><br># .       原始提交，使用注释部分的 oneline 作为提交说明）。使用<br><br># .       -c &lt;提交&gt; 可以编辑提交说明。<br><br>#<br><br># 可以对这些行重新排序，将从上至下执行。<br><br>#<br><br># 如果您在这里删除一行，对应的提交将会丢失。<br><br>#<br><br># 然而，如果您删除全部内容，变基操作将会终止。<br><br>#<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(dev) git rebase -i master <br><br>自动合并 file.txt<br><br>冲突（内容）：合并冲突于 file.txt<br><br>error: 不能应用 47f4c7f... D1<br><br>提示：Resolve all conflicts manually, mark them as resolved with<br><br>提示：&quot;git add/rm &lt;conflicted_files&gt;&quot;, then run &quot;git rebase --continue&quot;.<br><br>提示：You can instead skip this commit: run &quot;git rebase --skip&quot;.<br><br>提示：To abort and get back to the state before &quot;git rebase&quot;, run &quot;git rebase --abort&quot;.<br><br>不能应用 47f4c7f... D1<br></code></pre></td></tr></table></figure><p>合并冲突后，执行<code>git add .</code> ，<span style="background-color:#ffff00"><font color=#ff0000> 不需要执行<code>git commit</code></font></span> ，然后执行<code>git rebase --continue</code>，继续交互</p><figure class="highlight plaintext"><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 plaintext">D1&#x27;<br><br>  <br><br># 请为您的变更输入提交说明。以 &#x27;#&#x27; 开始的行将被忽略，而一个空的提交<br><br># 说明将会终止提交。<br><br>#<br><br># 交互式变基操作正在进行中；至 d48f03a<br><br># 最后完成的命令（1 条命令被执行）：<br><br>#    pick 47f4c7f D1<br><br># 接下来要执行的命令（剩余 2 条命令）：<br><br>#    fixup 37d76b0 D2<br><br>#    pick b9639e3 D3<br><br># 您在执行将分支 &#x27;dev&#x27; 变基到 &#x27;d48f03a&#x27; 的操作。<br><br>#<br><br># 要提交的变更：<br><br>#       修改：     file.txt<br><br>#<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(42c38aa) ✗ git rebase --continue<br><br>[分离头指针 ab44b6f] D1&#x27;<br><br> 1 file changed, 1 insertion(+)<br><br>成功变基并更新 refs/heads/dev。<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">cf8116c (HEAD -&gt; dev) D3<br><br>39784fd D1&#x27;<br><br>3ddbd5d (master) M2<br><br>5fb7fba M1<br><br>daf563a B<br><br>edfa896 A<br><br>(END)<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917213834.png"></p><h3 id="1-2-2-第二步执行merge操作"><a href="#1-2-2-第二步执行merge操作" class="headerlink" title="1.2.2. 第二步执行merge操作"></a>1.2.2. 第二步执行merge操作</h3><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(dev) git checkout master  <br><br>切换到分支 &#x27;master&#x27;<br><br>➜  mergedemo git:(master) git merge dev      <br><br>更新 42c38aa..bec7048<br><br>Fast-forward<br><br> file.txt | 3 +++<br><br> 1 file changed, 3 insertions(+)<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917214010.png"></p><figure class="highlight plaintext"><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 plaintext">bec7048 (HEAD -&gt; master, dev) D3<br><br>5243e08 D1&#x27;<br><br>42c38aa M2<br><br>7132c18 M1<br><br>1de9541 B<br><br>37f978a A<br><br>(END)<br></code></pre></td></tr></table></figure><h3 id="1-2-3-最终效果"><a href="#1-2-3-最终效果" class="headerlink" title="1.2.3. 最终效果"></a>1.2.3. 最终效果</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221019122702.png"></p><p>原图请见：<a href="https://www.processon.com/view/6325a4f01efad46b0ab39c8d?fromnew=1">https://www.processon.com/view/6325a4f01efad46b0ab39c8d?fromnew=1</a></p><h3 id="1-2-4-交互式commands"><a href="#1-2-4-交互式commands" class="headerlink" title="1.2.4. 交互式commands"></a>1.2.4. 交互式commands</h3><ul><li><p>修改提交信息</p></li><li><p>合并多个提交</p></li><li><p>拆分、编辑已有提交</p></li><li><p>对提交重排序</p></li><li><p>删除提交</p></li><li><p>p, pick &#x3D; 合并这次提交</p></li><li><p>r, reword &#x3D; 合并这次提交, 但是需要修改此次提交的日志信息</p></li><li><p>e, edit &#x3D; 合并这次提交, 但需要对这次提交进行内容和日志的修改</p></li><li><p>s, squash &#x3D;合并这次提交，但把这次提交和上一次提交融合在一起提交，会让提交者重新编辑提交日志。squash这个词用的好，把多个提交压扁在一起。</p></li><li><p>f, fixup &#x3D; 和squash基本一样，差别在于直接丢弃此次提交的日志，使用上一次提交的日志，所以没有交互过程。</p></li><li><p>x, exec &#x3D; 不合并这次提交，而是执行后面的shell命令。</p></li></ul><p>[[..&#x2F;..&#x2F;..&#x2F;..&#x2F;cubox&#x2F;006-ChromeCapture&#x2F;20220917-Git Rebase交互式合并详解  Walker’s Blog]]</p><h1 id="2-其他妙用-onto"><a href="#2-其他妙用-onto" class="headerlink" title="2. 其他妙用 onto"></a>2. 其他妙用 onto</h1><p>使用onto之后, 后面会跟3个参数，可以更精确地变基<br>$ git rebase –onto base from to<br>命令的意义<code>使用(from, to]所指定的范围内的所有commit在base这个commit之上进行重建，相当于找到from和to公共节点之后，to分支所做的内容，在base上重演</code>.</p><h2 id="2-1-踢除分支上临时功能提交"><a href="#2-1-踢除分支上临时功能提交" class="headerlink" title="2.1. 踢除分支上临时功能提交"></a>2.1. 踢除分支上临时功能提交</h2><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220918113150.png"></p><figure class="highlight plaintext"><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 plaintext">1.当前工作空间---分支A<br>2.从当前工作的分支新建一个分支,并且换到该分支----git checkout -b newbranch<br>3.git rebase --onto B 开始的commitId 结束的commitId<br>4.生成一个基于B分支和选择的提交区间的片段生成一个新的分支（detached Head）<br>5.从当前工作空间新建切换到一个新的分支---git checkout -b branch_bank<br>6.分支branch_bank的代码就是我们所需要的了<br>7.branch_bank强制覆盖A分支，切换到A分支，---git reset --hard origin branch_bank<br>8.代码就恢复正常啦<br></code></pre></td></tr></table></figure><h2 id="2-2-跳过某分支合并"><a href="#2-2-跳过某分支合并" class="headerlink" title="2.2. 跳过某分支合并"></a>2.2. 跳过某分支合并</h2><p>我们可以通过 <code>rebase</code> 对两个分支进行对比，取出相应的修改，然后应用到另一个分支上。例如：</p><figure class="highlight bash"><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 bash">    F---G patch<br>   /<br>  D---E feature<br> /<br>A---B---C master<br></code></pre></td></tr></table></figure><p>假设我们基于 <code>feature</code> 分支的提交 D 创建了分支 <code>patch</code>，并且新增了提交 F、G，现在我们想将 <code>patch</code> 所做的更改合并到 <code>master</code> 并发布，但暂时还不想合并 <code>feature</code> ，这种情况下可以使用 <code>rebase</code> 的 <code>--onto &lt;branch&gt;</code> 选项：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git rebase --onto master feature patch<br></code></pre></td></tr></table></figure><p>以上操作将取出 <code>patch</code> 分支，对比它基于 <code>feature</code> 所做的更改， 然后把这些更改在 <code>master</code> 分支上重新应用，让 <code>patch</code> 看起来就像直接基于 <code>master</code> 进行更改一样。执行后的 <code>patch</code> 如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">A---B---C---F<span class="hljs-string">&#x27;---G&#x27;</span> patch<br></code></pre></td></tr></table></figure><p>然后我们可以切换到 <code>master</code> 分支，并对 <code>patch</code> 执行快进合并：</p><figure class="highlight bash"><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 bash">git checkout master<br>git merge patch<br></code></pre></td></tr></table></figure><h3 id="2-2-1-演示示例"><a href="#2-2-1-演示示例" class="headerlink" title="2.2.1. 演示示例"></a>2.2.1. 演示示例</h3><figure class="highlight plaintext"><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 plaintext">cd ~<br><br>rm -rf mergedemo<br><br>mkdir mergedemo;cd mergedemo<br><br>git init .<br><br>echo &quot;hello A&quot; &gt; file.txt<br><br>git add .<br><br>git commit -m &quot;A&quot;<br><br>echo &quot;hello B&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;B&quot;<br><br><br>echo &quot;hello C&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;C&quot;<br><br>git log --oneline --all --graph<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">git checkout -b feature<br><br>git reset --hard HEAD~2<br><br>echo &quot;hello D&quot; &gt; file.txt<br><br>git add .<br><br>git commit -m &quot;D&quot;<br><br>echo &quot;hello E&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;E&quot;<br><br>git log --oneline --all --graph<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">git checkout -b patch<br><br>git reset --hard HEAD^<br><br>echo &quot;hello F&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;F&quot;<br><br>echo &quot;hello G&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;G&quot;<br><br>git log --oneline --all --graph<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">* b3bdd3a (HEAD -&gt; patch) G<br><br>* d6cbc3d F<br><br>| * 8a5a826 (feature) E<br><br>|/  <br><br>* fe63584 D<br><br>| * ed6ecfe (master) C<br><br>| * 76f5d76 B<br><br>|/  <br><br>* 0d7ae83 A<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(patch) git rebase --onto master feature patch<br>自动合并 file.txt<br><br>冲突（内容）：合并冲突于 file.txt<br><br>error: 不能应用 c5872ed... F<br><br>提示：Resolve all conflicts manually, mark them as resolved with<br><br>提示：&quot;git add/rm &lt;conflicted_files&gt;&quot;, then run &quot;git rebase --continue&quot;.<br><br>提示：You can instead skip this commit: run &quot;git rebase --skip&quot;.<br><br>提示：To abort and get back to the state before &quot;git rebase&quot;, run &quot;git rebase --abort&quot;.<br><br>不能应用 c5872ed... F<br><br>➜  mergedemo git:(3877ba7) ✗ vim file.txt<br><br>➜  mergedemo git:(3877ba7) ✗ git add .                             <br><br>➜  mergedemo git:(3877ba7) ✗ git rebase --continue                 <br><br>[分离头指针 864b843] F<br><br> 1 file changed, 2 insertions(+)<br><br>成功变基并更新 refs/heads/patch。<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">* e8f974c (HEAD -&gt; patch) G<br><br>* 864b843 F<br><br>* 3877ba7 (master) C<br><br>* 0d80606 B<br><br>| * 4a6d01f (feature) E<br><br>| * a9019fa D<br><br>|/  <br><br>* 34bfc52 A<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">➜  mergedemo git:(patch) git checkout master            <br><br>切换到分支 &#x27;master&#x27;<br><br>➜  mergedemo git:(master) git merge patch    <br><br>更新 3877ba7..e8f974c<br><br>Fast-forward<br><br> file.txt | 3 +++<br><br> 1 file changed, 3 insertions(+)<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">* e8f974c (HEAD -&gt; master, patch) G<br><br>* 864b843 F<br><br>* 3877ba7 C<br><br>* 0d80606 B<br><br>| * 4a6d01f (feature) E<br><br>| * a9019fa D<br><br>|/  <br><br>* 34bfc52 A<br><br>(END)<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220918124909.png"></p><h1 id="3-参考"><a href="#3-参考" class="headerlink" title="3. 参考"></a>3. 参考</h1><p><a href="http://walkerdu.com/2019/12/05/git_rebase/">http://walkerdu.com/2019/12/05/git_rebase/</a><br><a href="https://www.qikegu.com/docs/4399">https://www.qikegu.com/docs/4399</a><br><a href="https://juejin.cn/post/7093416208296312846#heading-0">https://juejin.cn/post/7093416208296312846#heading-0</a><br><a href="https://morningspace.github.io/tech/git-merge-stories-7/">https://morningspace.github.io/tech/git-merge-stories-7/</a><br><a href="https://waynerv.com/posts/git-rebase-intro/">https://waynerv.com/posts/git-rebase-intro/</a><br><a href="https://juejin.cn/post/6844903762415337479">https://juejin.cn/post/6844903762415337479</a><br><a href="https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/fen-zhi-cao-zuo-yu-guan-li/git-rebase">https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/fen-zhi-cao-zuo-yu-guan-li/git-rebase</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--8、Git-后悔药-回退撤销-大总结</title>
      <link href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--8%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-%E5%A4%A7%E6%80%BB%E7%BB%93/"/>
      <url>/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--8%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-%E5%A4%A7%E6%80%BB%E7%BB%93/</url>
      
        <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917104834.png"></p><p>原图可去我的processon下载<br><a href="https://www.processon.com/view/link/63248872f346fb3377e76017">https://www.processon.com/view/link/63248872f346fb3377e76017</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--9、git-头指针分离原因及解决办法</title>
      <link href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--9%E3%80%81git-%E5%A4%B4%E6%8C%87%E9%92%88%E5%88%86%E7%A6%BB%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/"/>
      <url>/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--9%E3%80%81git-%E5%A4%B4%E6%8C%87%E9%92%88%E5%88%86%E7%A6%BB%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/</url>
      
        <content type="html"><![CDATA[<h1 id="分离头是什么"><a href="#分离头是什么" class="headerlink" title="分离头是什么"></a>分离头是什么</h1><p>我们知道在Git中分支是指向提交，而HEAD指针指向分支。所谓的分离头指针状态就是HEAD不再指向分支，而是直接指向某个commit。</p><h1 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h1><h2 id="解决办法1"><a href="#解决办法1" class="headerlink" title="解决办法1"></a>解决办法1</h2><figure class="highlight shell"><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 shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">将HEAD指向master分支</span><br>git checkout master<br><span class="hljs-meta prompt_"># </span><span class="language-bash">将HEAD和master一起指向0c72e64</span><br>git reset --hard  0c72e64<br></code></pre></td></tr></table></figure><h2 id="解决办法2"><a href="#解决办法2" class="headerlink" title="解决办法2"></a>解决办法2</h2><p>&#96;&#96;&#96;shell</p><h1 id="强制将-master-分支指向当前头指针的位置-：将master指向HEAD所指向的commit，"><a href="#强制将-master-分支指向当前头指针的位置-：将master指向HEAD所指向的commit，" class="headerlink" title="强制将 master 分支指向当前头指针的位置   ：将master指向HEAD所指向的commit，"></a>强制将 master 分支指向当前头指针的位置   ：将master指向HEAD所指向的commit，</h1><h1 id="此时master和HEAD都指向同一个commit"><a href="#此时master和HEAD都指向同一个commit" class="headerlink" title="此时master和HEAD都指向同一个commit"></a>此时master和HEAD都指向同一个commit</h1><p>$ git branch -f master HEAD</p><h1 id="检出-master-分支-：将HEAD指向master分支"><a href="#检出-master-分支-：将HEAD指向master分支" class="headerlink" title="检出 master 分支  ：将HEAD指向master分支"></a>检出 master 分支  ：将HEAD指向master分支</h1><p>$ git checkout master</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶--7、git-reset、git-revert、git-checkout、git-restore区别</title>
      <link href="/2022/09/15/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--7%E3%80%81git-reset%E3%80%81git-revert%E3%80%81git-checkout%E3%80%81git-restore%E5%8C%BA%E5%88%AB/"/>
      <url>/2022/09/15/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--7%E3%80%81git-reset%E3%80%81git-revert%E3%80%81git-checkout%E3%80%81git-restore%E5%8C%BA%E5%88%AB/</url>
      
        <content type="html"><![CDATA[<h1 id="reset与checkout的异同"><a href="#reset与checkout的异同" class="headerlink" title="reset与checkout的异同"></a>reset与checkout的异同</h1><h2 id="commit模式"><a href="#commit模式" class="headerlink" title="commit模式"></a>commit模式</h2><h3 id="checkout"><a href="#checkout" class="headerlink" title="checkout"></a>checkout</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git checkout [-q] [-f] [-m] [&lt;branch&gt;|&lt;commit&gt;]<br></code></pre></td></tr></table></figure><p>当你checkout分支的时候，git做了这么三件事情</p><figure class="highlight plaintext"><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 plaintext">&gt; 将HEAD指向那个分支的最后一次commit或者指定的commit<br>&gt; 将HEAD指向的commit里所有的文件的snapshot替换掉Index区域里面的内容<br>&gt; 将Index区域里面的内容填充到Working Directory里<br></code></pre></td></tr></table></figure><p>所以你可以发现，HEAD，Index，Working Directory这个时候里的内容都是一摸一样的。<br><strong>注意：一般会误解为，Index中的内容是空的，只有git add后才会有东西。实际上不是，<span style="background-color:#ffff00"><font color=#ff0000>add完第一次之后</font></span>，Index里一直是有东西的。</strong> 所以，git里的所有操作就是对这三个区域的状态的操作。</p><p><span style="background-color:#00ff00">此操作是WD(Working Directory)安全的</span></p><h3 id="reset"><a href="#reset" class="headerlink" title="reset"></a>reset</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git reset (&lt;branch_name&gt;|HEAD|&lt;tree-ish&gt;|&lt;tag_name&gt;) [--mixed | --soft | --hard | --merge | --keep]<br></code></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ol><li>两者都可以加branch也可以加commit</li><li>checkout在commit模式下是WD安全的，reset –hard下是WD不安全的</li><li>checkout贯穿3棵树，reset可以控制贯穿哪几棵树</li><li>checkout不改变分支的指向的commit，只是改变HEAD指向的分支；而reset会将HEAD和branch一起移动来改变指向的commit。所以reset会丢失commit log，checkout不会</li></ol><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916162042.png"></p><h2 id="file模式"><a href="#file模式" class="headerlink" title="file模式"></a>file模式</h2><h3 id="checkout-1"><a href="#checkout-1" class="headerlink" title="checkout"></a>checkout</h3><p>贯穿3棵树，WD不安全，相当于<code>git reset --hard commitid</code>的作用，但checkout这里后面的参数是filename或者filepath</p><h3 id="reset-1"><a href="#reset-1" class="headerlink" title="reset"></a>reset</h3><p>file模式下，只会覆盖索引区文件内容，执行后，索引区会变绿，表示commit被撤销，工作区会变红，表示工作区文件被修改，其实内容没变，因为索引区变了，导致校验和不一致，所以变成了红色</p><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><ol><li>checkout撤销3个区的内容，而reset只覆盖索引区，即只撤销了commit动作</li><li>checkout此时是WD不安全的，而reset反而是WD安全的</li></ol><h1 id="revert与reset的异同"><a href="#revert与reset的异同" class="headerlink" title="revert与reset的异同"></a>revert与reset的异同</h1><h2 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h2><ol><li><p>revert只有commit模式，且是WD安全的。</p></li><li><p>evert撤销一个提交的同时会创建一个新的提交。这是一个安全的方法，因为它不会重写提交历史。</p></li></ol><blockquote><p>相比<code>git reset</code>，它不会改变现在的提交历史。因此，<code>git revert</code> 可以用在公共分支上，<code>git reset</code> 应该用在私有分支上。</p></blockquote><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916162257.png"></p><h1 id="restore"><a href="#restore" class="headerlink" title="restore"></a>restore</h1><p>请见<a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--4%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-restore/" title="Git进阶--4、Git-后悔药-回退撤销-restore">Git进阶--4、Git-后悔药-回退撤销-restore</a></p><h1 id="对commit历史的影响"><a href="#对commit历史的影响" class="headerlink" title="对commit历史的影响"></a>对commit历史的影响</h1><table><thead><tr><th>命令</th><th>log</th><th>reflog</th></tr></thead><tbody><tr><td>git revert</td><td>新增</td><td>可跟踪</td></tr><tr><td>git checkout</td><td>无变化</td><td>无变化</td></tr><tr><td>git reset</td><td>丢失</td><td>可跟踪</td></tr><tr><td>git restore</td><td>无变化</td><td>无变化</td></tr><tr><td>git amend</td><td>改变</td><td>改变</td></tr></tbody></table><h1 id="对work-directory的影响"><a href="#对work-directory的影响" class="headerlink" title="对work directory的影响"></a>对work directory的影响</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917104834.png"></p><p>原图可去我的processon下载<br><a href="https://www.processon.com/view/link/63248872f346fb3377e76017">https://www.processon.com/view/link/63248872f346fb3377e76017</a></p><h1 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h1><table><thead><tr><th>命令</th><th>作用域</th><th>常用情景</th></tr></thead><tbody><tr><td>git reset</td><td>提交层面</td><td>在私有分支上舍弃一些没有提交的更改</td></tr><tr><td>git reset</td><td>文件层面</td><td>将文件从缓存区中移除</td></tr><tr><td>git checkout</td><td>提交层面</td><td>切换分支或查看旧版本</td></tr><tr><td>git checkout</td><td>文件层面</td><td>舍弃工作目录中的更改</td></tr><tr><td>git revert</td><td>提交层面</td><td>在公共分支上回滚更改</td></tr><tr><td>git revert</td><td>文件层面</td><td>（木有）</td></tr></tbody></table>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git原理 — 2、Git对象和存储原理-Tree对象</title>
      <link href="/2022/09/15/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--3%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Tree%E5%AF%B9%E8%B1%A1/"/>
      <url>/2022/09/15/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--3%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Tree%E5%AF%B9%E8%B1%A1/</url>
      
        <content type="html"><![CDATA[<h2 id="1、Tree对象介绍"><a href="#1、Tree对象介绍" class="headerlink" title="1、Tree对象介绍"></a>1、Tree对象介绍</h2><p>接下来要探讨的 Git 对象类型是树对象（<code>tree object</code>），它能解决文件名保存的问题。<code>tree</code>对象可以存储文件名，也允许我们将多个文件组织到一起。</p><p>Git以一种类似于UNIX文件系统的方式存储内容，但做了一些简化。所有内容均以树（<code>tree</code>）对象和数据（<code>blob</code> ）对象的形式存储，其中树对象对应了UNIX中的目录项，数据对象<code>blob</code>则大致上对应了文件中的内容。</p><p>一个树对象可以包含一条或多条记录（<code>tree</code>对象和<code>blob</code> 对象），每条记录含有一个指向<code>blob</code> 对象或者子<code>tree</code>对象的<code>SHA-1</code>指针，以及相应的模式、类型、文件名信息。</p><p>如下图：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt<br></code></pre></td></tr></table></figure><p><strong>Tree对象存储方式如下图所示</strong>：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916085622.png"></p><h2 id="2、Tree对象说明"><a href="#2、Tree对象说明" class="headerlink" title="2、Tree对象说明"></a>2、Tree对象说明</h2><h3 id="2-1-（1）初始化一个新的本地版本库"><a href="#2-1-（1）初始化一个新的本地版本库" class="headerlink" title="2.1. （1）初始化一个新的本地版本库"></a>2.1. （1）初始化一个新的本地版本库</h3><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) git init gitObject &amp;&amp; cd gitObjec<br><br></code></pre></td></tr></table></figure><h3 id="（2）创建一个树对象（重点）"><a href="#（2）创建一个树对象（重点）" class="headerlink" title="（2）创建一个树对象（重点）"></a>（2）创建一个树对象（重点）</h3><p><strong>1）新建一个文件，然后把文件提交到本地版本库。</strong></p><p>例如：新建文件<code>test.txt</code>，文件内容<code>version 1</code>。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) echo &quot;version 1&quot; &gt;&gt; test.txt<br><br>➜  gitObject git:(master) ✗ cat test.txt     <br><br>version 1<br></code></pre></td></tr></table></figure><p><strong>2）把<code>test.txt</code>文件，提交到本地版本库。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗  git hash-object -w ./test.txt<br><br>83baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ git cat-file -t 83ba<br><br>blob<br><br>➜  gitObject git:(master) ✗ git cat-file -p 83ba<br><br>version 1<br></code></pre></td></tr></table></figure><p>以上就和我们讲<code>blob</code>对象的操作一样。</p><p>此时<code>test.txt</code>文件被管理在Git本地版本库中。</p><p><strong>3）创建一个树对象。</strong></p><p>通常Git是<strong>根据暂存区或者索引文件index来创建tree对象</strong>，因此要把文件存储到暂存区进并建立<code>index</code>文件。</p><p>提示1：</p><blockquote><p><code>index</code>文件在<code>.git</code>目录中，最新初始化的Git本地仓库是没有<code>index</code>文件，只有添加过一次数据到暂存区之后，才会在<code>.git</code>目录中自动生成<code>index</code>文件。</p></blockquote><p>新初始化的<code>.git</code>目录内容如下：是没有<code>index</code>文件的。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ ll .git/<br><br>total 24<br><br>-rw-r--r--   1 taylor  staff    23B  9 16 11:45 HEAD<br><br>-rw-r--r--   1 taylor  staff   137B  9 16 11:45 config<br><br>-rw-r--r--   1 taylor  staff    73B  9 16 11:45 description<br><br>drwxr-xr-x  15 taylor  staff   480B  9 16 11:45 hooks<br><br>drwxr-xr-x   3 taylor  staff    96B  9 16 11:45 info<br><br>drwxr-xr-x   5 taylor  staff   160B  9 16 11:48 objects<br><br>drwxr-xr-x   4 taylor  staff   128B  9 16 11:45 refs<br></code></pre></td></tr></table></figure><p>提示2：</p><p>可以通过<code>git ls-files</code>命令查看暂存区的文件信息。</p><p>参数信息如下，括号中简写：</p><ul><li><code>--cached(-c)</code>： 查看暂存区中文件。<code>git ls-files</code>命令默认执行此选项。</li><li><code>--midified(-m)</code>：查看修改的文件。</li><li><code>--delete(-d)</code>：查看删除过的文件。</li><li><code>--other(-o)</code> ：查看没有被Git跟踪的文件。</li><li><code>--stage(-s)</code>：显示mode以及文件对应的Blob对象，进而我们可以获取暂存区中对应文件里面的内容。stage：阶段编号，普通（未合并）文件通常为0。</li></ul><p>例如：<code>git ls-files -c</code>或者<code>git ls-files --cached</code> （其他命令同理）</p><p>我们常用<code>git ls-files -s</code>命令查看暂存区的文件信息。</p><p>接下来，我们可以通过底层命令：<code>update-index</code>、<code>write-tree</code>、<code>read-tree</code>等命令，轻松创建自己的<code>tree</code>对象。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git ls-files -s<br><br>➜  gitObject git:(master) ✗ git update-index --add --cacheinfo 100644 \<br><br> 83baae61804e65cc73a7201a7252750c76066a30 test.txt<br><br>➜  gitObject git:(master) ✗ git ls-files -s<br><br>100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt<br></code></pre></td></tr></table></figure><p>命令说明：</p><ul><li>为创建一个树对象，首先需要通过暂存一些文件到暂存区。<br>通过底层命令 <code>git update-index</code>将一个单独文件存入暂存区中。</li><li><code>--add</code> 选项：因为此前该文件并不在暂存区中，一个文件首次添加到暂存区，需要使用<code>--add</code> 选项。</li><li><code>--cacheinfo</code> 选项：因为要添加的<code>test.txt</code>文件位于Git 数据库中（上一步的操作），而不是位于当前工作目录，所以需要<code>--cacheinfo</code> 选项。</li><li>最后需要指定<code>文件模式</code>、<code>SHA-1</code> 与<code>文件名</code>。</li></ul><p>文件模式说明：</p><ul><li><code>100644</code>：表明这是一个普通文件。（blob对象的文件模式一般都为100644）</li><li><code>100755</code>：表示一个可执行文件。</li><li><code>120000</code>：表示一个符号链接。</li></ul><p>继续，下面来观察生成的树对象：：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">➜  gitObject git:(master) ✗ ll .git/<br><br>total 32<br><br>-rw-r--r--   1 taylor  staff    23B  9 16 11:45 HEAD<br><br>-rw-r--r--   1 taylor  staff   137B  9 16 11:45 config<br><br>-rw-r--r--   1 taylor  staff    73B  9 16 11:45 description<br><br>drwxr-xr-x  15 taylor  staff   480B  9 16 11:45 hooks<br><br>-rw-r--r--   1 taylor  staff   104B  9 16 11:52 index<br><br>drwxr-xr-x   3 taylor  staff    96B  9 16 11:45 info<br><br>drwxr-xr-x   5 taylor  staff   160B  9 16 11:48 objects<br><br>drwxr-xr-x   4 taylor  staff   128B  9 16 11:45 refs<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ git write-tree<br><br>d8329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ git cat-file -t d832<br><br>tree<br><br>➜  gitObject git:(master) ✗ git ls-files -s<br><br>100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt<br></code></pre></td></tr></table></figure><p><strong>4）总结</strong></p><p>以上就是在Git中，使用底层命令手动创建一个树对象的过程。</p><ul><li>创建一个文件，把该文件通过<code>git hash-object</code>命令存储到本地版本库中。</li><li>通过<code>git update-index</code>命令，把文件存储到暂存区中。</li><li>通过<code>git write-tree</code>命令，把暂存区中的文件索引信息提交到本地版本库，生成了一个树对象。</li></ul><h3 id="（3）创建第二个文件（重点）"><a href="#（3）创建第二个文件（重点）" class="headerlink" title="（3）创建第二个文件（重点）"></a>（3）创建第二个文件（重点）</h3><p><strong>1）新增<code>new.txt</code>文件，并修改<code>test.txt</code>文件内容。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ echo &quot;new file&quot; &gt; new.txt<br><br>➜  gitObject git:(master) ✗ echo &quot;version 2&quot; &gt;&gt; test.txt<br><br>➜  gitObject git:(master) ✗ cat new.txt<br><br>new file<br><br>➜  gitObject git:(master) ✗ cat test.txt<br><br>version 1<br><br>version 2<br></code></pre></td></tr></table></figure><p><strong>2）将<code>new.txt</code>文件和<code>test.txt</code>文件的第二个版本添加到暂存区。</strong></p><p>将<code>test.txt</code>文件添加到暂存区。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git ls-files -s<br><br>100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt<br><br>➜  gitObject git:(master) ✗ git hash-object -w ./test.txt<br><br>0c1e7391ca4e59584f8b773ecdbbb9467eba1547<br><br>➜  gitObject git:(master) ✗ git ls-files -s              <br><br>100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547<br><br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ git update-index --cacheinfo 100644 \<br><br> 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt<br><br>➜  gitObject git:(master) ✗ git ls-files -s                      <br><br>100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt<br></code></pre></td></tr></table></figure><p>将<code>new.txt</code>文件添加到暂存区。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git update-index --add new.txt<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547<br><br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ git ls-files -s<br><br>100644 fa49b077972391ad58037050f2a75f74e3671e92 0 new.txt<br><br>100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt<br></code></pre></td></tr></table></figure><p>说明：<code>git update-index --add 文件名</code>完成了之前的两步操作。</p><ol><li>把<code>new.txt</code>文件内容存入了Git版本库。</li><li>把<code>new.txt</code>文件添加到了暂存区中。</li></ol><p><strong>3）把暂存区的内容提交的本地版本库。</strong></p><p>此时工作目录和暂存区中的文件状态是一样的， 可以通过<code>git write-tree</code>命令提交到本地版本库，生成树对像了。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git write-tree<br><br>163b45f0a0925b0655da232ea8a4188ccec615f5<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547<br><br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5<br><br>.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br><br>➜  gitObject git:(master) ✗ git cat-file -t 163b<br><br>tree<br></code></pre></td></tr></table></figure><p>此时Git版本库中的5个对象，即表示了项目的2个版本。（不明白这句话？继续往下看）</p><h3 id="（4）将第一个树对象加入暂存区，使其成为新的树对"><a href="#（4）将第一个树对象加入暂存区，使其成为新的树对" class="headerlink" title="（4）将第一个树对象加入暂存区，使其成为新的树对"></a>（4）将第一个树对象加入暂存区，使其成为新的树对</h3><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git ls-files -s<br><br>100644 fa49b077972391ad58037050f2a75f74e3671e92 0 new.txt<br><br>100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt<br><br>➜  gitObject git:(master) ✗ git read-tree --prefix=bak d832<br><br>➜  gitObject git:(master) ✗ git ls-files -s<br><br>100644 83baae61804e65cc73a7201a7252750c76066a30 0 bak/test.txt<br><br>100644 fa49b077972391ad58037050f2a75f74e3671e92 0 new.txt<br><br>100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt<br></code></pre></td></tr></table></figure><p>说明：</p><ul><li><code>read-tree</code>命令：可以把树对象读入暂存区。</li><li><code>--prefix=bak</code>选项：将一个已有的树对象作为子树读入暂存区。</li><li>我们可以回忆一下，tree d832是什么时候write出来的，write的时候暂存区里有什么？然后看看read到暂存区之后，多出来的第一行数据，是不是就是当时生成tree d832时暂存区的内容</li></ul><p>接下来继续，再提交暂存区的内容，会继续生成一个新的<code>tree</code>对象在Git仓库中。</p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git write-tree<br><br>01ab2a43b1eb150bcf00f375800727df240cf653<br><br>➜  gitObject git:(master) ✗ git cat-file -t 01ab<br><br>tree<br><br>➜  gitObject git:(master) ✗ git cat-file -p 01ab<br><br>040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak<br><br>100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt<br><br>100644 blob 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt<br><br>➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547<br><br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br><br>.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5<br><br>.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653<br><br>.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92<br><br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br></code></pre></td></tr></table></figure><p>到这里我们的演示就完成了，请看下面的总结。</p><h2 id="3、总结"><a href="#3、总结" class="headerlink" title="3、总结"></a>3、总结</h2><h3 id="3-1-（1）分析每个树对象的存储结构"><a href="#3-1-（1）分析每个树对象的存储结构" class="headerlink" title="3.1. （1）分析每个树对象的存储结构"></a>3.1. （1）分析每个树对象的存储结构</h3><p>我们可以先查看一下Git本地库中的对象，如下</p><figure class="highlight plaintext"><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 plaintext">.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653<br>.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547<br>.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5<br>.git/objects/83/baae61804e65cc73a7201a7252750c76066a30<br>.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579<br>.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92<br></code></pre></td></tr></table></figure><p>我们接下来用三个图，描述一下三个树对象的结构关系。</p><p><strong>第一个树对象结构如下图</strong>：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220907084853.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916121420.png"></p><p><strong>第二个树对象结构如下图</strong>：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220907085300.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916121447.png"></p><p><strong>第三个树对象结构如下图</strong>：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220907085547.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916121512.png"></p><p>也可以换Git对象类型表示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220916121652.png"></p><h3 id="（2）blob对象和tree对象（重点）"><a href="#（2）blob对象和tree对象（重点）" class="headerlink" title="（2）blob对象和tree对象（重点）"></a>（2）blob对象和tree对象（重点）</h3><p>从上图我们可以分析出：</p><ul><li><code>blob</code>对象代表文件一次次的版本。</li><li><code>tree</code>对象代表项目的一次次的版本。</li></ul><p>这就是我前面<code>2-(3)</code>描述过的<strong>Git版本库中的5个对象，即表示了项目的2个版本。</strong></p><p>（就先这样理解）</p><h3 id="（3）总结（重点）"><a href="#（3）总结（重点）" class="headerlink" title="（3）总结（重点）"></a>（3）总结（重点）</h3><p>暂存区的概念和相关理解：</p><ol><li>所谓的暂存区<code>Stage</code>只是一个简单的索引文件而已。指的是是 <code>.git/index</code>文件。</li><li>暂存区这个索引文件里面包含的是文件的目录树，像一个虚拟的工作区，在这个虚拟工作区的目录树中，记录了文件名、文件的时间戳、文件长度、文件类型以及最重要的SHA-1值，文件的内容并没有存储在其中，所以说它像一个虚拟的工作区。<br>即：暂存区，也就是<code>.git/index</code>文件中存放的是文件内容的索引（快照），<del>也可以是<code>tree</code>对象的索引</del>。</li><li>索引指向的是<code>.git/objects/</code>目录下的文件（Git对象）。</li><li>Git通过暂存区的文件索引信息来创建<code>tree</code>对象的。</li><li><code>tree</code>对象可以使文件内容和文件名进行关联。</li><li>一个树对象可以包含一条或多条记录（<code>tree</code>对象和<code>blob</code> 对象）。</li><li><span style="background-color:#00ff00">暂存区内容写到版本库中后，暂存区索引内容不清空。</span></li><li>暂存区中的文件内容索引，是按对应文件覆盖的，也就是修改一个文件内容，添加到缓存区，只会把对应的文件覆盖，其他文件不会被覆盖，即：暂存区不是整体覆盖的。</li></ol><p><strong>暂存区的作用</strong>：除非是绕过暂存区直接提交，否则Git想把修改提交上去，就必须将修改存入暂存区最后才能commit。每次提交的是暂存区所对应的文件快照。</p><blockquote><p>提示：Git对象的hash键，我们截取前几位就行，练习时对象不那么对，就不用全部都写，能够表示唯一对象就行。</p></blockquote><h2 id="4、问题"><a href="#4、问题" class="headerlink" title="4、问题"></a>4、问题</h2><p>现在有三个树对象（因为执行了三次<code>write-tree</code>命令），分别代表了我们想要跟踪项目的三次快照。然而问题依旧：若想重用这些快照，你必须记住这三个树对象的<code>SHA-1</code>哈希值。</p><p>并且，你也完全不知道是谁保存了这些快照，在什么时刻保存的，以及为什么保存这些快照。</p><p>而以上这些，提交对象<code>commit object</code>为你保存了这些基本信息。</p><h2 id="5、本文用到的命令总结"><a href="#5、本文用到的命令总结" class="headerlink" title="5、本文用到的命令总结"></a>5、本文用到的命令总结</h2><p>Git底层命令：</p><ul><li><code>git update-index --add</code>：把文件索引（快照）存入暂存区中。</li><li><code>git write-tree</code>：将当前暂存区的索引内容同步到一个树对象中。</li><li><code>git ls-files -s</code>：查看暂存区的文件信息。</li><li><code>git read-tree --prefix=bak</code>：将一个已存在的树对象添加到暂存区。</li><li><code>git cat-file -t 键</code>：查看Git对象的类型。</li><li><code>git cat-file -p 键</code>：查看Git对象的内容。</li></ul><h2 id="6、演示示例代码"><a href="#6、演示示例代码" class="headerlink" title="6、演示示例代码"></a>6、演示示例代码</h2><h3 id="写文件到本地版本库"><a href="#写文件到本地版本库" class="headerlink" title="写文件到本地版本库"></a>写文件到本地版本库</h3><figure class="highlight plaintext"><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 plaintext">cd ~ &amp;&amp; rm -rf ~/gitObject<br><br>git init gitObject &amp;&amp; cd gitObject<br><br>echo &quot;version 1&quot; &gt;&gt; test.txt<br><br>cat test.txt<br><br>find .git/objects -type f<br><br>git hash-object -w ./test.txt<br></code></pre></td></tr></table></figure><h3 id="将本地版本库文件放入暂存区"><a href="#将本地版本库文件放入暂存区" class="headerlink" title="将本地版本库文件放入暂存区"></a>将本地版本库文件放入暂存区</h3><figure class="highlight plaintext"><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 plaintext">git cat-file -t 83ba<br><br>git cat-file -p 83ba<br><br>ll .git/<br><br>git ls-files -s<br><br>git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt<br></code></pre></td></tr></table></figure><h3 id="根据暂存区创建树对象"><a href="#根据暂存区创建树对象" class="headerlink" title="根据暂存区创建树对象"></a>根据暂存区创建树对象</h3><figure class="highlight plaintext"><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 plaintext">git ls-files -s<br><br>ll .git/<br><br>find .git/objects -type f<br><br>git write-tree<br><br>find .git/objects -type f<br><br>git cat-file -t d832<br><br>git ls-files -s<br></code></pre></td></tr></table></figure><h3 id="新增文件放入版本库"><a href="#新增文件放入版本库" class="headerlink" title="新增文件放入版本库"></a>新增文件放入版本库</h3><figure class="highlight plaintext"><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 plaintext">echo &quot;new file&quot; &gt; new.txt<br><br>echo &quot;version 2&quot; &gt;&gt; test.txt<br><br>cat new.txt<br><br>cat test.txt<br><br>git ls-files -s<br><br>git hash-object -w ./test.txt<br></code></pre></td></tr></table></figure><h3 id="更新暂存区已有文件"><a href="#更新暂存区已有文件" class="headerlink" title="更新暂存区已有文件"></a>更新暂存区已有文件</h3><figure class="highlight plaintext"><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 plaintext">git ls-files -s<br><br>find .git/objects -type f<br><br>git update-index --cacheinfo 100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt<br></code></pre></td></tr></table></figure><h3 id="新文件加入暂存区"><a href="#新文件加入暂存区" class="headerlink" title="新文件加入暂存区"></a>新文件加入暂存区</h3><figure class="highlight plaintext"><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 plaintext">git ls-files -s<br><br>git update-index --add new.txt<br><br>find .git/objects -type f<br><br>git ls-files -s<br></code></pre></td></tr></table></figure><h3 id="创建第二棵树对象"><a href="#创建第二棵树对象" class="headerlink" title="创建第二棵树对象"></a>创建第二棵树对象</h3><figure class="highlight plaintext"><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 plaintext">git write-tree<br><br>find .git/objects -type f<br><br>git cat-file -t 163b<br><br>git ls-files -s<br></code></pre></td></tr></table></figure><h3 id="创建第三棵树对象"><a href="#创建第三棵树对象" class="headerlink" title="创建第三棵树对象"></a>创建第三棵树对象</h3><figure class="highlight shell"><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 shell">git read-tree --prefix=bak d832<br><br>git ls-files -s<br><br>git write-tree<br><br>git cat-file -t 01ab<br><br>git cat-file -p 01ab<br><br>find .git/objects -type f<br></code></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.cnblogs.com/liuyuelinfighting/p/16189429.html">https://www.cnblogs.com/liuyuelinfighting/p/16189429.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git原理 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>知识管理--个人博客-1、Hexo博客搭建方法和问题记录</title>
      <link href="/2022/09/14/013-%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86--%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2-1%E3%80%81Hexo%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E6%96%B9%E6%B3%95%E5%92%8C%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/"/>
      <url>/2022/09/14/013-%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86--%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2-1%E3%80%81Hexo%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E6%96%B9%E6%B3%95%E5%92%8C%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/</url>
      
        <content type="html"><![CDATA[<h1 id="1-搭建过程"><a href="#1-搭建过程" class="headerlink" title="1. 搭建过程"></a>1. 搭建过程</h1><p>todo</p><h1 id="2-修改记录"><a href="#2-修改记录" class="headerlink" title="2. 修改记录"></a>2. 修改记录</h1><p>todo</p><h1 id="3-问题记录"><a href="#3-问题记录" class="headerlink" title="3. 问题记录"></a>3. 问题记录</h1><h2 id="3-1-博客内文章跳转一"><a href="#3-1-博客内文章跳转一" class="headerlink" title="3.1. 博客内文章跳转一"></a>3.1. 博客内文章跳转一</h2><h3 id="3-1-1-安装依赖"><a href="#3-1-1-安装依赖" class="headerlink" title="3.1.1. 安装依赖"></a>3.1.1. 安装依赖</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm install hexo-backlink<br></code></pre></td></tr></table></figure><h3 id="3-1-2-Obsidian配置"><a href="#3-1-2-Obsidian配置" class="headerlink" title="3.1.2. Obsidian配置"></a>3.1.2. Obsidian配置</h3><p>Obsidian里本身支持内部链接，如果需要在Hexo上生效，需要做以下改动<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220915083633.png"></p><h3 id="3-1-3-避坑指南"><a href="#3-1-3-避坑指南" class="headerlink" title="3.1.3. 避坑指南"></a>3.1.3. 避坑指南</h3><p>但是<font color=#ff0000><span style="background-color:#ffff00">这里有个坑</span></font>，文章名称中不能有空格，否则会报文章找不到的错误，这个插件不是很完善，所以还是建议使用下面的方法二。<br>但是在obsidian中方法二就无法使用obsidian的内部链接了。最终折中办法是文件名称中去掉空格，标题中加上空格，然后在obsidian内部链接中加别名设置，如下：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220915102634.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&#123;% post_link 000-工具箱专题/gitconfig(global、system、local) &#x27;git config(global、system、local)&#x27; %&#125;<br></code></pre></td></tr></table></figure><h2 id="3-2-博客内文章跳转二"><a href="#3-2-博客内文章跳转二" class="headerlink" title="3.2. 博客内文章跳转二"></a>3.2. 博客内文章跳转二</h2><p>如果有空格，加上双引号就解决了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&#123;% post_link &quot;git config(global、system、local)&quot; %&#125;<br></code></pre></td></tr></table></figure><h2 id="3-3-文章内锚点跳转"><a href="#3-3-文章内锚点跳转" class="headerlink" title="3.3. 文章内锚点跳转"></a>3.3. 文章内锚点跳转</h2><p>📢 首先需要注意的是：<span style="background-color:#ffff00">Obsidian中Markdown的跳转方式在Hexo中是无效的</span></p><p>需要使用下面的语法格式</p><figure class="highlight plaintext"><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 plaintext">[3.演示示例](#3-演示示例)<br>[]中是显示的名字，()中的内容，可以去页面上进行拷贝，比如我这里就是:#3-演示示例<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220915082842.png"></p>]]></content>
      
      
      <categories>
          
          <category> 知识管理 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Hexo </tag>
            
            <tag> 个人博客 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶 — 5、Git-后悔药-重写历史-git commit--amend</title>
      <link href="/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--5%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2-gitcommit--amend/"/>
      <url>/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--5%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2-gitcommit--amend/</url>
      
        <content type="html"><![CDATA[<p>修改最近一次的提交，可以是提交备注信息，也可以是文件内容。会生成一个新的commit替换掉最新的一条commit</p><blockquote><p>发散： 在rebase的交互式操作选择e(edit)时也存在这个命令的使用，这里使用时，替换的是选中的commit</p></blockquote><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.qikegu.com/docs/4399">https://www.qikegu.com/docs/4399</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶 — 6、Git找回丢失代码 git fsck --lost-found</title>
      <link href="/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--6%E3%80%81Git%E6%89%BE%E5%9B%9E%E4%B8%A2%E5%A4%B1%E4%BB%A3%E7%A0%81-gitfsck--lost-found/"/>
      <url>/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--6%E3%80%81Git%E6%89%BE%E5%9B%9E%E4%B8%A2%E5%A4%B1%E4%BB%A3%E7%A0%81-gitfsck--lost-found/</url>
      
        <content type="html"><![CDATA[<h1 id="1-查找丢失数据"><a href="#1-查找丢失数据" class="headerlink" title="1. 查找丢失数据"></a>1. 查找丢失数据</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] [--[no-]full] [--strict] [--verbose] [--lost-found] [--[no-]dangling] [--[no-]progress] [--connectivity-only] [--[no-]name-objects] [&lt;object&gt;*]<br></code></pre></td></tr></table></figure><p>git fsck –lost-found 是一个写出文件动作，<span style="background-color:#ffff00">执行该命令前不会有以下 lost-found目录</span>。执行之后会根据情况将悬挂对象写入.git&#x2F;lost-found&#x2F;commit&#x2F;或.git&#x2F;lost-found&#x2F;other&#x2F;</p><p>具体取决于类型。如果对象是blob，则将内容写入文件中，而不是其对象名称。</p><h2 id="1-1-commit目录"><a href="#1-1-commit目录" class="headerlink" title="1.1. commit目录"></a>1.1. commit目录</h2><p>丢失的commit信息，丢失的stash信息</p><h2 id="1-2-other目录"><a href="#1-2-other目录" class="headerlink" title="1.2. other目录"></a>1.2. other目录</h2><p>add但未commit的，请看示例<br><a href="#3-6-add%E4%BD%86%E6%9C%AAcommit%E6%81%A2%E5%A4%8D%E6%96%B9%E6%B3%95">add但未commit恢复方法</a></p><h1 id="2-适用场景"><a href="#2-适用场景" class="headerlink" title="2. 适用场景"></a>2. 适用场景</h1><h2 id="2-1-误删branch"><a href="#2-1-误删branch" class="headerlink" title="2.1. 误删branch"></a>2.1. 误删branch</h2><h2 id="2-2-误删stash"><a href="#2-2-误删stash" class="headerlink" title="2.2. 误删stash"></a>2.2. 误删stash</h2><h2 id="2-3-切换branch丢失commit"><a href="#2-3-切换branch丢失commit" class="headerlink" title="2.3. 切换branch丢失commit"></a>2.3. 切换branch丢失commit</h2><h2 id="2-4-add后未commit执行了git-reset-–hard"><a href="#2-4-add后未commit执行了git-reset-–hard" class="headerlink" title="2.4. add后未commit执行了git reset –hard"></a>2.4. add后未commit执行了git reset –hard</h2><blockquote><p>如果你没有<code>commit</code>你的本地修改（甚至于你都没有通过<code>git add</code>追踪过这些文件，当他们被删除，<code>git reset --hard</code>对于这些没有被commit过也没有git add过的修改来说就是具有毁灭性的<br>but，如果你幸运的是曾经通过git add命令追踪过这些文件，只是没有commit它们而已！那么试试<code>git fsck --lost-found</code>这个命令吧！然后你就可以在本地项目文件中路径为<code>.git/lost-found/other</code>中找到它们！！并且呢，这里面包含了所有的没有被commit（指定到某次commit）的文件，甚至可能还包括你每次git add的版本（version一词实在不知道在这里怎么翻译，姑且就认为是版本吧）！<br>使用git fsck –lost-found这个命令，通过.git&#x2F;lost-found&#x2F;other这个路径，你可以恢复任何你git add过的文件！再通过find .git&#x2F;objects -type f | xargs ls -lt | sed 60q这个命令，你就可以找到最近被你add到本地仓库的60个文件<br>当然咯，如果你没有git add过的文件呢，被git reset –hard这个命令整过之后呢，就如你自己执行delete命令一样，再也尸骨难寻啦（也就是真的毛都不剩了！！默哀三秒）！！！！</p></blockquote><ul><li><code>q</code> 表示退出，不再处理后续内容。比如 <code>sed &#39;2q&#39; p.txt</code> 执行到第二行后退出。</li></ul><h1 id="3-演示示例"><a href="#3-演示示例" class="headerlink" title="3. 演示示例"></a>3. 演示示例</h1><figure class="highlight shell"><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 shell">mkdir recovery;cd recovery  <br>git init  <br>touch file  <br>git add file  <br>git commit -m &quot;First commit&quot;  <br>echo &quot;Hello World&quot; &gt; file  <br>git add .  <br>git commit -m &quot;Greetings&quot;  <br>git branch cool_branch<br>git checkout cool_branch<br>echo &quot;What up world?&quot; &gt; cool_file  <br>git add .  <br>git commit -m &quot;Now that was cool&quot;  <br>git checkout master<br>echo &quot;What does that mean?&quot; &gt;&gt; file<br></code></pre></td></tr></table></figure><p>存储当前仓库未提交的改动，也便于后面演示如何恢复stash</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git stash save &quot;temp save&quot;<br></code></pre></td></tr></table></figure><h2 id="3-1-恢复误删除分支提交"><a href="#3-1-恢复误删除分支提交" class="headerlink" title="3.1. 恢复误删除分支提交"></a>3.1. 恢复误删除分支提交</h2><p>现在repo里有两个branch</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git branch<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914175750.png"></p><p>删除一个分支</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git branch -D cool_branch<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914182513.png"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git log<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914182242.png"></p><h2 id="3-2-查看当天的丢失记录"><a href="#3-2-查看当天的丢失记录" class="headerlink" title="3.2. 查看当天的丢失记录"></a>3.2. 查看当天的丢失记录</h2><p>用git fsck –lost-found命令找出刚才删除的分支里面的提交对象</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914182624.png"><br>或者使用下面的shell脚本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git fsck --lost-found 2&amp;&gt;/dev/null | while read i; do; git show `echo $i | cut -d &#x27; &#x27; -f 3` | head -n 6; done<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914182542.png"></p><p>可以看到我们丢失的分支cool_branch中的commit为9144，正是我们演示示例中的commit<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914180054.png"></p><p><span style="background-color:#ffff00">值得注意的是</span>：此时我们执行<code>git fsck --unreachable</code> 不会得到任何信息，因为已经删除了</p><h2 id="3-3-commit恢复方法"><a href="#3-3-commit恢复方法" class="headerlink" title="3.3. commit恢复方法"></a>3.3. commit恢复方法</h2><h3 id="3-3-1-git-rebase"><a href="#3-3-1-git-rebase" class="headerlink" title="3.3.1. git rebase"></a>3.3.1. git rebase</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git rebase 21a2<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914182827.png"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git log<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914182853.png"></p><p>我们可以发现被误删分支cool_branch的提交21a2被rebase到了master分支上<br><span style="background-color:#ffff00">但丢失的分支是无法找回的</span></p><h3 id="3-3-2-git-merge"><a href="#3-3-2-git-merge" class="headerlink" title="3.3.2. git merge"></a>3.3.2. git merge</h3><p>为了演示git merge方法，我们使用下面的命令把上面恢复的分支提交再次删掉</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git reset --hard HEAD^<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914183138.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914183208.png"><br>我们可以看到丢失的分支提交仍然是21a2</p><p>执行git merge</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git merge 21a2<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914183327.png"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git log<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914183352.png"></p><h3 id="3-3-3-patch"><a href="#3-3-3-patch" class="headerlink" title="3.3.3. patch"></a>3.3.3. patch</h3><blockquote><p>git-format-patch将提交导出为补丁文件，然后可以将其应用于另一个分支或克隆的存储库。补丁文件表示单个提交，Git在导入补丁文件时<font color=#ff0000><span style="background-color:#ffff00">重新提交</span></font>。</p></blockquote><p>git-format-patch是将更改从一个存储库副本转移到另一个存储库副本的短流程中的第一步。当Git只在本地使用而没有远程存储库时，旧的方式是通过电子邮件将补丁互相发送。如果您只需要向某人提交一次提交，而不需要合并分支和随之而来的开销，那么这是非常方便的。</p><p>为方便演示，这里重新执行了<a href="#3-%E6%BC%94%E7%A4%BA%E7%A4%BA%E4%BE%8B">3.演示示例</a> 查找丢失commit信息如下：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914190113.png"></p><p>漾我们把丢失的commit打成补丁</p><figure class="highlight shell"><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 shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">n指从sha1 <span class="hljs-built_in">id</span>对应的commit开始算起n个提交。</span><br>git format-patch 【commit sha1 id】-n<br><span class="hljs-meta prompt_"># </span><span class="language-bash">eg</span><br>git format-patch e24e -1<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914190411.png"></p><p>检查patch能否正常应用</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git apply --check 0001-Now-that-was-cool.patch<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914190542.png"><br>不报错就是最好的暗示🤭</p><p>应用patch</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git apply 0001-Now-that-was-cool.patch<br></code></pre></td></tr></table></figure><p>或者</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git am 0001-Now-that-was-cool.patch<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914190833.png"><br>同样的，不报错就是最好的暗示😉</p><p>查看版本库状态<code>git status</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191325.png"></p><p><code>git add .</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191419.png"></p><p><code>git commit</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191536.png"></p><p><code>git log</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191627.png"><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191827.png"></p><p><code>git show c73c</code><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191758.png"></p><p>查看工作区文件<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191916.png"></p><p>查看cool_file内容<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914191950.png"></p><p>可以看到丢失的分支cool_branch上的提交，以打补丁的方式补在了master分支上</p><h2 id="3-4-blob对象恢复"><a href="#3-4-blob对象恢复" class="headerlink" title="3.4. blob对象恢复"></a>3.4. blob对象恢复</h2><p>可以通过<a href="#3-2-%E6%9F%A5%E7%9C%8B%E5%BD%93%E5%A4%A9%E7%9A%84%E4%B8%A2%E5%A4%B1%E8%AE%B0%E5%BD%95">查看当天的丢失记录</a>，也可以通过以下脚本查看最近的60次add的文件列表，然后进去查看内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">find .git/objects -type f | xargs ls -lt | sed 60q<br></code></pre></td></tr></table></figure><p>我们恢复的时候只能恢复unreachable commit 开头的记录，unreach blob是不能用git statsh apply+sha-1来恢复的，会报not a stash-like commit 错误</p><h3 id="3-4-1-手动粘贴"><a href="#3-4-1-手动粘贴" class="headerlink" title="3.4.1. 手动粘贴"></a>3.4.1. 手动粘贴</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git cat-file -p ID &gt; a.md<br></code></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git show eb6e &gt;/Volumes/shaw/project/temp/1.txt<br></code></pre></td></tr></table></figure><h2 id="3-5-stash恢复方法"><a href="#3-5-stash恢复方法" class="headerlink" title="3.5. stash恢复方法"></a>3.5. stash恢复方法</h2><p>把前面演示示例stash的内容清空</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git stash clear<br></code></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git fsck --lost-found<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914184109.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914184207.png"></p><p>也可以用<code>git show e38f</code>或者<code>git cat-file -p e38f</code>查看commit对象的具体内容<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914184442.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914184405.png"></p><h3 id="3-5-1-git-merge"><a href="#3-5-1-git-merge" class="headerlink" title="3.5.1. git merge"></a>3.5.1. git merge</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git merge e38f<br></code></pre></td></tr></table></figure><p>因为这里的类型还是commit，git merge方法上面已经演示过，我们只演示下面的git stash apply方法</p><h3 id="3-5-2-git-stash-apply"><a href="#3-5-2-git-stash-apply" class="headerlink" title="3.5.2. git stash apply"></a>3.5.2. git stash apply</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git stash apply e38f<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914184854.png"></p><p>值得注意的是，stash类型的commit恢复可以使用git stash apply和git merge方法，但是非stash的commit对象无法使用git stash apply方法，否则会提示以下错误信息</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914185226.png"></p><h2 id="3-6-add但未commit恢复方法"><a href="#3-6-add但未commit恢复方法" class="headerlink" title="3.6. add但未commit恢复方法"></a>3.6. add但未commit恢复方法</h2><p>演示准备</p><figure class="highlight shell"><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 shell">mkdir recovery;cd recovery  <br>git init  <br>touch file  <br>git add file  <br>git commit -m &quot;First commit&quot;  <br>echo &quot;Hello World&quot; &gt; file  <br>git add . <br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914193253.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220914215707.png"></p><p>我们可以看到它是一个blog对象，这种对象是无法直接rebase或者merge或者打补丁，也没法apply，只能通过手动粘贴的方式找回自己的劳动成果，请见<a href="#3-4-blob%E5%AF%B9%E8%B1%A1%E6%81%A2%E5%A4%8D">3.4.blob对象恢复</a></p><h1 id="4-参考"><a href="#4-参考" class="headerlink" title="4. 参考"></a>4. 参考</h1><p><a href="https://git-scm.com/docs/git-fsck">https://git-scm.com/docs/git-fsck</a></p><p><a href="https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E7%BB%B4%E6%8A%A4%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D#_data_recovery">https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E7%BB%B4%E6%8A%A4%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D#_data_recovery</a></p><p><a href="https://www.jianshu.com/p/2c900550c076">https://www.jianshu.com/p/2c900550c076</a></p><p><a href="https://www.cnblogs.com/liulaolaiu/archive/2012/08/08/11744888.html">https://www.cnblogs.com/liulaolaiu/archive/2012/08/08/11744888.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
            <tag> Git维护与数据恢复 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git原理 — 1、Git目录结构及Git三棵树(工作区、暂存区、版本库)</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--1%E3%80%81Git%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E5%8F%8AGit%E4%B8%89%E6%A3%B5%E6%A0%91(%E5%B7%A5%E4%BD%9C%E5%8C%BA%E3%80%81%E6%9A%82%E5%AD%98%E5%8C%BA%E3%80%81%E7%89%88%E6%9C%AC%E5%BA%93)/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--1%E3%80%81Git%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E5%8F%8AGit%E4%B8%89%E6%A3%B5%E6%A0%91(%E5%B7%A5%E4%BD%9C%E5%8C%BA%E3%80%81%E6%9A%82%E5%AD%98%E5%8C%BA%E3%80%81%E7%89%88%E6%9C%AC%E5%BA%93)/</url>
      
        <content type="html"><![CDATA[<h2 id="1-Git目录结构"><a href="#1-Git目录结构" class="headerlink" title="1. Git目录结构"></a>1. Git目录结构</h2><pre><code>* hooks (钩子函数的一个库 类似于回调函数)* info (包含一个全局性的排除文件)* objects (目录存储所有数据内容)* refs (目录存储指向数据（分支）的提交对象的指针)* config (文件包含项目特有的配置选项)* description (显示对仓库的描述信息)* HEAD (文件目前被检出的分支) * logs (日志信息)* index (文件保存暂存区的信息)</code></pre><h2 id="2-三个区的位置"><a href="#2-三个区的位置" class="headerlink" title="2. 三个区的位置"></a>2. 三个区的位置</h2><p>我们先来理解下Git 工作区、暂存区和版本库概念，这对以后我们学习Git命令会有非常大的帮助。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912153534.png"></p><h3 id="2-1-工作区"><a href="#2-1-工作区" class="headerlink" title="2.1. 工作区"></a>2.1. 工作区</h3><p>一般我们执行<code>git init</code>或者<code>git clone</code>命令，就能把一个目录初始化成Git本地版本库。</p><p>而这个目录就是该Git本地版本库的工作区。</p><p>如下图：<code>gittest</code>目录就是一个本地仓库。</p><p>具体结构如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912152703.png"></p><h3 id="2-2-暂存区"><a href="#2-2-暂存区" class="headerlink" title="2.2. 暂存区"></a>2.2. 暂存区</h3><ul><li>暂存区从字面上去理解就是用来暂时保存文件的地方，实际上它的作用和它的名字是一致的，暂存区可以起到过渡的作用，当我们写代码修改了一些文件的时候，可以把修改的代码提交到暂存区保存，然后接着写代码，接着再提交到暂存区保存，写完某些代码觉得没什么可以修改的时候，可以将暂存区里面的文件一次性提交到版本库。</li><li>暂存区英文叫<code>stage</code>，或<code>index</code>。</li><li>暂存区是包含在版本库中的，一般存放在<code>.git</code>目录下的<code>index文件</code>（<code>.git/index</code>）中，所以我们把暂存区有时也叫作索引（<code>index</code>）。</li></ul><p>暂存区位置如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912153015.png"></p><h3 id="2-3-版本库"><a href="#2-3-版本库" class="headerlink" title="2.3. 版本库"></a>2.3. 版本库</h3><p><strong>版本库</strong>：工作区（项目根目录）有一个隐藏目录<code>.git</code>就是版本库，包括<code>索引树(index文件)</code>和<code>本地版本库(objects目录)</code>，而该目录不算工作区。</p><p>具体结构如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912152830.png"></p><p><strong>说明：</strong></p><p>版本库又存在两个很重要的区域：暂存区与分支区。</p><p>分支区：该区域中可以包含很多分支，而每个分支都可以记录当前工作区中文件状态的快照。</p><p>如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912154910.png"></p><p>即：分支区<code>.git/objects</code>就相当于本地版本库。</p><h2 id="3-三个区的关系"><a href="#3-三个区的关系" class="headerlink" title="3. 三个区的关系"></a>3. 三个区的关系</h2><p><strong>1）工作区新加文件 <code>index.html</code></strong></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912160646.png"></p><p><strong>2）将index.html提交到暂存区</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912160818.png"></p><p><strong>3）将暂存区内的内容提交到版本库</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912180650.png"></p><p><strong>4）将本地版本推送到远程仓库上</strong><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912180351.png"></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git原理 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git原理 — 2、Git对象和存储原理-Blob对象</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--2%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Blob%E5%AF%B9%E8%B1%A1/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--2%E3%80%81Git%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86-Blob%E5%AF%B9%E8%B1%A1/</url>
      
        <content type="html"><![CDATA[<blockquote><p>Git 是一套内容寻址文件系统。什么意思呢？</p><p>就是Git的核心部分是一个简单的键值数据库（<code>key-value data store</code>）。你可以向该数据库插入任意类型的内容，并会返回一个键值，通过该键值可以在任何时候再取出该内容。</p></blockquote><h1 id="1-Git对象的存放目录"><a href="#1-Git对象的存放目录" class="headerlink" title="1. Git对象的存放目录"></a>1. Git对象的存放目录</h1><p>Git中对象都保存在本地版本库的<code>.git/objects</code> 目录（即：<strong>对象数据库</strong>）中。</p><p>首先初使化一个Git仓库：</p><figure class="highlight plaintext"><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 plaintext">➜  ~ mkdir gitObject           <br><br>➜  ~ cd gitObject       <br><br>➜  gitObject git init .             <br><br>已初始化空的 Git 仓库于 /Users/taylor/gitObject/.git/<br><br>➜  gitObject git:(master) cd .git     <br><br>➜  .git git:(master) ll<br><br>total 24<br><br>-rw-r--r--   1 taylor  staff    23B  9 15 20:30 HEAD<br><br>-rw-r--r--   1 taylor  staff   137B  9 15 20:30 config<br><br>-rw-r--r--   1 taylor  staff    73B  9 15 20:30 description<br><br>drwxr-xr-x  15 taylor  staff   480B  9 15 20:30 hooks<br><br>drwxr-xr-x   3 taylor  staff    96B  9 15 20:30 info<br><br>drwxr-xr-x   4 taylor  staff   128B  9 15 20:30 objects<br><br>drwxr-xr-x   4 taylor  staff   128B  9 15 20:30 refs<br><br>➜  .git git:(master) ll -a objects <br><br>total 0<br><br>drwxr-xr-x  4 taylor  staff   128B  9 15 20:30 .<br><br>drwxr-xr-x  9 taylor  staff   288B  9 15 20:30 ..<br><br>drwxr-xr-x  2 taylor  staff    64B  9 15 20:30 info<br><br>drwxr-xr-x  2 taylor  staff    64B  9 15 20:30 pack<br></code></pre></td></tr></table></figure><p>从上可以看到Git初始化一个本地版本库的时候，就已经初始化了<code>objects</code>目录，并在其中创建了<code>pack</code>和<code>info</code>子目录，但是没有其他常规文件，<code>pack</code>和<code>info</code>子目录中也没有文件。我们只关注<code>objects</code>目录下除了<code>info</code>和<code>pack</code>目录之外的变化。</p><h1 id="2-Git中对象类型"><a href="#2-Git中对象类型" class="headerlink" title="2. Git中对象类型"></a>2. Git中对象类型</h1><p>Git中对象类型有四种：<code>blob（块）</code>对象，<code>tree（目录树）</code>对象，<code>commit（提交）</code>对象和<code>tag（标签）</code>对象，这四种原子对象构成了Git高层数据结构的基础。</p><h2 id="对象数据结构及SHA值"><a href="#对象数据结构及SHA值" class="headerlink" title="对象数据结构及SHA值"></a>对象数据结构及SHA值</h2><p><strong>首先</strong>，对象文件数据结构如下图：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220921095453.png"></p><ul><li>content： 表示数据内容</li><li>head: 对象头部信息<ul><li>object type：对象类型，可选值为 blob, tree, commit</li><li>whitespace: 一个空格字符</li><li>content byte size：数据内容的字节数<strong>字符串</strong></li><li>NUL：空字符，ASCII码值为0</li></ul></li></ul><p><strong>然后</strong>, 对象的SHA值就是对上面这个数据结构执行SHA1 hash摘要算法得到的。</p><p>3种对象不同的是content部分，查看content命令如下：</p><figure class="highlight shell"><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 shell">git cat-file -p SHA-1<br><span class="hljs-meta prompt_"># </span><span class="language-bash">或者</span><br>git show SHA-A<br></code></pre></td></tr></table></figure><h1 id="3-blob对象定义"><a href="#3-blob对象定义" class="headerlink" title="3. blob对象定义"></a>3. blob对象定义</h1><p><code>blob</code>对象又叫数据对象。</p><p><code>blob</code>对象是用来存储文本内容的。即把一个文本文件的内容，作为一个<code>blob</code>对象存储在Git系统中。</p><ul><li>Git中<code>blob</code>对象就是对应文件系统中的文件，确切的说是文件的内容，包含<br>键：一个hash值和校验值的组合，<br>值：文件的内容。</li><li>比较特殊的是：<code>blob</code>对象只存内容，不存文件名，文件名在<code>tree</code>对象中保存。</li></ul><p><strong><code>blob</code>对象存储方式如下图</strong>：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220915203645.png"></p><h1 id="4-blob对象说明"><a href="#4-blob对象说明" class="headerlink" title="4. blob对象说明"></a>4. blob对象说明</h1><p>通过底层命令 <code>git hash-object</code> 来演示，该命令可将任意数据保存于 <code>.git/objects</code> 目录（即 <strong>对象数据库</strong>）中，并返回指向该数据对象的唯一的键。</p><p><strong>1）创建一个新的数据对象，并将它手动存入你的新Git数据库中：</strong></p><figure class="highlight shell"><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 shell">➜ gitObject git:(master) ✗ echo &#x27;git object test content&#x27; | git hash-object -w --stdin<br><br>cb2eb834126f53952590c448f14fece6cbb1bff3<br></code></pre></td></tr></table></figure><p><strong>说明</strong>：这是在Git中以最简单的形式存储数据到Git版本库中，<code>git hash-object</code>命令会接受你传给它的东西，而它只会返回，可以存储在Git仓库中数据对象的唯一的键。</p><p>命令含义如下：</p><ul><li><code>git hash-object</code>：Git底层命令，可以根据传入的文本内容返回表示这些内容的键值。</li><li><code>git object test content</code>：为文本文件中的内容。</li><li><code>-w</code>选项：表示<code>hash-object</code>命令将数据对象存储到Git数据库中；若不指定此选项，则该命令仅返回对应的键（也就是那串Hash值）</li><li><code>--stdin</code> 选项：表示该命令从标准输入（比如键盘）读取内容，若不指定此选项，则须在命令尾部给出待存储文件的路径。例如：<code>git hash-object -w 文件路径</code>。</li></ul><p>此命令输出一个长度为40个字符的校验和，这是一个<code>SHA-1</code>哈希值，如上<code>cb2eb834126f53952590c448f14fece6cbb1bff3</code>。该值是文件原内容加上特定头部信息拼接起来，做散列计算得到的数值。（只要文本内容相同，计算出的结果都是一样的）</p><p><strong>2）在计算机中查看本地版本库<code>.git/objects</code> 目录中的变化。</strong></p><p>可以看到<code>.git/objects</code> 目录中多了一个<code>cb</code>文件夹，如下：</p><figure class="highlight plaintext"><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 plaintext">➜ gitObject git:(master) ✗ ll -a .git/objects/cb<br><br>total 8<br><br>drwxr-xr-x  3 taylor  staff    96B  9 15 20:37 .<br><br>drwxr-xr-x  5 taylor  staff   160B  9 15 20:37 ..<br><br>-r--r--r--  1 taylor  staff    40B  9 15 20:37 2eb834126f53952590c448f14fece6cbb1bff3<br></code></pre></td></tr></table></figure><p><strong>3）看本地版本库<code>.git/objects</code> 目录中的变化。</strong></p><figure class="highlight plaintext"><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 plaintext">➜ gitObject git:(master) ✗ find .git/objects -type f                         <br><br>.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3<br></code></pre></td></tr></table></figure><p>我们可以看到<code>cb</code>目录和<code>cb</code>目录中的<code>2eb834126f53952590c448f14fece6cbb1bff3</code>文件。</p><p>这就是Git中一个<code>blob</code>对象的存储。</p><h1 id="5-blob对象存储的方式"><a href="#5-blob对象存储的方式" class="headerlink" title="5. blob对象存储的方式"></a>5. blob对象存储的方式</h1><p>Git对象的寻址使用40位的16进制数表示，也就是<code>SHA-1</code>散列码，例如：<code>cb2eb834126f53952590c448f14fece6cbb1bff3</code>。</p><p><strong>为了管理方便，在文件系统中前两位作为<code>.git/objects/</code> 子目录的名字，后38为作为文件名字。</strong></p><blockquote><p>提示：你可能感觉用40位作为Git对象的寻址ID</p></blockquote><h1 id="6-查看blob对象内容"><a href="#6-查看blob对象内容" class="headerlink" title="6. 查看blob对象内容"></a>6. 查看blob对象内容</h1><p>**我们需要根据Hash键读取数据，使用命令<code>git cat-file -p 键</code>**。</p><p><code>-p</code>选项可指示该命令自动判断内容的类型，并为我们使用友好的格式显示内容。</p><figure class="highlight plaintext"><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 plaintext">➜ gitObject git:(master) ✗ git cat-file -p cb2e              <br><br>git object test content<br></code></pre></td></tr></table></figure><blockquote><p>提示：<br>用<code>cat</code>命令直接读取Git对象文件，为什么是乱码信息？</p><p>文件内容是先通过 <code>zlib</code> 压缩，然后将 <code>zlib</code> 压缩后的内容写入磁盘文件(<code>SHA-1</code> 前两个字符作为子目录名称，后 38 个字符作为子目录文件的名称)</p></blockquote><h1 id="7-查看Git对象的类型"><a href="#7-查看Git对象的类型" class="headerlink" title="7. 查看Git对象的类型"></a>7. 查看Git对象的类型</h1><p>通过<code>git cat-file -t 键</code>命令，可以查看<code>.git/objects</code> 目录中Git对象的类型</p><figure class="highlight plaintext"><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 plaintext">➜ gitObject git:(master) ✗ git cat-file -t cb2e                                    <br><br>blob<br></code></pre></td></tr></table></figure><p>这里也说明，我们之前存储的Git对象是一个<code>blob</code>对象。</p><h1 id="8-Git管理文件"><a href="#8-Git管理文件" class="headerlink" title="8. Git管理文件"></a>8. Git管理文件</h1><p>至此，你已经掌握了如何向 Git 中存入内容，以及如何将它们取出。</p><p>我们同样可以将这些操作应用于文件中的内容。 例如，可以对一个文件进行简单的版本控制。</p><p><strong>1）首先，创建一个文件。</strong></p><figure class="highlight plaintext"><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 plaintext">➜ gitObject git:(master) ✗ echo &quot;hello-git.txt v1&quot; &gt; hello-git.txt<br><br>➜ gitObject git:(master) ✗  cat hello-git.txt<br><br>hello-git.txt v1<br></code></pre></td></tr></table></figure><p>此时文件还有被Git管理。</p><p><strong>2）将<code>hello-git.txt</code>文件存入Git数据库。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git hash-object -w ./hello-git.txt<br><br>a620c95d3001e1f64cecfc6715f9750cc7bbbf98<br></code></pre></td></tr></table></figure><p><strong>3）查看Git数据库内容。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98<br><br>.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3<br></code></pre></td></tr></table></figure><p>可以看到有多了一个<code>a6</code>子目录，就说明有新增了一个对象。</p><p><strong>4）查看<code>a6</code>对象的内容。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git cat-file -p a620<br><br>hello-git.txt v1<br></code></pre></td></tr></table></figure><p><strong>5）查看<code>a6</code>对象的类型。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git cat-file -t a620<br><br>blob<br></code></pre></td></tr></table></figure><p>可以看到，不管是你存储一个文件还是存储控制台内容到Git中，最终存储到Git数据库中的都是一个<code>blob</code>类型的Git对象。（即：<code>blob</code>对象是存储数据内容的）</p><h1 id="9-Git管理修改过的文件"><a href="#9-Git管理修改过的文件" class="headerlink" title="9. Git管理修改过的文件"></a>9. Git管理修改过的文件</h1><p><strong>1）我们继续向<code>hello-git.txt</code>文件中添加内容。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ echo &quot;hello-git.txt v2&quot; &gt;&gt; hello-git.txt<br><br>➜  gitObject git:(master) ✗ cat hello-git.txt  <br><br>hello-git.txt v1<br><br>hello-git.txt v2<br></code></pre></td></tr></table></figure><p><strong>2）查看Git数据库中的对象。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98<br><br>.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3<br></code></pre></td></tr></table></figure><p>可以看到还是之间的两个对象<code>cb</code>和<code>a6</code>，说明我们修改过的文件不会自动的存储到Git数据库中。</p><p>我们还需要手动的把修改后的<code>hello-git.txt</code>文件，存储到Git数据库中。</p><p><strong>3）把修改后的<code>hello-git.txt</code>文件添加到Git数据库中。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗  git hash-object -w ./hello-git.txt<br><br>7c320a2d671f2ff177063f98343a0123432521dd<br></code></pre></td></tr></table></figure><p><strong>4）再次查看Git数据库中的对象。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ find .git/objects -type f<br><br>.git/objects/7c/320a2d671f2ff177063f98343a0123432521dd<br><br>.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98<br><br>.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3<br></code></pre></td></tr></table></figure><p>我们可以看到Git数据库中多了一个<code>7c</code>对象。</p><p><strong>5）查看<code>7c</code>对象存储的内容。</strong></p><figure class="highlight plaintext"><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 plaintext">➜  gitObject git:(master) ✗ git cat-file -p 7c32<br><br>hello-git.txt v1<br><br>hello-git.txt v2<br></code></pre></td></tr></table></figure><p>如上所示，我们可以看到<code>7c</code>对象存储了<code>v1</code>和<code>v2</code>的内容，<code>v1</code>内容即在<code>a6</code>对象中，也在<code>7c</code>对象中。所以对于Git来说，存储的<span style="background-color:#ffff00"><font color=#ff0000>并不是文件内容的增量，而是全量文件。只要有变动，校验和不同就会产生一个新的Blob对象</font></span>。</p><h1 id="10-blob对象总结"><a href="#10-blob对象总结" class="headerlink" title="10. blob对象总结"></a>10. blob对象总结</h1><ul><li>Git的核心部分是一个简单的键值数据库（<code>key-value data store</code>），键就是文本内容的<code>Hash</code>，值就是文本内容。</li><li><code>blob</code>对象都存储在<code>.git/objects</code> 目录中，子目录+目录中的文件名，就是40位<code>Hash</code>值，也就是对象的键值。</li><li>通过这个键就能找到对应的内容。</li><li>每个文本内容存储到Git数据库的时候，内容都会进行<code>zlib</code> 压缩再存储。<br><span style="background-color:#00ff00">-   <code>blob</code>对象存储的是文件的内容，相同的内容(校验和相同)不产生新的<code>blob</code>对象，不同就会生成一个新的blob。</span></li><li><code>blob</code>对象并没有存储文件名。</li></ul><h1 id="11-问题"><a href="#11-问题" class="headerlink" title="11. 问题"></a>11. 问题</h1><p>我们对文件做一次修改，存储到Git数据库中，都会在Git数据库中创建一个新的<code>blob</code>对象。而在实际的工作中，我们需要做很多的改动，才提交一个版本，我们是否可以用一个<code>blob</code>对象代表整个项目的一次快照。</p><p><strong>不能</strong>，只能代表一次存储时，一个文件中的内容，与之前数据内容相同时不新增Git对象，数据内容不同时再次新增<code>blob</code>对象。即：只要有新的内容被Git纳入管理，必定有一个<code>blob</code>对象与之对应。</p><p>那么还有如下问题：</p><ol><li>记住文件的每一个版本所对应的<code>SHA-1</code>值并不现实。</li><li>在<code>blob</code>对象中，文件名并没有被保存，仅保存了文件的内容。</li></ol><p>所以，没有文件名就没有办法通过文件名来读取数据，只能用40位<code>Hash</code>值读取，非常的不现实。</p><p>解决方案：<span style="background-color:#00ff00">树对象</span>。</p><blockquote><p>提示：以上的操作都是在工作区和本地版本库之间进行操，不涉及暂存区，因为我们直接存储到了本地版本库中。</p></blockquote><h1 id="12-本文用到的命令总结"><a href="#12-本文用到的命令总结" class="headerlink" title="12. 本文用到的命令总结"></a>12. 本文用到的命令总结</h1><p>Git底层命令：</p><ul><li><code>git hash-object -w 文件路径</code>：把工作区的一个文件提交到本地版本库中。</li><li><code>find .git/objects -type f</code>：查看Git数据库中的对象。（Linux命令）</li><li><code>git cat-file -p 键</code>：查看该Git对象的内容。</li><li><code>git cat-file -t 键</code>：查看该Git对象的类型。</li><li><code>git show SHA-1</code>:  也可以查看Git对象的内容</li></ul><p>参考：</p><p><a href="https://www.cnblogs.com/liuyuelinfighting/p/16189429.html">https://www.cnblogs.com/liuyuelinfighting/p/16189429.html</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git原理 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git基础 — 3、Git基本命令及介绍</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%9F%BA%E7%A1%80--3%E3%80%81Git%E5%9F%BA%E6%9C%AC%E5%91%BD%E4%BB%A4%E5%8F%8A%E4%BB%8B%E7%BB%8D/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%9F%BA%E7%A1%80--3%E3%80%81Git%E5%9F%BA%E6%9C%AC%E5%91%BD%E4%BB%A4%E5%8F%8A%E4%BB%8B%E7%BB%8D/</url>
      
        <content type="html"><![CDATA[<p><span style="display:none">#flashcards</span><br><span style="display:none">Git基础 — 3、Git基本命令及介绍::不断费曼，坚持不懈！</span></p><!--SR:2022-12-12,3,250--><h1 id="1-Git的基本操作流程"><a href="#1-Git的基本操作流程" class="headerlink" title="1. Git的基本操作流程"></a>1. Git的基本操作流程</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912120132.png"></p><ol><li>如果是全新项目，使用命令<code>git init repo_name</code>初始化一个本地版本库(每个版本库仅需要执行一次)，后续推送到远程仓库。</li><li>大多数情况是非全新项目，需要使用命令<code>git clont xxx</code>将中央版本库内容克隆到本地版本库，每个客户机仅需要执行一次。该命令会在执行命令的所在目录下自动创建本地版本库。</li><li>使用命令<code>git add xxx</code>添加指定文件到版本控制管理（这一步只是添加到Git暂存区）。</li><li>使用命令<code>git commit -m &quot;xxx&quot;</code>将添加、修改等操作，提交到本地版本库（将暂存区的内容提交到本地版本库）。<br>如果远程仓库的内容被别人修改了，需要先同步远程的内容，直接<code>git pull</code>就可以更新本地的文件，然后再提交。再这过程中可能需要解决冲突。<br>在修改完成后，如果发现错误，可以撤回提交并再次修改并提交。</li><li>使用命令<code>git push</code>将本地版本库中的修改内容“推送”到中央版本库，客户机需要在一阶段性工作完成之后，或在某些时间点（下班，周五），将修改过的内容备份到中央版本库，方便他人更新到最新的代码。</li><li>使用命令<code>git pull</code>将中央版本库中的变化内容“拉取”本地版本库，客户机需要不定时的更新才可以获取最新的内容。</li></ol><blockquote><p>提示：实际工作中push前先pull是一种美德。</p></blockquote><h1 id="2-基本命令"><a href="#2-基本命令" class="headerlink" title="2. 基本命令"></a>2. 基本命令</h1><h2 id="2-1-查看本地库状态"><a href="#2-1-查看本地库状态" class="headerlink" title="2.1. 查看本地库状态"></a>2.1. 查看本地库状态</h2><h3 id="2-1-1-基本语法"><a href="#2-1-1-基本语法" class="headerlink" title="2.1.1. 基本语法"></a>2.1.1. 基本语法</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git status<br></code></pre></td></tr></table></figure><h3 id="2-1-2-案例实操"><a href="#2-1-2-案例实操" class="headerlink" title="2.1.2. 案例实操"></a>2.1.2. 案例实操</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912142358.png"></p><h2 id="2-2-新增文件（testgit-txt）"><a href="#2-2-新增文件（testgit-txt）" class="headerlink" title="2.2. 新增文件（testgit.txt）"></a>2.2. 新增文件（testgit.txt）</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">vim testgit.txt<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912142803.png"></p><h2 id="2-3-添加暂存区"><a href="#2-3-添加暂存区" class="headerlink" title="2.3. 添加暂存区"></a>2.3. 添加暂存区</h2><h3 id="2-3-1-将工作区的文件添加到暂存区"><a href="#2-3-1-将工作区的文件添加到暂存区" class="headerlink" title="2.3.1. 将工作区的文件添加到暂存区"></a>2.3.1. 将工作区的文件添加到暂存区</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git add .<br></code></pre></td></tr></table></figure><p>或者<code>git add testgit.txt</code></p><h3 id="2-3-2-查看状态（检测到暂存区有新文件）"><a href="#2-3-2-查看状态（检测到暂存区有新文件）" class="headerlink" title="2.3.2. 查看状态（检测到暂存区有新文件）"></a>2.3.2. 查看状态（检测到暂存区有新文件）</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912143018.png"></p><h2 id="2-4-提交本地库"><a href="#2-4-提交本地库" class="headerlink" title="2.4. 提交本地库"></a>2.4. 提交本地库</h2><h3 id="2-4-1-将暂存区的文件提交到本地库"><a href="#2-4-1-将暂存区的文件提交到本地库" class="headerlink" title="2.4.1. 将暂存区的文件提交到本地库"></a>2.4.1. 将暂存区的文件提交到本地库</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git commit -m &quot;my first commit&quot; gittest.txt<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912143250.png"></p><h3 id="2-4-2-查看状态（没有文件需要提交）"><a href="#2-4-2-查看状态（没有文件需要提交）" class="headerlink" title="2.4.2. 查看状态（没有文件需要提交）"></a>2.4.2. 查看状态（没有文件需要提交）</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git status<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912143411.png"></p><h2 id="2-5-修改文件"><a href="#2-5-修改文件" class="headerlink" title="2.5. 修改文件"></a>2.5. 修改文件</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">echo &quot;update file&quot; &gt;&gt; gittest.txt<br></code></pre></td></tr></table></figure><h3 id="2-5-1-查看状态（检测到工作区有文件被修改）"><a href="#2-5-1-查看状态（检测到工作区有文件被修改）" class="headerlink" title="2.5.1. 查看状态（检测到工作区有文件被修改）"></a>2.5.1. 查看状态（检测到工作区有文件被修改）</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912143542.png"></p><h3 id="2-5-2-将修改的文件再次添加暂存区"><a href="#2-5-2-将修改的文件再次添加暂存区" class="headerlink" title="2.5.2. 将修改的文件再次添加暂存区"></a>2.5.2. 将修改的文件再次添加暂存区</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git add .<br></code></pre></td></tr></table></figure><p>或者<code>git add testgit.txt</code></p><h3 id="2-5-3-查看状态（工作区的修改添加到了暂存区）"><a href="#2-5-3-查看状态（工作区的修改添加到了暂存区）" class="headerlink" title="2.5.3. 查看状态（工作区的修改添加到了暂存区）"></a>2.5.3. 查看状态（工作区的修改添加到了暂存区）</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912143732.png"></p><h2 id="2-6-历史版本"><a href="#2-6-历史版本" class="headerlink" title="2.6. 历史版本"></a>2.6. 历史版本</h2><h3 id="2-6-1-查看历史版本"><a href="#2-6-1-查看历史版本" class="headerlink" title="2.6.1. 查看历史版本"></a>2.6.1. 查看历史版本</h3><p><code>git reflog</code> 查看版本信息<br><code>git log</code> 查看版本详细信息<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912144618.png"></p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912144541.png"></p><h3 id="2-6-2-reflog和log区别"><a href="#2-6-2-reflog和log区别" class="headerlink" title="2.6.2. reflog和log区别"></a>2.6.2. reflog和log区别</h3><ol><li>git log 命令可以显示所有提交过的版本信息</li><li>git reflog 可以查看所有分支的所有操作记录（包括已经被删除的 commit 记录和 reset 的操作）</li><li>用git log则是看不出来被删除的commitid，用git reflog则可以看到被删除的commitid，我们就可以买后悔药，恢复到被删除的那个版本</li></ol><p>版本穿梭请看<a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--1%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-reset/" title="Git进阶--1、Git-后悔药-回退撤销-reset">Git进阶--1、Git-后悔药-回退撤销-reset</a></p><p>😃 如果感觉太繁琐，可以加上参数  –pretty&#x3D;oneline，只会显示版本号和提交时的备注信息</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git基础 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶 — 2、Git-后悔药-回退撤销-revert</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--2%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-revert/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--2%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-revert/</url>
      
        <content type="html"><![CDATA[<h1 id="1-revert作用"><a href="#1-revert作用" class="headerlink" title="1. revert作用"></a>1. revert作用</h1><p>Revert撤销一个提交的同时会创建一个新的提交。这是一个<span style="background-color:#ffff00">安全的方法</span>，因为它不会重写提交历史。比如，下面的命令会找出倒数第二个提交，然后创建一个新的提交来撤销这些更改，然后把这个提交加入项目中。</p><p>相比<code>git reset</code>，它不会改变现在的提交历史。因此，<code>git revert</code> 可以用在公共分支上，<code>git reset</code> 应该用在私有分支上。</p><p>你也可以把<code>git revert</code> 当作撤销已经提交的更改，而<code>git reset HEAD</code> 用来撤销没有提交的更改。</p><p>就像<code>git checkout</code> 一样，<code>git revert</code> 也有可能会重写文件。所以，但与<code>git checkout</code>不同的是，Git会在你执行 <code>revert</code> 之前要求你提交或者缓存你工作目录中的更改。否则执行失败</p><h1 id="2-work-directory-安全的"><a href="#2-work-directory-安全的" class="headerlink" title="2. work directory 安全的"></a>2. work directory 安全的</h1><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git revert HEAD<br><br>error: 您对下列文件的本地修改将被合并操作覆盖：<br><br>file.txt<br><br>请在合并前提交或贮藏您的修改。<br><br>正在终止<br><br>fatal: 还原失败<br></code></pre></td></tr></table></figure><p>没有文件模式</p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) git revert file.txt       <br><br>fatal: bad revision &#x27;file.txt&#x27;<br></code></pre></td></tr></table></figure><h1 id="3-commit-id"><a href="#3-commit-id" class="headerlink" title="3. commit id"></a>3. commit id</h1><p>表示撤销commit id的内容，还原到这个commit id的前一次内容<br>比如还原到上一次版本，就是 <code>git revert HEAD</code></p><h1 id="4-对commit历史的影响"><a href="#4-对commit历史的影响" class="headerlink" title="4. 对commit历史的影响"></a>4. 对commit历史的影响</h1><table><thead><tr><th>命令</th><th>log</th><th>reflog</th></tr></thead><tbody><tr><td>git revert</td><td>新增</td><td>可跟踪</td></tr><tr><td>git checkout</td><td>无变化</td><td>无变化</td></tr><tr><td>git reset</td><td>丢失</td><td>可跟踪</td></tr><tr><td>git restore</td><td>无变化</td><td>无变化</td></tr><tr><td>git amend</td><td>改变</td><td>改变</td></tr></tbody></table><p><strong>参考：</strong></p><p><a href="https://static.kancloud.cn/apachecn/git-doc-zh/1945519">https://static.kancloud.cn/apachecn/git-doc-zh/1945519</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶 — 3、Git-后悔药-回退撤销-checkout</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--3%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-checkout/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--3%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-checkout/</url>
      
        <content type="html"><![CDATA[<h1 id="1-commit模式"><a href="#1-commit模式" class="headerlink" title="1. commit模式"></a>1. commit模式</h1><blockquote><p><strong>注意</strong> commit模式下git checkout，因为只切换HEAD位置，不会影响索引区与工作目录。所以请调用git status查看当前git三树状态。如果索引树或工作目录有修改，git是会终止操作。</p></blockquote><h2 id="1-1-查看当前分支的HEAD信息"><a href="#1-1-查看当前分支的HEAD信息" class="headerlink" title="1.1. 查看当前分支的HEAD信息"></a>1.1. 查看当前分支的HEAD信息</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git checkout [HEAD]<br></code></pre></td></tr></table></figure><p>Your branch is up to date with ‘origin&#x2F;checkTag’.</p><h2 id="1-2-切换HEAD到某个分支上"><a href="#1-2-切换HEAD到某个分支上" class="headerlink" title="1.2. 切换HEAD到某个分支上"></a>1.2. 切换HEAD到某个分支上</h2><p>git checkout &lt;branch_name&gt;</p><p>操作HEAD切换到某个分支，HEAD默认是目标分支的最后一次提交快照</p><h2 id="1-3-新建一个分支，切换HEAD该分支上"><a href="#1-3-新建一个分支，切换HEAD该分支上" class="headerlink" title="1.3. 新建一个分支，切换HEAD该分支上"></a>1.3. 新建一个分支，切换HEAD该分支上</h2><p>git checkout -b &lt;branch_name&gt; [&lt;remote_name&#x2F;branch_name&gt;]</p><ul><li><ul><li>remote_name&#x2F;branch_name (创建跟踪分支)</li></ul></li></ul><blockquote><p>如果想拉取一个，在本地分支没有跟踪分支的远程分支，可以先执行git fetch然后再 git checkout -b</p></blockquote><p>Switched to a new branch ‘iss53’</p><h2 id="1-4-切换HEAD到某个提交快照上"><a href="#1-4-切换HEAD到某个提交快照上" class="headerlink" title="1.4. 切换HEAD到某个提交快照上"></a>1.4. 切换HEAD到某个提交快照上</h2><p><code>git checkout (&lt;tree-ish&gt;|&lt;tag\_name&gt;)</code></p><p><strong>注意</strong> 1. 如果该目标的提交快照不是其所属的分支中最后一次提交，HEAD的状态是detached HEAD，git会建议你使用git checkout -b 在这个快照上新建一个分支。</p><p>git 头指针分离问题，请见<a href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--9%E3%80%81git-%E5%A4%B4%E6%8C%87%E9%92%88%E5%88%86%E7%A6%BB%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/" title="Git进阶--8、git-头指针分离原因及解决办法">Git进阶--8、git-头指针分离原因及解决办法</a></p><h1 id="2-file模式"><a href="#2-file模式" class="headerlink" title="2. file模式"></a>2. file模式</h1><blockquote><p><strong>注意</strong> file模式下git checkout，不会更改HEAD，但是会修改目标文件的索引树与工作目录，保持它们与HEAD一致</p></blockquote><h2 id="2-1-切换某个文件到某个分支-x2F-提交快照上"><a href="#2-1-切换某个文件到某个分支-x2F-提交快照上" class="headerlink" title="2.1. 切换某个文件到某个分支&#x2F;提交快照上"></a>2.1. 切换某个文件到某个分支&#x2F;提交快照上</h2><p><code>git checkout (&lt;branch\_name&gt;|HEAD|&lt;tree-ish&gt;|&lt;tag\_name&gt;) \[--\] &lt;file\_path&gt;</code></p><p>还原git三树，与HEAD保持一致，返回到初次提交的状态，<strong>文件上原有修改会被丢弃</strong></p><p><strong>注意</strong> 前提是目标分支&#x2F;提交快照是存在目标文件，否则git会拒绝执行</p><h2 id="2-2-强制撤销修改"><a href="#2-2-强制撤销修改" class="headerlink" title="2.2. 强制撤销修改"></a>2.2. 强制撤销修改</h2><p>git checkout [–] &lt;file_path&gt;</p><p>更改索引树与工作目录，强制回滚到最近一次提交的状态，<strong>文件上原有修改会被丢弃</strong></p><p>git checkout . 与 git reset –hard效果大致相同，但是前者可以控制撤销某个文件夹（更改.为其它路径），后者不能。</p><h2 id="2-3-对commit历史的影响"><a href="#2-3-对commit历史的影响" class="headerlink" title="2.3. 对commit历史的影响"></a>2.3. 对commit历史的影响</h2><table><thead><tr><th>命令</th><th>log</th><th>reflog</th></tr></thead><tbody><tr><td>git revert</td><td>新增</td><td>可跟踪</td></tr><tr><td>git checkout</td><td>无变化</td><td>无变化</td></tr><tr><td>git reset</td><td>丢失</td><td>可跟踪</td></tr><tr><td>git restore</td><td>无变化</td><td>无变化</td></tr><tr><td>git amend</td><td>改变</td><td>改变</td></tr></tbody></table><p><strong>参考：</strong></p><p><a href="https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/ti-jiao-che-xiao-yu-la-qu/git-checkout">https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/ti-jiao-che-xiao-yu-la-qu/git-checkout</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶 — 4、Git-后悔药-回退撤销-restore</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--4%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-restore/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--4%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-restore/</url>
      
        <content type="html"><![CDATA[<h1 id="1-万能演示代码"><a href="#1-万能演示代码" class="headerlink" title="1. 万能演示代码"></a>1. 万能演示代码</h1><figure class="highlight plaintext"><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 plaintext">cd ~<br><br>rm -rf recovery<br><br>mkdir recovery;cd recovery<br><br>git init .<br><br>echo &quot;hello v1&quot; &gt; file.txt<br><br>git status<br><br>git add .<br><br>git commit -m &quot;v1&quot;<br><br>echo &quot;hello v2&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;v2&quot;<br><br>echo &quot;hello v3&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;v3&quot;<br><br>git log<br></code></pre></td></tr></table></figure><p>修改file.txt然后添加到暂存区，然后再次修改file.txt，让修改保留在工作区</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">➜  recovery git:(master) echo &quot;version 4&quot; &gt;&gt; file.txt <br><br>➜  recovery git:(master) ✗ git status     <br><br>位于分支 master<br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>修改尚未加入提交（使用 &quot;git add&quot; 和/或 &quot;git commit -a&quot;）<br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>➜  recovery git:(master) ✗ git add . <br><br>➜  recovery git:(master) ✗ git status<br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4             <br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">➜  recovery git:(master) ✗ echo &quot;version 5&quot; &gt;&gt; file.txt<br><br>➜  recovery git:(master) ✗ git status<br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>version 5<br><br>➜  recovery git:(master) ✗ git ls-files -s      <br><br>100644 b4e054860a983eda010551066303ce95a7ee3709 0 file.txt<br><br>➜  recovery git:(master) ✗ git show b4e0  <br><br>  <br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>(END)<br></code></pre></td></tr></table></figure><h1 id="2-不加参数"><a href="#2-不加参数" class="headerlink" title="2. 不加参数"></a>2. 不加参数</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917112523.png"></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git restore file.txt<br><br>➜  recovery git:(master) ✗ git status          <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br></code></pre></td></tr></table></figure><p><span style="background-color:#ffff00">我们可以看到工作区的修改被暂存区覆盖了</span></p><h1 id="3-加–staged"><a href="#3-加–staged" class="headerlink" title="3. 加–staged"></a>3. 加–staged</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917112550.png"></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git status   <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>version 5<br><br>➜  recovery git:(master) ✗ git ls-files -s<br><br>100644 b4e054860a983eda010551066303ce95a7ee3709 0 file.txt<br><br>➜  recovery git:(master) ✗ git show b4e0   <br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git restore --staged file.txt</code></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git restore --staged file.txt<br><br>➜  recovery git:(master) ✗ git status                   <br><br>位于分支 master<br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>修改尚未加入提交（使用 &quot;git add&quot; 和/或 &quot;git commit -a&quot;）<br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>version 5<br><br>➜  recovery git:(master) ✗ git ls-files -s<br><br>100644 56dc9b1633a77efb15e9a9c869dfe4522b0ae0ac 0 file.txt<br><br>➜  recovery git:(master) ✗ git show 56dc  <br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>(END)<br></code></pre></td></tr></table></figure><p>对比发现，<span style="background-color:#ffff00"><font color=#ff0000>工作区没有变化，暂存区被HEAD覆盖</font></span></p><h1 id="4-加-–worktree"><a href="#4-加-–worktree" class="headerlink" title="4. 加 –worktree"></a>4. 加 –worktree</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917122822.png"></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git status   <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>version 5<br><br>➜  recovery git:(master) ✗ git ls-files -s<br><br>100644 b4e054860a983eda010551066303ce95a7ee3709 0 file.txt<br><br>➜  recovery git:(master) ✗ git show b4e0  <br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git restore --worktree file.txt</code></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git restore --worktree file.txt<br><br>➜  recovery git:(master) ✗ git status                     <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>➜  recovery git:(master) ✗ git ls-files -s<br><br>100644 b4e054860a983eda010551066303ce95a7ee3709 0 file.txt<br><br>➜  recovery git:(master) ✗ git show b4e0  <br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>version 4<br><br>(END)<br></code></pre></td></tr></table></figure><p>我们发现，<span style="background-color:#ffff00"><font color=#ff0000>拿HEAD的文件内容来覆盖工作区，跳过了暂存区</font></span></p><h1 id="5-加–staged-–worktree"><a href="#5-加–staged-–worktree" class="headerlink" title="5. 加–staged –worktree"></a>5. 加–staged –worktree</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220917112611.png"></p><p><code>git restore --staged --worktree file.txt</code></p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">➜  recovery git:(master) ✗ git status<br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ git restore --staged --worktree  file.txt<br><br>➜  recovery git:(master) git status                               <br><br>位于分支 master<br><br>无文件要提交，干净的工作区<br><br>➜  recovery git:(master) git ls-files -s                          <br><br>100644 56dc9b1633a77efb15e9a9c869dfe4522b0ae0ac 0 file.txt<br><br>➜  recovery git:(master) git show 56dc  <br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>(END)<br><br>➜  recovery git:(master) cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br></code></pre></td></tr></table></figure><p>我们可以发现，<span style="background-color:#ffff00"><font color=#ff0000>暂存区和工作区都被HEAD覆盖</font></span></p><h1 id="6-加–source"><a href="#6-加–source" class="headerlink" title="6. 加–source"></a>6. 加–source</h1><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git restore --source=66062e1 --staged --worktree  file.txt<br><br>➜  recovery git:(master) ✗ git status                                                <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>➜  recovery git:(master) ✗ git ls-files -s<br><br>100644 d21fe316a0e9578aea75decc8c60e2a899534708 0 file.txt<br><br>➜  recovery git:(master) ✗ git show d21f  <br><br>hello v1<br><br>(END)<br></code></pre></td></tr></table></figure><p><span style="background-color:#ffff00"><font color=#ff0000>可以发现是拿commit 66062e1 来覆盖暂存区和工作区</font></span></p><p><strong>参考：</strong></p><p><a href="https://git-scm.com/docs/git-restore/zh_HANS-CN">https://git-scm.com/docs/git-restore/zh_HANS-CN</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git进阶 — 1、Git-后悔药-回退撤销-reset</title>
      <link href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--1%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-reset/"/>
      <url>/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--1%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-reset/</url>
      
        <content type="html"><![CDATA[<h1 id="1-Git三棵树"><a href="#1-Git三棵树" class="headerlink" title="1. Git三棵树"></a>1. Git三棵树</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912154910.png"></p><h1 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h1><h2 id="commit模式"><a href="#commit模式" class="headerlink" title="commit模式"></a>commit模式</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git reset (&lt;branch_name&gt;|HEAD|&lt;tree-ish&gt;|&lt;tag_name&gt;) [--mixed | --soft | --hard | --merge | --keep]<br></code></pre></td></tr></table></figure><h2 id="file模式"><a href="#file模式" class="headerlink" title="file模式"></a>file模式</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git reset (&lt;branch_name&gt;|HEAD|&lt;tree-ish&gt;|&lt;tag_name&gt;) [--] &lt;file_path&gt;<br></code></pre></td></tr></table></figure><h1 id="2-正向流程"><a href="#2-正向流程" class="headerlink" title="2. 正向流程"></a>2. 正向流程</h1><p>git的核心工作就是管理这三棵树。<code>git add</code>就是把你工作目录(Working Directory)的修改提交到暂存区(Index)，<code>git commit</code>就是把暂存区的内容同步到仓库里作为一个快照，并移动<code>HEAD</code>指向新快照；</p><p>三棵树<span style="background-color:#00ff00">从右向左</span>看    ⬅️<br>提交历史<span style="background-color:#ffff00">从左到右</span>看➡️</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912185930.gif"></p><h2 id="2-1-git-init"><a href="#2-1-git-init" class="headerlink" title="2.1. git init"></a>2.1. git init</h2><ul><li>让我们来可视化这个过程：假设进入到一个新目录，其中有一个文件，称其为该文件的 v1 版本，将它标记为蓝色，现在运行 git init，这会创建一个 Git 仓库，其中的 HEAD 引用指向未创建的 master 分支。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912210417.png"></li></ul><h2 id="2-2-git-add"><a href="#2-2-git-add" class="headerlink" title="2.2. git add"></a>2.2. git add</h2><ul><li>此时，只有工作目录有内容。现在想要提交这个文件，所以用 git add 来获取工作目录中的内容，并将其复制到索引中。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912210453.png"></li></ul><h2 id="2-3-git-commit"><a href="#2-3-git-commit" class="headerlink" title="2.3. git commit"></a>2.3. git commit</h2><ul><li>接着运行 git commit，它会取得索引中的内容并将它保存为一个永久的快照，然后创建一个指向该快照的提交对象，最后更新 master 来指向本次提交。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912210520.png"></li></ul><h2 id="2-4-edit-file"><a href="#2-4-edit-file" class="headerlink" title="2.4. edit file"></a>2.4. edit file</h2><ul><li>此时如果我们运行 git status，会发现没有任何改动，因为现在三棵树完全相同。现在想要对文件进行修改然后提交它，将会经历同样的过程；首先在工作目录中修改文件，称其为该文件的 v2 版本，并将它标记为红色：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912210556.png"></li></ul><h2 id="2-5-git-add"><a href="#2-5-git-add" class="headerlink" title="2.5. git add"></a>2.5. git add</h2><ul><li>如果现在运行 git status，将会看到文件显示在 “Changes not staged for commit” 下面并被标记为红色，这是因为该条目在索引与工作目录之间存在不同。接着运行 git add 来将它暂存到索引中：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912210623.png"></li></ul><h2 id="2-6-git-commit"><a href="#2-6-git-commit" class="headerlink" title="2.6. git commit"></a>2.6. git commit</h2><ul><li>此时，由于索引和 HEAD 不同，若运行 git status 的话就会看到 “Changes to be committed” 下的该文件变为绿色，也就是说，现在预期的下一次提交与上一次提交不同。最后，运行 git commit 来完成提交：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912210652.png"></li></ul><h1 id="3-逆向流程"><a href="#3-逆向流程" class="headerlink" title="3. 逆向流程"></a>3. 逆向流程</h1><h2 id="3-1-精简理解"><a href="#3-1-精简理解" class="headerlink" title="3.1. 精简理解"></a>3.1. 精简理解</h2><p>显然，<code>git reset</code>就是对上述行为的反向操作。</p><p><code>reset</code>的本质其实有2个动作：</p><ol><li>移动<code>HEAD</code>指针指向某一个快照。</li><li>通过指定参数(3选1，不写则默认mixed)，来<span style="background-color:#ffff00">递进的</span>控制，在哪几颗树上进行覆盖。</li></ol><ul><li><code>--soft</code>——如图示中序号1所示情况，只改变指针指向的快照，即只覆盖本地版本库中的文件</li><li><code>--mixed</code>——如图示中序号2所示情况，覆盖本地版本库中的文件的同时，也把快照内容同步到暂存区；</li><li><code>--hard</code>——如图示中序号3所示情况，HEAD、Index改变的同时，本地工作区也被覆盖。三棵树全同步为指针指向的快照；</li></ul><blockquote><p>可以结合命令<code>git diff</code>、<code>git diff --cached</code>、<code>git ls-files -s</code>、<code>git show</code>或者<code>git cat-file -p</code>进行验证理解<br><code>git diff</code>请见<a href="/2022/09/21/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--13%E3%80%81Git%E6%AF%94%E5%AF%B9%E5%90%84%E5%8C%BA%E5%B7%AE%E5%BC%82-git-diff/" title="Git进阶--13、Git比对各区差异-git diff">Git进阶--13、Git比对各区差异-git diff</a></p></blockquote><p>三棵树从左向右    ➡️<br>提交历史从右向左⬅️</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912192118.gif"></p><ul><li>假设再次修改了 file.txt 文件并第三次提交它，现在的历史看起来是这样的：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912214027.png"></li><li>跟着 reset 看看它都做了什么，它以一种简单可预见的方式直接操纵这三棵树。</li></ul><h2 id="3-2-深入解析"><a href="#3-2-深入解析" class="headerlink" title="3.2. 深入解析"></a>3.2. 深入解析</h2><h3 id="3-2-1-移动-HEAD-–soft"><a href="#3-2-1-移动-HEAD-–soft" class="headerlink" title="3.2.1. 移动 HEAD(–soft)"></a>3.2.1. 移动 HEAD(–soft)</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git reset --soft 9e5e<br></code></pre></td></tr></table></figure><ul><li>reset 做的第一件事是移动 HEAD 的指向，这与改变 HEAD 自身不同（checkout 所做的）；reset 移动 HEAD 指向的分支。这意味着如果 HEAD 设置为 master 分支（例如正在 master 分支上），运行 git reset 9e5e6a4 将会使 master 指向 9e5e6a4：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912214305.png"></li><li>无论调用了何种形式的带有一个提交的 reset，它首先都会尝试这样做，使用 reset –soft，它将仅仅停在那儿。</li><li>现在看一眼上图，理解一下发生的事情：它本质上是<span style="background-color:#00ff00">撤销了上一次 git commit 命令</span>。当在运行 git commit 时，Git 会创建一个新的提交，并移动 HEAD 所指向的分支来使其指向该提交；当将它 reset 回 HEAD~（HEAD 的父结点）时，其实就是把该分支移动回原来的位置，而不会改变索引和工作目录。现在可以更新索引(或者只更新提交备注信息)之后再次运行 git commit。这与 <a href="/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--5%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2-gitcommit--amend/" title="Git进阶 — 5、Git-后悔药-重写历史-git commit--amend">Git进阶 — 5、Git-后悔药-重写历史-git commit--amend</a> 所要做的事情是等同的。</li></ul><h3 id="3-2-2-更新索引（–mixed）"><a href="#3-2-2-更新索引（–mixed）" class="headerlink" title="3.2.2. 更新索引（–mixed）"></a>3.2.2. 更新索引（–mixed）</h3><ul><li>注意，如果现在运行 git status 的话，就会看到新的 HEAD 和以绿色标出的它和索引之间的区别。接下来，reset 会用 HEAD 指向的当前快照的内容来更新索引：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912215202.png"></li><li>如果指定 –mixed 选项，reset 将会在<span style="background-color:#ffff00">暂存区Index</span>这里停止，这也是默认行为，所以如果没有指定任何选项（例子中只是 git reset HEAD~），这就是命令将会停止的地方。</li><li>现在再看一眼上图，理解一下发生的事情：它依然会撤销一上次提交，但还会取消暂存所有的东西，于是，我们<span style="background-color:#00ff00">回滚到了所有 git add 和 git commit 的命令执行之前</span>。</li></ul><h3 id="3-2-3-更新工作目录（–hard）"><a href="#3-2-3-更新工作目录（–hard）" class="headerlink" title="3.2.3. 更新工作目录（–hard）"></a>3.2.3. 更新工作目录（–hard）</h3><ul><li><p>reset 要做的的第三件事情就是让工作目录看起来像索引。如果使用 –hard 选项，它将会继续这一步：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912215320.png"></p></li><li><p>现在来回想一下刚才发生的事情：撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。</p></li><li><p>必须注意，<u><span style="background-color:red">–hard 标记是 reset 命令唯一的危险用法，它也是 Git 会真正地销毁数据的仅有的几个操作之一。</span></u>其他任何形式的 reset 调用都可以轻松撤消，但是 –hard 选项不能，因为它强制覆盖了工作目录中的文件。在这种特殊情况下，Git 数据库中的一个提交内还留有该文件的 v3 版本，可以通过 reflog 来找回它，但是若该文件还未提交，Git 仍会覆盖它从而导致无法轻松恢复。若想恢复则需要其他命令，请见<a href="/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--6%E3%80%81Git%E6%89%BE%E5%9B%9E%E4%B8%A2%E5%A4%B1%E4%BB%A3%E7%A0%81-gitfsck--lost-found/" title="Git进阶 — 6、Git找回丢失代码 git fsck --lost-found">Git进阶 — 6、Git找回丢失代码 git fsck --lost-found</a><br>WD不安全操作，请见<a href="/2022/09/16/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--8%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-%E5%A4%A7%E6%80%BB%E7%BB%93/" title="Git进阶--8、Git-后悔药-回退撤销-大总结">Git进阶--8、Git-后悔药-回退撤销-大总结</a></p></li><li><p>reset 命令会以特定的顺序重写这三棵树，在指定以下选项时停止：</p></li><li><ul><li>移动 HEAD 分支的指向 （若指定了 –soft，则到此停止）；</li></ul></li><li><ul><li>使索引看起来像 HEAD （若未指定 –hard，则到此停止）；</li></ul></li><li><ul><li>使工作目录看起来像索引。</li></ul></li></ul><h5 id="3-2-3-1-演示示例"><a href="#3-2-3-1-演示示例" class="headerlink" title="3.2.3.1. 演示示例"></a>3.2.3.1. 演示示例</h5><p>📢 由于引用的是网络动图，所以演示示例跟动图中的SHA-1值不一致，请注意辨别理解</p><figure class="highlight shell"><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 shell">cd ~<br><br>rm -rf recovery<br><br>mkdir recovery;cd recovery<br><br>git init .<br><br>echo &quot;hello v1&quot; &gt; file.txt<br><br>git status<br><br>git add .<br><br>git commit -m &quot;v1&quot;<br><br>echo &quot;hello v2&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;v2&quot;<br><br>echo &quot;hello v3&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;v3&quot;<br><br>git log<br></code></pre></td></tr></table></figure><p><code>git reflog</code>查看引用历史</p><figure class="highlight plaintext"><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 plaintext">d4e752c (HEAD -&gt; master) HEAD@&#123;0&#125;: commit: v3<br><br>e32d991 HEAD@&#123;1&#125;: commit: v2<br><br>c3daf0c HEAD@&#123;2&#125;: commit (initial): v1<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git log</code></p><figure class="highlight plaintext"><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 plaintext">commit d4e752c4d65dddd8a45962c7bb1937256534cd48 (HEAD -&gt; master)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 12:39:51 2022 +0800<br><br>  <br><br>    v3<br><br>  <br><br>commit e32d991ec050e7b8d5183c1c9c02206d5b59982f<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 12:39:51 2022 +0800<br><br>  <br><br>    v2<br><br>  <br><br>commit c3daf0c0af95d50ec25af70f48741bb1e3ff27ca<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 12:39:51 2022 +0800<br><br>  <br><br>    v1<br><br>(END)<br></code></pre></td></tr></table></figure><p>使用–soft回到v2 commit之后，git log如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git reset --soft e32d<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">commit e32d991ec050e7b8d5183c1c9c02206d5b59982f (HEAD -&gt; master)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 12:39:51 2022 +0800<br><br>  <br><br>    v2<br><br>  <br><br>commit c3daf0c0af95d50ec25af70f48741bb1e3ff27ca<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 12:39:51 2022 +0800<br><br>  <br><br>    v1<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git reflog</code>如下：</p><figure class="highlight plaintext"><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 plaintext">e32d991 (HEAD -&gt; master) HEAD@&#123;0&#125;: reset: moving to e32d<br><br>d4e752c HEAD@&#123;1&#125;: commit: v3<br><br>e32d991 (HEAD -&gt; master) HEAD@&#123;2&#125;: commit: v2<br><br>c3daf0c HEAD@&#123;3&#125;: commit (initial): v1<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git status</code>如下：</p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git status<br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br></code></pre></td></tr></table></figure><p>我们可以看到–soft只是回退了commit到本地版本库的操作，文件为绿色，表示已经add到了暂存区</p><p>mixed和hard，不做演示</p><h2 id="3-3-通过路径来重置"><a href="#3-3-通过路径来重置" class="headerlink" title="3.3. 通过路径来重置"></a>3.3. 通过路径来重置</h2><p>理清了 reset 基本形式的行为，不过我们还可以给它提供一个作用路径：若指定了一个路径，reset 将会<span style="background-color:#ffff00"><font color=#ff0000>跳过</font></span><span style="background-color:#ffff00">第① 步(移动 HEAD)，即只要加了路径，HEAD是不动的</span>，并且将它的作用范围限定为指定的文件或文件集合。<br>这样做自然有它的道理，因为 HEAD 只是一个指针，无法让它同时指向两个提交中各自的一部分，不过索引和工作目录可以部分更新，所以重置可以<span style="background-color:#00ff00">继续进行第 ②步(更新索引) 和第 ③ 步(更新工作目录)</span>。</p><p>📢 值得注意的是，<span style="background-color:#ffff00">加了路径就不能再加–soft或者–hard</span></p><blockquote><p>因为git规定加了路径，HEAD就不会动，所以加–soft也不会起作用<br>硬重置的话，有git checkout HEAD –path，所以为了简化reset和减少错误发生的目的，–hard也是不允许的，否则会报<font color=#ff0000> fatal: 不能带路径进行软性&#x2F;硬性重置</font></p></blockquote><h4 id="3-3-1-不加分支名或commit的SHA-1"><a href="#3-3-1-不加分支名或commit的SHA-1" class="headerlink" title="3.3.1. 不加分支名或commit的SHA-1"></a>3.3.1. 不加分支名或commit的SHA-1</h4><h5 id="3-3-1-1-作用原理"><a href="#3-3-1-1-作用原理" class="headerlink" title="3.3.1.1. 作用原理"></a>3.3.1.1. 作用原理</h5><ul><li>现在，假如运行 git reset file.txt （这其实是 git reset –mixed HEAD file.txt 的简写形式，因为既没有指定一个提交的 SHA-1 或分支，也没有指定 –soft 或 –hard），它会：</li><li><ul><li>移动 HEAD 分支的指向 （已跳过），<font color=#ff0000><span style="background-color:#ffff00">使用当前HEAD指向的commit</span></font>；</li></ul></li><li><ul><li>让索引看起来像 HEAD （到此处停止），拿HEAD中的覆盖Index索引区；</li></ul></li><li>所以它本质上只是<span style="background-color:#00ff00">将 file.txt 从 HEAD 覆盖到索引中</span>。即保<span style="background-color:#00ff00">留工作区成果，回退add和commit</span><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912223302.png"></li></ul><p><strong>实际应用场景</strong><br><span style="background-color:#00ff00">取消暂存文件</span><br>如果查看该命令的示意图，然后再想想 git add 所做的事，就会发现它们正好相反。</p><h5 id="3-3-1-2-演示示例"><a href="#3-3-1-2-演示示例" class="headerlink" title="3.3.1.2. 演示示例"></a>3.3.1.2. 演示示例</h5><p>我们做到v2commit之前这一步，然后执行<code>git reset file.txt</code></p><figure class="highlight shell"><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 shell">cd ~<br><br>rm -rf recovery<br><br>mkdir recovery;cd recovery<br><br>git init .<br><br>echo &quot;hello v1&quot; &gt; file.txt<br><br>git status<br><br>git add .<br><br>git commit -m &quot;v1&quot;<br><br>echo &quot;hello v2&quot; &gt;&gt; file.txt<br><br>git add .<br></code></pre></td></tr></table></figure><p><code> </code>git status&#96;</p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git status   <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>➜  recovery git:(master) ✗<br></code></pre></td></tr></table></figure><p><code>git reflog</code>如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">f7138cf (HEAD -&gt; master) HEAD@&#123;0&#125;: commit (initial): v1<br></code></pre></td></tr></table></figure><p><code>git reset file.txt</code></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git reset file.txt<br><br>重置后取消暂存的变更：<br><br>M file.txt<br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>➜  recovery git:(master) ✗ git status        <br><br>位于分支 master<br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>修改尚未加入提交（使用 &quot;git add&quot; 和/或 &quot;git commit -a&quot;<br></code></pre></td></tr></table></figure><p>我们可以发现，暂存区的文件被清空了，工作区的文件内容没有变化</p><h4 id="3-3-2-加分支名或commit的SHA-1"><a href="#3-3-2-加分支名或commit的SHA-1" class="headerlink" title="3.3.2. 加分支名或commit的SHA-1"></a>3.3.2. 加分支名或commit的SHA-1</h4><h5 id="3-3-2-1-作用原理"><a href="#3-3-2-1-作用原理" class="headerlink" title="3.3.2.1. 作用原理"></a>3.3.2.1. 作用原理</h5><p>我们可以不让 Git 从 HEAD 拉取数据，而是通过<span style="background-color:#ffff00">具体指定一个提交来拉取该文件的对应版本</span>，只需运行类似于 git reset eb43bf file.txt 的命令即可：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912224320.png"></p><ul><li>如果现在运行 git commit，它就会记录一条“将该文件恢复到 v1 版本”的更改，尽管我们并未在工作目录中真正地再次拥有它。</li><li>还有一点同 git add 一样，就是 reset 命令也可以接受一个 –patch 选项来一块一块地取消暂存的内容，这样就可以根据选择来取消暂存或恢复内容。</li><li>那工作区能不能强制覆盖呢，即<code>git reset SHA-1 --hard file.txt</code>，答案是no，会提示<br>fatal: 不能带路径进行硬性重置。那–soft呢，聪明如你，可以猜到会得到 fatal: 不能带路径进行软性重置。原因前面已经讲过。</li><li>so，带路径带分支名或者commit的只有默认mixed参数，作用<font color=#ff0000><span style="background-color:#ffff00">可以理解为拿某个文件或路径的某个commit的内容仅仅覆盖暂存区</span></font></li></ul><blockquote><p>当检测到文件路径时，<code>git reset</code> 将缓存区同步到你指定的那个提交。比如，下面这个命令会将倒数第二个提交中的foo.py加入到缓存区中，供下一个提交使用。</p></blockquote><p>git reset HEAD~2 foo.py<br>和提交层面的<code>git reset</code>一样，通常我们使用HEAD而不是某个特定的提交。运行<code>git reset HEAD foo.py</code> 会将当前的foo.py从缓存区中移除出去，而不会影响工作目录中对foo.py的更改。<br>–soft、–mixed和–hard对文件层面的<code>git reset</code>毫无作用，因为缓存区中的文件一定会变化，而工作目录中的文件一定不变。</p><h5 id="3-3-2-2-演示示例"><a href="#3-3-2-2-演示示例" class="headerlink" title="3.3.2.2. 演示示例"></a>3.3.2.2. 演示示例</h5><figure class="highlight shell"><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 shell">cd ~<br><br>rm -rf recovery<br><br>mkdir recovery;cd recovery<br><br>git init .<br><br>echo &quot;hello v1&quot; &gt; file.txt<br><br>git status<br><br>git add .<br><br>git commit -m &quot;v1&quot;<br><br>echo &quot;hello v2&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;v2&quot;<br><br>echo &quot;hello v3&quot; &gt;&gt; file.txt<br><br>git add .<br><br>git commit -m &quot;v3&quot;<br><br>git log<br></code></pre></td></tr></table></figure><p><code>git reflog</code>如下：</p><figure class="highlight plaintext"><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 plaintext">ae08e71 (HEAD -&gt; master) HEAD@&#123;0&#125;: commit: v3<br><br>ec6224c HEAD@&#123;1&#125;: commit: v2<br><br>1c8edce HEAD@&#123;2&#125;: commit (initial): v1<br><br>(END)<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) git status<br><br>位于分支 master<br><br>无文件要提交，干净的工作区<br><br>➜  recovery git:(master) cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br><br>➜  recovery git:(master)<br></code></pre></td></tr></table></figure><p>我们照例用v1的commit进行回退<br><code>git reset 1c8e file.txt</code></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) git reset 1c8e file.txt<br><br>重置后取消暂存的变更：<br><br>M file.txt<br><br>➜  recovery git:(master) ✗ git status             <br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file.txt<br><br>  <br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br></code></pre></td></tr></table></figure><p>我们可以看到暂存区加入了文件，查看内容，发现暂存区确实变为了v1版本</p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git ls-files -s<br><br>100644 d21fe316a0e9578aea75decc8c60e2a899534708 0 file.txt<br><br>➜  recovery git:(master) ✗ git show d21f<br><br>hello v1<br><br>(END)<br></code></pre></td></tr></table></figure><blockquote><p>工作区文件为红色，表示被修改，虽然默认是mixed覆盖到暂存区，但工作区相当于做了覆盖，然后又改成了v3版本，所以是红色</p></blockquote><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ cat file.txt<br><br>hello v1<br><br>hello v2<br><br>hello v3<br></code></pre></td></tr></table></figure><p>此时我们<code>git commit</code>一下</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs plaintext">➜  recovery git:(master) ✗ git commit -m &quot;commit after reset&quot;<br><br>[master 08d4efd] commit after reset<br><br> 1 file changed, 2 deletions(-)<br><br>➜  recovery git:(master) ✗ git status                        <br><br>位于分支 master<br><br>尚未暂存以备提交的变更：<br><br>  （使用 &quot;git add &lt;文件&gt;...&quot; 更新要提交的内容）<br><br>  （使用 &quot;git restore &lt;文件&gt;...&quot; 丢弃工作区的改动）<br><br>修改：     file.txt<br><br>  <br><br>修改尚未加入提交（使用 &quot;git add&quot; 和/或 &quot;git commit -a&quot;）<br><br>➜  recovery git:(master) ✗ git log        <br><br>  <br><br>)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 15:01:53 2022 +0800<br><br>  <br><br>    commit after reset<br><br>  <br><br>commit ae08e7126daa0b323fc8bcf55a273ddd0f18ccb2<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 14:41:03 2022 +0800<br><br>  <br><br>    v3<br><br>  <br><br>commit ec6224cc5549e06ac0b8b65a00dd8472ac30128e<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 14:41:03 2022 +0800<br><br>  <br><br>    v2<br><br>  <br><br>commit 1c8edced86209178b3ff52ed782afbeecfe02420<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 14:41:03 2022 +0800<br><br>  <br><br>    v1<br></code></pre></td></tr></table></figure><p><code>git reflog</code></p><figure class="highlight plaintext"><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 plaintext">08d4efd (HEAD -&gt; master) HEAD@&#123;0&#125;: commit: commit after reset<br><br>ae08e71 HEAD@&#123;1&#125;: commit: v3<br><br>ec6224c HEAD@&#123;2&#125;: commit: v2<br><br>1c8edce HEAD@&#123;3&#125;: commit (initial): v1<br><br>(END)<br></code></pre></td></tr></table></figure><p>可以看到多了一次提交， <code>git show 08d4</code></p><figure class="highlight plaintext"><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 plaintext">commit 08d4efd3c3910ac07b0c19f47f754d797f4c2e98 (HEAD -&gt; master)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Thu Sep 15 15:01:53 2022 +0800<br><br>  <br><br>    commit after reset<br><br>  <br><br>diff --git a/file.txt b/file.txt<br><br>index 56dc9b1..d21fe31 100644<br><br>--- a/file.txt<br><br>+++ b/file.txt<br><br>@@ -1,3 +1 @@<br><br> hello v1<br><br>-hello v2<br><br>-hello v3<br><br>(END)<br></code></pre></td></tr></table></figure><p>可以看到回退到了v1版本<br>但此时工作区还是v3的版本，想也回退到v1，可以使用<code>git reset --hard</code>或者<code>git checkout file.txt</code></p><h2 id="3-4-压缩提交"><a href="#3-4-压缩提交" class="headerlink" title="3.4. 压缩提交"></a>3.4. 压缩提交</h2><h5 id="3-4-1-作用原理"><a href="#3-4-1-作用原理" class="headerlink" title="3.4.1. 作用原理"></a>3.4.1. 作用原理</h5><p>假设一系列提交信息中有 “oops.”“WIP” 和 “forgot this file”， 就能使用 reset 来轻松快速地将它们压缩成单个提交。假设有一个项目，第一次提交中有一个文件，第二次提交增加了一个新的文件并修改了第一个文件，第三次提交再次修改了第一个文件，由于第二次提交是一个未完成的工作，因此需要压缩它。<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912225040.png"></p><ul><li>那么可以运行 git reset –soft HEAD~2 来将 HEAD 分支移动到一个旧一点的提交上（即想要保留的最近的提交）：</li></ul><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912225127.png"></p><ul><li>然后只需再次运行 git commit：<br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220912225245.png"></li><li>现在可以查看可到达的历史，即将会推送的历史，现在看起来有个 v1 版 file-a.txt 的提交，接着第二个提交将 file-a.txt 修改成了 v3 版并增加了 file-b.txt，包含 v2 版本的文件已经不在历史中。</li></ul><h5 id="3-4-2-演示示例"><a href="#3-4-2-演示示例" class="headerlink" title="3.4.2. 演示示例"></a>3.4.2. 演示示例</h5><figure class="highlight plaintext"><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 plaintext">cd ~<br><br>rm -rf recovery<br><br>mkdir recovery;cd recovery<br><br>git init .<br><br>echo &quot;init file-a.txt v1&quot; &gt; file-a.txt<br><br>git status<br><br>git add .<br><br>git commit -m &quot;init file-a.txt v1&quot;<br><br><br><br>echo &quot;update file-a.txt v2&quot; &gt;&gt; file-a.txt<br><br>echo &quot;init file-b.txt v1&quot; &gt;&gt; file-b.txt<br><br>git add .<br><br>git commit -m &quot;update file-a.txt v2&quot;<br><br><br><br>echo &quot;finish fix file-a.txt v3&quot; &gt;&gt; file-a.txt<br><br>git add .<br><br>git commit -m &quot;finish fix file-a.txt v3&quot;<br><br>git log<br></code></pre></td></tr></table></figure><p><code>git log</code></p><figure class="highlight plaintext"><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 plaintext">commit de9545c70d3005351b1724d6c87e8610db4d0611 (HEAD -&gt; master)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Fri Sep 16 14:27:11 2022 +0800<br><br>  <br><br>    finish fix file-a.txt v3<br><br>  <br><br>commit cf4753faaf9cb1ac03eeacebb725e59408420b9a<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Fri Sep 16 14:27:11 2022 +0800<br><br>  <br><br>    update file-a.txt v2<br><br>  <br><br>commit cfb20be06b826848f1330426b06ae795cd09a9a3<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Fri Sep 16 14:27:11 2022 +0800<br><br>  <br><br>    init file-a.txt v1<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git reflog</code></p><figure class="highlight plaintext"><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 plaintext">de9545c (HEAD -&gt; master) HEAD@&#123;0&#125;: commit: finish fix file-a.txt v3<br><br>cf4753f HEAD@&#123;1&#125;: commit: update file-a.txt v2<br><br>cfb20be HEAD@&#123;2&#125;: commit (initial): init file-a.txt v1<br><br>(END)<br></code></pre></td></tr></table></figure><p> <code>git reset --soft HEAD~2</code></p><p><code>git log</code></p><figure class="highlight plaintext"><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 plaintext">commit cfb20be06b826848f1330426b06ae795cd09a9a3 (HEAD -&gt; master)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Fri Sep 16 14:27:11 2022 +0800<br><br>  <br><br>    init file-a.txt v1<br><br>(END)<br></code></pre></td></tr></table></figure><p><code>git reflog</code></p><figure class="highlight plaintext"><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 plaintext">cfb20be (HEAD -&gt; master) HEAD@&#123;0&#125;: reset: moving to HEAD~2<br><br>de9545c HEAD@&#123;1&#125;: commit: finish fix file-a.txt v3<br><br>cf4753f HEAD@&#123;2&#125;: commit: update file-a.txt v2<br><br>cfb20be (HEAD -&gt; master) HEAD@&#123;3&#125;: commit (initial): init file-a.txt v1<br><br>(END)<br></code></pre></td></tr></table></figure><p>可以看出来，此时HEAD指向了第一次的commit cfb2，<span style="background-color:#ffff00">相当于把commit回退了</span></p><p><code>git status</code></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git status<br><br>位于分支 master<br><br>要提交的变更：<br><br>  （使用 &quot;git restore --staged &lt;文件&gt;...&quot; 以取消暂存）<br><br>修改：     file-a.txt<br><br>新文件：   file-b.txt<br></code></pre></td></tr></table></figure><p>我们再次执行<code>git commit</code></p><figure class="highlight plaintext"><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 plaintext">➜  recovery git:(master) ✗ git commit -m &quot;compress commit test&quot;<br><br>[master 798ae56] compress commit test<br><br> 2 files changed, 3 insertions(+)<br><br> create mode 100644 file-b.txt<br></code></pre></td></tr></table></figure><p><code>git log</code></p><figure class="highlight plaintext"><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 plaintext">commit 798ae5608429362e4390b1eac74746e2fc78bb12 (HEAD -&gt; master)<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Fri Sep 16 14:32:17 2022 +0800<br><br>  <br><br>    compress commit test<br><br>  <br><br>commit cfb20be06b826848f1330426b06ae795cd09a9a3<br><br>Author: luoweile &lt;luoweile2008@126.com&gt;<br><br>Date:   Fri Sep 16 14:27:11 2022 +0800<br><br>  <br><br>    init file-a.txt v1<br><br>(END)<br></code></pre></td></tr></table></figure><p>git log 只看到了2次commit信息</p><p><code>git reflog</code></p><figure class="highlight plaintext"><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 plaintext">798ae56 (HEAD -&gt; master) HEAD@&#123;0&#125;: commit: compress commit test<br><br>cfb20be HEAD@&#123;1&#125;: reset: moving to HEAD~2<br><br>de9545c HEAD@&#123;2&#125;: commit: finish fix file-a.txt v3<br><br>cf4753f HEAD@&#123;3&#125;: commit: update file-a.txt v2<br><br>cfb20be HEAD@&#123;4&#125;: commit (initial): init file-a.txt v1<br><br>(END)<br></code></pre></td></tr></table></figure><p>不过git reflog还是看得很清楚</p><h1 id="4-对commit历史的影响"><a href="#4-对commit历史的影响" class="headerlink" title="4. 对commit历史的影响"></a>4. 对commit历史的影响</h1><table><thead><tr><th>命令</th><th>log</th><th>reflog</th></tr></thead><tbody><tr><td>revert</td><td>新增</td><td>可跟踪</td></tr><tr><td>checkout</td><td>无变化</td><td>无变化</td></tr><tr><td>reset</td><td>丢失</td><td>可跟踪</td></tr><tr><td>restore</td><td>无变化</td><td>无变化</td></tr><tr><td>git amend</td><td>改变</td><td>改变</td></tr></tbody></table><h1 id="5-其他后悔药"><a href="#5-其他后悔药" class="headerlink" title="5. 其他后悔药"></a>5. 其他后悔药</h1><h2 id="5-1-revert"><a href="#5-1-revert" class="headerlink" title="5.1. revert"></a>5.1. revert</h2><a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--2%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-revert/" title="Git进阶 — 2、Git-后悔药-回退撤销-revert">Git进阶 — 2、Git-后悔药-回退撤销-revert</a><h2 id="5-2-checkout"><a href="#5-2-checkout" class="headerlink" title="5.2. checkout"></a>5.2. checkout</h2><a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--3%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-checkout/" title="Git进阶 — 3、Git-后悔药-回退撤销-checkout">Git进阶 — 3、Git-后悔药-回退撤销-checkout</a><h2 id="5-3-restore"><a href="#5-3-restore" class="headerlink" title="5.3. restore"></a>5.3. restore</h2><a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--4%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E5%9B%9E%E9%80%80%E6%92%A4%E9%94%80-restore/" title="Git进阶 — 4、Git-后悔药-回退撤销-restore">Git进阶 — 4、Git-后悔药-回退撤销-restore</a><h2 id="5-4-git-commit-–amend"><a href="#5-4-git-commit-–amend" class="headerlink" title="5.4. git commit –amend"></a>5.4. git commit –amend</h2><a href="/2022/09/13/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E8%BF%9B%E9%98%B6--5%E3%80%81Git-%E5%90%8E%E6%82%94%E8%8D%AF-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2-gitcommit--amend/" title="Git进阶 — 5、Git-后悔药-重写历史-gitcommit--amend">Git进阶 — 5、Git-后悔药-重写历史-gitcommit--amend</a><h1 id="6-延长保存期限"><a href="#6-延长保存期限" class="headerlink" title="6. 延长保存期限"></a>6. 延长保存期限</h1><p>我们前面说到在Git上做的所有操作都被保存到记录里，一般是从你本地Git库执行clone开始的所有操作都保存了下来，所以不用担心很久之前的一些Commit log找不到，你或许期望去为已删除的提交设置一个更长的保存周期。例如：<br><code>$ git config gc.pruneexpire &quot;30 days&quot;</code>  默认2周<br>意思是一个被删除的提交会在删除30天后，且运行 <em>git gc</em> 以后，被永久丢弃。<br>你或许还想关掉 <em>git gc</em> 的自动运行：<br><code>$ git config gc.auto 0</code><br>在这种情况下提交将只在你手工运行 <em>git gc</em> 的情况下才永久删除。</p><h1 id="7-参考"><a href="#7-参考" class="headerlink" title="7. 参考"></a>7. 参考</h1><p><a href="https://bbs.huaweicloud.com/blogs/331355">https://bbs.huaweicloud.com/blogs/331355</a></p><p><a href="https://www.jianshu.com/p/8b4c95677ee0">https://www.jianshu.com/p/8b4c95677ee0</a>  </p><p><a href="https://static.kancloud.cn/apachecn/git-doc-zh/1945496">https://static.kancloud.cn/apachecn/git-doc-zh/1945496</a></p><p><a href="https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/ti-jiao-che-xiao-yu-la-qu/git-reset">https://wikinote.gitbook.io/git-learning/git-ji-ben-ming-ling/ti-jiao-che-xiao-yu-la-qu/git-reset</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git进阶 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git基础 — 1、版本控制系统(VCS)介绍</title>
      <link href="/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%9F%BA%E7%A1%80--1%E3%80%81%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E7%B3%BB%E7%BB%9F(VCS)%E4%BB%8B%E7%BB%8D/"/>
      <url>/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%9F%BA%E7%A1%80--1%E3%80%81%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E7%B3%BB%E7%BB%9F(VCS)%E4%BB%8B%E7%BB%8D/</url>
      
        <content type="html"><![CDATA[<p>在具体了解Git之前，首先需要我们了解一下VCS，即版本控制系统（<code>version control system</code>）</p><h2 id="1-什么是版本控制系统"><a href="#1-什么是版本控制系统" class="headerlink" title="1. 什么是版本控制系统"></a>1. 什么是版本控制系统</h2><p>版本控制是一种记录一个或若干个文件内容变化，以便将来查阅特定版本修订情况的系统。版本控制系统不仅可以应用于软件源代码的文本文件，而且可以对任何类型的文件进行版本控制。</p><p>有了它你就可以将某个文件回溯到之前的状态，甚至将整个项目都回退到过去某个时间点的状态，你可以比较不同版本文件的变化细节，查出最后是谁修改了哪个地方。也就是无论文件最后被修改成什么样子，你都可以轻松恢复到原先的样子，但是额外增加的工作量却微乎其微。</p><h2 id="2-我们为什么要用版本控制"><a href="#2-我们为什么要用版本控制" class="headerlink" title="2. 我们为什么要用版本控制"></a>2. 我们为什么要用版本控制</h2><pre><code>无论对于个人还是团队项目开发，项目文件总会产生不同的版本。对于个人来说，随着时间的推移，对于不同的时间节点，项目文件相应的会产生不同的版本。对于团队来说，不仅每个人自己的项目文件会有不同版本，而且团队成员之间协作，不管是文件级别，还是目录级别，都会产生更多的版本。若想要整个项目安全有序的迭代开发，版本控制势在必行。有了它你就可以将某个文件回溯到之前的状态，甚至将整个项目都回退到过去某 个时间点的状态。就算你乱来一气把整个项目中的文件改的改删的删，你也照样可以 轻松恢复到原先的样子。但额外增加的工作量却微乎其微。 你可以比较文件的变化细节，查出最后是谁修改了哪个地方，从而找出导致怪异 问题出现的原因，又是谁在何时报告了某个功能缺陷等等。</code></pre><h2 id="3-版本管理系统的演变"><a href="#3-版本管理系统的演变" class="headerlink" title="3. 版本管理系统的演变"></a>3. 版本管理系统的演变</h2><h3 id="3-1-本地版本控制系统"><a href="#3-1-本地版本控制系统" class="headerlink" title="3.1. 本地版本控制系统"></a>3.1. 本地版本控制系统</h3><p>许多人习惯用复制整个项目目录的方式来保存不同的版本，或许还会改名加上备份时间以示区别。 比如xxx_001，xxx_002，这么做唯一的好处就是简单，但是特别容易犯错。 有时候会混淆所在的工作目录，一不小心会写错文件或者覆盖意想外的文件。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911181827.png"></p><p>为了解决这个问题，人们很久以前就开发了许多种本地版本控制系统，大多都是采用某种简单的数据库来记录文件的历次更新差异。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911113945.png"></p><p>这种形式主要实现了基本的代码版本管理，但缺点是无法让多人同时对一个版本库进行修改。这个也和当时软件规模不够大有关，也没有这样的需求。</p><h3 id="3-2-集中化版本控制系统"><a href="#3-2-集中化版本控制系统" class="headerlink" title="3.2. 集中化版本控制系统"></a>3.2. 集中化版本控制系统</h3><p>接下来人们又遇到一个问题，如何让在不同系统上的开发者协同工作？ 于是，集中化的版本控制系统（<code>Centralized Version Control Systems</code>，简称 CVCS）应运而生。 这类系统，诸如 CVS、<code>Subversion</code>等，都有一个单一的集中管理的服务器，保存所有文件的修订版本，而协同工作的人们都通过客户端连到这台服务器，取出最新的文件或者提交更新。 多年以来，这已成为版本控制系统的标准做法。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911113542.png"></p><p>这种做法带来了许多好处，特别是相较于老式的本地 VCS 来说。 现在，每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限，并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。</p><p>事分两面，有好有坏，集中管理的服务器最显而易见的缺点是中央服务器的单点故障问题。 如果宕机一小时，那么在这一小时内，谁都无法提交更新，也就无法协同工作。 如果中心数据库所在的磁盘发生损坏，又没有做恰当备份，毫无疑问你将丢失所有数据，包括项目的整个变更历史，只剩下人们在各自机器上保留的单独快照。 本地版本控制系统也存在类似问题，只要整个项目的历史记录被保存在单一位置，就有丢失所有历史更新记录的风险。</p><p>集中式版本控制系统另外一个大的问题就是必须联网才能工作，如果不能连接到中央服务器上，就不能对文件进行提交，还原，对比等操作。</p><h3 id="3-3-分布式版本控制系统"><a href="#3-3-分布式版本控制系统" class="headerlink" title="3.3. 分布式版本控制系统"></a>3.3. 分布式版本控制系统</h3><p>于是分布式版本控制系统（<code>Distributed Version Control System</code>，简称 DVCS）面世了。 在这类系统中，像 <code>Git</code>、<code>Mercurial</code>、<code>Bazaar</code> 以及 <code>Darcs</code> 等，客户端并不只提取最新版本的文件快照，而是把代码仓库完整地镜像下来。 这么一来，任何一处协同工作用的服务器发生故障，事后都可以用任何一个镜像，把本地仓库恢复。 因为每一次的克隆操作，实际上都是一次对代码仓库的完整备份。</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911113601.png"></p><p>许多这类系统都可以指定和若干不同的远端代码仓库进行交互。因此你就可以在同一个项目中，分别和不同工作小组的人相互协作。 你可以根据需要设定不同的协作流程，比如层次模型式的工作流，而这在以前的集中式系统中是无法实现的。</p><p>分布式的版本控制系统在管理项目时 存放的不是项目版本与版本之间 的差异.它存的是索引(所需磁盘空间很少 所以每个客户端都可以放下整个 项目的历史记录) 分布式的版本控制系统出现之后,解决了集中式版本控制系统的缺陷: 1. 断网的情况下也可以进行开发(因为版本控制是在本地进行的) 2. 使用 github 进行团队协作,哪怕 github 挂了 每个客户端保存 的也都是整个完整的项目(包含历史记录的!!!)</p><blockquote><p>参考：</p><ul><li><a href="https://www.cnblogs.com/liuyuelinfighting/p/16111867.html">https://www.cnblogs.com/liuyuelinfighting/p/16111867.html</a></li></ul></blockquote>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git基础 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git基础 — 2、Git介绍及安装使用</title>
      <link href="/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%9F%BA%E7%A1%80--2%E3%80%81Git%E4%BB%8B%E7%BB%8D%E5%8F%8A%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8/"/>
      <url>/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%9F%BA%E7%A1%80--2%E3%80%81Git%E4%BB%8B%E7%BB%8D%E5%8F%8A%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8/</url>
      
        <content type="html"><![CDATA[<p><span style="display:none">#flashcards</span><br><span style="display:none">Git基础 — 2、Git介绍及安装使用::不断费曼，坚持不懈！</span></p><!--SR:2022-12-12,3,250--><h1 id="1-Git的历史"><a href="#1-Git的历史" class="headerlink" title="1. Git的历史"></a>1. Git的历史</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911182649.png"></p><h1 id="2-Git的特点"><a href="#2-Git的特点" class="headerlink" title="2. Git的特点"></a>2. Git的特点</h1><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911182833.png"></p><h1 id="3-Git安装"><a href="#3-Git安装" class="headerlink" title="3. Git安装"></a>3. Git安装</h1><h2 id="3-1-Windows安装"><a href="#3-1-Windows安装" class="headerlink" title="3.1. Windows安装"></a>3.1. Windows安装</h2><h3 id="3-1-1-下载地址"><a href="#3-1-1-下载地址" class="headerlink" title="3.1.1. 下载地址"></a>3.1.1. 下载地址</h3><p>git 地址 : <a href="https://git-scm.com/download/win">https://git-scm.com/download/win</a></p><p>下载完安装包之后，双击 exe 安装包，可以看到如下图窗口界面，一直点击</p><p>完成安装之后，就可以使用命令行的 git 工具（已经自带了 ssh 客户端）;</p><p>右键菜单中会出现Git Bash Here菜单，如图所示:</p><p><img src="https://github.com/TaylorLuo/typora-imgs/raw/master/img/20200602102005.png" alt="image-20200602102004434"></p><p>当你点击 git bash Here 菜单之后，可以看到一个终端窗口，在终端里面输入</p><p>命令 git –version，如果可以看到 git 的版本信息，则说明安装成功，如下图所</p><p>示：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911213455.png"></p><h2 id="3-2-MacOS安装"><a href="#3-2-MacOS安装" class="headerlink" title="3.2. MacOS安装"></a>3.2. MacOS安装</h2><h3 id="3-2-1-下载地址"><a href="#3-2-1-下载地址" class="headerlink" title="3.2.1. 下载地址"></a>3.2.1. 下载地址</h3><p>git 地址 :<a href="https://git-scm.com/download/mac">https://git-scm.com/download/mac</a></p><p>下载下来之后可以看到一个 dmg 文件，双击打开 压缩文件，可以看到里面有</p><p>一个文件， 再次双击 pkg 文件，就可以进行安装，然后按照引导一直点击继续</p><p>按钮就可以完成安装了.</p><h1 id="4-初始化配置"><a href="#4-初始化配置" class="headerlink" title="4. 初始化配置"></a>4. 初始化配置</h1><h2 id="4-1-设置用户签名"><a href="#4-1-设置用户签名" class="headerlink" title="4.1. 设置用户签名"></a>4.1. 设置用户签名</h2><h3 id="4-1-1-基本语法"><a href="#4-1-1-基本语法" class="headerlink" title="4.1.1. 基本语法"></a>4.1.1. 基本语法</h3><figure class="highlight shell"><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 shell">git config --global user.name 用户名<br>git config --global user.email 邮箱<br></code></pre></td></tr></table></figure><h3 id="4-1-2-案例实操"><a href="#4-1-2-案例实操" class="headerlink" title="4.1.2. 案例实操"></a>4.1.2. 案例实操</h3><p>全局范围的签名设置：</p><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911214554.png"></p><p>说明：<br>签名的作用是区分不同操作者身份。用户的签名信息在每一个版本的提交信息中能够看<br>到，以此确认本次提交是谁做的。<span style="background-color:#00ff00">Git 首次安装必须设置一下用户签名，否则无法提交代码</span>。<br>※注意：<span style="background-color:#ffff00">这里设置用户签名和将来登录 GitHub（或其他代码托管中心）的账号没有任</span><br><span style="background-color:#ffff00">何关系。</span></p><blockquote><p>一般在新的系统上，我们都需要先配置下自己的 Git 工作环境。配置工作只需一<br>次，以后升级时还会沿用现在的配置。当然，如果需要，你随时可以用相同的命令修<br>改已有的配置。</p><p>Git 提供了一个叫做 git config 的命令来配置或读取相应的工作环境变量而正是由<br>这些环境变量，决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放<br>在以下三个不同的地方：</p><p>1、&#x2F;etc&#x2F;gitconfig 文件：系统中对所有用户都普遍适用的配置。若使用 git config 时用 –system 选项，读写的就是这个文件。</p><p>2、~&#x2F;.gitconfig 文件：用户目录下的配置文件只适用于该用户。若使用 git config 时用 –global 选项，读写的就是这个文件。</p><p>3、.git&#x2F;config 文件：当前项目的 Git 目录中的配置文件（也就是工作目录中的 .git&#x2F;config 文件）这里的配置仅仅针对当前项目有效。若使用 git config 时用 –local 选项，读写的就是这个文件。</p></blockquote><p><font color=#ff0000>优先级从低到高依次为system 、global、local ，每一个级别的配置都会覆盖上层的相同配置</font></p><a href="/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/gitconfig(global%E3%80%81system%E3%80%81local)/" title="git config(global、system、local)">git config(global、system、local)</a><h2 id="4-2-初始化本地库"><a href="#4-2-初始化本地库" class="headerlink" title="4.2. 初始化本地库"></a>4.2. 初始化本地库</h2><h3 id="4-2-1-基本语法"><a href="#4-2-1-基本语法" class="headerlink" title="4.2.1. 基本语法"></a>4.2.1. 基本语法</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git init repository_name<br></code></pre></td></tr></table></figure><p>或者cd到目录中执行 <code>git init .</code></p><h3 id="4-2-2-案例实操"><a href="#4-2-2-案例实操" class="headerlink" title="4.2.2. 案例实操"></a>4.2.2. 案例实操</h3><p><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911220425.png"></p><p>有个提醒：提示的很清楚，就是说要配置一个默认的分支名称</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">git config --global init.defaultBranch master<br></code></pre></td></tr></table></figure><p><font color=#ff0000>注意：一定要进入到新建的本地库中执行Git命令</font><br><img src="https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20220911220909.png"><br>可以看到本地仓库中有一个.git文件夹</p><p>.git中具体内容请查阅<a href="/2022/09/11/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/Git%E5%8E%9F%E7%90%86--1%E3%80%81Git%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E5%8F%8AGit%E4%B8%89%E6%A3%B5%E6%A0%91(%E5%B7%A5%E4%BD%9C%E5%8C%BA%E3%80%81%E6%9A%82%E5%AD%98%E5%8C%BA%E3%80%81%E7%89%88%E6%9C%AC%E5%BA%93)/" title="Git原理--1、Git目录结构及Git三棵树(工作区、暂存区、版本库)">Git原理--1、Git目录结构及Git三棵树(工作区、暂存区、版本库)</a></p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git基础 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>git config(global、system、local)</title>
      <link href="/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/gitconfig(global%E3%80%81system%E3%80%81local)/"/>
      <url>/2022/09/10/000-%E5%B7%A5%E5%85%B7%E7%AE%B1%E4%B8%93%E9%A2%98/gitconfig(global%E3%80%81system%E3%80%81local)/</url>
      
        <content type="html"><![CDATA[<p>现在大部分的项目代码的都使用git做版本控制，因此我们有必要了解git的配置文件。如果对git的配置文件不了解会导致有些时候明明配置了却发现不生效，比如在某个非git仓库目录下执行配置用户名命令：<code>git config user.name xxx</code>，却发现某个项目仓库并没有生效。</p><p>首先，我们在命令行中输入<code>git config</code>可以看到如下图提示：</p><p><a href="https://img.panyanbin.com/img/2021/06/30/1624986904-3edb6dce65621f97c0e649eee007e1e5.png"><img src="https://img.panyanbin.com/img/2021/06/30/1624986904-3edb6dce65621f97c0e649eee007e1e5.png"></a></p><p>Git有3个级别的配置文件（<code>local</code>、<code>global</code>、<code>system</code>），worktree基本不使用此处不作考虑。</p><h2 id="1-system系统级"><a href="#1-system系统级" class="headerlink" title="1.system系统级"></a>1.system系统级</h2><p>系统级配置文件含有系统里每位用户及他们所拥有的仓库的配置值。其位置为git的安装目录下的<code>/etc/gitconfig</code>，即如果git的安装目录为<code>D:\Git</code>，则配置文件地址为<code>D:\Git\etc\gitconfig</code>。</p><p><strong>优先度最低，其配置值可被全局级配置和本地级配置的值覆盖</strong>。一般我们很少会使用系统级的配置。</p><h2 id="2-global全局级"><a href="#2-global全局级" class="headerlink" title="2.global全局级"></a>2.global全局级</h2><p>全局级配置文件包含当前系统用户的拥有的仓库配置值，每个系统用户的全局级配置相互隔离。全局级别的配置默认保存在当前系统用户的主目录下的 .gitconfig 文件内。Windows通常保存在<code>C:\Users\xxxx\.gitconfig</code>，Linux为<code>/home/xxx/.gitconfig</code>。</p><p><strong>优先度比系统级高，可覆盖系统级的配置值</strong>。全局级的配置平时使用得比较多，比如设置账号和邮箱：</p><figure class="highlight plaintext"><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 plaintext">git config --global user.name panyanbin  <br>git config --global user.email me@panyanbin.com  <br></code></pre></td></tr></table></figure><h2 id="3-local本地级"><a href="#3-local本地级" class="headerlink" title="3.local本地级"></a>3.local本地级</h2><p>本地级别的配置保存在当前仓库下面的 <code>.git\config</code> 文件内，通常 .git 文件夹是隐藏的，Window要在文件管理器的文件夹选项中打开显示隐藏文件夹才可以看到。这里的配置仅对当前仓库有效，不影响其他的仓库。</p><p><strong>优先级别最高，如果全局级别或系统级别的配置里出现了同一配置项，则以本地级别配置内容为准</strong></p><h2 id="4-配置使用"><a href="#4-配置使用" class="headerlink" title="4.配置使用"></a>4.配置使用</h2><ul><li>获取配置项的值</li></ul><p>获取某一项配置时若不指定级别，则会从本地级开始一级一级往上查找直到配置不存在。即：先从本地级配置中开始查找配置项，若本地级不存在则往上从全局级别配置查找，若全局级别还是不存在该配置项就从系统级配置查找，若还是不存在则返回空。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git config --global get user.name  <br></code></pre></td></tr></table></figure><ul><li>设置配置项的值</li></ul><p>设置某一项配置时若不指定级别，则会配置项默认会设置到本地级的配置文件中，即设置配置项时默认使用<code>--local</code>级别。若指定级别配置项就设置到该级别的配置文件中。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git config --global user.name panyanbin  <br></code></pre></td></tr></table></figure><ul><li>删除配置项</li></ul><p>与设置配置项一样，删除配置项若不指定级别，则默认使用<code>--local</code>级别。指定级别则删除该级别的配置文件中的配置项</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git config --global --unset user.name  <br></code></pre></td></tr></table></figure><ul><li>获取所有配置项</li></ul><p>若不指定配置级别，则会返回3个级别的配置，从上往下按系统级别、全局级别、本地级别的顺序进行输出显示。</p><p>了解更多内容可以看<a href="https://git-scm.com/book/zh/v2/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-%E9%85%8D%E7%BD%AE-Git">git官网</a>的配置说明。</p>]]></content>
      
      
      
    </entry>
    
    
  
  
</search>
