<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>复习-JavaGuide on 随记</title><link>https://blog.liuyi2026.asia/zh/tags/%E5%A4%8D%E4%B9%A0-javaGuide/</link><description>Recent content in 复习-JavaGuide on 随记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Tue, 14 Mar 2023 22:34:52 +0000</lastBuildDate><atom:link href="https://blog.liuyi2026.asia/zh/tags/%E5%A4%8D%E4%B9%A0-javaGuide/index.xml" rel="self" type="application/rss+xml"/><item><title>mysql执行计划</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0606lymysql-query-execution-plan/</link><pubDate>Tue, 14 Mar 2023 22:34:52 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0606lymysql-query-execution-plan/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>本文来自公号 MySQL 技术，JavaGuide 对其做了补充完善。原文地址：https://mp.weixin.qq.com/s/d5OowNLtXBGEAbT31sSH4g&lt;/p>&lt;/blockquote>&lt;p>优化 SQL 的第一步应该是读懂 SQL 的执行计划。本篇文章，我们一起来学习下 MySQL &lt;code>EXPLAIN&lt;/code> 执行计划相关知识。&lt;/p>
&lt;h2 id="什么是执行计划">什么是执行计划？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e6%89%a7%e8%a1%8c%e8%ae%a1%e5%88%92">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>执行计划&lt;/strong> 是指一条 SQL 语句在经过 &lt;strong>MySQL 查询优化器&lt;/strong> 的优化会后，具体的执行方式。&lt;/p>
&lt;p>执行计划通常用于 &lt;strong>SQL 性能分析&lt;/strong>、&lt;strong>优化&lt;/strong>等场景。通过 &lt;code>EXPLAIN&lt;/code> 的结果，可以了解到如&lt;strong>数据表的查询顺序&lt;/strong>、数据查询操作的&lt;strong>操作类型&lt;/strong>、&lt;strong>哪些索引&lt;/strong>可以被命中、哪些索引&lt;strong>实际&lt;/strong>会命中、每个数据表有多少&lt;strong>行记录被查询&lt;/strong>等信息。&lt;/p>
&lt;h2 id="如何获取执行计划">如何获取执行计划？&lt;a class="anchor" href="#%e5%a6%82%e4%bd%95%e8%8e%b7%e5%8f%96%e6%89%a7%e8%a1%8c%e8%ae%a1%e5%88%92">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 提交准备数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SET&lt;/span> NAMES utf8mb4;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SET&lt;/span> FOREIGN_KEY_CHECKS &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- ----------------------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Table structure for dept_emp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- ----------------------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>emp_no&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">varchar&lt;/span>(&lt;span style="color:#ae81ff">128&lt;/span>) &lt;span style="color:#66d9ef">CHARACTER&lt;/span> &lt;span style="color:#66d9ef">SET&lt;/span> utf8mb4 &lt;span style="color:#66d9ef">COLLATE&lt;/span> utf8mb4_0900_ai_ci &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>other1&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">varchar&lt;/span>(&lt;span style="color:#ae81ff">255&lt;/span>) &lt;span style="color:#66d9ef">CHARACTER&lt;/span> &lt;span style="color:#66d9ef">SET&lt;/span> utf8mb4 &lt;span style="color:#66d9ef">COLLATE&lt;/span> utf8mb4_0900_ai_ci &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>other2&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">varchar&lt;/span>(&lt;span style="color:#ae81ff">255&lt;/span>) &lt;span style="color:#66d9ef">CHARACTER&lt;/span> &lt;span style="color:#66d9ef">SET&lt;/span> utf8mb4 &lt;span style="color:#66d9ef">COLLATE&lt;/span> utf8mb4_0900_ai_ci &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">PRIMARY&lt;/span> &lt;span style="color:#66d9ef">KEY&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span>) &lt;span style="color:#66d9ef">USING&lt;/span> BTREE,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">INDEX&lt;/span> &lt;span style="color:#f92672">`&lt;/span>index_emp_no&lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#f92672">`&lt;/span>emp_no&lt;span style="color:#f92672">`&lt;/span>) &lt;span style="color:#66d9ef">USING&lt;/span> BTREE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">ENGINE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> InnoDB &lt;span style="color:#66d9ef">CHARACTER&lt;/span> &lt;span style="color:#66d9ef">SET&lt;/span> &lt;span style="color:#f92672">=&lt;/span> utf8mb4 &lt;span style="color:#66d9ef">COLLATE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> utf8mb4_0900_ai_ci ROW_FORMAT &lt;span style="color:#f92672">=&lt;/span> Dynamic;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- ----------------------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Records of dept_emp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- ----------------------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a1&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o11&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;012&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a2&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o21&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o22&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#ae81ff">3&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a3&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o31&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o32&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#ae81ff">4&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a4&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o41&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o42&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#f92672">`&lt;/span>dept_emp&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a5&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o51&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;o52&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SET&lt;/span> FOREIGN_KEY_CHECKS &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>MySQL 为我们提供了 &lt;code>EXPLAIN&lt;/code> 命令，来获取执行计划的相关信息。&lt;/p></description></item><item><title>sql常见面试题总结01</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0503lysql-question-01/</link><pubDate>Sun, 12 Mar 2023 17:53:18 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0503lysql-question-01/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>题目来源于：
&lt;a target="_blank" href="https://www.nowcoder.com/exam/oj?page=1&amp;amp;tab=SQL%e7%af%87&amp;amp;topicId=298">牛客题霸 - SQL 必知必会&lt;/a>&lt;/p>&lt;/blockquote>&lt;h2 id="检索数据">检索数据&lt;a class="anchor" href="#%e6%a3%80%e7%b4%a2%e6%95%b0%e6%8d%ae">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>select&lt;/code> 用于从数据库中查询数据。&lt;/p>
&lt;h3 id="从-customers-表中检索所有的-id">从 Customers 表中检索所有的 ID&lt;a class="anchor" href="#%e4%bb%8e-customers-%e8%a1%a8%e4%b8%ad%e6%a3%80%e7%b4%a2%e6%89%80%e6%9c%89%e7%9a%84-id">#&lt;/a>&lt;/h3>
&lt;p>现有表 &lt;code>Customers&lt;/code> 如下：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>cust_id&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>A&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>C&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>编写 SQL 语句，从 &lt;code>Customers&lt;/code> 表中检索所有的 &lt;code>cust_id&lt;/code>。&lt;/p>
&lt;p>答案：&lt;/p>
&lt;pre tabindex="0">&lt;code>select cust_id
from Customers;&lt;/code>&lt;/pre>&lt;h3 id="检索并列出已订购产品的清单">检索并列出已订购产品的清单&lt;a class="anchor" href="#%e6%a3%80%e7%b4%a2%e5%b9%b6%e5%88%97%e5%87%ba%e5%b7%b2%e8%ae%a2%e8%b4%ad%e4%ba%a7%e5%93%81%e7%9a%84%e6%b8%85%e5%8d%95">#&lt;/a>&lt;/h3>
&lt;p>表 &lt;code>OrderItems&lt;/code> 含有非空的列 &lt;code>prod_id&lt;/code> 代表商品 id，包含了所有已订购的商品（有些已被订购多次）。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>prod_id&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>a1&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a3&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a4&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a5&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a6&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a7&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>编写 SQL 语句，检索并列出所有已订购商品（&lt;code>prod_id&lt;/code>）的去重后的清单。&lt;/p>
&lt;p>答案：&lt;/p>
&lt;pre tabindex="0">&lt;code>select distinct prod_id
from OrderItems;&lt;/code>&lt;/pre>&lt;p>知识点：&lt;code>distinct&lt;/code> 用于返回列中的唯一不同值。&lt;/p>
&lt;h3 id="检索所有列">检索所有列&lt;a class="anchor" href="#%e6%a3%80%e7%b4%a2%e6%89%80%e6%9c%89%e5%88%97">#&lt;/a>&lt;/h3>
&lt;p>现在有 &lt;code>Customers&lt;/code> 表（表中含有列 &lt;code>cust_id&lt;/code> 代表客户 id，&lt;code>cust_name&lt;/code> 代表客户姓名）&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>cust_id&lt;/th>
 &lt;th>cust_name&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>a1&lt;/td>
 &lt;td>andy&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a2&lt;/td>
 &lt;td>ben&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a3&lt;/td>
 &lt;td>tony&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a4&lt;/td>
 &lt;td>tom&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a5&lt;/td>
 &lt;td>an&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a6&lt;/td>
 &lt;td>lee&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>a7&lt;/td>
 &lt;td>hex&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>需要编写 SQL 语句，检索所有列。&lt;/p></description></item><item><title>sql语法基础知识总结</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0504lysql-syntax-summary/</link><pubDate>Sun, 12 Mar 2023 17:52:48 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0504lysql-syntax-summary/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>本文整理完善自下面这两份资料：&lt;/p>
&lt;ul>
&lt;li>
&lt;a target="_blank" href="https://juejin.cn/post/6844903790571700231">SQL 语法速成手册&lt;/a>&lt;/li>
&lt;li>
&lt;a target="_blank" href="https://www.begtut.com/mysql/mysql-tutorial.html">MySQL 超全教程&lt;/a>&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;h2 id="基本概念">基本概念&lt;a class="anchor" href="#%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5">#&lt;/a>&lt;/h2>
&lt;h3 id="数据库术语">数据库术语&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e6%9c%af%e8%af%ad">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>&lt;code>数据库（database）&lt;/code> - 保存有组织的数据的容器（通常是一个文件或一组文件）。&lt;/li>
&lt;li>&lt;code>数据表（table）&lt;/code> - 某种特定类型数据的结构化清单。&lt;/li>
&lt;li>&lt;code>模式（schema）&lt;/code> - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储，包含存储什么样的数据，数据如何分解，各部分信息如何命名等信息。数据库和表都有模式。&lt;/li>
&lt;li>&lt;code>列（column）&lt;/code> - 表中的一个字段。所有表都是由一个或多个列组成的。&lt;/li>
&lt;li>&lt;code>行（row）&lt;/code> - 表中的一个记录。&lt;/li>
&lt;li>&lt;code>主键（primary key）&lt;/code> - 一列（或一组列），其值能够唯一标识表中每一行。&lt;/li>
&lt;/ul>
&lt;h3 id="sql-语法">SQL 语法&lt;a class="anchor" href="#sql-%e8%af%ad%e6%b3%95">#&lt;/a>&lt;/h3>
&lt;p>SQL（Structured Query Language)，标准 SQL 由 ANSI 标准委员会管理，从而称为 ANSI SQL。各个 DBMS 都有自己的实现，如 PL/SQL、Transact-SQL 等。&lt;/p>
&lt;h4 id="sql-语法结构">SQL 语法结构&lt;a class="anchor" href="#sql-%e8%af%ad%e6%b3%95%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h4>
&lt;p>&lt;img src="img/ly-20241212141857484.jpg" alt="img" />&lt;/p>
&lt;p>SQL 语法结构包括：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>子句&lt;/code>&lt;/strong> - 是语句和查询的组成成分。（在某些情况下，这些都是可选的。）&lt;/li>
&lt;li>&lt;strong>&lt;code>表达式&lt;/code>&lt;/strong> - 可以产生任何标量值，或由列和行的数据库表&lt;/li>
&lt;li>&lt;strong>&lt;code>谓词&lt;/code>&lt;/strong> - 给需要评估的 SQL 三值逻辑（3VL）（true/false/unknown）或布尔真值指定条件，并限制语句和查询的效果，或改变程序流程。&lt;/li>
&lt;li>&lt;strong>&lt;code>查询&lt;/code>&lt;/strong> - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。&lt;/li>
&lt;li>&lt;strong>&lt;code>语句&lt;/code>&lt;/strong> - 可以持久地影响纲要和数据，也可以控制数据库事务、程序流程、连接、会话或诊断。&lt;/li>
&lt;/ul>
&lt;h4 id="sql-语法要点">SQL 语法要点&lt;a class="anchor" href="#sql-%e8%af%ad%e6%b3%95%e8%a6%81%e7%82%b9">#&lt;/a>&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>SQL 语句不区分大小写&lt;/strong>，但是数据库表名、列名和值是否区分，依赖于具体的 DBMS 以及配置。例如：&lt;code>SELECT&lt;/code> 与 &lt;code>select&lt;/code> 、&lt;code>Select&lt;/code> 是相同的。&lt;/li>
&lt;li>&lt;strong>多条 SQL 语句必须以分号（&lt;code>;&lt;/code>）分隔&lt;/strong>。&lt;/li>
&lt;li>处理 SQL 语句时，&lt;strong>所有空格都被忽略&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>SQL 语句可以写成一行，也可以分写为多行。&lt;/p></description></item><item><title>redis问题图解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/diagram/</link><pubDate>Mon, 27 Feb 2023 22:11:02 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/diagram/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;ol>
&lt;li>
&lt;p>主从复制原理&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141920958.png" alt="主从复制原理" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>哨兵模式(简单)&lt;br>
&lt;img src="img/ly-20241212141921249.png" alt="ly-20241212141921249" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>哨兵模式详解&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>先配置&lt;strong>主从模式&lt;/strong>，再配置&lt;strong>哨兵模式&lt;/strong>&lt;/p>&lt;/blockquote>&lt;p>所有的哨兵 sentinel.conf 都是配置为监听master&amp;ndash;&amp;gt; 192.168.14.101，如果主机宕机，sentinel.conf 中的配置也会自动更改为选举后的&lt;img src="img/ly-20241212141921408.png" alt="ly-20241212141921408" />&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Java客户端连接原理&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>客户端是和Sentinel来进行交互的,通过Sentinel来获取真正的Redis节点信息,然后来操作.实际工作时,Sentinel 内部维护了一个主题队列,用来保存Redis的节点信息,并实时更新,客户端订阅了这个主题,然后实时的去获取这个队列的Redis节点信息.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">代码相对比较简单
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">**/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//1.设置sentinel 各个节点集合&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Set&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span> sentinelSet &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> HashSet&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sentinelSet.&lt;span style="color:#a6e22e">add&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;192.168.14.101:26379&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sentinelSet.&lt;span style="color:#a6e22e">add&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;192.168.14.102:26380&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sentinelSet.&lt;span style="color:#a6e22e">add&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;192.168.14.103:26381&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//2.设置jedispool 连接池配置文件&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>JedisPoolConfig config &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> JedisPoolConfig();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>config.&lt;span style="color:#a6e22e">setMaxTotal&lt;/span>(10);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>config.&lt;span style="color:#a6e22e">setMaxWaitMillis&lt;/span>(1000);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//3.设置mastername,sentinelNode集合,配置文件,Redis登录密码&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>JedisSentinelPool jedisSentinelPool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> JedisSentinelPool(&lt;span style="color:#e6db74">&amp;#34;mymaster&amp;#34;&lt;/span>,sentinelSet,config,&lt;span style="color:#e6db74">&amp;#34;123&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Jedis jedis &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> jedis &lt;span style="color:#f92672">=&lt;/span> jedisSentinelPool.&lt;span style="color:#a6e22e">getResource&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//获取Redis中key=hello的值&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> String value &lt;span style="color:#f92672">=&lt;/span> jedis.&lt;span style="color:#a6e22e">get&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;hello&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(value);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;span style="color:#66d9ef">catch&lt;/span> (Exception e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.&lt;span style="color:#a6e22e">printStackTrace&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;span style="color:#66d9ef">finally&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(jedis &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> jedis.&lt;span style="color:#a6e22e">close&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212141921579.png" alt="ly-20241212141921579" />&lt;/p></description></item><item><title>3种常用的缓存读写策略详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0703ly3-commonly-used-cache-read-and-write-strategies/</link><pubDate>Fri, 24 Feb 2023 09:31:52 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0703ly3-commonly-used-cache-read-and-write-strategies/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>看到很多小伙伴简历上写了“&lt;strong>熟练使用缓存&lt;/strong>”，但是被我问到“&lt;strong>缓存常用的3种读写策略&lt;/strong>”的时候却一脸懵逼。&lt;/p>
&lt;p>在我看来，造成这个问题的原因是我们在学习 Redis 的时候，可能只是简单了写一些 Demo，并没有去关注缓存的读写策略，或者说压根不知道这回事。&lt;/p>
&lt;p>但是，搞懂3种常见的缓存读写策略对于实际工作中使用缓存以及面试中被问到缓存都是非常有帮助的！&lt;/p>
&lt;p>&lt;strong>下面介绍到的三种模式各有优劣，不存在最佳模式，根据具体的业务场景选择适合自己的缓存读写模式。&lt;/strong>&lt;/p>
&lt;h3 id="cache-aside-pattern旁路缓存模式">Cache Aside Pattern（旁路缓存模式）&lt;a class="anchor" href="#cache-aside-pattern%e6%97%81%e8%b7%af%e7%bc%93%e5%ad%98%e6%a8%a1%e5%bc%8f">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式，比较适合读请求比较多的场景。&lt;/strong>&lt;/p>
&lt;p>Cache Aside Pattern 中服务端需要同时维系 db 和 cache，并且是&lt;strong>以 db 的结果为准&lt;/strong>。&lt;/p>
&lt;p>下面我们来看一下这个策略模式下的缓存读写步骤。&lt;/p>
&lt;p>&lt;strong>写&lt;/strong> ：&lt;/p>
&lt;ul>
&lt;li>先更新 db&lt;/li>
&lt;li>然后直接删除 cache 。&lt;/li>
&lt;/ul>
&lt;p>简单画了一张图帮助大家理解写的步骤。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141916648.jpg" alt="img" />&lt;/p>
&lt;p>&lt;strong>读&lt;/strong> :&lt;/p>
&lt;ul>
&lt;li>从 cache 中读取数据，读取到就直接返回&lt;/li>
&lt;li>cache 中读取不到的话，就从 db 中读取数据返回&lt;/li>
&lt;li>再把数据放到 cache 中。&lt;/li>
&lt;/ul>
&lt;p>简单画了一张图帮助大家理解读的步骤。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141916935.png" alt="ly-20241212141916935" />&lt;/p>
&lt;p>你仅仅了解了上面这些内容的话是远远不够的，我们还要搞懂其中的原理。&lt;/p>
&lt;p>比如说面试官很可能会追问：“&lt;strong>在写数据的过程中，可以先删除 cache ，后更新 db 么？&lt;/strong>”&lt;/p>
&lt;p>&lt;strong>答案：&lt;/strong> 那肯定是不行的！因为这样可能会造成 &lt;strong>数据库（db）和缓存（Cache）数据不一致&lt;/strong>的问题。&lt;/p>
&lt;p>举例：请求 1 先写数据 A，请求 2 随后读数据 A 的话，就很有可能产生数据不一致性的问题。&lt;/p>
&lt;p>这个过程可以简单描述为：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>请求 1 先把 cache 中的 A 数据删除 -&amp;gt; 请求 2 从 db 中读取数据【&lt;strong>此时请求2把脏数据(对于请求1来说是)更新到缓存去了&lt;/strong>】-&amp;gt;请求 1 再把 db 中的 A 数据更新，即请求1的操作非原子&lt;/p></description></item><item><title>redis内存碎片</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0704lyredis-memory-fragmentation/</link><pubDate>Fri, 24 Feb 2023 09:26:00 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0704lyredis-memory-fragmentation/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="什么是内存碎片">什么是内存碎片?&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%86%85%e5%ad%98%e7%a2%8e%e7%89%87">#&lt;/a>&lt;/h2>
&lt;p>你可以将内存碎片简单地理解为那些&lt;strong>不可用的空闲内存&lt;/strong>。&lt;/p>
&lt;p>举个例子：操作系统为你分配了 32 字节的连续内存空间，而你存储数据实际只需要使用 24 字节内存空间，那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话，就可以被称为内存碎片。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141917438.png" alt="ly-20241212141917438" />&lt;/p>
&lt;p>Redis 内存碎片虽然不会影响 Redis 性能，但是会&lt;strong>增加内存消耗&lt;/strong>。&lt;/p>
&lt;h2 id="为什么会有-redis-内存碎片">为什么会有 Redis 内存碎片?&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e6%9c%89-redis-%e5%86%85%e5%ad%98%e7%a2%8e%e7%89%87">#&lt;/a>&lt;/h2>
&lt;p>Redis 内存碎片产生比较常见的 2 个原因：&lt;/p>
&lt;p>&lt;strong>1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。&lt;/strong>&lt;/p>
&lt;p>以下是这段 Redis 官方的原话：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>To store user keys, Redis allocates at most as much memory as the &lt;code>maxmemory&lt;/code> setting enables (however there are small extra allocations possible).&lt;/p>&lt;/blockquote>&lt;p>Redis 使用 &lt;code>zmalloc&lt;/code> 方法(Redis 自己实现的内存分配方法)进行内存分配的时候，除了要分配 &lt;code>size&lt;/code> 大小的内存之外，还会多分配 &lt;code>PREFIX_SIZE&lt;/code> 大小的内存。&lt;/p>
&lt;p>&lt;code>zmalloc&lt;/code> 方法源码如下（源码地址：https://github.com/antirez/redis-tools/blob/master/zmalloc.c）：&lt;/p>
&lt;pre tabindex="0">&lt;code>void *zmalloc(size_t size) {
 // 分配指定大小的内存
 void *ptr = malloc(size+PREFIX_SIZE);
 if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
 update_zmalloc_stat_alloc(zmalloc_size(ptr));
 return ptr;
#else
 *((size_t*)ptr) = size;
 update_zmalloc_stat_alloc(size+PREFIX_SIZE);
 return (char*)ptr+PREFIX_SIZE;
#endif
}&lt;/code>&lt;/pre>&lt;p>另外，Redis 可以使用&lt;strong>多种内存分配器&lt;/strong>来分配内存（ &lt;strong>libc&lt;/strong>、&lt;strong>jemalloc&lt;/strong>、&lt;strong>tcmalloc&lt;/strong>），默认使用 
&lt;a target="_blank" href="https://github.com/jemalloc/jemalloc">jemalloc&lt;/a>，而 jemalloc 按照一系列固定的大小（8 字节、16 字节、32 字节&amp;hellip;&amp;hellip;）来分配内存的。jemalloc 划分的内存单元如下图所示：&lt;/p></description></item><item><title>redis特殊数据结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0702lyredis-spec-data-structure/</link><pubDate>Thu, 23 Feb 2023 23:02:58 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0702lyredis-spec-data-structure/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>除了 5 种基本的数据结构之外，Redis 还支持 3 种特殊的数据结构 ：&lt;strong>Bitmap&lt;/strong>、&lt;strong>HyperLogLog&lt;/strong>、&lt;strong>GEO&lt;/strong>。&lt;/p>
&lt;h2 id="bitmap">Bitmap&lt;a class="anchor" href="#bitmap">#&lt;/a>&lt;/h2>
&lt;h3 id="介绍">介绍&lt;a class="anchor" href="#%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h3>
&lt;p>Bitmap 存储的是连续的二进制数字（0 和 1），通过 Bitmap, &lt;strong>只需要一个 bit 位来表示某个元素对应的值&lt;/strong>或者&lt;strong>状态&lt;/strong>，key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte，所以 Bitmap 本身会极大的节省储存空间。&lt;/p>
&lt;p>你可以将 Bitmap 看作是一个&lt;strong>存储二进制数字（0 和 1）的数组&lt;/strong>，数组中每个元素的下标叫做 offset（偏移量）。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://camo.githubusercontent.com/1c1ce8351556fe9727cadfd385fdbd6f449d92a174bbd65db5383791fec05a13/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6769746875622f6a61766167756964652f64617461626173652f72656469732f696d6167652d32303232303732303139343135343133332e706e67">&lt;img src="https://camo.githubusercontent.com/1c1ce8351556fe9727cadfd385fdbd6f449d92a174bbd65db5383791fec05a13/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6769746875622f6a61766167756964652f64617461626173652f72656469732f696d6167652d32303232303732303139343135343133332e706e67" alt="img" />&lt;/a>&lt;/p>
&lt;h3 id="常用命令">常用命令&lt;a class="anchor" href="#%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4">#&lt;/a>&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>命令&lt;/th>
 &lt;th>介绍&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>SETBIT key offset value&lt;/td>
 &lt;td>设置指定 offset 位置的值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GETBIT key offset&lt;/td>
 &lt;td>获取指定 offset 位置的值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>BITCOUNT key start end&lt;/td>
 &lt;td>获取 start 和 end 之前值为 1 的元素个数&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>BITOP operation destkey key1 key2 &amp;hellip;&lt;/td>
 &lt;td>对一个或多个 Bitmap 进行运算，可用运算符有 AND, OR, XOR 以及 NOT&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Bitmap 基本操作演示&lt;/strong> ：&lt;/p></description></item><item><title>redis基本数据结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0701lyredis-base-data-structures/</link><pubDate>Thu, 23 Feb 2023 14:22:58 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0701lyredis-base-data-structures/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>Redis 共有 5 种基本数据结构：&lt;strong>String&lt;/strong>（字符串）、&lt;strong>List&lt;/strong>（列表）、&lt;strong>Set&lt;/strong>（集合）、&lt;strong>Hash&lt;/strong>（散列）、&lt;strong>Zset&lt;/strong>（有序集合）。&lt;/p>
&lt;p>这 5 种数据结构是直接提供给用户使用的，是数据的保存形式，其底层实现主要依赖这 8 种数据结构：简单动态字符串（&lt;strong>SDS&lt;/strong>）、&lt;strong>LinkedList&lt;/strong>（双向链表）、&lt;strong>Hash Table&lt;/strong>（哈希表）、&lt;strong>SkipList&lt;/strong>（跳跃表）、&lt;strong>Intset&lt;/strong>（整数集合）、&lt;strong>ZipList&lt;/strong>（压缩列表）、&lt;strong>QuickList&lt;/strong>（快速列表）。&lt;/p>
&lt;p>Redis 基本数据结构的底层数据结构实现如下：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>String&lt;/th>
 &lt;th>List&lt;/th>
 &lt;th>Hash&lt;/th>
 &lt;th>Set&lt;/th>
 &lt;th>Zset&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>SDS&lt;/td>
 &lt;td>LinkedList/ZipList/QuickList&lt;/td>
 &lt;td>Hash Table、ZipList&lt;/td>
 &lt;td>ZipList、Intset&lt;/td>
 &lt;td>ZipList、SkipList&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Redis 3.2 之前，List 底层实现是 LinkedList 或者 ZipList。 Redis 3.2 之后，引入了 LinkedList 和 ZipList 的结合 QuickList，List 的底层实现变为 QuickList。&lt;/p>
&lt;p>你可以在 Redis 官网上找到 Redis 数据结构非常详细的介绍：&lt;/p>
&lt;ul>
&lt;li>
&lt;a target="_blank" href="https://redis.com/redis-enterprise/data-structures/">Redis Data Structures&lt;/a>&lt;/li>
&lt;li>
&lt;a target="_blank" href="https://redis.io/docs/manual/data-types/data-types-tutorial/">Redis Data types tutorial&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>未来随着 Redis 新版本的发布，可能会有新的数据结构出现，通过查阅 Redis 官网对应的介绍，你总能获取到最靠谱的信息。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141914862.png" alt="ly-20241212141914862" />&lt;/p>
&lt;h2 id="string字符串">String（字符串）&lt;a class="anchor" href="#string%e5%ad%97%e7%ac%a6%e4%b8%b2">#&lt;/a>&lt;/h2>
&lt;h3 id="介绍">介绍&lt;a class="anchor" href="#%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h3>
&lt;p>String 是 Redis 中最简单同时也是最常用的一个数据结构。&lt;/p>
&lt;p>String 是一种&lt;strong>二进制安全&lt;/strong>的数据结构，可以用来&lt;strong>存储任何类型的数据&lt;/strong>比如&lt;strong>字符串&lt;/strong>、&lt;strong>整数&lt;/strong>、&lt;strong>浮点数&lt;/strong>、&lt;strong>图片&lt;/strong>（图片的 base64 编码或者解码或者图片的路径）、&lt;strong>序列化后的对象&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141915150.png" alt="ly-20241212141915150" />&lt;/p></description></item><item><title>redis面试题02</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0706lyredis-questions-02/</link><pubDate>Thu, 23 Feb 2023 11:16:04 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0706lyredis-questions-02/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="redis-事务">Redis 事务&lt;a class="anchor" href="#redis-%e4%ba%8b%e5%8a%a1">#&lt;/a>&lt;/h2>
&lt;h3 id="如何使用-redis-事务">如何使用 Redis 事务？&lt;a class="anchor" href="#%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8-redis-%e4%ba%8b%e5%8a%a1">#&lt;/a>&lt;/h3>
&lt;p>Redis 可以通过 &lt;strong>&lt;code>MULTI&lt;/code>，&lt;code>EXEC&lt;/code>，&lt;code>DISCARD&lt;/code> 和 &lt;code>WATCH&lt;/code>&lt;/strong> 等命令来实现事务(transaction)功能。&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; MULTI
OK
&amp;gt; SET PROJECT &amp;#34;JavaGuide&amp;#34;
QUEUED
&amp;gt; GET PROJECT
QUEUED
&amp;gt; EXEC
1) OK
2) &amp;#34;JavaGuide&amp;#34;&lt;/code>&lt;/pre>&lt;p>
&lt;a target="_blank" href="https://redis.io/commands/multi">&lt;code>MULTI&lt;/code>&lt;/a> 命令后可以输入多个命令，Redis 不会立即执行这些命令，而是将它们放到队列，当调用了 
&lt;a target="_blank" href="https://redis.io/commands/exec">&lt;code>EXEC&lt;/code>&lt;/a> 命令后，再执行所有的命令。&lt;/p>
&lt;p>这个过程是这样的：&lt;/p>
&lt;ol>
&lt;li>开始事务（&lt;code>MULTI&lt;/code>）；&lt;/li>
&lt;li>命令入队(批量操作 Redis 的命令，先进先出（FIFO）的顺序执行)；&lt;/li>
&lt;li>执行事务(&lt;code>EXEC&lt;/code>)。&lt;/li>
&lt;/ol>
&lt;p>你也可以通过 
&lt;a target="_blank" href="https://redis.io/commands/discard">&lt;code>DISCARD&lt;/code>&lt;/a> 命令取消一个事务，它会清空事务队列中保存的所有命令。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&amp;gt; MULTI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>OK
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; SET PROJECT &lt;span style="color:#e6db74">&amp;#34;JavaGuide&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>QUEUED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; GET PROJECT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>QUEUED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; DISCARD
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>OK&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你可以通过
&lt;a target="_blank" href="https://redis.io/commands/watch">&lt;code>WATCH&lt;/code>&lt;/a> 命令监听指定的 Key，当调用 &lt;code>EXEC&lt;/code> 命令执行事务时，如果一个被 &lt;code>WATCH&lt;/code> 命令监视的 Key 被 &lt;strong>其他客户端/Session&lt;/strong> 修改的话，整个事务都不会被执行。&lt;/p>
&lt;pre tabindex="0">&lt;code># 客户端 1
&amp;gt; SET PROJECT &amp;#34;RustGuide&amp;#34;
OK
&amp;gt; WATCH PROJECT
OK
&amp;gt; MULTI
OK
&amp;gt; SET PROJECT &amp;#34;JavaGuide&amp;#34;
QUEUED

# 客户端 2
# 在客户端 1 执行 EXEC 命令提交事务之前修改 PROJECT 的值
&amp;gt; SET PROJECT &amp;#34;GoGuide&amp;#34;

# 客户端 1
# 修改失败，因为 PROJECT 的值被客户端2修改了
&amp;gt; EXEC
(nil)
&amp;gt; GET PROJECT
&amp;#34;GoGuide&amp;#34;&lt;/code>&lt;/pre>&lt;p>不过，如果 &lt;strong>WATCH&lt;/strong> 与 &lt;strong>事务&lt;/strong> 在同一个 Session 里，并且被 &lt;strong>WATCH&lt;/strong> 监视的 Key 被修改的操作发生在事务内部，这个事务是可以被执行成功的（相关 issue ：
&lt;a target="_blank" href="https://github.com/Snailclimb/JavaGuide/issues/1714">WATCH 命令碰到 MULTI 命令时的不同效果&lt;/a>）。&lt;/p></description></item><item><title>redis面试题01</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0705lyredis-questions-01/</link><pubDate>Wed, 22 Feb 2023 11:31:11 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/Redis/ly0705lyredis-questions-01/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="redis-基础">Redis 基础&lt;a class="anchor" href="#redis-%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是-redis">什么是 Redis？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-redis">#&lt;/a>&lt;/h3>
&lt;p>
&lt;a target="_blank" href="https://redis.io/">Redis&lt;/a> 是一个基于 C 语言开发的开源数据库（BSD 许可），与传统数据库不同的是 Redis 的数据是存在&lt;strong>内存&lt;/strong>中的（内存数据库），&lt;strong>读写&lt;/strong>速度非常&lt;strong>快&lt;/strong>，被广泛应用于&lt;strong>缓存方向&lt;/strong>。并且，Redis 存储的是 &lt;strong>KV 键值对&lt;/strong>数据。&lt;/p>
&lt;p>为了满足不同的业务场景，Redis 内置了多种数据类型实现（比如 &lt;strong>String&lt;/strong>、&lt;strong>Hash&lt;/strong>、【&lt;strong>List&lt;/strong>、&lt;strong>Set&lt;/strong>、】&lt;strong>Sorted Set&lt;/strong>、&lt;strong>Bitmap&lt;/strong>）。并且，Redis 还支持&lt;strong>事务&lt;/strong> 、&lt;strong>持久化&lt;/strong>、&lt;strong>Lua 脚本&lt;/strong>、多种开箱即用的&lt;strong>集群&lt;/strong>方案（&lt;strong>Redis Sentinel&lt;/strong>、&lt;strong>Redis Cluster&lt;/strong>）。&lt;/p>
&lt;p>Redis 没有外部依赖，Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统，官方推荐生产环境使用 Linux 部署 Redis。&lt;/p>
&lt;p>个人学习的话，你可以自己本机安装 Redis 或者通过 Redis 官网提供的
&lt;a target="_blank" href="https://try.redis.io/">在线 Redis 环境&lt;/a>来实际体验 Redis。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141918257.png" alt="ly-20241212141918257" />&lt;/p>
&lt;p>全世界有非常多的网站使用到了 Redis ，
&lt;a target="_blank" href="https://techstacks.io/">techstacks.io&lt;/a> 专门维护了一个
&lt;a target="_blank" href="https://techstacks.io/tech/redis">使用 Redis 的热门站点列表&lt;/a> ，感兴趣的话可以看看。&lt;/p>
&lt;h3 id="redis-为什么这么快">Redis 为什么这么快？&lt;a class="anchor" href="#redis-%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%99%e4%b9%88%e5%bf%ab">#&lt;/a>&lt;/h3>
&lt;p>Redis 内部做了非常多的性能优化，比较重要的主要有下面 3 点：&lt;/p>
&lt;ul>
&lt;li>Redis 基于&lt;strong>内存&lt;/strong>，内存的访问速度是磁盘的上千倍；&lt;/li>
&lt;li>Redis &lt;strong>基于 Reactor 模式&lt;/strong>设计开发了一套&lt;strong>高效的事件处理模型&lt;/strong>，主要是&lt;strong>单线程事件循环&lt;/strong>和 &lt;strong>IO 多路复用&lt;/strong>（Redis 线程模式后面会详细介绍到）；&lt;/li>
&lt;li>Redis 内置了多种&lt;strong>优化过后的数据结构&lt;/strong>实现，性能非常高。&lt;/li>
&lt;/ul>
&lt;p>下面这张图片总结的挺不错的，分享一下，出自 
&lt;a target="_blank" href="https://twitter.com/alexxubyte/status/1498703822528544770">Why is Redis so fast?&lt;/a> 。&lt;/p></description></item><item><title>web-real-time-message-push</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/web-real-time-message-push/</link><pubDate>Tue, 21 Feb 2023 09:26:53 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/web-real-time-message-push/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>原文地址：https://juejin.cn/post/7122014462181113887，JavaGuide 对本文进行了完善总结。&lt;/p>&lt;/blockquote>&lt;p>我有一个朋友做了一个小破站，现在要实现一个站内信 Web 消息推送的功能，对，就是下图这个小红点，一个很常用的功能。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142025733.png" alt="站内信 Web 消息推送" />&lt;/p>
&lt;p>不过他还没想好用什么方式做，这里我帮他整理了一下几种方案，并简单做了实现。&lt;/p>
&lt;h2 id="-什么是消息推送">
&lt;a target="_blank" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81">#&lt;/a> 什么是消息推送？&lt;a class="anchor" href="#-%e4%bb%80%e4%b9%88%e6%98%af%e6%b6%88%e6%81%af%e6%8e%a8%e9%80%81">#&lt;/a>&lt;/h2>
&lt;p>推送的场景比较多，比如有人关注我的公众号，这时我就会收到一条推送消息，以此来吸引我点击打开应用。&lt;/p>
&lt;p>消息推送通常是指网站的运营工作等人员，通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。&lt;/p>
&lt;p>消息推送一般又分为 Web 端消息推送和移动端消息推送。&lt;/p>
&lt;p>移动端消息推送示例 ：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142025900.png" alt="移动端消息推送示例" />&lt;/p>
&lt;p>Web 端消息推送示例：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142026025.png" alt="Web 端消息推送示例" />&lt;/p>
&lt;p>在具体实现之前，咱们再来分析一下前边的需求，其实功能很简单，&lt;strong>只要触发某个事件（主动分享了资源或者后台主动推送消息）&lt;/strong>，&lt;strong>Web 页面的通知小红点就会实时的 &lt;code>+1&lt;/code>&lt;/strong> 就可以了。&lt;/p>
&lt;p>通常在服务端会有若干张消息推送表，用来记录用户触发不同事件所推送不同类型的消息，前端主动查询（拉）或者被动接收（推）用户所有未读的消息数。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142026150.png" alt="ly-20241212142026150" />&lt;/p>
&lt;p>消息推送无非是推（push）和拉（pull）两种形式，下边我们逐个了解下。&lt;/p>
&lt;h2 id="-消息推送常见方案">
&lt;a target="_blank" href="#%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81%E5%B8%B8%E8%A7%81%E6%96%B9%E6%A1%88">#&lt;/a> 消息推送常见方案&lt;a class="anchor" href="#-%e6%b6%88%e6%81%af%e6%8e%a8%e9%80%81%e5%b8%b8%e8%a7%81%e6%96%b9%e6%a1%88">#&lt;/a>&lt;/h2>
&lt;h3 id="-短轮询">
&lt;a target="_blank" href="#%E7%9F%AD%E8%BD%AE%E8%AF%A2">#&lt;/a> 短轮询&lt;a class="anchor" href="#-%e7%9f%ad%e8%bd%ae%e8%af%a2">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>轮询(polling)&lt;/strong> 应该是实现消息推送方案中最简单的一种，这里我们暂且将轮询分为&lt;strong>短轮询&lt;/strong>和&lt;strong>长轮询&lt;/strong>。&lt;/p>
&lt;p>短轮询很好理解，&lt;strong>指定的时间间隔&lt;/strong>，&lt;strong>由浏览器向服务器发出 HTTP 请求&lt;/strong>，&lt;strong>服务器实时返回未读消息数据给客户端，浏览器再做渲染显示&lt;/strong>。&lt;/p>
&lt;p>一个简单的 JS 定时器就可以搞定，&lt;strong>每秒钟请求一次未读消息数接口&lt;/strong>，返回的数据展示即可。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-typescript" data-lang="typescript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">setInterval&lt;/span>(() &lt;span style="color:#f92672">=&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 方法请求
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">messageCount&lt;/span>().&lt;span style="color:#a6e22e">then&lt;/span>((&lt;span style="color:#a6e22e">res&lt;/span>) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">res&lt;/span>.&lt;span style="color:#a6e22e">code&lt;/span> &lt;span style="color:#f92672">===&lt;/span> &lt;span style="color:#ae81ff">200&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">messageCount&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">res&lt;/span>.&lt;span style="color:#a6e22e">data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}, &lt;span style="color:#ae81ff">1000&lt;/span>);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>效果还是可以的，短轮询实现固然简单，缺点也是显而易见，&lt;strong>由于推送数据并不会频繁变更，无论后端此时是否有新的消息产生，客户端都会进行请求&lt;/strong>，势必会对服务端造成很大压力，浪费带宽和服务器资源。&lt;/p>
&lt;h3 id="-长轮询">
&lt;a target="_blank" href="#%E9%95%BF%E8%BD%AE%E8%AF%A2">#&lt;/a> 长轮询&lt;a class="anchor" href="#-%e9%95%bf%e8%bd%ae%e8%af%a2">#&lt;/a>&lt;/h3>
&lt;p>长轮询是对上边短轮询的一种改进版本，在尽可能减少对服务器资源浪费的同时，保证消息的相对实时性。长轮询在中间件中应用的很广泛，比如 Nacos 和 Apollo 配置中心，消息队列 Kafka、RocketMQ 中都有用到长轮询。&lt;/p></description></item><item><title>Java定时任务详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/schedule-task/</link><pubDate>Tue, 21 Feb 2023 09:17:31 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/schedule-task/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="为什么需要定时任务">为什么需要定时任务？&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e5%ae%9a%e6%97%b6%e4%bb%bb%e5%8a%a1">#&lt;/a>&lt;/h2>
&lt;p>我们来看一下几个非常常见的业务场景：&lt;/p>
&lt;ol>
&lt;li>某系统&lt;strong>凌晨要进行数据备份&lt;/strong>。&lt;/li>
&lt;li>某电商平台，用户&lt;strong>下单半个小时未支付的情况下需要自动取消&lt;/strong>订单。&lt;/li>
&lt;li>某媒体聚合平台，&lt;strong>每 10 分钟动态抓取某某网站的数据&lt;/strong>为自己所用。&lt;/li>
&lt;li>某博客平台，支持&lt;strong>定时发送文章&lt;/strong>。&lt;/li>
&lt;li>某基金平台，&lt;strong>每晚定时计算用户当日收益情况并推送给用户最新的数据&lt;/strong>。&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ol>
&lt;p>这些场景往往都要求我们在某个特定的时间去做某个事情。&lt;/p>
&lt;h2 id="单机定时任务技术选型">单机定时任务技术选型&lt;a class="anchor" href="#%e5%8d%95%e6%9c%ba%e5%ae%9a%e6%97%b6%e4%bb%bb%e5%8a%a1%e6%8a%80%e6%9c%af%e9%80%89%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;h3 id="timer">Timer&lt;a class="anchor" href="#timer">#&lt;/a>&lt;/h3>
&lt;p>&lt;code>java.util.Timer&lt;/code>是 JDK 1.3 开始就已经支持的一种定时任务的实现方式。&lt;/p>
&lt;p>&lt;code>Timer&lt;/code> 内部使用一个叫做 &lt;code>TaskQueue&lt;/code> 的类存放定时任务，它是一个&lt;strong>基于最小堆实现的优先级队列&lt;/strong>。&lt;code>TaskQueue&lt;/code> 会按照&lt;strong>任务距离下一次执行时间的大小&lt;/strong>将任务排序，保证在堆顶的任务最先执行。这样在需要执行任务时，每次&lt;strong>只需要取出堆顶的任务&lt;/strong>运行即可！&lt;/p>
&lt;p>&lt;code>Timer&lt;/code> 使用起来比较简单，通过下面的方式我们就能创建一个 1s 之后执行的定时任务。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 示例代码：&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TimerTask task &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> TimerTask() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;当前时间: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Date() &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;n&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;线程名称: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;当前时间: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Date() &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;n&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;线程名称: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Timer timer &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Timer(&lt;span style="color:#e6db74">&amp;#34;Timer&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">long&lt;/span> delay &lt;span style="color:#f92672">=&lt;/span> 1000L;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>timer.&lt;span style="color:#a6e22e">schedule&lt;/span>(task, delay);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//输出：&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>当前时间: Fri May 28 15:18:47 CST 2021n线程名称: main
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>当前时间: Fri May 28 15:18:48 CST 2021n线程名称: Timer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不过其缺陷较多，比如一个 &lt;code>Timer&lt;/code> 一个线程，这就导致 &lt;code>Timer&lt;/code> 的任务的执行&lt;strong>只能串行执行，一个任务执行时间过长的话会影响其他任务（性能非常差），再比如发生异常时任务直接停止（&lt;code>Timer&lt;/code> 只捕获了 &lt;code>InterruptedException&lt;/code> ）&lt;/strong>。&lt;/p></description></item><item><title>敏感词过滤方案总结</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly06ly_sentive-words-filter/</link><pubDate>Tue, 21 Feb 2023 08:57:26 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly06ly_sentive-words-filter/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>系统需要对用户输入的文本进行&lt;strong>敏感词过滤&lt;/strong>如色情、政治、暴力相关的词汇。&lt;/p>
&lt;p>敏感词过滤用的使用比较多的 &lt;strong>Trie 树算法&lt;/strong> 和 &lt;strong>DFA 算法&lt;/strong>。&lt;/p>
&lt;h2 id="算法实现">算法实现&lt;a class="anchor" href="#%e7%ae%97%e6%b3%95%e5%ae%9e%e7%8e%b0">#&lt;/a>&lt;/h2>
&lt;h3 id="trie-树">Trie 树&lt;a class="anchor" href="#trie-%e6%a0%91">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>Trie 树&lt;/strong> 也称为&lt;strong>字典树&lt;/strong>、&lt;strong>单词查找树&lt;/strong>，&lt;strong>哈系树&lt;/strong>(&lt;strong>这里是不是写错了，哈希树？&lt;/strong>)的一种变种，通常被用于&lt;strong>字符串匹配&lt;/strong>，用来解决在一组字符串集合中快速查找某个字符串的问题。像&lt;strong>浏览器搜索的关键词提示&lt;/strong>一般就是基于 Trie 树来做的。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142025084.png" alt="img" />&lt;/p>
&lt;p>假如我们的敏感词库中有以下敏感词：&lt;/p>
&lt;ul>
&lt;li>高清有码&lt;/li>
&lt;li>高清 AV&lt;/li>
&lt;li>东京冷&lt;/li>
&lt;li>东京热&lt;/li>
&lt;/ul>
&lt;p>我们构造出来的敏感词 Trie 树就是下面这样的：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142025337.png" alt="ly-20241212142025337" />&lt;/p>
&lt;p>当我们要查找对应的字符串“东京热”的话，我们会把这个字符串切割成单个的字符“东”、“京”、“热”，然后我们&lt;strong>从 Trie 树的根节点开始匹配&lt;/strong>。&lt;/p>
&lt;p>可以看出， &lt;strong>Trie 树的核心原理其实很简单，就是通过公共前缀来提高字符串匹配效率。&lt;/strong>&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://mvnrepository.com/artifact/org.apache.commons/commons-collections4">Apache Commons Collecions&lt;/a> 这个库中就有 Trie 树实现：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142025461.png" alt="ly-20241212142025461.png" />&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>Trie&lt;span style="color:#f92672">&amp;lt;&lt;/span>String, String&lt;span style="color:#f92672">&amp;gt;&lt;/span> trie &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> PatriciaTrie&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>trie.&lt;span style="color:#a6e22e">put&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Abigail&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;student&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>trie.&lt;span style="color:#a6e22e">put&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Abi&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;doctor&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>trie.&lt;span style="color:#a6e22e">put&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Annabel&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;teacher&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>trie.&lt;span style="color:#a6e22e">put&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Christina&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;student&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>trie.&lt;span style="color:#a6e22e">put&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Chris&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;doctor&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assertions.&lt;span style="color:#a6e22e">assertTrue&lt;/span>(trie.&lt;span style="color:#a6e22e">containsKey&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Abigail&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>assertEquals(&lt;span style="color:#e6db74">&amp;#34;{Abi=doctor, Abigail=student}&amp;#34;&lt;/span>, trie.&lt;span style="color:#a6e22e">prefixMap&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Abi&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">toString&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>assertEquals(&lt;span style="color:#e6db74">&amp;#34;{Chris=doctor, Christina=student}&amp;#34;&lt;/span>, trie.&lt;span style="color:#a6e22e">prefixMap&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Chr&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">toString&lt;/span>());&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Aho-Corasick（AC）自动机是一种建立在 Trie 树上的一种改进算法，是一种多模式匹配算法，由贝尔实验室的研究人员 Alfred V. Aho 和 Margaret J.Corasick 发明。&lt;/p>
&lt;p>AC 自动机算法使用 Trie 树来存放模式串的前缀，通过失败匹配指针（失配指针）来处理匹配失败的跳转。&lt;/p></description></item><item><title>权限系统设计详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly05ly_design-of-authority-system/</link><pubDate>Mon, 20 Feb 2023 15:54:05 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly05ly_design-of-authority-system/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>作者：转转技术团队&lt;/p>
&lt;p>原文：https://mp.weixin.qq.com/s/ONMuELjdHYa0yQceTj01Iw&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>ly：比较繁琐，大概看了前面的部分&lt;/p>&lt;/blockquote>&lt;h2 id="老权限系统的问题与现状">老权限系统的问题与现状&lt;a class="anchor" href="#%e8%80%81%e6%9d%83%e9%99%90%e7%b3%bb%e7%bb%9f%e7%9a%84%e9%97%ae%e9%a2%98%e4%b8%8e%e7%8e%b0%e7%8a%b6">#&lt;/a>&lt;/h2>
&lt;p>转转公司在过去并没有一个统一的权限管理系统，权限管理由各业务自行研发或是使用其他业务的权限系统，权限管理的不统一带来了不少问题：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>各业务重复造轮子&lt;/strong>，维护成本高&lt;/li>
&lt;li>&lt;strong>各系统只解决部分场景&lt;/strong>问题，方案不够通用，新项目选型时没有可靠的权限管理方案&lt;/li>
&lt;li>&lt;strong>缺乏统一的日志管理&lt;/strong>与&lt;strong>审批流程&lt;/strong>，在授权信息追溯上十分困难&lt;/li>
&lt;/ol>
&lt;p>基于上述问题，去年底公司启动建设转转统一权限系统，目标是开发一套灵活、易用、安全的权限管理系统，供各业务使用。&lt;/p>
&lt;h2 id="业界权限系统的设计方式">业界权限系统的设计方式&lt;a class="anchor" href="#%e4%b8%9a%e7%95%8c%e6%9d%83%e9%99%90%e7%b3%bb%e7%bb%9f%e7%9a%84%e8%ae%be%e8%ae%a1%e6%96%b9%e5%bc%8f">#&lt;/a>&lt;/h2>
&lt;p>目前业界主流的权限模型有两种，下面分别介绍下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>基于角色的访问控制（RBAC）&lt;/strong>&lt;/li>
&lt;li>&lt;strong>基于属性的访问控制（ABAC）&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="rbac-模型">RBAC 模型&lt;a class="anchor" href="#rbac-%e6%a8%a1%e5%9e%8b">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>基于角色的访问控制（Role-Based Access Control，简称 RBAC）&lt;/strong> 指的是通过用户的&lt;strong>角色（Role）&lt;strong>授权其&lt;/strong>相关权限&lt;/strong>，实现了灵活的访问控制，&lt;strong>相比直接授予用户权限&lt;/strong>，要更加简单、高效、可扩展。&lt;/p>
&lt;p>一个用户可以拥有若干角色，每一个角色又可以被&lt;strong>分配若干权限&lt;/strong>这样，就构造成“&lt;strong>用户-角色-权限&lt;/strong>” 的授权模型。在这种模型中，用户与角色、角色与权限之间构成了多对多的关系。&lt;/p>
&lt;p>用一个图来描述如下：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142024163.png" alt="ly-20241212142024163" />&lt;/p>
&lt;p>当使用 &lt;code>RBAC模型&lt;/code> 时，通过分析用户的实际情况，基于共同的职责和需求，&lt;strong>授予他们不同角色&lt;/strong>。这种 &lt;strong>&lt;code>用户 -&amp;gt; 角色 -&amp;gt; 权限&lt;/code>&lt;/strong> 间的关系，让我们可以不用再单独管理单个用户权限，用户&lt;strong>从授予的角色里面获取所需的权限&lt;/strong>。&lt;/p>
&lt;p>以一个简单的场景（Gitlab 的权限系统）为例，用户系统中有 &lt;code>Admin&lt;/code>、&lt;code>Maintainer&lt;/code>、&lt;code>Operator&lt;/code> 三种角色，这三种角色分别具备不同的权限，比如只有 &lt;code>Admin&lt;/code> 具备创建代码仓库、删除代码仓库的权限，其他的角色都不具备。我们授予某个用户 &lt;code>Admin&lt;/code> 这个角色，他就具备了 &lt;strong>创建代码仓库&lt;/strong> 和 &lt;strong>删除代码仓库&lt;/strong> 这两个权限。&lt;/p>
&lt;p>通过 &lt;code>RBAC模型&lt;/code> ，当存在多个用户拥有相同权限时，我们只需要创建好拥有该权限的角色，然后给不同的用户分配不同的角色，后续只需要修改角色的权限，就能自动修改角色内所有用户的权限。&lt;/p>
&lt;h3 id="abac-模型">ABAC 模型&lt;a class="anchor" href="#abac-%e6%a8%a1%e5%9e%8b">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>基于属性的访问控制（Attribute-Based Access Control，简称 ABAC）&lt;/strong> 是一种比 &lt;code>RBAC模型&lt;/code> 更加灵活的授权模型，它的原理是&lt;strong>通过各种属性来动态判断一个操作是否可以被允许&lt;/strong>。这个模型在云系统中使用的比较多，比如 AWS，阿里云等。&lt;/p>
&lt;p>考虑下面这些场景的权限控制：&lt;/p>
&lt;ol>
&lt;li>授权&lt;strong>某个人具体某本书的编辑&lt;/strong>权限&lt;/li>
&lt;li>当一个文档的&lt;strong>所属部门&lt;/strong>跟&lt;strong>用户的部门相同&lt;/strong>时，用户可以访问这个文档&lt;/li>
&lt;li>当&lt;strong>用户是一个文档的拥有者&lt;/strong>并且&lt;strong>文档的状态是草稿&lt;/strong>，用户可以编辑这个文档&lt;/li>
&lt;li>早上&lt;strong>九点前禁止 A 部门的人访问&lt;/strong> B 系统&lt;/li>
&lt;li>在&lt;strong>除了上海以外的地方禁止以管理员身份访问&lt;/strong> A 系统&lt;/li>
&lt;li>用户&lt;strong>对 2022-06-07 之前创建的订单&lt;/strong>有操作权限&lt;/li>
&lt;/ol>
&lt;p>可以发现上述的场景通过 &lt;code>RBAC模型&lt;/code> 很难去实现，因为 &lt;code>RBAC模型&lt;/code> 仅仅描述了用户可以做什么操作，但是操作的条件，以及操作的数据，&lt;code>RBAC模型&lt;/code> 本身是没有这些限制的。但这恰恰是 &lt;code>ABAC模型&lt;/code> 的长处，&lt;code>ABAC模型&lt;/code> 的思想是基于&lt;strong>用户&lt;/strong>、访问的&lt;strong>数据的属性&lt;/strong>、以及各&lt;strong>种环境因素&lt;/strong>去动态计算用户是否有权限进行操作。&lt;/p></description></item><item><title>sso单点登录</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly04ly_sso-intro/</link><pubDate>Mon, 20 Feb 2023 15:20:23 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly04ly_sso-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>本文授权转载自 ： 
&lt;a target="_blank" href="https://ken.io/note/sso-design-implement">https://ken.io/note/sso-design-implement&lt;/a> 作者：ken.io&lt;/p>&lt;/blockquote>&lt;h2 id="sso-介绍">SSO 介绍&lt;a class="anchor" href="#sso-%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是-sso">什么是 SSO？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-sso">#&lt;/a>&lt;/h3>
&lt;p>SSO 英文全称 &lt;strong>Single Sign On&lt;/strong>，单点登录。SSO 是在多个应用系统中，用户只需要登录一次就可以访问所有相互信任的应用系统。&lt;/p>
&lt;p>例如你登录网易账号中心（https://reg.163.com/ ）之后访问以下站点都是登录状态。&lt;/p>
&lt;ul>
&lt;li>网易直播 
&lt;a target="_blank" href="https://v.163.com/">https://v.163.com&lt;/a>&lt;/li>
&lt;li>网易博客 
&lt;a target="_blank" href="https://blog.163.com/">https://blog.163.com&lt;/a>&lt;/li>
&lt;li>网易花田 
&lt;a target="_blank" href="https://love.163.com/">https://love.163.com&lt;/a>&lt;/li>
&lt;li>网易考拉 
&lt;a target="_blank" href="https://www.kaola.com/">https://www.kaola.com&lt;/a>&lt;/li>
&lt;li>网易 Lofter 
&lt;a target="_blank" href="http://www.lofter.com/">http://www.lofter.com&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="sso-有什么好处">SSO 有什么好处？&lt;a class="anchor" href="#sso-%e6%9c%89%e4%bb%80%e4%b9%88%e5%a5%bd%e5%a4%84">#&lt;/a>&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>用户角度&lt;/strong> :用户能够做到一次登录多次使用，无需记录多套用户名和密码，省心。&lt;/li>
&lt;li>&lt;strong>系统管理员角度&lt;/strong> : 管理员只需维护好一个统一的账号中心就可以了，方便。&lt;/li>
&lt;li>&lt;strong>新系统开发角度:&lt;/strong> 新系统开发时只需直接对接统一的账号中心即可，简化开发流程，省时。&lt;/li>
&lt;/ol>
&lt;h2 id="sso-设计与实现">SSO 设计与实现&lt;a class="anchor" href="#sso-%e8%ae%be%e8%ae%a1%e4%b8%8e%e5%ae%9e%e7%8e%b0">#&lt;/a>&lt;/h2>
&lt;p>本篇文章也主要是为了探讨如何设计&amp;amp;实现一个 SSO 系统&lt;/p>
&lt;p>以下为需要实现的核心功能：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>单点登录&lt;/strong>&lt;/li>
&lt;li>&lt;strong>单点登出&lt;/strong>&lt;/li>
&lt;li>支持&lt;strong>跨域单点登录&lt;/strong>&lt;/li>
&lt;li>支持&lt;strong>跨域单点登出&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="核心应用与依赖">核心应用与依赖&lt;a class="anchor" href="#%e6%a0%b8%e5%bf%83%e5%ba%94%e7%94%a8%e4%b8%8e%e4%be%9d%e8%b5%96">#&lt;/a>&lt;/h3>
&lt;p>&lt;img src="img/ly-20241212142023133.jpg" alt="单点登录（SSO）设计" />&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>应用/模块/对象&lt;/th>
 &lt;th>说明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>前台站点&lt;/td>
 &lt;td>需要登录的站点&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SSO 站点-登录&lt;/td>
 &lt;td>提供登录的页面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SSO 站点-登出&lt;/td>
 &lt;td>提供注销登录的入口&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SSO 服务-登录&lt;/td>
 &lt;td>提供登录服务&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SSO 服务-登录状态&lt;/td>
 &lt;td>提供登录状态校验/登录信息查询的服务&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SSO 服务-登出&lt;/td>
 &lt;td>提供用户注销登录的服务&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>数据库&lt;/td>
 &lt;td>存储用户账户信息&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>缓存&lt;/td>
 &lt;td>存储用户的登录信息，通常使用 Redis&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="用户登录状态的存储与校验">用户登录状态的存储与校验&lt;a class="anchor" href="#%e7%94%a8%e6%88%b7%e7%99%bb%e5%bd%95%e7%8a%b6%e6%80%81%e7%9a%84%e5%ad%98%e5%82%a8%e4%b8%8e%e6%a0%a1%e9%aa%8c">#&lt;/a>&lt;/h3>
&lt;p>常见的 Web 框架对于 Session 的实现都是&lt;strong>生成一个 SessionId 存储在浏览器 Cookie&lt;/strong> 中。然后&lt;strong>将 Session 内容存储在服务器端内存&lt;/strong>中，这个 
&lt;a target="_blank" href="https://ken.io/">ken.io&lt;/a> 在之前
&lt;a target="_blank" href="https://ken.io/note/session-principle-skill">Session 工作原理&lt;/a>中也提到过。整体也是借鉴这个思路。&lt;/p></description></item><item><title>jwt身份认证优缺点</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly03ly_jwt-advantages-disadvantages/</link><pubDate>Mon, 20 Feb 2023 14:40:15 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly03ly_jwt-advantages-disadvantages/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>在 
&lt;a target="_blank" href="https://javaguide.cn/system-design/security/jwt-intro.html">JWT 基本概念详解&lt;/a>这篇文章中，我介绍了：&lt;/p>
&lt;ul>
&lt;li>什么是 JWT?&lt;/li>
&lt;li>JWT 由哪些部分组成？&lt;/li>
&lt;li>如何基于 JWT 进行身份验证？&lt;/li>
&lt;li>JWT 如何防止 Token 被篡改？&lt;/li>
&lt;li>如何加强 JWT 的安全性？&lt;/li>
&lt;/ul>
&lt;p>这篇文章，我们一起探讨一下 JWT 身份认证的&lt;strong>优缺点&lt;/strong>以及&lt;strong>常见问题的解决办法&lt;/strong>。&lt;/p>
&lt;h2 id="jwt-的优势">JWT 的优势&lt;a class="anchor" href="#jwt-%e7%9a%84%e4%bc%98%e5%8a%bf">#&lt;/a>&lt;/h2>
&lt;p>相比于 Session 认证的方式来说，使用 JWT 进行身份认证主要有下面 4 个优势。&lt;/p>
&lt;h3 id="无状态">无状态&lt;a class="anchor" href="#%e6%97%a0%e7%8a%b6%e6%80%81">#&lt;/a>&lt;/h3>
&lt;p>JWT 自身&lt;strong>包含了身份验证所需要的所有信息&lt;/strong>，因此，我们的&lt;strong>服务器不需要存储 Session&lt;/strong> 信息。这显然增加了系统的可用性和伸缩性，大大减轻了服务端的压力。&lt;/p>
&lt;p>不过，也正是由于 JWT 的无状态，也导致了它最大的缺点：&lt;strong>不可控！&lt;/strong>&lt;/p>
&lt;p>就比如说，我们想要在 &lt;strong>JWT 有效期内废弃一个 JWT&lt;/strong> 或者&lt;strong>更改它的权限&lt;/strong>的话，&lt;strong>并不会立即生效&lt;/strong>，通常需要等到有效期过后才可以。再比如说，&lt;strong>当用户 Logout 的话，JWT 也还有效&lt;/strong>。除非，我们在&lt;strong>后端增加额外的处理&lt;/strong>逻辑比如&lt;strong>将失效的 JWT 存储&lt;/strong>起来，后端先验证 JWT 是否有效再进行处理。具体的解决办法，我们会在后面的内容中详细介绍到，这里只是简单提一下。&lt;/p>
&lt;h3 id="有效避免了-csrf-攻击">有效避免了 CSRF 攻击&lt;a class="anchor" href="#%e6%9c%89%e6%95%88%e9%81%bf%e5%85%8d%e4%ba%86-csrf-%e6%94%bb%e5%87%bb">#&lt;/a>&lt;/h3>
&lt;p>&lt;code>[ˈfɔːdʒəri] forgery 伪造&lt;/code>&lt;/p>
&lt;p>&lt;strong>CSRF（Cross Site Request Forgery）&lt;/strong> 一般被翻译为 &lt;strong>跨站请求伪造&lt;/strong>，属于网络攻击领域范围。相比于 SQL 脚本注入、XSS 等安全攻击方式，&lt;strong>CSRF&lt;/strong> 的知名度并没有它们高。但是，它的确是我们开发系统时必须要考虑的安全隐患。就连业内技术标杆 Google 的产品 Gmail 也曾在 2007 年的时候爆出过 CSRF 漏洞，这给 Gmail 的用户造成了很大的损失。&lt;/p></description></item><item><title>jwt-intro</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly02ly_jwt-intro/</link><pubDate>Mon, 20 Feb 2023 14:28:07 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly02ly_jwt-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212142022219.png" alt="image.png" />&lt;/p>
&lt;h2 id="什么是-jwt">什么是 JWT?&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-jwt">#&lt;/a>&lt;/h2>
&lt;p>JWT （JSON Web Token） 是目前&lt;strong>最流行的跨域认证解决方案&lt;/strong>，是一种&lt;strong>基于 Token 的认证授权机制&lt;/strong>。 从 JWT 的全称可以看出，JWT 本身也是 Token，一种规范化之后的 JSON 结构的 Token。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>跨域认证的问题&lt;br>
互联网服务离不开用户认证。一般流程是下面这样。&lt;/p>
&lt;p>这种模式的问题在于，扩展性（scaling）不好。单机当然没有问题，如果是&lt;strong>服务器集群，或者是跨域的服务导向架构&lt;/strong>，就要求 &lt;strong>session 数据共享&lt;/strong>，&lt;strong>每台服务器都能够读取 session&lt;/strong>。&lt;/p>
&lt;p>举例来说，A 网站和 B 网站是同一家公司的关联服务。现在要求，用户只要在其中一个网站登录，再访问另一个网站就会自动登录，请问怎么实现？&lt;/p>
&lt;p>一种解决方案是 &lt;strong>session 数据持久化&lt;/strong>，&lt;strong>写入数据库&lt;/strong>或&lt;strong>别的持久层&lt;/strong>。各种服务收到请求后，都&lt;strong>向持久层请求数据&lt;/strong>。这种方案的优点是架构清晰，缺点是工程量比较大。另外，&lt;strong>持久层&lt;/strong>万一挂了，就会单点失败。&lt;/p>
&lt;p>另一种方案是服务器索性&lt;strong>不保存 session 数据&lt;/strong>了，所有&lt;strong>数据都保存在客户端&lt;/strong>，每次请求都发回服务器。JWT 就是这种方案的一个代表。&lt;/p>&lt;/blockquote>&lt;p>JWT 自身包含了身份验证所需要的所有信息，因此，我们的&lt;strong>服务器不需要存储 Session 信息&lt;/strong>。这显然增加了系统的可用性和伸缩性，大大减轻了服务端的压力。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>ly：我觉得这里的重点就是，服务器不存储Session以维护&amp;quot;用户&amp;quot;和cookie(session id)的关系了&lt;/p>&lt;/blockquote>&lt;p>可以看出，&lt;strong>JWT 更符合设计 RESTful API 时的「Stateless（无状态）」原则&lt;/strong> 。&lt;/p>
&lt;p>并且， 使用 JWT 认证可以有效&lt;strong>避免 CSRF 攻击&lt;/strong>，因为 JWT 一般是&lt;strong>存在在 localStorage&lt;/strong> 中，使用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。&lt;/p>
&lt;p>我在 
&lt;a target="_blank" href="https://github.com/Snailclimb/JavaGuide/blob/main/docs/system-design/security/advantages&amp;amp;disadvantages-of-jwt.md">JWT 优缺点分析&lt;/a>这篇文章中有详细介绍到使用 JWT 做身份认证的优势和劣势。&lt;/p>
&lt;p>下面是 
&lt;a target="_blank" href="https://tools.ietf.org/html/rfc7519">RFC 7519&lt;/a> 对 JWT 做的较为正式的定义。&lt;/p></description></item><item><title>认证授权基础概念详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly01ly_basis-of-authority-certification/</link><pubDate>Mon, 20 Feb 2023 09:44:23 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/security/ly01ly_basis-of-authority-certification/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="认证-authentication-和授权-authorization的区别是什么">认证 (Authentication) 和授权 (Authorization)的区别是什么？&lt;a class="anchor" href="#%e8%ae%a4%e8%af%81-authentication-%e5%92%8c%e6%8e%88%e6%9d%83-authorization%e7%9a%84%e5%8c%ba%e5%88%ab%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h2>
&lt;p>这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词，很多人都会把它俩的读音搞混，所以我建议你先先去查一查这两个单词到底该怎么读，他们的具体含义是什么。&lt;/p>
&lt;p>说简单点就是：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>认证 (Authentication)：&lt;/strong> 你是谁。&lt;code>[ɔːˌθentɪˈkeɪʃn] 身份验证&lt;/code>&lt;/li>
&lt;li>&lt;strong>授权 (Authorization)：&lt;/strong> 你有权限干什么。&lt;code>[ˌɔːθəraɪˈzeɪʃn] 授权&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>稍微正式点（啰嗦点）的说法就是 ：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Authentication（认证）&lt;/strong> 是&lt;strong>验证&lt;/strong>您的身份的凭据（例如用户名/用户 ID 和密码），通过这个凭据，系统得以知道你就是你，也就是说系统存在你这个用户。所以，Authentication 被称为身份/用户验证。&lt;/li>
&lt;li>&lt;strong>Authorization（授权）&lt;/strong> 发生在 &lt;strong>Authentication（认证）&lt;/strong> 之后。授权嘛，光看意思大家应该就明白，它主要&lt;strong>掌管&lt;/strong>我们访问系统的&lt;strong>权限&lt;/strong>。比如有些特定资源只能具有特定权限的人才能访问比如 admin，有些对系统资源操作比如删除、添加、更新只能特定人才具有。&lt;/li>
&lt;/ul>
&lt;p>认证 ：&lt;/p>
&lt;p>&lt;img src="https://camo.githubusercontent.com/0fa88e0ac6b2ed5aec983f25d3e7cce0b16dc1935630dd4edbef73b335fe8137/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303231303630343136303930383335322e706e67" alt="img" />&lt;/p>
&lt;p>授权：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142020901.jpg" alt="img" />&lt;/p>
&lt;p>这两个一般在我们的系统中被结合在一起使用，目的就是为了保护我们系统的安全性。&lt;/p>
&lt;h2 id="rbac-模型了解吗">RBAC 模型了解吗？&lt;a class="anchor" href="#rbac-%e6%a8%a1%e5%9e%8b%e4%ba%86%e8%a7%a3%e5%90%97">#&lt;/a>&lt;/h2>
&lt;p>系统权限控制最常采用的访问控制模型就是 &lt;strong>RBAC 模型&lt;/strong> 。&lt;/p>
&lt;p>&lt;strong>什么是 RBAC 呢？&lt;/strong>&lt;/p>
&lt;p>RBAC 即&lt;strong>基于角色的权限访问控制&lt;/strong>（&lt;strong>Role-Based Access Control&lt;/strong>）。这是一种通过角色关联权限，角色同时又关联用户的授权的方式。&lt;/p>
&lt;p>简单地说：一个&lt;strong>用户可以拥有若干角色&lt;/strong>，每一个&lt;strong>角色又可以被分配若干权限&lt;/strong>，这样就构造成“&lt;strong>用户-角色-权限&lt;/strong>” 的授权模型。在这种模型中，用户与角色、角色与权限之间构成了多对多的关系，如下图&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142021163.png" alt="image.png" />&lt;/p>
&lt;p>&lt;strong>在 RBAC 中，权限与角色相关联，用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。&lt;/strong>&lt;/p>
&lt;p>本系统的权限设计相关的表如下（一共 5 张表，2 张用户建立表之间的联系）：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142021291.png" alt="ly-20241212142021291.png" />&lt;/p>
&lt;p>通过这个权限模型，我们可以创建不同的角色并为不同的角色分配不同的权限范围（菜单）。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142021421.png" alt="ly-20241212142021421" />&lt;/p>
&lt;p>通常来说，如果系统对于权限控制要求&lt;strong>比较严格&lt;/strong>的话，一般都会选择使用 &lt;strong>RBAC&lt;/strong> 模型来做权限控制。&lt;/p>
&lt;h2 id="什么是-cookie--cookie-的作用是什么">什么是 Cookie ? Cookie 的作用是什么?&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-cookie--cookie-%e7%9a%84%e4%bd%9c%e7%94%a8%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212142021557.jpg" alt="img" />&lt;/p></description></item><item><title>单元测试</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/unit-test/</link><pubDate>Mon, 20 Feb 2023 09:38:24 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/unit-test/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="何谓单元测试">何谓单元测试？&lt;a class="anchor" href="#%e4%bd%95%e8%b0%93%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95">#&lt;/a>&lt;/h2>
&lt;p>维基百科是这样介绍单元测试的：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>在计算机编程中，单元测试（Unit Testing）是针对&lt;strong>程序模块&lt;/strong>（软件设计的最小单位）进行的&lt;strong>正确性检验测试&lt;/strong>工作。&lt;/p>
&lt;p>程序单元是应用的 &lt;strong>最小可测试部件&lt;/strong> 。在过程化编程中，一个单元就是单个程序、函数、过程等；对于面向对象编程，最小单元就是&lt;strong>方法&lt;/strong>，包括基类（超类）、抽象类、或者派生类（子类）中的方法。&lt;/p>&lt;/blockquote>&lt;p>由于每个单元有独立的逻辑，在做单元测试时，为了隔离外部依赖，确保这些依赖&lt;strong>不影响验证逻辑&lt;/strong>，我们经常会用到 Fake、Stub 与 Mock 。&lt;/p>
&lt;p>关于 Fake、Mock 与 Stub 这几个概念的解读，可以看看这篇文章：
&lt;a target="_blank" href="https://zhuanlan.zhihu.com/p/26942686">测试中 Fakes、Mocks 以及 Stubs 概念明晰 - 王下邀月熊 - 2018&lt;/a> 。&lt;/p>
&lt;h2 id="为什么需要单元测试">为什么需要单元测试？&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95">#&lt;/a>&lt;/h2>
&lt;h3 id="为重构保驾护航">为重构保驾护航&lt;a class="anchor" href="#%e4%b8%ba%e9%87%8d%e6%9e%84%e4%bf%9d%e9%a9%be%e6%8a%a4%e8%88%aa">#&lt;/a>&lt;/h3>
&lt;p>我在
&lt;a target="_blank" href="https://github.com/Snailclimb/JavaGuide/blob/main/docs/system-design/basis/refactoring.md">重构&lt;/a>这篇文章中这样写到：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>单元测试可以&lt;strong>为重构提供信心&lt;/strong>，降低重构的成本。我们要像重视生产代码那样，重视单元测试。&lt;/p>&lt;/blockquote>&lt;p>每个开发者都会经历重构，重构后把代码改坏了的情况并不少见，很可能你只是修改了一个很简单的方法就导致系统出现了一个比较严重的错误。&lt;/p>
&lt;p>如果有了单元测试的话，就不会存在这个隐患了。写完一个类，把单元测试写了，确保这个类逻辑正确；写第二个类，单元测试&amp;hellip;..写 100 个类，道理一样，每个类做到第一点“&lt;strong>保证逻辑正确性&lt;/strong>”，100 个类拼在一起肯定不出问题。你大可以放心一边重构，一边运行 APP；而不是整体重构完，提心吊胆地 run。&lt;/p>
&lt;h3 id="提高代码质量">提高代码质量&lt;a class="anchor" href="#%e6%8f%90%e9%ab%98%e4%bb%a3%e7%a0%81%e8%b4%a8%e9%87%8f">#&lt;/a>&lt;/h3>
&lt;p>由于每个单元有独立的逻辑，做单元测试时需要&lt;strong>隔离外部依赖&lt;/strong>，确保这些依赖&lt;strong>不影响验证逻辑&lt;/strong>。因为要把各种依赖分离，单元测试会&lt;strong>促进工程进行组件拆分&lt;/strong>，整理工程依赖关系，更大程度减少代码耦合。这样写出来的代码，更好维护，更好扩展，从而提高代码质量。&lt;/p>
&lt;h3 id="减少-bug">减少 bug&lt;a class="anchor" href="#%e5%87%8f%e5%b0%91-bug">#&lt;/a>&lt;/h3>
&lt;p>一个机器，由各种细小的零件组成，如果其中某件零件坏了，机器运行故障。必须保证每个零件都按设计图要求的规格，机器才能正常运行。&lt;/p>
&lt;p>一个&lt;strong>可单元测试的工程&lt;/strong>，会把&lt;strong>业务&lt;/strong>、&lt;strong>功能&lt;/strong>分割成规模更小、有独立的逻辑部件，称为单元。单元测试的目标，就是保证&lt;strong>各个单元的逻辑正确性&lt;/strong>。单元测试保障工程各个“零件”按“规格”（需求）执行，从而保证整个“机器”（项目）运行正确，最大限度减少 bug。&lt;/p>
&lt;h3 id="快速定位-bug">快速定位 bug&lt;a class="anchor" href="#%e5%bf%ab%e9%80%9f%e5%ae%9a%e4%bd%8d-bug">#&lt;/a>&lt;/h3>
&lt;p>如果程序有 bug，我们运行一次全部单元测试，找到&lt;strong>不通过的测试&lt;/strong>，可以很快地定位对应的执行代码。修复代码后，运行对应的单元测试；如还不通过，继续修改，运行测试&amp;hellip;..直到&lt;strong>测试通过&lt;/strong>。&lt;/p>
&lt;h3 id="持续集成依赖单元测试">持续集成依赖单元测试&lt;a class="anchor" href="#%e6%8c%81%e7%bb%ad%e9%9b%86%e6%88%90%e4%be%9d%e8%b5%96%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95">#&lt;/a>&lt;/h3>
&lt;p>持续集成需要依赖单元测试，当持续集成服务自动构建新代码之后，会自动运行单元测试来发现代码错误。&lt;/p>
&lt;h2 id="谁逼你写单元测试">谁逼你写单元测试？&lt;a class="anchor" href="#%e8%b0%81%e9%80%bc%e4%bd%a0%e5%86%99%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95">#&lt;/a>&lt;/h2>
&lt;h3 id="领导要求">领导要求&lt;a class="anchor" href="#%e9%a2%86%e5%af%bc%e8%a6%81%e6%b1%82">#&lt;/a>&lt;/h3>
&lt;p>有些经验丰富的领导，或多或少都会要求团队写单元测试。对于有一定工作经验的队友，这要求挺合理；对于经验尚浅的、毕业生，恐怕要死要活了，连代码都写不好，还要写单元测试，are you kidding me？&lt;/p>
&lt;p>培训新人单元测试用法，是一项艰巨的任务。新人代码风格未形成，也不知道单元测试多重要，强制单元测试会让他们感到困惑，没办法按自己思路写代码。&lt;/p>
&lt;h3 id="大牛都写单元测试">大牛都写单元测试&lt;a class="anchor" href="#%e5%a4%a7%e7%89%9b%e9%83%bd%e5%86%99%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95">#&lt;/a>&lt;/h3>
&lt;p>国外很多家喻户晓的开源项目，都有大量单元测试。例如，
&lt;a target="_blank" href="https://link.jianshu.com/?t=https://github.com/square/retrofit/tree/master/retrofit/src/test/java/retrofit2">retrofit&lt;/a>、
&lt;a target="_blank" href="https://link.jianshu.com/?t=https://github.com/square/okhttp/tree/master/okhttp-tests/src/test/java/okhttp3">okhttp&lt;/a>、
&lt;a target="_blank" href="https://link.jianshu.com/?t=https://github.com/JakeWharton/butterknife/tree/master/butterknife-compiler/src/test/java/butterknife">butterknife&lt;/a>&amp;hellip;. 国外大牛都写单元测试，我们也写吧！&lt;/p>
&lt;p>很多读者都有这种想法，一开始满腔热血。当真要对自己项目单元测试时，便困难重重，很大原因是项目对单元测试不友好。最后只能对一些不痛不痒的工具类做单元测试，久而久之，当初美好愿望也不了了之。&lt;/p>
&lt;h3 id="保住面子">保住面子&lt;a class="anchor" href="#%e4%bf%9d%e4%bd%8f%e9%9d%a2%e5%ad%90">#&lt;/a>&lt;/h3>
&lt;p>都是有些许年经验的老鸟，还天天被测试同学追 bug，好意思么？花多一点时间写单元测试，确保没低级 bug，还能彰显大牛风范，何乐而不为？&lt;/p>
&lt;h3 id="心虚">心虚&lt;a class="anchor" href="#%e5%bf%83%e8%99%9a">#&lt;/a>&lt;/h3>
&lt;p>笔者也是个不太相信自己代码的人，总觉得哪里会突然冒出莫名其妙的 bug，也怕别人不小心改了自己的代码（被害妄想症），新版本上线提心吊胆&amp;hellip;&amp;hellip;花点时间写单元测试，有事没事跑一下测试，确保原逻辑没问题，至少能睡安稳一点。&lt;/p></description></item><item><title>代码重构指南</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/refactoring/</link><pubDate>Fri, 17 Feb 2023 17:28:30 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/refactoring/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>前段时间重读了
&lt;a target="_blank" href="https://book.douban.com/subject/30468597/">《重构：改善代码既有设计》&lt;/a>，收货颇多。于是，简单写了一篇文章来聊聊我对重构的看法。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142016683.jpg" alt="img" />&lt;/p>
&lt;h2 id="何谓重构">何谓重构？&lt;a class="anchor" href="#%e4%bd%95%e8%b0%93%e9%87%8d%e6%9e%84">#&lt;/a>&lt;/h2>
&lt;p>学习重构必看的一本神书《重构：改善代码既有设计》从两个角度给出了重构的定义：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ul>
&lt;li>重构（名词）：对&lt;strong>软件内部结构的一种调整&lt;/strong>，目的是在&lt;strong>不改变软件可观察行为&lt;/strong>的前提下，&lt;strong>提高其可理解性&lt;/strong>，&lt;strong>降低其修改成本&lt;/strong>。&lt;/li>
&lt;li>重构（动词）：使用一系列重构手法，在不改变软件可观察行为的前提下，&lt;strong>调整&lt;/strong>其结构。&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;p>用更贴近工程师的语言来说： &lt;strong>重构就是利用设计模式(如组合模式、策略模式、责任链模式)、软件设计原则（如 SOLID 原则、YAGNI 原则、KISS 原则）和重构手段（如封装、继承、构建测试体系）来让代码更容易理解，更易于修改。&lt;/strong>&lt;/p>
&lt;p>软件设计原则指导着我们组织和规范代码，同时，重构也是为了能够尽量设计出尽量满足软件设计原则的软件。&lt;/p>
&lt;p>正确重构的核心在于 &lt;strong>步子一定要小，每一步的重构都不会影响软件的正常运行，可以随时停止重构。&lt;/strong>&lt;/p>
&lt;p>&lt;strong>常见的设计模式如下&lt;/strong> ：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142016942.jpg" alt="img" />&lt;/p>
&lt;p>更全面的设计模式总结，可以看 &lt;strong>
&lt;a target="_blank" href="https://github.com/iluwatar/java-design-patterns">java-design-patterns&lt;/a>&lt;/strong> 这个开源项目。&lt;/p>
&lt;p>&lt;strong>常见的软件设计原则如下&lt;/strong> ：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142017073.jpg" alt="img" />&lt;/p>
&lt;p>更全面的设计原则总结，可以看 &lt;strong>
&lt;a target="_blank" href="https://github.com/iluwatar/java-design-patterns">java-design-patterns&lt;/a>&lt;/strong> 和 &lt;strong>
&lt;a target="_blank" href="https://github.com/nusr/hacker-laws-zh">hacker-laws-zh&lt;/a>&lt;/strong> 这两个开源项目。&lt;/p>
&lt;h2 id="为什么要重构">为什么要重构？&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e9%87%8d%e6%9e%84">#&lt;/a>&lt;/h2>
&lt;p>在上面介绍重构定义的时候，我从比较抽象的角度介绍了重构的好处：重构的主要目的主要是&lt;strong>提升代码&amp;amp;架构&lt;/strong>的&lt;strong>灵活性/可扩展性&lt;/strong>以及复用性。&lt;/p>
&lt;p>如果对应到一个真实的项目，重构具体能为我们带来什么好处呢？&lt;/p>
&lt;ol>
&lt;li>&lt;strong>让代码更容易理解&lt;/strong> ： 通过添加注释、命名规范、逻辑优化等手段可以让我们的代码更容易被理解；&lt;/li>
&lt;li>&lt;strong>避免代码腐化&lt;/strong> ：通过重构干掉坏味道代码；&lt;/li>
&lt;li>&lt;strong>加深对代码的理解&lt;/strong> ：重构代码的过程会加深你对某部分代码的理解；&lt;/li>
&lt;li>&lt;strong>发现潜在 bug&lt;/strong> ：是这样的，很多潜在的 bug ，都是我们在重构的过程中发现的；&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ol>
&lt;p>看了上面介绍的关于重构带来的好处之后，你会发现重构的最终目标是 &lt;strong>提高软件开发速度和质量&lt;/strong> 。&lt;/p>
&lt;p>重构并不会减慢软件开发速度，相反，如果代码质量和软件设计较差，当我们想要添加新功能的话，开发速度会越来越慢。到了最后，甚至都有想要重写整个系统的冲动。&lt;/p>
&lt;p>[&lt;img src="img/ly-20241212142017203.jpg" alt="img" />&lt;/p>
&lt;p>《重构：改善代码既有设计》这本书中这样说：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>重构的唯一目的就是让我们开发更快，用更少的工作量创造更大的价值。&lt;/p>&lt;/blockquote>&lt;h2 id="何时进行重构">何时进行重构？&lt;a class="anchor" href="#%e4%bd%95%e6%97%b6%e8%bf%9b%e8%a1%8c%e9%87%8d%e6%9e%84">#&lt;/a>&lt;/h2>
&lt;p>重构在是开发过程中随时可以进行的，&lt;strong>见机行事&lt;/strong>即可，并不需要单独分配一两天的时间专门用来重构。&lt;/p>
&lt;h3 id="提交代码之前">提交代码之前&lt;a class="anchor" href="#%e6%8f%90%e4%ba%a4%e4%bb%a3%e7%a0%81%e4%b9%8b%e5%89%8d">#&lt;/a>&lt;/h3>
&lt;p>《重构：改善代码既有设计》这本书介绍了一个 &lt;strong>营地法则&lt;/strong> 的概念:&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>编程时，需要遵循营地法则：保证&lt;strong>你离开时的代码库一定比来时更健康&lt;/strong>。&lt;/p>&lt;/blockquote>&lt;p>这个概念表达的核心思想其实很简单：在你提交代码的之前，花一会时间想一想，我这次的提交是让项目代码变得更健康了，还是更腐化了，或者说没什么变化？&lt;/p>
&lt;p>项目团队的每一个人只有保证自己的提交没有让项目代码变得更腐化，项目代码才会朝着健康的方向发展。&lt;/p>
&lt;p>&lt;strong>当我们离开营地（项目代码）的时候，请不要留下垃圾（代码坏味道）！尽量确保营地变得更干净了！&lt;/strong>&lt;/p>
&lt;h3 id="开发一个新功能之后之前">开发一个新功能之后&amp;amp;之前&lt;a class="anchor" href="#%e5%bc%80%e5%8f%91%e4%b8%80%e4%b8%aa%e6%96%b0%e5%8a%9f%e8%83%bd%e4%b9%8b%e5%90%8e%e4%b9%8b%e5%89%8d">#&lt;/a>&lt;/h3>
&lt;p>在开发一个新功能之后，我们应该回过头看看是不是有可以改进的地方。在添加一个新功能之前，我们可以思考一下自己是否可以&lt;strong>重构代码以让新功能的开发更容易&lt;/strong>。&lt;/p>
&lt;p>一个新功能的开发不应该仅仅只有&lt;strong>功能验证通过&lt;/strong>那么简单，我们还应该尽量保证&lt;strong>代码质量&lt;/strong>。&lt;/p>
&lt;p>有一个两顶帽子的比喻：在我开发新功能之前，我发现&lt;strong>重构可以让新功能的开发更容易&lt;/strong>，于是我戴上了重构的帽子。重构之后，我换回原来的帽子，继续开发新能功能。新功能开发完成之后，我又发现自己的代码难以理解，于是我又戴上了重构帽子。比较好的开发状态就是就是这样在&lt;strong>重构和开发新功能之间来回切换&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142017337.jpg" alt="refractor-two-hats" />&lt;/p></description></item><item><title>代码命名指南</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/naming/</link><pubDate>Fri, 17 Feb 2023 17:22:14 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/naming/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>我还记得我刚工作那一段时间， 项目 Code Review 的时候，我经常因为变量命名不规范而被 “diss”!&lt;/p>
&lt;p>究其原因还是自己那会经验不足，而且，大学那会写项目的时候不太注意这些问题，想着只要把功能实现出来就行了。&lt;/p>
&lt;p>但是，工作中就不一样，为了代码的可读性、可维护性，项目组对于代码质量的要求还是很高的！&lt;/p>
&lt;p>前段时间，项目组新来的一个实习生也经常在 Code Review 因为变量命名不规范而被 “diss”，这让我想到自己刚到公司写代码那会的日子。&lt;/p>
&lt;p>于是，我就简单写了这篇关于变量命名规范的文章，希望能对同样有此困扰的小伙伴提供一些帮助。&lt;/p>
&lt;p>确实，编程过程中，有太多太多让我们头疼的事情了，比如命名、维护其他人的代码、写测试、与其他人沟通交流等等。&lt;/p>
&lt;p>据说之前在 Quora 网站，由接近 5000 名程序员票选出来的最难的事情就是“命名”。&lt;/p>
&lt;p>大名鼎鼎的《重构》的作者老马（Martin Fowler）曾经在
&lt;a target="_blank" href="https://martinfowler.com/bliki/TwoHardThings.html">TwoHardThings&lt;/a>这篇文章中提到过CS 领域有两大最难的事情：一是 &lt;strong>缓存失效&lt;/strong> ，一是 &lt;strong>程序命名&lt;/strong> 。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://camo.githubusercontent.com/55cdff0a451c1d8245d7734247bdb08cfaa79257ce2a03763cb03c27a3056f19/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6a6176612d67756964652d626c6f672f6d617274696e672d6e616d696e672e706e67">&lt;img src="img/ly-20241212142015914.jpg" alt="img" />&lt;/a>&lt;/p>
&lt;p>这个句话实际上也是老马引用别人的，类似的表达还有很多。比如分布式系统领域有两大最难的事情：一是 &lt;strong>保证消息顺序&lt;/strong> ，一是 &lt;strong>严格一次传递&lt;/strong> 。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://camo.githubusercontent.com/595f6479028a874a759e29bfbc9df6c33e8e83b77c9cf7adeafd9aca696072ba/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6a6176612d67756964652d626c6f672f32303231303632393130343834343634352e706e67">&lt;img src="img/ly-20241212142016111.jpg" alt="img" />&lt;/a>&lt;/p>
&lt;p>今天咱们就单独拎出 “&lt;strong>命名&lt;/strong>” 来聊聊！&lt;/p>
&lt;p>这篇文章配合我之前发的 
&lt;a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&amp;amp;mid=2247486449&amp;amp;idx=1&amp;amp;sn=c3b502529ff991c7180281bcc22877af&amp;amp;chksm=cea2443af9d5cd2c1c87049ed15ccf6f88275419c7dbe542406166a703b27d0f3ecf2af901f8&amp;amp;token=999884676&amp;amp;lang=zh_CN#rd">《编码 5 分钟，命名 2 小时？史上最全的 Java 命名规范参考！》&lt;/a> 这篇文章阅读效果更佳哦！&lt;/p>
&lt;h2 id="为什么需要重视命名">为什么需要重视命名？&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e9%87%8d%e8%a7%86%e5%91%bd%e5%90%8d">#&lt;/a>&lt;/h2>
&lt;p>咱们需要先搞懂为什么要重视编程中的命名这一行为，它对于我们的编码工作有着什么意义。&lt;/p>
&lt;p>&lt;strong>为什么命名很重要呢？&lt;/strong> 这是因为 &lt;strong>好的命名即是注释，别人一看到你的命名就知道你的变量、方法或者类是做什么的！&lt;/strong>&lt;/p>
&lt;p>简单来说就是 &lt;strong>别人根据你的命名就能知道你的代码要表达的意思&lt;/strong> （不过，前提这个人也要有&lt;strong>基本的英语&lt;/strong>知识，对于一些编程中常见的单词比较熟悉）。&lt;/p>
&lt;p>简单举个例子说明一下命名的重要性。&lt;/p>
&lt;p>《Clean Code》这本书明确指出：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>&lt;strong>好的代码本身就是注释，我们要尽量规范和美化自己的代码来减少不必要的注释。&lt;/strong>&lt;/p>
&lt;p>&lt;strong>若编程语言足够有表达力，就不需要注释，尽量通过代码来阐述。&lt;/strong>&lt;/p>
&lt;p>举个例子：&lt;/p>
&lt;p>去掉下面复杂的注释，只需要创建一个&lt;strong>与注释所言同一事物的函数&lt;/strong>即可&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// check to see if the employee is eligible for full benefits&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> ((employee.&lt;span style="color:#a6e22e">flags&lt;/span> &lt;span style="color:#f92672">&amp;amp;&lt;/span> HOURLY_FLAG) &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> (employee.&lt;span style="color:#a6e22e">age&lt;/span> &lt;span style="color:#f92672">&amp;gt;&lt;/span> 65))&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>应替换为&lt;/p></description></item><item><title>软件工程简明教程</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/software-engineering/</link><pubDate>Fri, 17 Feb 2023 15:38:09 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/software-engineering/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>大部分软件开发从业者，都会忽略软件开发中的一些最基础、最底层的一些概念。但是，这些软件开发的概念对于软件开发来说非常重要，就像是软件开发的基石一样。这也是我写这篇文章的原因。&lt;/p>
&lt;h2 id="何为软件工程">何为软件工程？&lt;a class="anchor" href="#%e4%bd%95%e4%b8%ba%e8%bd%af%e4%bb%b6%e5%b7%a5%e7%a8%8b">#&lt;/a>&lt;/h2>
&lt;p>1968 年 NATO（北大西洋公约组织）提出了&lt;strong>软件危机&lt;/strong>（&lt;strong>Software crisis&lt;/strong>）一词。同年，为了解决软件危机问题，“&lt;strong>软件工程&lt;/strong>”的概念诞生了。一门叫做软件工程的学科也就应运而生。&lt;/p>
&lt;p>随着时间的推移，软件工程这门学科也经历了一轮又一轮的完善，其中的一些核心内容比如软件开发模型越来越丰富实用！&lt;/p>
&lt;p>&lt;strong>什么是软件危机呢？&lt;/strong>&lt;/p>
&lt;p>简单来说，软件危机描述了当时软件开发的一个痛点：我们&lt;strong>很难高效地开发出质量高&lt;/strong>的软件。&lt;/p>
&lt;p>Dijkstra（Dijkstra算法的作者） 在 1972年图灵奖获奖感言中也提高过软件危机，他是这样说的：“导致软件危机的主要原因是机器变得功能强大了几个数量级！坦率地说：只要没有机器，编程就完全没有问题。当我们有一些弱小的计算机时，编程成为一个温和的问题，而现在我们有了庞大的计算机，编程也同样成为一个巨大的问题”。&lt;/p>
&lt;p>&lt;strong>说了这么多，到底什么是软件工程呢？&lt;/strong>&lt;/p>
&lt;p>工程是&lt;strong>为了解决实际的问题将理论应用于实践&lt;/strong>。软件工程指的就是将&lt;strong>工程思想&lt;/strong>应用于&lt;strong>软件开发&lt;/strong>。&lt;/p>
&lt;p>上面是我对软件工程的定义，我们再来看看比较权威的定义。IEEE 软件工程汇刊给出的定义是这样的：　(1)将&lt;strong>系统化&lt;/strong>的、&lt;strong>规范&lt;/strong>的、&lt;strong>可量化&lt;/strong>的方法应用到软件的&lt;strong>开发&lt;/strong>、运&lt;strong>行&lt;/strong>及&lt;strong>维护&lt;/strong>中，即将工程化方法应用于软件。　(2)在(1)中所述方法的研究。&lt;/p>
&lt;p>总之，软件工程的终极目标就是：&lt;strong>在更少资源消耗的情况下，创造出更好、更容易维护的软件。&lt;/strong>&lt;/p>
&lt;h2 id="软件开发过程">软件开发过程&lt;a class="anchor" href="#%e8%bd%af%e4%bb%b6%e5%bc%80%e5%8f%91%e8%bf%87%e7%a8%8b">#&lt;/a>&lt;/h2>
&lt;p>
&lt;a target="_blank" href="https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B">维基百科是这样定义软件开发过程&lt;/a>的：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>软件开发过程（英语：software development process），或软件过程（英语：software process），是软件开发的开发生命周期（software development life cycle），其各个阶段实现了软件的&lt;strong>需求定义与分析&lt;/strong>、&lt;strong>设计&lt;/strong>、&lt;strong>实现&lt;/strong>、&lt;strong>测试&lt;/strong>、交付和&lt;strong>维护&lt;/strong>。软件过程是在开发与构建系统时应遵循的步骤，是软件开发的路线图。&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>需求分析 ：分析用户的需求，建立逻辑模型。&lt;/li>
&lt;li>软件设计 ： 根据需求分析的结果对软件架构进行设计。&lt;/li>
&lt;li>编码 ：编写程序运行的源代码。&lt;/li>
&lt;li>测试 : 确定测试用例，编写测试报告。&lt;/li>
&lt;li>交付 ：将做好的软件交付给客户。&lt;/li>
&lt;li>维护 ：对软件进行维护比如解决 bug，完善功能。&lt;/li>
&lt;/ul>
&lt;p>软件开发过程只是比较笼统的层面上，一定义了一个软件开发可能涉及到的一些流程。&lt;/p>
&lt;p>软件开发模型更具体地定义了软件开发过程，对开发过程提供了强有力的理论支持。&lt;/p>
&lt;h2 id="软件开发模型">软件开发模型&lt;a class="anchor" href="#%e8%bd%af%e4%bb%b6%e5%bc%80%e5%8f%91%e6%a8%a1%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>软件开发模型有很多种，比如&lt;strong>瀑布模型（Waterfall Model）&lt;/strong>、&lt;strong>快速原型模型（Rapid Prototype Model）&lt;/strong>、&lt;strong>V模型（V-model）&lt;/strong>、&lt;strong>W模型（W-model）&lt;/strong>、&lt;strong>敏捷开发模型&lt;/strong>。其中最具有代表性的还是 &lt;strong>瀑布模型&lt;/strong> 和 &lt;strong>敏捷开发&lt;/strong> 。&lt;/p>
&lt;p>&lt;strong>瀑布模型&lt;/strong> 定义了一套完成的软件开发周期，完整地展示了一个软件的的生命周期。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142018153.png" alt="ly-20241212142018153" />&lt;/p>
&lt;p>&lt;strong>敏捷开发模型&lt;/strong> 是目前使用的最多的一种软件开发模型。
&lt;a target="_blank" href="https://wiki.mbalib.com/wiki/%E6%95%8F%E6%8D%B7%E5%BC%80%E5%8F%91">MBA智库百科对敏捷开发的描述&lt;/a>是这样的:&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>&lt;strong>敏捷开发&lt;/strong> 是一种以人为核心、迭代、循序渐进的开发方法。在敏捷开发中，软件项目的构建被切分成多个子项目，各个子项目的成果都经过&lt;strong>测试&lt;/strong>，具备&lt;strong>集成&lt;/strong>和&lt;strong>可运行&lt;/strong>的特征。换言之，就是把一个大项目分为多个相互联系，但也可独立运行的小项目，并分别完成，在此过程中软件一直处于可使用状态。&lt;/p>&lt;/blockquote>&lt;p>像现在比较常见的一些概念比如 &lt;strong>持续集成&lt;/strong> 、&lt;strong>重构&lt;/strong> 、&lt;strong>小版本发布&lt;/strong> 、&lt;strong>低文档&lt;/strong> 、&lt;strong>站会&lt;/strong> 、&lt;strong>结对编程&lt;/strong> 、&lt;strong>测试驱动开发&lt;/strong> 都是敏捷开发的核心。&lt;/p></description></item><item><title>restFul</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/restful/</link><pubDate>Fri, 17 Feb 2023 14:35:02 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lycly_system-design/basis/restful/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212142017481.png" alt="img" />&lt;/p>
&lt;p>这篇文章简单聊聊后端程序员必备的 RESTful API 相关的知识。&lt;/p>
&lt;p>开始正式介绍 RESTful API 之前，我们需要首先搞清 ：&lt;strong>API 到底是什么？&lt;/strong>&lt;/p>
&lt;h2 id="-何为-api">
&lt;a target="_blank" href="#%E4%BD%95%E4%B8%BA-api">#&lt;/a> 何为 API？&lt;a class="anchor" href="#-%e4%bd%95%e4%b8%ba-api">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212142017744.png" alt="img" />&lt;/p>
&lt;p>&lt;strong>API（Application Programming Interface）&lt;/strong> 翻译过来是应用程序编程接口的意思。&lt;/p>
&lt;p>我们在进行后端开发的时候，主要的工作就是&lt;strong>为前端&lt;/strong>或者&lt;strong>其他后端服务&lt;/strong>提供 &lt;strong>AP&lt;/strong>I 比如&lt;strong>查询用户数据的 API&lt;/strong> 。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142017877.png" alt="ly-20241212142017877" />&lt;/p>
&lt;p>但是， API 不仅仅代表后端系统暴露的接口，像框架中提供的方法也属于 API 的范畴。&lt;/p>
&lt;p>为了方便大家理解，我再列举几个例子 🌰：&lt;/p>
&lt;ol>
&lt;li>你通过某电商网站搜索某某商品，电商网站的前端就调用了后端提供了&lt;strong>搜索商品相关的 API&lt;/strong>。&lt;/li>
&lt;li>你使用 JDK 开发 Java 程序，想要读取用户的输入的话，你就需要使用 JDK 提供的 &lt;strong>IO 相关的 API&lt;/strong>。&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ol>
&lt;p>你可以把 API 理解为&lt;strong>程序与程序之间通信的桥梁&lt;/strong>，其本质就是一个&lt;strong>函数&lt;/strong>而已。另外，API 的使用也不是没有章法的，它的规则由（比如&lt;strong>数据输入&lt;/strong>和&lt;strong>输出&lt;/strong>的格式）API 提供方制定。&lt;/p>
&lt;h2 id="-何为-restful-api">
&lt;a target="_blank" href="#%E4%BD%95%E4%B8%BA-restful-api">#&lt;/a> 何为 RESTful API？&lt;a class="anchor" href="#-%e4%bd%95%e4%b8%ba-restful-api">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>RESTful API&lt;/strong> 经常也被叫做 &lt;strong>REST API&lt;/strong>，它是基于 REST 构建的 API。这个 REST 到底是什么，我们后文在讲，涉及到的概念比较多。&lt;/p>
&lt;p>如果你看 RESTful API 相关的文章的话一般都比较晦涩难懂，主要是因为 REST 涉及到的一些概念比较难以理解。但是，实际上，我们平时开发用到的 RESTful API 的知识非常简单也很容易概括！&lt;/p></description></item><item><title>性能测试入门</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly05ly_performance-test/</link><pubDate>Fri, 17 Feb 2023 11:37:08 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly05ly_performance-test/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>性能测试一般情况下都是由测试这个职位去做的，那还需要我们开发学这个干嘛呢？&lt;strong>了解性能测试的指标&lt;/strong>、&lt;strong>分类&lt;/strong>以及&lt;strong>工具&lt;/strong>等知识有助于我们更好地去&lt;strong>写出性能更好&lt;/strong>的程序，另外作为开发这个角色，如果你会性能测试的话，相信也会为你的履历加分不少。&lt;/p>
&lt;p>这篇文章是我会结合自己的实际经历以及在测试这里取的经所得，除此之外，我还借鉴了一些优秀书籍，希望对你有帮助。&lt;/p>
&lt;p>本文思维导图：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142046226.png" alt="img" />&lt;/p>
&lt;h2 id="-一-不同角色看网站性能">
&lt;a target="_blank" href="#%E4%B8%80-%E4%B8%8D%E5%90%8C%E8%A7%92%E8%89%B2%E7%9C%8B%E7%BD%91%E7%AB%99%E6%80%A7%E8%83%BD">#&lt;/a> 一 不同角色看网站性能&lt;a class="anchor" href="#-%e4%b8%80-%e4%b8%8d%e5%90%8c%e8%a7%92%e8%89%b2%e7%9c%8b%e7%bd%91%e7%ab%99%e6%80%a7%e8%83%bd">#&lt;/a>&lt;/h2>
&lt;h3 id="-11-用户">
&lt;a target="_blank" href="#_1-1-%E7%94%A8%E6%88%B7">#&lt;/a> 1.1 用户&lt;a class="anchor" href="#-11-%e7%94%a8%e6%88%b7">#&lt;/a>&lt;/h3>
&lt;p>当用户打开一个网站的时候，最关注的是什么？当然是&lt;strong>网站响应速度的快慢&lt;/strong>。比如我们点击了淘宝的主页，淘宝需要多久将首页的内容呈现在我的面前，我点击了提交订单按钮需要多久返回结果等等。&lt;/p>
&lt;p>所以，用户在体验我们系统的时候往往根据你的响应速度的快慢来评判你的网站的性能。&lt;/p>
&lt;h3 id="-12-开发人员">
&lt;a target="_blank" href="#_1-2-%E5%BC%80%E5%8F%91%E4%BA%BA%E5%91%98">#&lt;/a> 1.2 开发人员&lt;a class="anchor" href="#-12-%e5%bc%80%e5%8f%91%e4%ba%ba%e5%91%98">#&lt;/a>&lt;/h3>
&lt;p>用户与开发人员都关注速度，这个速度实际上就是我们的系统&lt;strong>处理用户请求的速度&lt;/strong>。&lt;/p>
&lt;p>开发人员一般情况下很难直观的去评判自己网站的性能，我们往往会根据网站&lt;strong>当前的架构&lt;/strong>以及&lt;strong>基础设施&lt;/strong>情况给一个大概的值,比如：&lt;/p>
&lt;ol>
&lt;li>项目架构是分布式的吗？&lt;/li>
&lt;li>用到了&lt;strong>缓存&lt;/strong>和&lt;strong>消息队列&lt;/strong>没有？&lt;/li>
&lt;li>&lt;strong>高并发&lt;/strong>的业务有没有特殊处理？&lt;/li>
&lt;li>&lt;strong>数据库设计&lt;/strong>是否合理？&lt;/li>
&lt;li>系统用到的&lt;strong>算法&lt;/strong>是否还需要优化？&lt;/li>
&lt;li>系统是否存在&lt;strong>内存泄露&lt;/strong>的问题？&lt;/li>
&lt;li>项目使用的 &lt;strong>Redis 缓存多大&lt;/strong>？服务器&lt;strong>性能&lt;/strong>如何？用的是&lt;strong>机械硬盘&lt;/strong>还是&lt;strong>固态硬盘&lt;/strong>？&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ol>
&lt;h3 id="-13-测试人员">
&lt;a target="_blank" href="#_1-3-%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98">#&lt;/a> 1.3 测试人员&lt;a class="anchor" href="#-13-%e6%b5%8b%e8%af%95%e4%ba%ba%e5%91%98">#&lt;/a>&lt;/h3>
&lt;p>测试人员一般会根据性能测试工具来测试，然后一般会做出一个表格。这个表格可能会涵盖下面这些重要的内容：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>响应时间&lt;/strong>；&lt;/li>
&lt;li>&lt;strong>请求成功率&lt;/strong>；&lt;/li>
&lt;li>吞吐量；&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ol>
&lt;h3 id="-14-运维人员">
&lt;a target="_blank" href="#_1-4-%E8%BF%90%E7%BB%B4%E4%BA%BA%E5%91%98">#&lt;/a> 1.4 运维人员&lt;a class="anchor" href="#-14-%e8%bf%90%e7%bb%b4%e4%ba%ba%e5%91%98">#&lt;/a>&lt;/h3>
&lt;p>运维人员会倾向于根据&lt;strong>基础设施&lt;/strong>和&lt;strong>资源的利用率&lt;/strong>来判断网站的性能，比如我们的服务器资源使用是否合理、数据库资源是否存在滥用的情况、当然，这是传统的运维人员，现在 Devpos 火起来后，单纯干运维的很少了。我们这里暂且还保留有这个角色。&lt;/p>
&lt;h2 id="-二-性能测试需要注意的点">
&lt;a target="_blank" href="#%E4%BA%8C-%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E7%9A%84%E7%82%B9">#&lt;/a> 二 性能测试需要注意的点&lt;a class="anchor" href="#-%e4%ba%8c-%e6%80%a7%e8%83%bd%e6%b5%8b%e8%af%95%e9%9c%80%e8%a6%81%e6%b3%a8%e6%84%8f%e7%9a%84%e7%82%b9">#&lt;/a>&lt;/h2>
&lt;p>几乎没有文章在讲性能测试的时候提到这个问题，大家都会讲如何去性能测试，有哪些性能测试指标这些东西。&lt;/p>
&lt;h3 id="-21-了解系统的业务场景">
&lt;a target="_blank" href="#_2-1-%E4%BA%86%E8%A7%A3%E7%B3%BB%E7%BB%9F%E7%9A%84%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF">#&lt;/a> 2.1 了解系统的业务场景&lt;a class="anchor" href="#-21-%e4%ba%86%e8%a7%a3%e7%b3%bb%e7%bb%9f%e7%9a%84%e4%b8%9a%e5%8a%a1%e5%9c%ba%e6%99%af">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>性能测试之前更需要你了解当前的系统的业务场景。&lt;/strong> 对系统业务了解的不够深刻，我们很容易犯测试方向偏执的错误，从而导致我们&lt;strong>忽略了对系统某些更需要性能测试的地方进行测试&lt;/strong>。比如我们的系统可以为用户提供发送邮件的功能，用户配置成功邮箱后只需输入相应的邮箱之后就能发送，系统每天&lt;strong>大概能处理上万次&lt;/strong>发邮件的请求。很多人看到这个可能就直接开始使用相关工具测试邮箱发送接口，但是，发送邮件这个场景可能不是当前系统的性能瓶颈，这么多人用我们的系统发邮件， 还可能有&lt;strong>很多人一起发&lt;/strong>邮件，单单这个场景就这么人用，那&lt;strong>用户管理可能才是性能瓶颈&lt;/strong>吧！&lt;/p>
&lt;h3 id="-22-历史数据非常有用">
&lt;a target="_blank" href="#_2-2-%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E9%9D%9E%E5%B8%B8%E6%9C%89%E7%94%A8">#&lt;/a> 2.2 历史数据非常有用&lt;a class="anchor" href="#-22-%e5%8e%86%e5%8f%b2%e6%95%b0%e6%8d%ae%e9%9d%9e%e5%b8%b8%e6%9c%89%e7%94%a8">#&lt;/a>&lt;/h3>
&lt;p>当前系统所留下的历史数据非常重要，一般情况下，我们可以&lt;strong>通过相应的些历史数据&lt;/strong>初步判定这个系统&lt;strong>哪些接口调用的比较多&lt;/strong>、&lt;strong>哪些 service&lt;/strong> 承受的压力最大，这样的话，我们就可以针对这些地方进行更细致的性能测试与分析。&lt;/p>
&lt;p>另外，这些地方也就像这个系统的一个短板一样，优化好了这些地方会为我们的系统带来质的提升。&lt;/p>
&lt;h3 id="-三-性能测试的指标">
&lt;a target="_blank" href="#%E4%B8%89-%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E7%9A%84%E6%8C%87%E6%A0%87">#&lt;/a> 三 性能测试的指标&lt;a class="anchor" href="#-%e4%b8%89-%e6%80%a7%e8%83%bd%e6%b5%8b%e8%af%95%e7%9a%84%e6%8c%87%e6%a0%87">#&lt;/a>&lt;/h3>
&lt;h3 id="-31-响应时间">
&lt;a target="_blank" href="#_3-1-%E5%93%8D%E5%BA%94%E6%97%B6%E9%97%B4">#&lt;/a> 3.1 响应时间&lt;a class="anchor" href="#-31-%e5%93%8d%e5%ba%94%e6%97%b6%e9%97%b4">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>响应时间就是用户发出请求到用户收到系统处理结果所需要的时间。&lt;/strong> 重要吗？实在太重要！&lt;/p>
&lt;p>比较出名的 2-5-8 原则是这样描述的：通常来说，&lt;strong>2到5秒&lt;/strong>，页面体验会比较好，5到8秒还可以接受，8秒以上基本就很难接受了。另外，据统计当网站慢一秒就会流失十分之一的客户。&lt;/p></description></item><item><title>超时&amp;重试详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly04ly_timout-and-retry/</link><pubDate>Fri, 17 Feb 2023 10:57:02 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly04ly_timout-and-retry/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>由于&lt;strong>网络&lt;/strong>问题、&lt;strong>系统&lt;/strong>或者&lt;strong>服务内部&lt;/strong>的 Bug、服务器&lt;strong>宕机&lt;/strong>、操作&lt;strong>系统崩溃&lt;/strong>等问题的不确定性，我们的系统或者服务永远不可能保证时刻都是可用的状态。&lt;/p>
&lt;p>为了最大限度的减小系统或者服务出现故障之后带来的影响，我们需要用到的 &lt;strong>超时（Timeout）&lt;/strong> 和 &lt;strong>重试（Retry）&lt;/strong> 机制。&lt;/p>
&lt;p>想要把超时和重试机制讲清楚其实很简单，因为它俩本身就不是什么高深的概念。&lt;/p>
&lt;p>虽然超时和重试机制的思想很简单，但是它俩是真的非常实用。你平时接触到的绝大部分涉及到&lt;strong>远程调用&lt;/strong>的系统或者服务都会应用超时和重试机制。尤其是对于微服务系统来说，正确设置超时和重试非常重要。单体服务通常只涉及&lt;strong>数据库&lt;/strong>、&lt;strong>缓存&lt;/strong>、&lt;strong>第三方 API&lt;/strong>、&lt;strong>中间件&lt;/strong>等的网络调用，而微服务&lt;strong>系统内部各个服务之间还存在着网络调用&lt;/strong>。&lt;/p>
&lt;h2 id="-超时机制">
&lt;a target="_blank" href="#%E8%B6%85%E6%97%B6%E6%9C%BA%E5%88%B6">#&lt;/a> 超时机制&lt;a class="anchor" href="#-%e8%b6%85%e6%97%b6%e6%9c%ba%e5%88%b6">#&lt;/a>&lt;/h2>
&lt;h3 id="-什么是超时机制">
&lt;a target="_blank" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E8%B6%85%E6%97%B6%E6%9C%BA%E5%88%B6">#&lt;/a> 什么是超时机制？&lt;a class="anchor" href="#-%e4%bb%80%e4%b9%88%e6%98%af%e8%b6%85%e6%97%b6%e6%9c%ba%e5%88%b6">#&lt;/a>&lt;/h3>
&lt;p>超时机制说的是&lt;strong>当一个请求超过指定的时间&lt;/strong>（比如 1s）还没有被处理的话，这个请求就会&lt;strong>直接被取消并抛出指定的异常&lt;/strong>或者&lt;strong>错误&lt;/strong>（比如 &lt;code>504 Gateway Timeout&lt;/code>）。&lt;/p>
&lt;p>我们平时接触到的超时可以简单分为下面 2 种：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>连接超时（ConnectTimeout）&lt;/strong> ：客户端与服务端&lt;strong>建立连接&lt;/strong>的最长等待时间。&lt;/li>
&lt;li>&lt;strong>读取超时（ReadTimeout）&lt;/strong> ：客户端和服务端&lt;strong>已经建立连接&lt;/strong>，&lt;strong>客户端等待服务端处理完请求的最长时间&lt;/strong>。实际项目中，我们关注比较多的还是&lt;strong>读取超时&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>一些连接池客户端框架中可能还会有&lt;strong>获取连接超&lt;/strong>时&lt;strong>和&lt;/strong>空闲连接清理超时**。&lt;/p>
&lt;p>如果没有设置超时的话，就可能会导致服务端&lt;strong>连接数爆炸&lt;/strong>和&lt;strong>大量请求堆积&lt;/strong>的问题。&lt;/p>
&lt;p>这些堆积的连接和请求会&lt;strong>消耗系统资源&lt;/strong>，&lt;strong>影响新收到的请求&lt;/strong>的处理。严重的情况下，甚至会&lt;strong>拖垮整个系统或者服务&lt;/strong>。&lt;/p>
&lt;p>我之前在实际项目就遇到过类似的问题，整个网站无法正常处理请求，服务器负载直接快被拉满。后面发现原因是项目超时设置错误加上客户端请求处理异常，导致服务端连接数直接接近 40w+，这么多堆积的连接直接把系统干趴了。&lt;/p>
&lt;h3 id="-超时时间应该如何设置">
&lt;a target="_blank" href="#%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4%E5%BA%94%E8%AF%A5%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE">#&lt;/a> 超时时间应该如何设置？&lt;a class="anchor" href="#-%e8%b6%85%e6%97%b6%e6%97%b6%e9%97%b4%e5%ba%94%e8%af%a5%e5%a6%82%e4%bd%95%e8%ae%be%e7%bd%ae">#&lt;/a>&lt;/h3>
&lt;p>超时到底设置多长时间是一个难题！超时值设置太高或者太低都有风险。如果设置太高的话，会&lt;strong>降低超时机制的有效性&lt;/strong>，比如你设置超时为 10s 的话，那设置超时就没啥意义了，系统&lt;strong>依然可能会出现大量慢请求堆积&lt;/strong>的问题。如果&lt;strong>设置太低&lt;/strong>的话，就可能会导致在&lt;strong>系统或者服务在某些处理请求速度变慢&lt;/strong>的情况下（比如请求突然增多），&lt;strong>大量请求重试（超时通常会结合重试）继续加重系统或者服务的压力&lt;/strong>，进而导致整个系统或者服务被拖垮的问题。&lt;/p>
&lt;p>通常情况下，我们建议读取超时设置为 &lt;strong>1500ms&lt;/strong> ,这是一个比较普适的值。如果你的系统或者服务对于延迟比较敏感的话，那读取超时值可以适当在 &lt;strong>1500ms&lt;/strong> 的基础上进行缩短。反之，读取超时值也可以在 &lt;strong>1500ms&lt;/strong> 的基础上进行加长，不过，尽量还是不要超过 &lt;strong>1500ms&lt;/strong> 。连接超时可以适当设置长一些，建议在 &lt;strong>1000ms ~ 5000ms&lt;/strong> 之内。&lt;/p>
&lt;p>没有银弹！超时值具体该设置多大，还是要根据实际项目的需求和情况慢慢调整优化得到。&lt;/p>
&lt;p>更上一层，参考
&lt;a target="_blank" href="https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html">美团的Java线程池参数动态配置open in new window&lt;/a>思想，我们也可以将超时弄成可配置化的参数而不是固定的，比较简单的一种办法就是将超时的值放在配置中心中。这样的话，我们就可以根据系统或者服务的状态动态调整超时值了。&lt;/p>
&lt;h2 id="-重试机制">
&lt;a target="_blank" href="#%E9%87%8D%E8%AF%95%E6%9C%BA%E5%88%B6">#&lt;/a> 重试机制&lt;a class="anchor" href="#-%e9%87%8d%e8%af%95%e6%9c%ba%e5%88%b6">#&lt;/a>&lt;/h2>
&lt;h3 id="-什么是重试机制">
&lt;a target="_blank" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E9%87%8D%E8%AF%95%E6%9C%BA%E5%88%B6">#&lt;/a> 什么是重试机制？&lt;a class="anchor" href="#-%e4%bb%80%e4%b9%88%e6%98%af%e9%87%8d%e8%af%95%e6%9c%ba%e5%88%b6">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>重试机制一般配合超时机制&lt;/strong>一起使用，指的是&lt;strong>多次发送相同的请求&lt;/strong>来&lt;strong>避免瞬态故障&lt;/strong>和&lt;strong>偶然性故障&lt;/strong>。&lt;/p>
&lt;p>瞬态故障可以简单理解为&lt;strong>某一瞬间系统偶然出现的故障&lt;/strong>，并不会持久。偶然性故障可以理解为哪些在&lt;strong>某些情况下偶尔出现的故障&lt;/strong>，频率通常较低。&lt;/p>
&lt;p>重试的核心思想是&lt;strong>通过消耗服务器的资源来尽可能获得请求更大概率被成功处理&lt;/strong>。由于瞬态故障和偶然性故障是很少发生的，因此，重试对于服务器的资源消耗几乎是可以被忽略的。&lt;/p>
&lt;h3 id="-重试的次数如何设置">
&lt;a target="_blank" href="#%E9%87%8D%E8%AF%95%E7%9A%84%E6%AC%A1%E6%95%B0%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE">#&lt;/a> 重试的次数如何设置？&lt;a class="anchor" href="#-%e9%87%8d%e8%af%95%e7%9a%84%e6%ac%a1%e6%95%b0%e5%a6%82%e4%bd%95%e8%ae%be%e7%bd%ae">#&lt;/a>&lt;/h3>
&lt;p>重试的次数不宜过多，否则依然会对系统负载造成比较大的压力。&lt;/p>
&lt;p>重试的次数&lt;strong>通常建议设为 3 次&lt;/strong>。并且，我们通常还会设置&lt;strong>重试的间隔&lt;/strong>，比如说我们要重试 3 次的话，第 1 次请求失败后，等待 1 秒再进行重试，第 2 次请求失败后，等待 2 秒再进行重试，第 3 次请求失败后，等待 3 秒再进行重试。&lt;/p></description></item><item><title>服务限流详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly03ly_limit-request/</link><pubDate>Thu, 16 Feb 2023 17:06:11 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly03ly_limit-request/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>针对软件系统来说，限流就是&lt;strong>对请求的速率进行限制&lt;/strong>，&lt;strong>避免瞬时的大量请求击垮软件系统&lt;/strong>。毕竟，软件系统的处理能力是有限的。如果说超过了其处理能力的范围，软件系统可能直接就挂掉了。&lt;/p>
&lt;p>限流可能会&lt;strong>导致用户的请求无法被正确处理&lt;/strong>，不过，这往往也是&lt;strong>权衡了软件系统的稳定性之后得到的最优解&lt;/strong>。&lt;/p>
&lt;p>现实生活中，处处都有限流的实际应用，就比如&lt;strong>排队买票&lt;/strong>是为了避免大量用户涌入购票而导致&lt;strong>售票员无法处理&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142045484.jpg" alt="排队示意图" />&lt;/p>
&lt;h2 id="常见限流算法有哪些">常见限流算法有哪些？&lt;a class="anchor" href="#%e5%b8%b8%e8%a7%81%e9%99%90%e6%b5%81%e7%ae%97%e6%b3%95%e6%9c%89%e5%93%aa%e4%ba%9b">#&lt;/a>&lt;/h2>
&lt;p>简单介绍 4 种非常好理解并且容易实现的限流算法！&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>图片来源于 InfoQ 的一篇文章
&lt;a target="_blank" href="https://www.infoq.cn/article/Qg2tX8fyw5Vt-f3HH673">《分布式服务限流实战，已经为你排好坑了》&lt;/a>。&lt;/p>&lt;/blockquote>&lt;h3 id="固定窗口计数器算法">固定窗口计数器算法&lt;a class="anchor" href="#%e5%9b%ba%e5%ae%9a%e7%aa%97%e5%8f%a3%e8%ae%a1%e6%95%b0%e5%99%a8%e7%ae%97%e6%b3%95">#&lt;/a>&lt;/h3>
&lt;p>固定窗口其实就是时间窗口。&lt;strong>固定窗口计数器算法&lt;/strong> 规定了我们&lt;strong>单位时间处理的请求数量&lt;/strong>。&lt;/p>
&lt;p>假如我们规定系统中某个接口 1 分钟只能访问 33 次的话，使用固定窗口计数器算法的实现思路如下：&lt;/p>
&lt;ul>
&lt;li>给定一个变量 &lt;code>counter&lt;/code> 来&lt;strong>记录当前接口处理的请求数量&lt;/strong>，初始值为 0（代表接口当前 1 分钟内还未处理请求）。&lt;/li>
&lt;li>1 分钟之内每处理一个请求之后就将 &lt;code>counter+1&lt;/code> ，当 &lt;code>counter=33&lt;/code> 之后（也就是说在这 1 分钟内接口已经被访问 33 次的话），后续的请求就会被全部拒绝。&lt;/li>
&lt;li>等到 1 分钟结束后，将 &lt;code>counter&lt;/code> 重置 0，重新开始计数。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>这种限流算法无法保证限流速率，因而无法保证突然激增的流量。&lt;/strong>&lt;/p>
&lt;p>就比如说我们限制某个接口 1 分钟只能访问 1000 次，该接口的 QPS 为 500，前 55s 这个接口 1 个请求没有接收，后 1s 突然接收了 1000 个请求。然后，在当前场景下，这 1000 个请求在 1s 内是没办法被处理的，系统直接就被瞬时的大量请求给击垮了。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142045643.jpg" alt="固定窗口计数器算法" />&lt;/p>
&lt;h3 id="滑动窗口计数器算法">滑动窗口计数器算法&lt;a class="anchor" href="#%e6%bb%91%e5%8a%a8%e7%aa%97%e5%8f%a3%e8%ae%a1%e6%95%b0%e5%99%a8%e7%ae%97%e6%b3%95">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>滑动窗口计数器算法&lt;/strong> 算的上是固定窗口计数器算法的升级版。&lt;/p></description></item><item><title>冗余设计</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly02ly_redundancy/</link><pubDate>Thu, 16 Feb 2023 16:54:24 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly02ly_redundancy/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>title&lt;/th>
 &lt;th>category&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>冗余设计详解&lt;/td>
 &lt;td>高可用&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>冗余设计是&lt;strong>保证系统和数据高可用&lt;/strong>的最常的手段。&lt;/p>
&lt;p>对于服务来说，冗余的思想就是&lt;strong>相同的服务部署多份&lt;/strong>，如果正在使用的服务突然挂掉的话，系统可以&lt;strong>很快切换&lt;/strong>到备份服务上，大大减少系统的不可用时间，提高系统的可用性。&lt;/p>
&lt;p>对于数据来说，冗余的思想就是&lt;strong>相同的数据备份多份&lt;/strong>，这样就可以很简单地&lt;strong>提高数据的安全性&lt;/strong>。&lt;/p>
&lt;p>实际上，日常生活中就有非常多的冗余思想的应用。&lt;/p>
&lt;p>拿我自己来说，我对于重要文件的保存方法就是冗余思想的应用。我日常所使用的重要文件都会&lt;strong>同步一份在 Github 以及个人云盘&lt;/strong>上，这样就可以保证即使电脑硬盘损坏，我也可以通过 Github 或者个人云盘找回自己的重要文件。&lt;/p>
&lt;p>&lt;strong>高可用集群（High Availability Cluster，简称 HA Cluster）&lt;/strong>、&lt;strong>同城灾备&lt;/strong>、&lt;strong>异地灾备&lt;/strong>、&lt;strong>同城多活&lt;/strong>和&lt;strong>异地多活&lt;/strong>是冗余思想在高可用系统设计中最典型的应用。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>高可用集群&lt;/strong> : 同一份服务部署两份或者多份，当正在使用的服务突然挂掉的话，可以切换到另外一台服务，从而保证服务的高可用。&lt;/li>
&lt;li>&lt;strong>同城灾备&lt;/strong> ：一整个集群可以部署在同一个机房，而同城灾备中相同服务部署在&lt;strong>同一个城市&lt;/strong>的&lt;strong>不同机房&lt;/strong>中。并且，&lt;strong>备用服务不处理请求&lt;/strong>。这样可以避免机房出现意外情况比如停电、火灾。&lt;/li>
&lt;li>&lt;strong>异地灾备&lt;/strong> ：类似于同城灾备，不同的是，相同服务部署在&lt;strong>异地&lt;/strong>（通常距离较远，甚至是在不同的城市或者国家）的&lt;strong>不同机房&lt;/strong>中&lt;/li>
&lt;li>&lt;strong>同城多活&lt;/strong> ：类似于同城灾备，但&lt;strong>备用服务可以处理请求&lt;/strong>，这样可以充分利用系统资源，提高系统的并发。&lt;/li>
&lt;li>&lt;strong>异地多活&lt;/strong> : 将服务部署在&lt;strong>异地的不同机房&lt;/strong>中，并且，它们可以同时&lt;strong>对外提供服务&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>高可用集群&lt;strong>单纯是服务的冗余&lt;/strong>，并没有强调地域。同城灾备、异地灾备、同城多活和异地多活实现了地域上的冗余。&lt;/p>
&lt;p>同城和异地的主要区别在于&lt;strong>机房之间的距离&lt;/strong>。异地通常距离较远，甚至是在不同的城市或者国家。&lt;/p>
&lt;p>和传统的灾备设计相比，同城多活和异地多活最明显的改变在于**“多活”&lt;strong>，即所有站点都是&lt;/strong>同时在对外提供服务&lt;strong>的。异地多活是为了应对突发状况比如&lt;/strong>火灾**、&lt;strong>地震&lt;/strong>等自然或者人为灾害。&lt;/p>
&lt;p>光做好冗余还不够，必须要配合上 &lt;strong>故障转移&lt;/strong> 才可以！ 所谓&lt;strong>故障转移&lt;/strong>，简单来说就是&lt;strong>实现不可用服务快速且自动地切换到可用服务&lt;/strong>，整个&lt;strong>过程不需要人为干涉&lt;/strong>。&lt;/p>
&lt;p>举个例子：哨兵模式的 Redis 集群中，&lt;strong>如果 Sentinel（哨兵） 检测到 master 节点出现故障的话， 它就会帮助我们实现故障转移，自动将某一台 slave 升级为 master&lt;/strong>，确保整个 Redis 系统的可用性。整个过程完全自动，不需要人工介入。我在
&lt;a target="_blank" href="https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7">《Java 面试指北》&lt;/a>的「技术面试题篇」中的数据库部分详细介绍了 Redis 集群相关的知识点&amp;amp;面试题，感兴趣的小伙伴可以看看。&lt;/p>
&lt;p>再举个例子：&lt;strong>Nginx 可以结合 Keepalived 来实现高可用。如果 Nginx 主服务器宕机的话，Keepalived 可以自动进行故障转移，备用 Nginx 主服务器升级为主服务。并且，这个切换对外是透明的，因为使用的虚拟 IP，虚拟 IP 不会改变&lt;/strong>。我在
&lt;a target="_blank" href="https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7">《Java 面试指北》&lt;/a>的「技术面试题篇」中的「服务器」部分详细介绍了 Nginx 相关的知识点&amp;amp;面试题，感兴趣的小伙伴可以看看。&lt;/p></description></item><item><title>高可用系统设计指南</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly01ly_high-availability-system-design/</link><pubDate>Thu, 16 Feb 2023 15:43:06 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyfly_high-availability/ly01ly_high-availability-system-design/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="什么是高可用可用性的判断标准是啥">什么是高可用？可用性的判断标准是啥？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e9%ab%98%e5%8f%af%e7%94%a8%e5%8f%af%e7%94%a8%e6%80%a7%e7%9a%84%e5%88%a4%e6%96%ad%e6%a0%87%e5%87%86%e6%98%af%e5%95%a5">#&lt;/a>&lt;/h2>
&lt;p>高可用描述的是&lt;strong>一个系统在大部分时间都是可用的&lt;/strong>，可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候，&lt;strong>服务仍然是可用的&lt;/strong>。&lt;/p>
&lt;p>一般情况下，我们使用多少个 9 来评判一个系统的可用性，比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的，这样的系统就是非常非常高可用的了！当然，也会有系统如果可用性不太好的话，可能连 9 都上不了。&lt;/p>
&lt;p>除此之外，系统的可用性还可以用&lt;strong>某功能的失败次数&lt;/strong>与&lt;strong>总的请求次数&lt;/strong>之比来衡量，比如对网站请求 1000 次，其中有 10 次请求失败，那么可用性就是 99%。&lt;/p>
&lt;h2 id="哪些情况会导致系统不可用">哪些情况会导致系统不可用？&lt;a class="anchor" href="#%e5%93%aa%e4%ba%9b%e6%83%85%e5%86%b5%e4%bc%9a%e5%af%bc%e8%87%b4%e7%b3%bb%e7%bb%9f%e4%b8%8d%e5%8f%af%e7%94%a8">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>黑客攻击；&lt;/li>
&lt;li>&lt;strong>硬件故障&lt;/strong>，比如服务器坏掉。&lt;/li>
&lt;li>&lt;strong>并发量/用户请求量激增&lt;/strong>导致整个服务宕掉或者部分服务不可用。&lt;/li>
&lt;li>代码中的坏味道导致内存泄漏或者其他问题导致&lt;strong>程序挂掉&lt;/strong>。&lt;/li>
&lt;li>网站架构某个&lt;strong>重要的角色&lt;/strong>比如 &lt;strong>Nginx&lt;/strong> 或者&lt;strong>数据库&lt;/strong>突然不可用。&lt;/li>
&lt;li>自然灾害或者&lt;strong>人为破坏&lt;/strong>。&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ol>
&lt;h2 id="有哪些提高系统可用性的方法">有哪些提高系统可用性的方法？&lt;a class="anchor" href="#%e6%9c%89%e5%93%aa%e4%ba%9b%e6%8f%90%e9%ab%98%e7%b3%bb%e7%bb%9f%e5%8f%af%e7%94%a8%e6%80%a7%e7%9a%84%e6%96%b9%e6%b3%95">#&lt;/a>&lt;/h2>
&lt;h3 id="注重代码质量测试严格把关">注重代码质量，测试严格把关&lt;a class="anchor" href="#%e6%b3%a8%e9%87%8d%e4%bb%a3%e7%a0%81%e8%b4%a8%e9%87%8f%e6%b5%8b%e8%af%95%e4%b8%a5%e6%a0%bc%e6%8a%8a%e5%85%b3">#&lt;/a>&lt;/h3>
&lt;p>我觉得这个是最最最重要的，代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。大家都喜欢谈限流、降级、熔断，但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。如何提高代码质量？比较实际可用的就是 &lt;strong>CodeReview&lt;/strong>，不要在乎每天多花的那 1 个小时左右的时间，作用可大着呢！&lt;/p>
&lt;p>另外，安利几个对提高代码质量有实际效果的神器：&lt;/p>
&lt;ul>
&lt;li>
&lt;a target="_blank" href="https://www.sonarqube.org/">Sonarqube&lt;/a>；&lt;/li>
&lt;li>Alibaba 开源的 Java 诊断工具 
&lt;a target="_blank" href="https://arthas.aliyun.com/doc/">Arthas&lt;/a>；&lt;/li>
&lt;li>
&lt;a target="_blank" href="https://github.com/alibaba/p3c">阿里巴巴 Java 代码规范&lt;/a>（Alibaba Java Code Guidelines）；&lt;/li>
&lt;li>IDEA 自带的代码分析等工具。&lt;/li>
&lt;/ul>
&lt;h3 id="使用集群减少单点故障">使用集群，减少单点故障&lt;a class="anchor" href="#%e4%bd%bf%e7%94%a8%e9%9b%86%e7%be%a4%e5%87%8f%e5%b0%91%e5%8d%95%e7%82%b9%e6%95%85%e9%9a%9c">#&lt;/a>&lt;/h3>
&lt;p>先拿常用的 Redis 举个例子！我们如何保证我们的 Redis 缓存高可用呢？答案就是使用&lt;strong>集群&lt;/strong>，&lt;strong>避免单点故障&lt;/strong>。当我们使用一个 Redis 实例作为缓存的时候，这个 Redis 实例挂了之后，整个缓存服务可能就挂了。使用了集群之后，即使一台 Redis 实例挂了，不到一秒就会有另外一台 Redis 实例顶上。&lt;/p>
&lt;h3 id="限流">限流&lt;a class="anchor" href="#%e9%99%90%e6%b5%81">#&lt;/a>&lt;/h3>
&lt;p>流量控制（flow control），其原理是&lt;strong>监控应用流量的 QPS&lt;/strong> 或&lt;strong>并发线程数&lt;/strong>等指标，当达到指定的阈值时对流量进行控制，以避免被瞬时的流量高峰冲垮，从而保障应用的高可用性。——来自 
&lt;a target="_blank" href="https://github.com/alibaba/Sentinel">alibaba-Sentinel&lt;/a> 的 wiki。&lt;/p>
&lt;h3 id="超时和重试机制设置">超时和重试机制设置&lt;a class="anchor" href="#%e8%b6%85%e6%97%b6%e5%92%8c%e9%87%8d%e8%af%95%e6%9c%ba%e5%88%b6%e8%ae%be%e7%bd%ae">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>一旦用户请求超过某个时间的得不到响应，就抛出异常&lt;/strong>。这个是非常重要的，很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候，尤其适合设置超时和重试机制。一般我们使用一些 RPC 框架的时候，这些框架都自带的超时重试的配置。&lt;strong>如果不进行超时设置可能会导致请求响应速度慢&lt;/strong>，甚至导致请求堆积进而让系统无法再处理请求。重试的次数一般设为 3 次，再多次的重试没有好处，反而会加重服务器压力（部分场景使用失败重试机制会不太适合）。&lt;/p></description></item><item><title>rocketmq常见面试题</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/message-mq/rocketmq-questions/</link><pubDate>Wed, 15 Feb 2023 09:58:47 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/message-mq/rocketmq-questions/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>本文来自读者 
&lt;a target="_blank" href="https://github.com/Snailclimb/JavaGuide/pull/291">PR&lt;/a>。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>主要是rocket mq的几个问题&lt;/p>&lt;/blockquote>&lt;/blockquote>&lt;h2 id="1-单机版消息中心">1 单机版消息中心&lt;a class="anchor" href="#1-%e5%8d%95%e6%9c%ba%e7%89%88%e6%b6%88%e6%81%af%e4%b8%ad%e5%bf%83">#&lt;/a>&lt;/h2>
&lt;p>一个消息中心，最基本的需要支持多生产者、多消费者，例如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Scratch&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 实际中会有 nameserver 服务来找到 broker 具体位置以及 broker 主从信息&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Broker broker &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Broker();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Producer producer1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Producer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> producer1.&lt;span style="color:#a6e22e">connectBroker&lt;/span>(broker);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Producer producer2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Producer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> producer2.&lt;span style="color:#a6e22e">connectBroker&lt;/span>(broker);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Consumer consumer1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Consumer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> consumer1.&lt;span style="color:#a6e22e">connectBroker&lt;/span>(broker);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Consumer consumer2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Consumer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> consumer2.&lt;span style="color:#a6e22e">connectBroker&lt;/span>(broker);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> 0; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> 2; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> producer1.&lt;span style="color:#a6e22e">asyncSendMsg&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;producer1 send msg&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> producer2.&lt;span style="color:#a6e22e">asyncSendMsg&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;producer2 send msg&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;broker has msg:&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> broker.&lt;span style="color:#a6e22e">getAllMagByDisk&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> 0; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> 1; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;consumer1 consume msg：&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> consumer1.&lt;span style="color:#a6e22e">syncPullMsg&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> 0; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> 3; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;consumer2 consume msg：&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> consumer2.&lt;span style="color:#a6e22e">syncPullMsg&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Producer&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Broker broker;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">connectBroker&lt;/span>(Broker broker) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">broker&lt;/span> &lt;span style="color:#f92672">=&lt;/span> broker;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">asyncSendMsg&lt;/span>(String msg) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (broker &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> RuntimeException(&lt;span style="color:#e6db74">&amp;#34;please connect broker first&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(() &lt;span style="color:#f92672">-&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> broker.&lt;span style="color:#a6e22e">sendMsg&lt;/span>(msg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Consumer&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Broker broker;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">connectBroker&lt;/span>(Broker broker) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">broker&lt;/span> &lt;span style="color:#f92672">=&lt;/span> broker;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">syncPullMsg&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> broker.&lt;span style="color:#a6e22e">getMsg&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Broker&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 对应 RocketMQ 中 MessageQueue，默认情况下 1 个 Topic 包含 4 个 MessageQueue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> LinkedBlockingQueue&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span> messageQueue &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> LinkedBlockingQueue(Integer.&lt;span style="color:#a6e22e">MAX_VALUE&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 实际发送消息到 broker 服务器使用 Netty 发送&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">sendMsg&lt;/span>(String msg) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> messageQueue.&lt;span style="color:#a6e22e">put&lt;/span>(msg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 实际会同步或异步落盘，异步落盘使用的定时任务定时扫描落盘&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (InterruptedException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">getMsg&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> messageQueue.&lt;span style="color:#a6e22e">take&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (InterruptedException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">getAllMagByDisk&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StringBuilder sb &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> StringBuilder(&lt;span style="color:#e6db74">&amp;#34;\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> messageQueue.&lt;span style="color:#a6e22e">iterator&lt;/span>().&lt;span style="color:#a6e22e">forEachRemaining&lt;/span>((msg) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sb.&lt;span style="color:#a6e22e">append&lt;/span>(msg &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\n&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> sb.&lt;span style="color:#a6e22e">toString&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>问题：&lt;/p></description></item><item><title>rocketmq介绍</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/message-mq/rocketmq-intro/</link><pubDate>Wed, 15 Feb 2023 09:58:20 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/message-mq/rocketmq-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="消息队列扫盲">消息队列扫盲&lt;a class="anchor" href="#%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e6%89%ab%e7%9b%b2">#&lt;/a>&lt;/h2>
&lt;p>消息队列顾名思义就是存放消息的队列，队列我就不解释了，别告诉我你连队列都不知道是啥吧？&lt;/p>
&lt;p>所以问题并不是消息队列是什么，而是 &lt;strong>消息队列为什么会出现？消息队列能用来干什么？用它来干这些事会带来什么好处？消息队列会带来副作用吗？&lt;/strong>&lt;/p>
&lt;h3 id="-消息队列为什么会出现">
&lt;a target="_blank" href="#%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E5%87%BA%E7%8E%B0">#&lt;/a> 消息队列为什么会出现？&lt;a class="anchor" href="#-%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e5%87%ba%e7%8e%b0">#&lt;/a>&lt;/h3>
&lt;p>消息队列算是作为后端程序员的一个必备技能吧，因为&lt;strong>分布式应用必定涉及到各个系统之间的通信问题&lt;/strong>，这个时候消息队列也应运而生了。可以说&lt;strong>分布式&lt;/strong>的产生是消息队列的基础，而分布式怕是一个很古老的概念了吧，所以消息队列也是一个很古老的&lt;strong>中间件&lt;/strong>了。&lt;/p>
&lt;h3 id="-消息队列能用来干什么">
&lt;a target="_blank" href="#%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E8%83%BD%E7%94%A8%E6%9D%A5%E5%B9%B2%E4%BB%80%E4%B9%88">#&lt;/a> 消息队列能用来干什么？&lt;a class="anchor" href="#-%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e8%83%bd%e7%94%a8%e6%9d%a5%e5%b9%b2%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h3>
&lt;h4 id="-异步">
&lt;a target="_blank" href="#%E5%BC%82%E6%AD%A5">#&lt;/a> 异步&lt;a class="anchor" href="#-%e5%bc%82%e6%ad%a5">#&lt;/a>&lt;/h4>
&lt;p>你可能会反驳我，应用之间的通信又不是只能由消息队列解决，好好的通信为什么&lt;strong>中间非要插一个消息队列&lt;/strong>呢？我不能直接进行通信吗？&lt;/p>
&lt;p>很好👍，你又提出了一个概念，&lt;strong>同步通信&lt;/strong>。就比如现在业界使用比较多的 &lt;code>Dubbo&lt;/code> 就是一个适用于各个系统之间&lt;strong>同步通信的 &lt;code>RPC&lt;/code> 框架&lt;/strong>。&lt;/p>
&lt;p>我来举个🌰吧，比如我们有一个购票系统，需求是用户在购买完之后能接收到购买完成的短信。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142042093.jpg" alt="img" />&lt;/p>
&lt;p>我们省略中间的网络通信时间消耗，假如购票系统处理需要 150ms ，短信系统处理需要 200ms ，那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。&lt;/p>
&lt;p>当然，乍看没什么问题。可是仔细一想你就感觉有点问题，我用户购票在购票系统的时候其实就已经完成了购买，而我现在通过同步调用非要让整个请求拉长时间，而短信系统这玩意又不是很有必要，它仅仅是一个&lt;strong>辅助功能增强用户体验感&lt;/strong>而已。我现在整个调用流程就有点 &lt;strong>头重脚轻&lt;/strong> 的感觉了，购票是一个不太耗时的流程，而我现在因为同步调用，非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢？&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142042330.jpg" alt="img" />&lt;/p>
&lt;p>这样&lt;strong>整个系统的调用链又变长&lt;/strong>了，整个时间就变成了550ms。&lt;/p>
&lt;p>当我们在学生时代需要在食堂排队的时候，我们和食堂大妈就是一个同步的模型。&lt;/p>
&lt;p>我们需要告诉食堂大妈：“姐姐，给我加个鸡腿，再加个酸辣土豆丝，帮我浇点汁上去，多打点饭哦😋😋😋” 咦~~~ 为了多吃点，真恶心。&lt;/p>
&lt;p>然后大妈帮我们打饭配菜，我们看着大妈那颤抖的手和掉落的土豆丝不禁咽了咽口水。&lt;/p>
&lt;p>最终我们从大妈手中接过饭菜然后去寻找座位了&amp;hellip;&lt;/p>
&lt;p>回想一下，我们在给大妈发送需要的信息之后我们是 &lt;strong>同步等待大妈给我配好饭菜&lt;/strong> 的，上面我们只是加了鸡腿和土豆丝，万一我再加一个番茄牛腩，韭菜鸡蛋，这样是不是大妈打饭配菜的流程就会变长，我们等待的时间也会相应的变长。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142042452.jpg" alt="img" />&lt;/p>
&lt;p>那后来，我们工作赚钱了有钱去饭店吃饭了，我们告诉服务员来一碗牛肉面加个荷包蛋 &lt;strong>(传达一个消息)&lt;/strong> ，然后我们就可以在饭桌上安心的玩手机了 &lt;strong>(干自己其他事情)&lt;/strong> ，等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息，然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短，但是我们只需要传达一个消息就可以干其他事情了，这是一个 &lt;strong>异步&lt;/strong> 的概念。&lt;/p>
&lt;p>所以，为了解决这一个问题，聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。&lt;/p>
&lt;p>&lt;img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef38124f55eaea.jpg" alt="img" />&lt;/p>
&lt;p>这样，我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机)，所以整个耗时只是 &lt;strong>150ms + 10ms = 160ms&lt;/strong>。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>但是你需要注意的是，整个流程的时长是没变的，就像你仅仅告诉服务员要吃什么是不会影响到做面的速度的。&lt;/p>&lt;/blockquote>&lt;h4 id="-解耦">
&lt;a target="_blank" href="#%E8%A7%A3%E8%80%A6">#&lt;/a> 解耦&lt;a class="anchor" href="#-%e8%a7%a3%e8%80%a6">#&lt;/a>&lt;/h4>
&lt;p>回到最初同步调用的过程，我们写个伪代码简单概括一下。&lt;/p></description></item><item><title>message-queue</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/message-mq/base/</link><pubDate>Wed, 15 Feb 2023 09:56:36 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/message-mq/base/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>“RabbitMQ？”“Kafka？”“RocketMQ？”&amp;hellip;在日常学习与开发过程中，我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手，又或者你是不懂消息队列的新手，不论你了不了解消息队列，本文都将带你搞懂消息队列的一些&lt;strong>基本理论&lt;/strong>。&lt;/p>
&lt;p>如果你是老手，你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念，如果你是新手，相信本文将是你打开消息队列大门的一板砖。&lt;/p>
&lt;h2 id="什么是消息队列">什么是消息队列？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97">#&lt;/a>&lt;/h2>
&lt;p>我们可以把消息队列看作是一个&lt;strong>存放消息的容器&lt;/strong>，当我们需要使用消息的时候，直接&lt;strong>从容器中取出消息&lt;/strong>供自己使用即可。由于队列 Queue 是一种&lt;strong>先进先出&lt;/strong>的数据结构，所以消费消息时也是&lt;strong>按照顺序来消费&lt;/strong>的。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142040452.jpg" alt="Message queue" />&lt;/p>
&lt;p>参与消息传递的双方称为&lt;strong>生产者&lt;/strong>和&lt;strong>消费者&lt;/strong>，生产者负责&lt;strong>发送&lt;/strong>消息，消费者负责&lt;strong>处理&lt;/strong>消息。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142040705.png" alt="发布/订阅（Pub/Sub）模型" />&lt;/p>
&lt;p>我们知道操作系统中的进程通信的一种很重要的方式就是消息队列。我们这里提到的&lt;strong>消息队列&lt;/strong>稍微有点区别，更多指的是&lt;strong>各个服务&lt;/strong>以及&lt;strong>系统内部各个组件&lt;/strong>/&lt;strong>模块&lt;/strong>之前的通信，属于一种&lt;strong>中间件&lt;/strong>。&lt;/p>
&lt;p>随着分布式和微服务系统的发展，消息队列在系统设计中有了&lt;strong>更大的发挥&lt;/strong>空间，使用消息队列可以&lt;strong>降低系统耦合性&lt;/strong>、&lt;strong>实现任务异步&lt;/strong>、&lt;strong>有效地进行流量削峰&lt;/strong>，是分布式和微服务系统中重要的组件之一。&lt;/p>
&lt;h2 id="消息队列有什么用">消息队列有什么用？&lt;a class="anchor" href="#%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e6%9c%89%e4%bb%80%e4%b9%88%e7%94%a8">#&lt;/a>&lt;/h2>
&lt;p>通常来说，使用消息队列能为我们的系统带来下面三点好处：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>通过异步处理提高系统性能（减少响应所需时间）。&lt;/strong>&lt;/li>
&lt;li>&lt;strong>削峰/限流&lt;/strong>&lt;/li>
&lt;li>&lt;strong>降低系统耦合性。&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>如果在面试的时候你被面试官问到这个问题的话，一般情况是你在你的&lt;strong>简历上涉及到消息队列&lt;/strong>这方面的内容，这个时候推荐&lt;strong>你结合你自己的项目&lt;/strong>来回答。&lt;/p>
&lt;h3 id="通过异步处理提高系统性能减少响应所需时间">通过异步处理提高系统性能（减少响应所需时间）&lt;a class="anchor" href="#%e9%80%9a%e8%bf%87%e5%bc%82%e6%ad%a5%e5%a4%84%e7%90%86%e6%8f%90%e9%ab%98%e7%b3%bb%e7%bb%9f%e6%80%a7%e8%83%bd%e5%87%8f%e5%b0%91%e5%93%8d%e5%ba%94%e6%89%80%e9%9c%80%e6%97%b6%e9%97%b4">#&lt;/a>&lt;/h3>
&lt;p>&lt;img src="img/ly-20241212142040821.png" alt="ly-20241212142040821" />&lt;/p>
&lt;p>将用户的请求数据&lt;strong>存储到消息队列之后就立即返回结果&lt;/strong>。随后，系统再对消息进行消费。&lt;/p>
&lt;p>因为用户请求数据写入消息队列之后就立即返回给用户了，但是请求数据在后续的&lt;strong>业务校验&lt;/strong>、&lt;strong>写数据库&lt;/strong>等操作中&lt;strong>可能失败&lt;/strong>。因此，&lt;strong>使用消息队列进行异步处理之后，需要适当修改业务流程进行配合&lt;/strong>，比如用户在提交订单之后，订单数据写入消息队列，&lt;strong>不能立即返回用户订单提交成功&lt;/strong>，需要在消息队列的订单&lt;strong>消费者进程真正处理完该订单之后&lt;/strong>，甚至&lt;strong>出库&lt;/strong>后，再通过电子邮件或短信通知用户订单成功，&lt;strong>以免交易纠纷&lt;/strong>。这就类似我们平时手机订火车票和电影票。&lt;/p>
&lt;h3 id="削峰限流">削峰/限流&lt;a class="anchor" href="#%e5%89%8a%e5%b3%b0%e9%99%90%e6%b5%81">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>先将短时间高并发产生的事务消息存储在消息队列中，然后后端服务再慢慢根据自己的能力去消费这些消息，这样就避免直接把后端服务打垮掉。&lt;/strong>&lt;/p>
&lt;p>举例：在电子商务一些&lt;strong>秒杀&lt;/strong>、&lt;strong>促销&lt;/strong>活动中，合理使用消息队列可以&lt;strong>有效抵御促销活动&lt;/strong>刚开始大量订单涌入对系统的冲击。如下图所示：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142040941.jpg" alt="削峰" />&lt;/p>
&lt;h3 id="降低系统耦合性">降低系统耦合性&lt;a class="anchor" href="#%e9%99%8d%e4%bd%8e%e7%b3%bb%e7%bb%9f%e8%80%a6%e5%90%88%e6%80%a7">#&lt;/a>&lt;/h3>
&lt;p>使用消息队列还可以降低系统耦合性。我们知道如果&lt;strong>模块之间不存在直接调用&lt;/strong>，那么新增模块或者修改模块就对其他模块影响较小，这样系统的&lt;strong>可扩展性&lt;/strong>无疑更好一些。还是直接上图吧：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142041055.jpg" alt="解耦" />&lt;/p>
&lt;p>&lt;strong>生产者（客户端）发送消息到消息队列中去，接受者（服务端）处理消息&lt;/strong>，需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合，这显然也提高了系统的扩展性。&lt;/p>
&lt;p>&lt;strong>消息队列使用发布-订阅模式工作，消息发送者（生产者）发布消息，一个或多个消息接受者（消费者）订阅消息。&lt;/strong> 从上图可以看到&lt;strong>消息发送者（生产者）和消息接受者（消费者）之间没有直接耦合&lt;/strong>，消息发送者将消息&lt;strong>发送至分布式消息队列即结束&lt;/strong>对消息的处理，消息接受者&lt;strong>从分布式消息队列获取该消息后进行后续处理&lt;/strong>，并不需要知道该消息从何而来。&lt;strong>对新增业务，只要对该类消息感兴趣，即可订阅该消息，对原有系统和业务没有任何影响，从而实现网站业务的可扩展性设计&lt;/strong>。&lt;/p>
&lt;p>消息接受者对消息进行过滤、处理、包装后，构造成一个新的消息类型，将消息&lt;strong>继续发送出去，等待其他消息接受者订阅该消息&lt;/strong>。因此&lt;strong>基于事件（消息对象）驱动的业务架构&lt;/strong>可以是一系列流程。&lt;/p>
&lt;p>另外，为了&lt;strong>避免消息队列服务器宕机&lt;/strong>造成消息丢失，会将成功发送到消息队列的消息&lt;strong>存储在消息生产者服务器上&lt;/strong>，等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后，&lt;strong>生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息&lt;/strong>。&lt;/p>
&lt;p>&lt;strong>备注：&lt;/strong> 不要认为消息队列只能利用发布-订阅模式工作，只不过在解耦这个特定业务环境下是使用发布-订阅模式的。除了&lt;strong>发布-订阅&lt;/strong>模式，还有&lt;strong>点对点订阅&lt;/strong>模式（&lt;strong>一个消息只有一个消费者&lt;/strong>），我们比较常用的是发布-订阅模式。另外，这两种消息模型是 JMS 提供的，AMQP 协议还提供了 5 种消息模型。&lt;/p>
&lt;h2 id="使用消息队列哪些问题">使用消息队列哪些问题？&lt;a class="anchor" href="#%e4%bd%bf%e7%94%a8%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e5%93%aa%e4%ba%9b%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>系统可用性降低：&lt;/strong> 系统可用性在某种程度上降低，为什么这样说呢？在加入 MQ 之前，你&lt;strong>不用考虑消息丢失或者说 MQ 挂掉&lt;/strong>等等的情况，但是，引入 MQ 之后你就需要去考虑了！&lt;/li>
&lt;li>&lt;strong>系统复杂性提高：&lt;/strong> 加入 MQ 之后，你需要保证消息没有被&lt;strong>重复消费&lt;/strong>、处理&lt;strong>消息丢失&lt;/strong>的情况、保证&lt;strong>消息传递&lt;/strong>的顺序性等等问题！&lt;/li>
&lt;li>&lt;strong>一致性问题：&lt;/strong> 我上面讲了消息队列可以实现&lt;strong>异步&lt;/strong>，消息队列带来的异步确实可以提高系统响应速度。但是，&lt;strong>万一消息的真正消费者并没有正确消费消息&lt;/strong>怎么办？这样就会导致数据不一致的情况了!&lt;/li>
&lt;/ul>
&lt;h2 id="jms-和-amqp">JMS 和 AMQP&lt;a class="anchor" href="#jms-%e5%92%8c-amqp">#&lt;/a>&lt;/h2>
&lt;h3 id="jms-是什么">JMS 是什么？&lt;a class="anchor" href="#jms-%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h3>
&lt;p>JMS（JAVA Message Service,java 消息服务）是 java 的消息服务，JMS 的客户端之间可以通过 JMS 服务进行异步的消息传输。&lt;strong>JMS（JAVA Message Service，Java 消息服务）API 是一个消息服务的标准或者说是规范&lt;/strong>，允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息。它使分布式通信耦合度更低，消息服务更加可靠以及异步性。&lt;/p></description></item><item><title>cdn</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/ly02ly_cdn/</link><pubDate>Tue, 14 Feb 2023 16:57:38 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/ly02ly_cdn/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="什么是-cdn-">什么是 CDN ？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-cdn-">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>CDN&lt;/strong> 全称是 Content Delivery Network/&lt;strong>Content Distribution Network&lt;/strong>，翻译过的意思是 &lt;strong>内容分发网络&lt;/strong> 。&lt;/p>
&lt;p>我们可以将内容分发网络拆开来看：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>内容&lt;/strong> ：指的是&lt;strong>静态资源&lt;/strong>比如图片、视频、文档、JS、CSS、HTML。&lt;/li>
&lt;li>&lt;strong>分发网络&lt;/strong> ：指的是将这些静态资源&lt;strong>分发到位于多个不同的地理位置机房中的服务器&lt;/strong>上，这样，就可以实现静态资源的&lt;strong>就近访问&lt;/strong>比如北京的用户直接访问北京机房的数据。&lt;/li>
&lt;/ul>
&lt;p>所以，简单来说，&lt;strong>CDN 就是将静态资源分发到多个不同的地方以实现就近访问，进而加快静态资源的访问速度，减轻服务器以及带宽的负担。&lt;/strong>&lt;/p>
&lt;p>类似于京东建立的庞大的仓储运输体系，京东物流在全国拥有非常多的仓库，仓储网络几乎覆盖全国所有区县。这样的话，用户下单的第一时间，商品就从距离用户最近的仓库，直接发往对应的配送站，再由京东小哥送到你家。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142039678.jpg" alt="京东仓配系统" />&lt;/p>
&lt;p>你可以将 CDN 看作是服务上一层的特殊缓存服务，分布在全国各地，主要用来处理静态资源的请求。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142039917.jpg" alt="CDN 简易示意图" />&lt;/p>
&lt;p>我们经常拿&lt;strong>全站加速&lt;/strong>和&lt;strong>内容分发&lt;/strong>网络做对比，不要把两者搞混了！全站加速（不同云服务商叫法不同，腾讯云叫 ECDN、阿里云叫 DCDN）既可以加速&lt;strong>静态资源&lt;/strong>又可以加速&lt;strong>动态资源&lt;/strong>，**内容分发网络（CDN）**主要针对的是 &lt;strong>静态资源&lt;/strong> 。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142040034.png" alt="ly-20241212142040034" />&lt;/p>
&lt;p>绝大部分公司都会在项目开发中交使用 &lt;strong>CDN 服务&lt;/strong>，但很少会有自建 CDN 服务的公司。基于&lt;strong>成本、稳定性和易用性&lt;/strong>考虑，建议&lt;strong>直接选择专业的云厂商&lt;/strong>（比如阿里云、腾讯云、华为云、青云）或者 CDN 厂商（比如网宿、蓝汛）提供的开箱即用的 CDN 服务。&lt;/p>
&lt;p>很多朋友可能要问了：&lt;strong>既然是就近访问，为什么不直接将服务部署在多个不同的地方呢？&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>成本太高&lt;/strong>，需要部署多份相同的服务。&lt;/li>
&lt;li>静态资源通常占用空间比较大且经常会被访问到，如果直接使用&lt;strong>服务器或者缓存&lt;/strong>来处理静态资源请求的话，对&lt;strong>系统资源消耗非常大&lt;/strong>，可能会影响到系统其他服务的正常运行。&lt;/li>
&lt;/ul>
&lt;p>同一个服务在在多个不同的地方部署多份（比如&lt;strong>同城灾备&lt;/strong>、&lt;strong>异地灾备&lt;/strong>、&lt;strong>同城多活&lt;/strong>、&lt;strong>异地多活&lt;/strong>）是为了实现系统的高可用而不是就近访问。&lt;/p>
&lt;h2 id="cdn-工作原理是什么">CDN 工作原理是什么？&lt;a class="anchor" href="#cdn-%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h2>
&lt;p>搞懂下面 3 个问题也就搞懂了 CDN 的工作原理：&lt;/p>
&lt;ol>
&lt;li>静态资源是如何被缓存到 CDN 节点中的？&lt;/li>
&lt;li>如何找到最合适的 CDN 节点？&lt;/li>
&lt;li>如何防止静态资源被盗用？&lt;/li>
&lt;/ol>
&lt;h3 id="静态资源是如何被缓存到-cdn-节点中的">静态资源是如何被缓存到 CDN 节点中的？&lt;a class="anchor" href="#%e9%9d%99%e6%80%81%e8%b5%84%e6%ba%90%e6%98%af%e5%a6%82%e4%bd%95%e8%a2%ab%e7%bc%93%e5%ad%98%e5%88%b0-cdn-%e8%8a%82%e7%82%b9%e4%b8%ad%e7%9a%84">#&lt;/a>&lt;/h3>
&lt;p>你可以通过&lt;strong>预热&lt;/strong>的方式将源站的资源同步到 CDN 的节点中。这样的话，用户&lt;strong>首次请求资源&lt;/strong>可以直接从 CDN 节点中取，无需回源。这样可以降低源站压力，提升用户体验。&lt;/p></description></item><item><title>数据库读写分离&amp;分库分表详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/ly01ly_read-and-write-separation-and-library-subtable/</link><pubDate>Tue, 14 Feb 2023 16:38:42 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyely_high-performance/ly01ly_read-and-write-separation-and-library-subtable/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="读写分离">读写分离&lt;a class="anchor" href="#%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是读写分离">什么是读写分离？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb">#&lt;/a>&lt;/h3>
&lt;p>见名思意，根据读写分离的名字，我们就可以知道：&lt;strong>读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。&lt;/strong> 这样的话，就能够&lt;strong>小幅提升写&lt;/strong>性能，&lt;strong>大幅提升读&lt;/strong>性能。&lt;/p>
&lt;p>我简单画了一张图来帮助不太清楚读写分离的小伙伴理解。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142038662.png" alt="ly-20241212142038662" />&lt;/p>
&lt;p>一般情况下，我们都会选择&lt;strong>一主多从&lt;/strong>，也就是一台&lt;strong>主数据库负责写&lt;/strong>，其他的&lt;strong>从数据库负责读&lt;/strong>。主库和从库之间会进行数据&lt;strong>同步&lt;/strong>，以保证&lt;strong>从库&lt;/strong>中数据的&lt;strong>准确性&lt;/strong>。这样的架构实现起来比较&lt;strong>简单&lt;/strong>，并且也&lt;strong>符合系统的写少读多&lt;/strong>的特点。&lt;/p>
&lt;h3 id="读写分离会带来什么问题如何解决">读写分离会带来什么问题？如何解决？&lt;a class="anchor" href="#%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb%e4%bc%9a%e5%b8%a6%e6%9d%a5%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98%e5%a6%82%e4%bd%95%e8%a7%a3%e5%86%b3">#&lt;/a>&lt;/h3>
&lt;p>读写分离对于提升数据库的并发非常有效，但是，同时也会引来一个问题：主库和从库的数据存在延迟，比如你写完主库之后，主库的数据同步到从库是需要时间的，这个时间差就导致了主库和从库的数据不一致性问题。这也就是我们经常说的 &lt;strong>主从同步延迟&lt;/strong> 。&lt;/p>
&lt;p>主从同步延迟问题的解决，没有特别好的一种方案（可能是我太菜了，欢迎评论区补充）。你可以根据自己的业务场景，参考一下下面几种解决办法。&lt;/p>
&lt;p>&lt;strong>1.强制将读请求路由到主库处理。&lt;/strong>&lt;/p>
&lt;p>既然你从库的数据过期了，那我就&lt;strong>直接从主库读取&lt;/strong>嘛！这种方案虽然会增加主库的压力，但是，实现起来比较简单，也是我了解到的使用最多的一种方式。&lt;/p>
&lt;p>比如 &lt;strong>&lt;code>Sharding-JDBC&lt;/code>&lt;/strong> 就是采用的这种方案。通过使用 Sharding-JDBC 的 &lt;strong>&lt;code>HintManager&lt;/code> 分片键值管理器&lt;/strong>，我们可以&lt;strong>强制使用主库&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>HintManager hintManager &lt;span style="color:#f92672">=&lt;/span> HintManager.&lt;span style="color:#a6e22e">getInstance&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>hintManager.&lt;span style="color:#a6e22e">setMasterRouteOnly&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">//&lt;/span> 继续JDBC操作&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>对于这种方案，你可以将那些必须获取最新数据的读请求都交给主库处理。&lt;/p>
&lt;p>&lt;strong>2.延迟读取。&lt;/strong>&lt;/p>
&lt;p>还有一些朋友肯定会想既然主从同步存在延迟，那我就在&lt;strong>延迟之后读取&lt;/strong>啊，比如主从同步延迟 0.5s,那我就 1s 之后再读取数据。这样多方便啊！方便是方便，但是也很扯淡。&lt;/p>
&lt;p>不过，如果你是这样设计业务流程就会好很多：对于一些&lt;strong>对数据比较敏感&lt;/strong>的场景，你可以在&lt;strong>完成写请求之后&lt;/strong>，&lt;strong>避免立即&lt;/strong>进行请求操作。比如你支付成功之后，跳转到一个支付成功的页面，当你点击返回之后才返回自己的账户。&lt;/p>
&lt;p>另外，
&lt;a target="_blank" href="https://time.geekbang.org/column/intro/100020801?code=ieY8HeRSlDsFbuRtggbBQGxdTh-1jMASqEIeqzHAKrI%3D">《MySQL 实战 45 讲》&lt;/a>这个专栏中的
&lt;a target="_blank" href="https://time.geekbang.org/column/article/77636">《读写分离有哪些坑？》&lt;/a>这篇文章还介绍了很多其他比较实际的解决办法，感兴趣的小伙伴可以自行研究一下。&lt;/p>
&lt;h3 id="如何实现读写分离">如何实现读写分离？&lt;a class="anchor" href="#%e5%a6%82%e4%bd%95%e5%ae%9e%e7%8e%b0%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb">#&lt;/a>&lt;/h3>
&lt;p>不论是使用哪一种读写分离具体的实现方案，想要&lt;strong>实现读写分离&lt;/strong>一般包含如下几步：&lt;/p>
&lt;ol>
&lt;li>部署多台数据库，选择&lt;strong>其中的一台作为主&lt;/strong>数据库，&lt;strong>其他&lt;/strong>的一台或者多台&lt;strong>作为从&lt;/strong>数据库。&lt;/li>
&lt;li>&lt;strong>保证&lt;/strong>主数据库和从数据库之间的数据是&lt;strong>实时同步&lt;/strong>的，这个过程也就是我们常说的&lt;strong>主从复制&lt;/strong>。&lt;/li>
&lt;li>系统将&lt;strong>写请求交给主&lt;/strong>数据库处理，读请求交给从数据库处理。[ 使用上 ]&lt;/li>
&lt;/ol>
&lt;p>落实到项目本身的话，常用的方式有两种：&lt;/p>
&lt;p>&lt;strong>1.代理方式&lt;/strong>&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142038906.png" alt="ly-20241212142038906" />&lt;/p>
&lt;p>我们可以在&lt;strong>应用&lt;/strong>和&lt;strong>数据&lt;/strong>中间&lt;strong>加了一个代理层&lt;/strong>。&lt;strong>应用&lt;/strong>程序所有的&lt;strong>数据请求都交给代理层&lt;/strong>处理，代理层负责&lt;strong>分离读写&lt;/strong>请求，将它们&lt;strong>路由到对应的数据库&lt;/strong>中。&lt;/p>
&lt;p>提供类似功能的中间件有 &lt;strong>MySQL Router&lt;/strong>（官方）、&lt;strong>Atlas&lt;/strong>（基于 MySQL Proxy）、&lt;strong>Maxscale&lt;/strong>、&lt;strong>MyCat&lt;/strong>。&lt;/p>
&lt;p>&lt;strong>2.组件方式&lt;/strong>&lt;/p>
&lt;p>在这种方式中，我们可以通过引入第三方组件来帮助我们读写请求。&lt;/p>
&lt;p>这也是我&lt;strong>比较推荐&lt;/strong>的一种方式。这种方式目前在各种互联网公司中&lt;strong>用的最多&lt;/strong>的，相关的实际的案例也非常多。如果你要采用这种方式的话，推荐使用 &lt;strong>&lt;code>sharding-jdbc&lt;/code>&lt;/strong> ，&lt;strong>直接引入 jar&lt;/strong> 包即可使用，非常方便。同时，也&lt;strong>节省了很多运维的成本&lt;/strong>。&lt;/p>
&lt;p>你可以在 shardingsphere 官方找到 
&lt;a target="_blank" href="https://shardingsphere.apache.org/document/legacy/3.x/document/cn/manual/sharding-jdbc/usage/read-write-splitting/">sharding-jdbc 关于读写分离的操作&lt;/a>。&lt;/p>
&lt;h3 id="主从复制原理是什么">主从复制原理是什么？&lt;a class="anchor" href="#%e4%b8%bb%e4%bb%8e%e5%a4%8d%e5%88%b6%e5%8e%9f%e7%90%86%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h3>
&lt;p>MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据库中数据的所有变化(数据库执行的所有 DDL 和 DML 语句)。因此，我们根据主库的 MySQL binlog 日志就能够将主库的数据同步到从库中。&lt;/p></description></item><item><title>zookeeper实战</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly08ly_zookeeper-in-action/</link><pubDate>Tue, 14 Feb 2023 10:45:39 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly08ly_zookeeper-in-action/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="1-前言">1. 前言&lt;a class="anchor" href="#1-%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h2>
&lt;p>这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作，能满足日常工作的基本需要。&lt;/p>
&lt;p>如果文章有任何需要改善和完善的地方，欢迎在评论区指出，共同进步！&lt;/p>
&lt;h2 id="2-zookeeper-安装和使用">2. ZooKeeper 安装和使用&lt;a class="anchor" href="#2-zookeeper-%e5%ae%89%e8%a3%85%e5%92%8c%e4%bd%bf%e7%94%a8">#&lt;/a>&lt;/h2>
&lt;h3 id="21-使用docker-安装-zookeeper">2.1. 使用Docker 安装 zookeeper&lt;a class="anchor" href="#21-%e4%bd%bf%e7%94%a8docker-%e5%ae%89%e8%a3%85-zookeeper">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>a.使用 Docker 下载 ZooKeeper&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>docker pull zookeeper:3.5.8&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>b.运行 ZooKeeper&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="22-连接-zookeeper-服务">2.2. 连接 ZooKeeper 服务&lt;a class="anchor" href="#22-%e8%bf%9e%e6%8e%a5-zookeeper-%e6%9c%8d%e5%8a%a1">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>a.进入ZooKeeper容器中&lt;/strong>&lt;/p>
&lt;p>先使用 &lt;code>docker ps&lt;/code> 查看 ZooKeeper 的 ContainerID，然后使用 &lt;code>docker exec -it ContainerID /bin/bash&lt;/code> 命令进入容器中。&lt;/p>
&lt;p>&lt;strong>b.先进入 bin 目录,然后通过 &lt;code>./zkCli.sh -server 127.0.0.1:2181&lt;/code>命令连接ZooKeeper 服务&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你看到控制台成功打印出如下信息的话，说明你已经成功连接 ZooKeeper 服务。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142038401.png" alt="连接ZooKeeper服务.png" />&lt;/p>
&lt;h3 id="23-常用命令演示">2.3. 常用命令演示&lt;a class="anchor" href="#23-%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4%e6%bc%94%e7%a4%ba">#&lt;/a>&lt;/h3>
&lt;h4 id="231-查看常用命令help-命令">2.3.1. 查看常用命令(help 命令)&lt;a class="anchor" href="#231-%e6%9f%a5%e7%9c%8b%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4help-%e5%91%bd%e4%bb%a4">#&lt;/a>&lt;/h4>
&lt;p>通过 &lt;code>help&lt;/code> 命令查看 ZooKeeper 常用命令&lt;/p></description></item><item><title>zookeeper进阶</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly07ly_zookeeper-plus/</link><pubDate>Mon, 13 Feb 2023 17:30:07 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly07ly_zookeeper-plus/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>
&lt;a target="_blank" href="https://juejin.im/user/5c33853851882525ea106810">FrancisQ&lt;/a> 投稿。&lt;/p>&lt;/blockquote>&lt;h2 id="1-好久不见">1. 好久不见&lt;a class="anchor" href="#1-%e5%a5%bd%e4%b9%85%e4%b8%8d%e8%a7%81">#&lt;/a>&lt;/h2>
&lt;p>离上一篇文章的发布也快一个月了，想想已经快一个月没写东西了，其中可能有期末考试、课程设计和驾照考试，但这都不是借口！&lt;/p>
&lt;p>一到冬天就懒的不行，望广大掘友督促我🙄🙄✍️✍️。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>文章很长，先赞后看，养成习惯。❤️ 🧡 💛 💚 💙 💜&lt;/p>&lt;/blockquote>&lt;h2 id="2-什么是zookeeper">2. 什么是ZooKeeper&lt;a class="anchor" href="#2-%e4%bb%80%e4%b9%88%e6%98%afzookeeper">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>ZooKeeper&lt;/code> 由 &lt;code>Yahoo&lt;/code> 开发，后来捐赠给了 &lt;code>Apache&lt;/code> ，现已成为 &lt;code>Apache&lt;/code> 顶级项目。&lt;code>ZooKeeper&lt;/code> 是一个开源的&lt;strong>分布式应用程序协调服务器&lt;/strong>，其为&lt;strong>分布式系统提供一致性&lt;/strong>服务。其一致性是通过基于 &lt;code>Paxos&lt;/code> 算法的 &lt;code>ZAB&lt;/code> 协议完成的。其主要功能包括：&lt;strong>配置维护&lt;/strong>、&lt;strong>分布式同步&lt;/strong>、&lt;strong>集群管理&lt;/strong>、&lt;strong>分布式事务&lt;/strong>等。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142035025.png" alt="ly-20241212142035025" />&lt;/p>
&lt;p>简单来说， &lt;code>ZooKeeper&lt;/code> 是一个 &lt;strong>分布式协调服务框架&lt;/strong> 。分布式？协调服务？这啥玩意？🤔🤔&lt;/p>
&lt;p>其实解释到分布式这个概念的时候，我发现有些同学并不是能把 &lt;strong>分布式和集群&lt;/strong> 这两个概念很好的理解透。前段时间有同学和我探讨起分布式的东西，他说分布式不就是加机器吗？一台机器不够用再加一台抗压呗。当然加机器这种说法也无可厚非，你一个分布式系统必定涉及到多个机器，但是你别忘了，计算机学科中还有一个相似的概念—— &lt;code>Cluster&lt;/code> ，集群不也是加机器吗？但是 &lt;strong>集群 和 分布式&lt;/strong> 其实就是两个&lt;strong>完全不同的概念&lt;/strong>。&lt;/p>
&lt;p>比如，我现在有一个秒杀服务，并发量太大单机系统承受不住，那我加几台服务器也 &lt;strong>一样&lt;/strong> 提供秒杀服务，这个时候就是 &lt;strong>&lt;code>Cluster&lt;/code> 集群&lt;/strong> 。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://camo.githubusercontent.com/87c9624f298e404de4aa6387af959870f61d9461a2a60de3a31f5ee47385360d/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f66666362303830656236366632343266666364386432303437613766343661612e706e67">&lt;img src="img/ly-20241212142035275.jpg" alt="cluster" />&lt;/a>&lt;/p>
&lt;p>但是，我现在换一种方式，我将一个秒杀服务 &lt;strong>拆分成多个子服务&lt;/strong> ，比如创建订单服务，增加积分服务，扣优惠券服务等等，&lt;strong>然后我将这些子服务都部署在不同的服务器上&lt;/strong> ，这个时候就是 &lt;strong>&lt;code>Distributed&lt;/code> 分布式&lt;/strong> 。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://camo.githubusercontent.com/d2c1c6b9feb20352ecfa8182cdb3058d7dd3ffeec287cc66286461ca34226323/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f30373139316633386161393437623030373565356330613661303139613131642e706e67">&lt;img src="img/ly-20241212142035394.jpg" alt="distributed" />&lt;/a>&lt;/p>
&lt;p>而我为什么反驳同学所说的分布式就是加机器呢？因为我认为加机器更加适用于构建&lt;strong>集群&lt;/strong>，因为它真是只有加机器。而对于分布式来说，你首先需要将&lt;strong>业务进行拆分&lt;/strong>，然后&lt;strong>再加机器&lt;/strong>（不仅仅是加机器那么简单），同时你还要去解决&lt;strong>分布式带来的一系列问题&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142035517.jpg" alt="img" />&lt;/p>
&lt;p>比如各个分布式组件&lt;strong>如何协调&lt;/strong>起来，如何&lt;strong>减少&lt;/strong>各个系统之间的&lt;strong>耦合度&lt;/strong>，&lt;strong>分布式事务&lt;/strong>的处理，如何去&lt;strong>配置整个分布式系统&lt;/strong>等等。&lt;code>ZooKeeper&lt;/code> 主要就是解决这些问题的。&lt;/p>
&lt;h2 id="3-一致性问题">3. 一致性问题&lt;a class="anchor" href="#3-%e4%b8%80%e8%87%b4%e6%80%a7%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212142035639.png" alt="image.png" />&lt;/p>
&lt;p>设计一个分布式系统必定会遇到一个问题—— &lt;strong>因为分区容忍性（partition tolerance）的存在，就必定要求我们需要在系统可用性（availability）和数据一致性（consistency）中做出权衡&lt;/strong> 。这就是著名的 &lt;code>CAP&lt;/code> 定理。&lt;/p></description></item><item><title>zookeeper介绍</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly06ly_zookeeper-intro/</link><pubDate>Mon, 13 Feb 2023 15:59:20 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly06ly_zookeeper-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="1-前言">1. 前言&lt;a class="anchor" href="#1-%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h2>
&lt;p>相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 到底有啥用不？如果别人/面试官让你给他讲讲对于 ZooKeeper 的认识，你能回答到什么地步呢？&lt;/p>
&lt;p>拿我自己来说吧！我本人曾经使用 Dubbo 来做分布式项目的时候，使用了 ZooKeeper 作为&lt;strong>注册中心&lt;/strong>。为了保证分布式系统能够&lt;strong>同步访问某个资源&lt;/strong>，我还使用 ZooKeeper 做过&lt;strong>分布式锁&lt;/strong>。另外，我在学习 Kafka 的时候，知道 &lt;strong>Kafka&lt;/strong> 很多功能的实现&lt;strong>依赖了 ZooKeeper&lt;/strong>。&lt;/p>
&lt;p>前几天，总结项目经验的时候，我突然问自己 ZooKeeper 到底是个什么东西？想了半天，脑海中只是简单的能浮现出几句话：&lt;/p>
&lt;ol>
&lt;li>ZooKeeper 可以被用作&lt;strong>注册中心、分布式锁&lt;/strong>；&lt;/li>
&lt;li>ZooKeeper 是 &lt;strong>Hadoop 生态系统&lt;/strong>的一员；&lt;/li>
&lt;li>构建 ZooKeeper 集群的时候，使用的服务器最好是&lt;strong>奇数台&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;p>由此可见，我对于 ZooKeeper 的理解仅仅是停留在了表面。&lt;/p>
&lt;p>所以，通过本文，希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ，那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ，那么本文将带你回顾一下 ZooKeeper 的一些基础概念。&lt;/p>
&lt;p>另外，本文不光会涉及到 ZooKeeper 的一些概念，后面的文章会介绍到 ZooKeeper 常见命令的使用以及使用 Apache Curator 作为 ZooKeeper 的客户端。&lt;/p>
&lt;p>&lt;em>如果文章有任何需要改善和完善的地方，欢迎在评论区指出，共同进步！&lt;/em>&lt;/p>
&lt;h2 id="2-zookeeper-介绍">2. ZooKeeper 介绍&lt;a class="anchor" href="#2-zookeeper-%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;h3 id="21-zookeeper-由来">2.1. ZooKeeper 由来&lt;a class="anchor" href="#21-zookeeper-%e7%94%b1%e6%9d%a5">#&lt;/a>&lt;/h3>
&lt;p>正式介绍 ZooKeeper 之前，我们先来看看 ZooKeeper 的由来，还挺有意思的。&lt;/p></description></item><item><title>rpc_http</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly04ly_rpc-http/</link><pubDate>Sat, 11 Feb 2023 19:59:58 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly04ly_rpc-http/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212142032772.png" alt="" />&lt;/p>
&lt;p>我正在参与掘金技术社区创作者签约计划招募活动，
&lt;a target="_blank" href="https://juejin.cn/post/7112770927082864653">点击链接报名投稿&lt;/a>。&lt;/p>
&lt;p>我想起了我刚工作的时候，第一次接触RPC协议，当时就很懵，我HTTP协议用的好好的，为什么还要用RPC协议？&lt;/p>
&lt;p>于是就到网上去搜。&lt;/p>
&lt;p>不少解释显得非常官方，我相信大家在各种平台上也都看到过，解释了又好像没解释，都在&lt;strong>用一个我们不认识的概念去解释另外一个我们不认识的概念&lt;/strong>，懂的人不需要看，不懂的人看了还是不懂。&lt;/p>
&lt;p>这种看了，又好像没看的感觉，云里雾里的很难受，&lt;strong>我懂&lt;/strong>。&lt;/p>
&lt;p>为了避免大家有强烈的&lt;strong>审丑疲劳&lt;/strong>，今天我们来尝试重新换个方式讲一讲。&lt;/p>
&lt;h3 id="从tcp聊起">从TCP聊起&lt;a class="anchor" href="#%e4%bb%8etcp%e8%81%8a%e8%b5%b7">#&lt;/a>&lt;/h3>
&lt;p>作为一个程序员，假设我们需要在A电脑的进程发一段数据到B电脑的进程，我们一般会在代码里使用socket进行编程。&lt;/p>
&lt;p>这时候，我们可选项一般也就&lt;strong>TCP和UDP二选一。TCP可靠，UDP不可靠。&lt;/strong> 除非是马总这种神级程序员（早期QQ大量使用UDP），否则，只要稍微对可靠性有些要求，普通人一般无脑选TCP就对了。&lt;/p>
&lt;p>类似下面这样。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ini" data-lang="ini">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">fd&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">socket(AF_INET,SOCK_STREAM,0);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">复制代码&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中&lt;code>SOCK_STREAM&lt;/code>，是指使用&lt;strong>字节流&lt;/strong>传输数据，说白了就是&lt;strong>TCP协议&lt;/strong>。&lt;/p>
&lt;p>在定义了socket之后，我们就可以愉快的对这个socket进行操作，比如用&lt;code>bind()&lt;/code>绑定IP端口，用&lt;code>connect()&lt;/code>发起建连。&lt;/p>
&lt;p>&lt;img src="img/51d1bda27fea39bd96ed660ee4af549d_MD5.gif" alt="握手建立连接流程" />&lt;/p>
&lt;p>在连接建立之后，我们就可以使用&lt;code>send()&lt;/code>发送数据，&lt;code>recv()&lt;/code>接收数据。&lt;/p>
&lt;p>光这样一个纯裸的TCP连接，就可以做到收发数据了，那是不是就够了？&lt;/p>
&lt;p>不行，这么用会有问题。&lt;/p>
&lt;h3 id="使用纯裸tcp会有什么问题">使用纯裸TCP会有什么问题&lt;a class="anchor" href="#%e4%bd%bf%e7%94%a8%e7%ba%af%e8%a3%b8tcp%e4%bc%9a%e6%9c%89%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h3>
&lt;p>八股文常背，TCP是有三个特点，&lt;strong>面向连接&lt;/strong>、&lt;strong>可靠&lt;/strong>、基于&lt;strong>字节流&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/9b2a2a9eb7998867213b85c601e20513_MD5.webp" alt="TCP是什么" />&lt;/p>
&lt;p>这三个特点真的概括的&lt;strong>非常精辟&lt;/strong>，这个八股文我们没白背。&lt;/p>
&lt;p>每个特点展开都能聊一篇文章，而今天我们需要关注的是&lt;strong>基于字节流&lt;/strong>这一点。&lt;/p>
&lt;p>字节流可以理解为一个双向的通道里流淌的数据，这个&lt;strong>数据&lt;/strong>其实就是我们常说的二进制数据，简单来说就是一大堆 &lt;strong>01 串&lt;/strong>。纯裸TCP收发的这些 01 串之间是&lt;strong>没有任何边界&lt;/strong>的，你根本不知道到哪个地方才算一条完整消息。
&lt;img src="img/ly-20241212142033022.png" alt="" />&lt;/p>
&lt;p>正因为这个没有&lt;strong>任何边界&lt;/strong>的特点，所以当我们选择使用TCP发送 &lt;strong>&amp;ldquo;夏洛&amp;quot;和&amp;quot;特烦恼&amp;rdquo;&lt;/strong> 的时候，接收端收到的就是 &lt;strong>&amp;ldquo;夏洛特烦恼&amp;rdquo;&lt;/strong> ，这时候接收端没发区分你是想要表达 &lt;strong>&amp;ldquo;夏洛&amp;rdquo;+&amp;ldquo;特烦恼&amp;rdquo;&lt;/strong> 还是 &lt;strong>&amp;ldquo;夏洛特&amp;rdquo;+&amp;ldquo;烦恼&amp;rdquo;&lt;/strong> 。&lt;/p>
&lt;p>&lt;img src="img/babad254069eade7c0df5d9db1635c92_MD5.webp" alt="消息对比" />&lt;/p>
&lt;p>这就是所谓的&lt;strong>粘包问题&lt;/strong>，之前也写过一篇专门的
&lt;a target="_blank" href="https://link.juejin.cn/?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2F0-YBxU1cSbDdzcZEZjmQYA">文章&lt;/a>聊过这个问题。&lt;/p>
&lt;p>说这个的目的是为了告诉大家，纯裸TCP是不能直接拿来用的，你需要在这个基础上加入一些&lt;strong>自定义的规则&lt;/strong>，用于区分&lt;strong>消息边界&lt;/strong>。&lt;/p>
&lt;p>于是我们会把每条要发送的数据都包装一下，比如加入&lt;strong>消息头&lt;/strong>，&lt;strong>消息头里写清楚一个完整的包长度是多少&lt;/strong>，根据这个长度可以继续接收数据，截取出来后它们就是我们真正要传输的&lt;strong>消息体&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/7f2c3dd6224e63c56e4977708244e594_MD5.webp" alt="消息边界长度标志" />&lt;/p>
&lt;p>而这里头提到的&lt;strong>消息头&lt;/strong>，还可以放各种东西，比如消息体是否被压缩过和消息体格式之类的，只要上下游都约定好了，互相都认就可以了，这就是所谓的&lt;strong>协议。&lt;/strong>&lt;/p>
&lt;p>每个使用TCP的项目都可能会定义一套类似这样的协议解析标准，他们可能&lt;strong>有区别，但原理都类似&lt;/strong>。&lt;/p>
&lt;p>&lt;strong>于是基于TCP，就衍生了非常多的协议，比如HTTP和RPC。&lt;/strong>&lt;/p>
&lt;h3 id="http和rpc">HTTP和RPC&lt;a class="anchor" href="#http%e5%92%8crpc">#&lt;/a>&lt;/h3>
&lt;p>我们回过头来看网络的分层图。&lt;/p>
&lt;p>&lt;img src="img/dea756ded9c20f2a06905d73e33e08c9_MD5.webp" alt="四层网络协议" />&lt;/p>
&lt;p>&lt;strong>TCP是传输层的协议&lt;/strong>，而基于TCP造出来的HTTP和&lt;strong>各类&lt;/strong>RPC协议，它们都只是定义了不同消息格式的&lt;strong>应用层协议&lt;/strong>而已。&lt;/p>
&lt;p>&lt;strong>HTTP&lt;/strong>协议（&lt;strong>H&lt;/strong>yper &lt;strong>T&lt;/strong>ext &lt;strong>T&lt;/strong>ransfer &lt;strong>P&lt;/strong>rotocol），又叫做&lt;strong>超文本传输协议&lt;/strong>。我们用的比较多，平时上网在浏览器上敲个网址就能访问网页，这里用到的就是HTTP协议。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142033146.png" alt="" />&lt;/p>
&lt;p>而&lt;strong>RPC&lt;/strong>（&lt;strong>R&lt;/strong>emote &lt;strong>P&lt;/strong>rocedure &lt;strong>C&lt;/strong>all），又叫做&lt;strong>远程过程调用&lt;/strong>。它本身并不是一个具体的协议，而是一种&lt;strong>调用方式&lt;/strong>。&lt;/p>
&lt;p>举个例子，我们平时调用一个&lt;strong>本地方法&lt;/strong>就像下面这样。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ini" data-lang="ini">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">res&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">localFunc(req)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">复制代码&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果现在这不是个本地方法，而是个&lt;strong>远端服务器&lt;/strong>暴露出来的一个方法&lt;code>remoteFunc&lt;/code>，如果我们还能像调用本地方法那样去调用它，这样就可以&lt;strong>屏蔽掉一些网络细节&lt;/strong>，用起来更方便，岂不美哉？&lt;/p></description></item><item><title>rpc基础及面试题</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly05ly_rpc-intro/</link><pubDate>Sat, 11 Feb 2023 19:41:34 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly05ly_rpc-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>简单介绍一下 RPC 相关的基础概念。&lt;/p>
&lt;h2 id="何为-rpc">何为 RPC?&lt;a class="anchor" href="#%e4%bd%95%e4%b8%ba-rpc">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>RPC（Remote Procedure Call）&lt;/strong> 即远程过程调用，通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。&lt;/p>
&lt;p>&lt;strong>为什么要 RPC ？&lt;/strong> 因为，两个不同的服务器上的服务提供的方法不在一个内存空间，所以，需要通过网络编程才能传递方法调用所需要的参数。并且，方法调用的结果也需要通过网络编程来接收。但是，如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的，因为，我们需要考虑底层传输方式（TCP还是UDP）、序列化方式等等方面。&lt;/p>
&lt;p>&lt;strong>RPC 能帮助我们做什么呢？&lt;/strong> 简单来说，通过 RPC 可以帮助我们调用远程计算机上某个服务的方法，这个过程就像调用本地方法一样简单。并且！我们不需要了解底层网络编程的具体细节。&lt;/p>
&lt;p>举个例子：两个不同的服务 A、B 部署在两台不同的机器上，服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。&lt;/p>
&lt;p>一言蔽之：&lt;strong>RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。&lt;/strong>&lt;/p>
&lt;h2 id="rpc-的原理是什么">RPC 的原理是什么?&lt;a class="anchor" href="#rpc-%e7%9a%84%e5%8e%9f%e7%90%86%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h2>
&lt;p>为了能够帮助小伙伴们理解 RPC 原理，我们可以将整个 RPC的 核心功能看作是下面👇 5 个部分实现的：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>客户端（服务消费端）&lt;/strong> ：调用远程方法的一端。&lt;/li>
&lt;li>&lt;strong>客户端 Stub（桩）&lt;/strong> ： 这其实就是一代理类。代理类主要做的事情很简单，就是把你调用方法、类、方法参数等信息传递到服务端。&lt;/li>
&lt;li>&lt;strong>网络传输&lt;/strong> ： 网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端，然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket或者性能以及封装更加优秀的 Netty（推荐）。&lt;/li>
&lt;li>&lt;strong>服务端 Stub（桩）&lt;/strong> ：这个桩就不是代理类了。我觉得理解为桩实际不太好，大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后，去指定对应的方法然后返回结果给客户端的类。&lt;/li>
&lt;li>&lt;strong>服务端（服务提供端）&lt;/strong> ：提供远程方法的一端。&lt;/li>
&lt;/ol>
&lt;p>具体原理图如下，后面我会串起来将整个RPC的过程给大家说一下。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://camo.githubusercontent.com/5e8198844ec97b718219e83fec147089c73daadc5c987752787bfa4d4d538c24/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f31382d31322d362f33373334353835312e6a7067">&lt;img src="img/ly-20241212142033652.jpg" alt="RPC原理图" />&lt;/a>&lt;/p>
&lt;ol>
&lt;li>服务消费端（client）以本地调用的方式调用远程服务；&lt;/li>
&lt;li>客户端 Stub（client stub） 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体（序列化）：&lt;code>RpcRequest&lt;/code>；&lt;/li>
&lt;li>客户端 Stub（client stub） 找到远程服务的地址，并将消息发送到服务提供端；&lt;/li>
&lt;li>服务端 Stub（桩）收到消息将消息反序列化为Java对象: &lt;code>RpcRequest&lt;/code>；&lt;/li>
&lt;li>服务端 Stub（桩）根据&lt;code>RpcRequest&lt;/code>中的类、方法、方法参数等信息调用本地的方法；&lt;/li>
&lt;li>服务端 Stub（桩）得到方法执行结果并将组装成能够进行网络传输的消息体：&lt;code>RpcResponse&lt;/code>（序列化）发送至消费方；&lt;/li>
&lt;li>客户端 Stub（client stub）接收到消息并将消息反序列化为Java对象:&lt;code>RpcResponse&lt;/code> ，这样也就得到了最终结果。over!&lt;/li>
&lt;/ol>
&lt;p>相信小伙伴们看完上面的讲解之后，已经了解了 RPC 的原理。&lt;/p></description></item><item><title>分布式锁</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly03ly_distributed-lock/</link><pubDate>Sat, 11 Feb 2023 13:24:32 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly03ly_distributed-lock/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>网上有很多分布式锁相关的文章，写了一个相对简洁易懂的版本，针对面试和工作应该够用了。&lt;/p>
&lt;h2 id="什么是分布式锁">什么是分布式锁？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81">#&lt;/a>&lt;/h2>
&lt;p>对于单机多线程来说，在 Java 中，我们通常使用 &lt;code>ReetrantLock&lt;/code> 类、&lt;code>synchronized&lt;/code> 关键字这类 JDK 自带的 &lt;strong>本地锁&lt;/strong> 来控制&lt;strong>一个 JVM 进程&lt;/strong>内的&lt;strong>多个线程&lt;/strong>对&lt;strong>本地共享资源&lt;/strong>的访问。&lt;/p>
&lt;p>下面是我对本地锁画的一张示意图。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142031650.png" alt="ly-20241212142031650" />&lt;/p>
&lt;p>从图中可以看出，这些线程访问共享资源是互斥的，同一时刻只有一个线程可以获取到本地锁访问共享资源。&lt;/p>
&lt;p>&lt;strong>分布式系统&lt;/strong>下，&lt;strong>不同的服务/客户端&lt;/strong>通常&lt;strong>运行在独立的 JVM 进程&lt;/strong>上。如果&lt;strong>多个 JVM 进程共享同一份资源&lt;/strong>的话，使用&lt;strong>本地锁就没办法实现&lt;/strong>资源的互斥访问了。于是，&lt;strong>分布式锁&lt;/strong> 就诞生了。&lt;/p>
&lt;p>举个例子：系统的订单服务一共部署了 3 份，都对外提供服务。用户下订单之前需要检查库存，为了防止超卖，这里需要加锁以实现对检查库存操作的同步访问。由于订单服务位于不同的 JVM 进程中，本地锁在这种情况下就没办法正常工作了。我们需要用到分布式锁，这样的话，即使多个线程&lt;strong>不在同一个 JVM 进程&lt;/strong>中也能&lt;strong>获取到同一把锁&lt;/strong>，进而实现&lt;strong>共享资源的互斥访问&lt;/strong>。&lt;/p>
&lt;p>下面是我对分布式锁画的一张示意图。&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://github.com/Snailclimb/JavaGuide/blob/main/docs/distributed-system/images/distributed-lock/distributed-lock.png">&lt;img src="img/ly-20241212142031903.png" alt="分布式锁" />&lt;/a>&lt;/p>
&lt;p>从图中可以看出，这些独立的进程中的线程访问共享资源是互斥的，&lt;strong>同一时刻只有一个线程可以获取到分布式锁访问共享资源&lt;/strong>。&lt;/p>
&lt;p>一个最基本的分布式锁需要满足：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>互斥&lt;/strong> ：任意一个时刻，锁只能被一个线程持有；&lt;/li>
&lt;li>&lt;strong>高可用&lt;/strong> ：锁服务是高&lt;strong>可用&lt;/strong>的。并且，&lt;strong>即使客户端的释放锁的代码逻辑出现问题(这里说的是异常，不是说代码写的有问题)&lt;/strong>，锁最终&lt;strong>一定还是会被释放&lt;/strong>，&lt;strong>不会影响其他线程&lt;/strong>对共享资源的访问。&lt;/li>
&lt;li>&lt;strong>可重入&lt;/strong>：&lt;strong>(同)一个节点&lt;/strong>获取了锁之后，还可以&lt;strong>再次&lt;/strong>获取锁。&lt;/li>
&lt;/ul>
&lt;p>通常情况下，我们一般会选择基于 &lt;strong>Redis&lt;/strong> 或者 &lt;strong>ZooKeeper&lt;/strong> 实现分布式锁，Redis 用的要更多一点，我这里也以 Redis 为例介绍分布式锁的实现。&lt;/p>
&lt;h2 id="基于-redis-实现分布式锁">基于 Redis 实现分布式锁&lt;a class="anchor" href="#%e5%9f%ba%e4%ba%8e-redis-%e5%ae%9e%e7%8e%b0%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81">#&lt;/a>&lt;/h2>
&lt;h3 id="如何基于-redis-实现一个最简易的分布式锁">如何基于 Redis 实现一个最简易的分布式锁？&lt;a class="anchor" href="#%e5%a6%82%e4%bd%95%e5%9f%ba%e4%ba%8e-redis-%e5%ae%9e%e7%8e%b0%e4%b8%80%e4%b8%aa%e6%9c%80%e7%ae%80%e6%98%93%e7%9a%84%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81">#&lt;/a>&lt;/h3>
&lt;p>不论是实现&lt;strong>锁(本地)&lt;strong>还是&lt;/strong>分布式锁&lt;/strong>，核心都在于**“互斥”**。&lt;/p>
&lt;p>在 Redis 中， &lt;code>SETNX&lt;/code> 命令是可以帮助我们实现互斥。&lt;code>SETNX&lt;/code> 即 &lt;strong>SET&lt;/strong> if &lt;strong>N&lt;/strong>ot e&lt;strong>X&lt;/strong>ists (对应 Java 中的 &lt;code>setIfAbsent&lt;/code> 方法)，如果 key 不存在的话，才会设置 key 的值。如果 key 已经存在， &lt;code>SETNX&lt;/code> 啥也不做。&lt;/p></description></item><item><title>分布式id</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly02ly_distributed-id/</link><pubDate>Sat, 11 Feb 2023 13:00:31 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly02ly_distributed-id/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="分布式-id-介绍">分布式 ID 介绍&lt;a class="anchor" href="#%e5%88%86%e5%b8%83%e5%bc%8f-id-%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是-id">什么是 ID？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-id">#&lt;/a>&lt;/h3>
&lt;p>日常开发中，我们需要对系统中的各种数据使用 ID 唯一表示，比如用户 ID 对应且仅对应一个人，商品 ID 对应且仅对应一件商品，订单 ID 对应且仅对应一个订单。&lt;/p>
&lt;p>我们现实生活中也有各种 ID，比如身份证 ID 对应且仅对应一个人、地址 ID 对应且仅对应&lt;/p>
&lt;p>简单来说，&lt;strong>ID 就是数据的唯一标识&lt;/strong>。&lt;/p>
&lt;h3 id="什么是分布式-id">什么是分布式 ID？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%88%86%e5%b8%83%e5%bc%8f-id">#&lt;/a>&lt;/h3>
&lt;p>分布式 ID 是&lt;strong>分布式系统下的 ID&lt;/strong>。分布式 ID &lt;strong>不存在与现实生活中(属于技术上的问题，跟业务无关)&lt;/strong>，属于计算机系统中的一个概念。&lt;/p>
&lt;p>我简单举一个&lt;strong>分库分表&lt;/strong>的例子。&lt;/p>
&lt;p>我司的一个项目，使用的是单机 MySQL 。但是，没想到的是，项目上线一个月之后，随着使用人数越来越多，整个系统的数据量将越来越大。单机 MySQL 已经没办法支撑了，需要进行分库分表（推荐 &lt;strong>Sharding-JDBC&lt;/strong>）。&lt;/p>
&lt;p>在分库之后， &lt;strong>数据遍布在不同服务器上的数据库&lt;/strong>，数据库的自增主键已经没办法满足生成的主键唯一了。&lt;strong>我们如何为不同的数据节点生成全局唯一主键呢？&lt;/strong>&lt;/p>
&lt;p>这个时候就需要生成&lt;strong>分布式 ID&lt;/strong>了。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142029621.png" alt="ly-20241212142029621" />&lt;/p>
&lt;h3 id="分布式-id-需要满足哪些要求">分布式 ID 需要满足哪些要求?&lt;a class="anchor" href="#%e5%88%86%e5%b8%83%e5%bc%8f-id-%e9%9c%80%e8%a6%81%e6%bb%a1%e8%b6%b3%e5%93%aa%e4%ba%9b%e8%a6%81%e6%b1%82">#&lt;/a>&lt;/h3>
&lt;p>&lt;img src="img/ly-20241212142029864.png" alt="ly-20241212142029864" />&lt;/p>
&lt;p>分布式 ID 作为分布式系统中必不可少的一环，很多地方都要用到分布式 ID。&lt;/p>
&lt;p>一个最基本的分布式 ID 需要满足下面这些要求：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>全局唯一&lt;/strong> ：ID 的&lt;strong>全局唯一性&lt;/strong>肯定是首先要满足的！&lt;/li>
&lt;li>&lt;strong>高性能&lt;/strong> ： 分布式 ID 的&lt;strong>生成速度要快&lt;/strong>，对本地资源消耗要小。&lt;/li>
&lt;li>&lt;strong>高可用&lt;/strong> ：生成分布式 ID 的服务要保证&lt;strong>可用性无限接近于 100%&lt;/strong>。&lt;/li>
&lt;li>&lt;strong>方便易用&lt;/strong> ：拿来即用，&lt;strong>使用方便，快速接入&lt;/strong>！&lt;/li>
&lt;/ul>
&lt;p>除了这些之外，一个比较好的分布式 ID 还应保证：&lt;/p></description></item><item><title>api网关</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly01ly_api-gateway/</link><pubDate>Fri, 10 Feb 2023 22:26:59 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/ly01ly_api-gateway/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="什么是网关有什么用">什么是网关？有什么用？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e7%bd%91%e5%85%b3%e6%9c%89%e4%bb%80%e4%b9%88%e7%94%a8">#&lt;/a>&lt;/h2>
&lt;p>微服务背景下，一个系统被&lt;strong>拆分为多个服务&lt;/strong>，但是像&lt;strong>安全认证&lt;/strong>，&lt;strong>流量控制&lt;/strong>，&lt;strong>日志&lt;/strong>，&lt;strong>监控&lt;/strong>等功能是每个服务都需要的，没有网关的话，我们就需要在&lt;strong>每个服务&lt;/strong>中单独实现，这使得我们做了很多重复的事情并且没有一个&lt;strong>全局的视图来统一管理&lt;/strong>这些功能。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142028983.png" alt="ly-20241212142028983.png" />&lt;/p>
&lt;p>一般情况下，网关可以为我们提供&lt;strong>请求转发&lt;/strong>、&lt;strong>安全认证（身份/权限认证）&lt;/strong>、&lt;strong>流量控制&lt;/strong>、&lt;strong>负载均衡&lt;/strong>、&lt;strong>降级熔断&lt;/strong>、&lt;strong>日志&lt;/strong>、&lt;strong>监控&lt;/strong>等功能。&lt;/p>
&lt;p>上面介绍了这么多功能，实际上，网关主要做了一件事情：&lt;strong>请求过滤&lt;/strong> 。&lt;/p>
&lt;h2 id="有哪些常见的网关系统">有哪些常见的网关系统？&lt;a class="anchor" href="#%e6%9c%89%e5%93%aa%e4%ba%9b%e5%b8%b8%e8%a7%81%e7%9a%84%e7%bd%91%e5%85%b3%e7%b3%bb%e7%bb%9f">#&lt;/a>&lt;/h2>
&lt;h3 id="netflix-zuul">Netflix Zuul&lt;a class="anchor" href="#netflix-zuul">#&lt;/a>&lt;/h3>
&lt;p>Zuul 是 Netflix 开发的一款提供&lt;strong>动态路由&lt;/strong>、&lt;strong>监控&lt;/strong>、&lt;strong>弹性&lt;/strong>、&lt;strong>安全&lt;/strong>的网关服务。&lt;/p>
&lt;p>Zuul 主要通过&lt;strong>过滤器（类似于 AOP）来过滤请求&lt;/strong>，从而实现网关必备的各种功能。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142029228.jpg" alt="Zuul架构" />&lt;/p>
&lt;p>我们可以自定义过滤器来处理请求，并且，Zuul 生态本身就有很多现成的过滤器供我们使用。就比如限流可以直接用国外朋友写的 
&lt;a target="_blank" href="https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit">spring-cloud-zuul-ratelimit&lt;/a> (这里只是举例说明，一般是配合 hystrix 来做限流)：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;lt;dependency&amp;gt;
 &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
 &amp;lt;artifactId&amp;gt;spring-cloud-starter-netflix-zuul&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
 &amp;lt;groupId&amp;gt;com.marcosbarbero.cloud&amp;lt;/groupId&amp;gt;
 &amp;lt;artifactId&amp;gt;spring-cloud-zuul-ratelimit&amp;lt;/artifactId&amp;gt;
 &amp;lt;version&amp;gt;2.2.0.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code>&lt;/pre>&lt;p>Zuul 1.x &lt;strong>基于同步 IO，性能较差&lt;/strong>。Zuul 2.x &lt;strong>基于 Netty 实现了异步 IO&lt;/strong>，性能得到了大幅改进。&lt;/p>
&lt;ul>
&lt;li>Github 地址 ： 
&lt;a target="_blank" href="https://github.com/Netflix/zuul">https://github.com/Netflix/zuul&lt;/a>&lt;/li>
&lt;li>官方 Wiki ： 
&lt;a target="_blank" href="https://github.com/Netflix/zuul/wiki">https://github.com/Netflix/zuul/wiki&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="spring-cloud-gateway">Spring Cloud Gateway&lt;a class="anchor" href="#spring-cloud-gateway">#&lt;/a>&lt;/h3>
&lt;p>SpringCloud Gateway 属于 Spring Cloud 生态系统中的网关，其诞生的目标是为了替代老牌网关 **Zuul **。准确点来说，应该是 &lt;strong>Zuul 1.x&lt;/strong>。SpringCloud Gateway 起步要比 Zuul 2.x 更早。&lt;/p>
&lt;p>为了提升网关的性能，SpringCloud Gateway &lt;strong>基于 Spring WebFlux&lt;/strong> 。Spring WebFlux 使用 Reactor 库来实现&lt;strong>响应式编程模型&lt;/strong>，&lt;strong>底层基于 Netty 实现异步 IO&lt;/strong>。&lt;/p></description></item><item><title>raft算法</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/base/raft-algorithm/</link><pubDate>Fri, 10 Feb 2023 17:10:36 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/base/raft-algorithm/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="1-背景">1 背景&lt;a class="anchor" href="#1-%e8%83%8c%e6%99%af">#&lt;/a>&lt;/h2>
&lt;p>当今的数据中心和应用程序在高度动态的环境中运行，为了应对&lt;strong>高度动态&lt;/strong>的环境，它们&lt;strong>通过额外的服务器进行横向扩展&lt;/strong>，并且&lt;strong>根据需求进行扩展和收&lt;/strong>缩。同时，服务器和网络故障也很常见。&lt;/p>
&lt;p>因此，系统必须在正常操作期间处理服务器的上下线。它们必须对变故&lt;strong>做出反应并在几秒钟内自动适应&lt;/strong>；对客户来说的话，明显的中断通常是不可接受的。&lt;/p>
&lt;p>幸运的是，分布式共识可以帮助应对这些挑战。&lt;/p>
&lt;h3 id="11-拜占庭将军">1.1 拜占庭将军&lt;a class="anchor" href="#11-%e6%8b%9c%e5%8d%a0%e5%ba%ad%e5%b0%86%e5%86%9b">#&lt;/a>&lt;/h3>
&lt;p>在介绍共识算法之前，先介绍一个简化版拜占庭将军的例子来帮助理解共识算法。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>假设多位拜占庭将军中没有叛军，信使的信息可靠但有可能被暗杀的情况下，将军们如何达成是否要进攻的一致性决定？&lt;/p>&lt;/blockquote>&lt;p>解决方案大致可以理解成：先在所有的将军中选出一个大将军，用来做出所有的决定。&lt;/p>
&lt;p>举例如下：假如现在一共有 3 个将军 A，B 和 C，每个将军都有一个随机时间的倒计时器，&lt;strong>倒计时一结束&lt;/strong>，这个将军就&lt;strong>把自己当成大将军候选人&lt;/strong>，然后派信使&lt;strong>传递选举投票的信息给将军 B 和 C&lt;/strong>，如果将军 B 和 C &lt;strong>还没有把自己当作候选人&lt;/strong>（自己的倒计时还没有结束），并且&lt;strong>没有把选举票投给其他人&lt;/strong>，它们就会把票投给将军 A，信使回到将军 A 时，将军 A 知道自己收到了足够的票数，成为大将军。在有了大将军之后，&lt;strong>是否需要进攻就由大将军 A&lt;/strong> 决定，然后再去派信使通知另外两个将军，自己已经成为了大将军。如果一段时间还没收到将军 B 和 C 的回复（信使可能会被暗杀），那就再重派一个信使，直到收到回复。&lt;/p>
&lt;h3 id="12-共识算法">1.2 共识算法&lt;a class="anchor" href="#12-%e5%85%b1%e8%af%86%e7%ae%97%e6%b3%95">#&lt;/a>&lt;/h3>
&lt;p>共识是可容错系统中的一个基本问题：&lt;strong>即使面对故障，服务器也可以在共享状态上达成一致&lt;/strong>。&lt;/p>
&lt;p>共识算法允许一组节点像一个整体一样一起工作，即使其中的一些节点出现故障也能够继续工作下去，其正确性主要是源于复制状态机的性质：一组&lt;code>Server&lt;/code>的状态机计算相同状态的副本，即使有一部分的&lt;code>Server&lt;/code>宕机了它们仍然能够继续运行。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142028468.jpg" alt="rsm-architecture.png" />&lt;/p>
&lt;pre tabindex="0">&lt;code>图-1 复制状态机架构&lt;/code>&lt;/pre>&lt;p>一般通过&lt;strong>使用复制日志来实现复制状态机&lt;/strong>。&lt;strong>每个&lt;code>Server&lt;/code>存储着一份包括命令序列的日志文件&lt;/strong>，状态机会按顺序执行这些命令。因为每个日志包含相同的命令，并且顺序也相同，所以每个状态机处理相同的命令序列。由于状态机是确定性的，所以&lt;strong>处理相同的状态，得到相同的输出&lt;/strong>。&lt;/p>
&lt;p>因此共识算法的工作就是&lt;strong>保持复制日志的一致性&lt;/strong>。服务器上的共识模块从客户端接收命令并将它们添加到日志中。它与其他服务器上的共识模块通信，以确保即使某些服务器发生故障。每个日志最终包含相同顺序的请求。一旦命令被正确地复制，它们就被称为已提交。每个服务器的状态机按照日志顺序处理已提交的命令，并将输出返回给客户端，因此，这些服务器形成了一个单一的、高度可靠的状态机。&lt;/p>
&lt;p>适用于实际系统的共识算法通常具有以下特性：&lt;/p>
&lt;ul>
&lt;li>安全。&lt;strong>确保在非拜占庭条件（也就是上文中提到的简易版拜占庭）下的安全性&lt;/strong>，包括&lt;strong>网络延迟&lt;/strong>、&lt;strong>分区&lt;/strong>、&lt;strong>包丢失&lt;/strong>、&lt;strong>复制&lt;/strong>和&lt;strong>重新排序&lt;/strong>。&lt;/li>
&lt;li>高可用。只要&lt;strong>大多数服务器都是可操作&lt;/strong>的，并且可以相互通信，也可以与客户端进行通信，那么这些服务器就可以看作完全功能可用的。因此，一个典型的由五台服务器组成的集群可以容忍任何两台服务器端故障。假设服务器因停止而发生故障；它们&lt;strong>稍后可能会从稳定存储上的状态中恢复并重新加入集群&lt;/strong>。&lt;/li>
&lt;li>一致性不依赖时序。错误的时钟和极端的消息延迟，在&lt;strong>最坏的情况下也只会造成可用性问题，而不会产生一致性问题&lt;/strong>。&lt;/li>
&lt;li>在集群中大多数服务器响应，&lt;strong>命令就可以完成&lt;/strong>，不会被少数运行缓慢的服务器来影响整体系统性能。&lt;/li>
&lt;/ul>
&lt;h2 id="2-基础">2 基础&lt;a class="anchor" href="#2-%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h2>
&lt;h3 id="21-节点类型">2.1 节点类型&lt;a class="anchor" href="#21-%e8%8a%82%e7%82%b9%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h3>
&lt;p>一个 Raft 集群包括若干服务器，以典型的 5 服务器集群举例。在任意的时间，每个服务器一定会处于以下三个状态中的一个：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Leader&lt;/code>：负责发起心跳，响应客户端，创建日志，同步日志。&lt;/li>
&lt;li>&lt;code>Candidate&lt;/code>：Leader 选举过程中的临时角色，由 Follower 转化而来，发起投票参与竞选。&lt;/li>
&lt;li>&lt;code>Follower&lt;/code>：接受 Leader 的心跳和日志同步数据，投票给 Candidate。&lt;/li>
&lt;/ul>
&lt;p>在正常的情况下，只有一个服务器是 Leader，剩下的服务器是 Follower。Follower 是被动的，它们不会发送任何请求，只是响应来自 Leader 和 Candidate 的请求。&lt;/p></description></item><item><title>paxos算法</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/base/paxos-algorithm/</link><pubDate>Fri, 10 Feb 2023 17:02:45 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/base/paxos-algorithm/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="背景">背景&lt;a class="anchor" href="#%e8%83%8c%e6%99%af">#&lt;/a>&lt;/h2>
&lt;p>Paxos 算法是 Leslie Lamport（
&lt;a target="_blank" href="https://zh.wikipedia.org/wiki/%E8%8E%B1%E6%96%AF%E5%88%A9%C2%B7%E5%85%B0%E4%BC%AF%E7%89%B9">莱斯利·兰伯特&lt;/a>）在 &lt;strong>1990&lt;/strong> 年提出了一种分布式系统 &lt;strong>共识&lt;/strong> 算法。这也是第一个被证明完备的共识算法（前提是不存在拜占庭将军问题，也就是&lt;strong>没有恶意节点&lt;/strong>）。&lt;/p>
&lt;p>为了介绍 Paxos 算法，兰伯特专门写了一篇幽默风趣的论文。在这篇论文中，他虚拟了一个叫做 Paxos 的希腊城邦来更形象化地介绍 Paxos 算法。&lt;/p>
&lt;p>不过，审稿人并不认可这篇论文的幽默。于是，他们就给兰伯特说：“如果你想要成功发表这篇论文的话，必须删除所有 Paxos 相关的故事背景”。兰伯特一听就不开心了：“我凭什么修改啊，你们这些审稿人就是缺乏幽默细胞，发不了就不发了呗！”。&lt;/p>
&lt;p>于是乎，提出 Paxos 算法的那篇论文在当时并没有被成功发表。&lt;/p>
&lt;p>直到 1998 年，系统研究中心 (Systems Research Center，SRC）的两个技术研究员需要找一些合适的分布式算法来服务他们正在构建的分布式系统，Paxos 算法刚好可以解决他们的部分需求。因此，兰伯特就把论文发给了他们。在看了论文之后，这俩大佬觉得论文还是挺不错的。于是，兰伯特在 &lt;strong>1998&lt;/strong> 年重新发表论文 
&lt;a target="_blank" href="http://lamport.azurewebsites.net/pubs/lamport-paxos.pdf">《The Part-Time Parliament》&lt;/a>。&lt;/p>
&lt;p>论文发表之后，各路学者直呼看不懂，言语中还略显调侃之意。这谁忍得了，在 &lt;strong>2001&lt;/strong> 年的时候，兰伯特专门又写了一篇 
&lt;a target="_blank" href="http://lamport.azurewebsites.net/pubs/paxos-simple.pdf">《Paxos Made Simple》&lt;/a> 的论文来简化对 Paxos 的介绍，主要讲述&lt;strong>两阶段共识协议&lt;/strong>部分，顺便还不忘嘲讽一下这群学者。&lt;/p>
&lt;p>《Paxos Made Simple》这篇论文就 14 页，相比于 《The Part-Time Parliament》的 33 页精简了不少。最关键的是这篇论文的摘要就一句话：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142028075.png" alt="ly-20241212142028075" />&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>The Paxos algorithm, when presented in plain English, is very simple.&lt;/p>&lt;/blockquote>&lt;p>翻译过来的意思大概就是：&lt;strong>当我用无修饰的英文来描述时，Paxos 算法真心简单！&lt;/strong>&lt;/p>
&lt;p>有没有感觉到来自兰伯特大佬满满地嘲讽的味道？&lt;/p></description></item><item><title>CAP&amp;BASE 理论</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/base/cap_base-theorem/</link><pubDate>Fri, 10 Feb 2023 15:03:48 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lydly_distributed_system/base/cap_base-theorem/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>经历过技术面试的小伙伴想必对 CAP &amp;amp; BASE 这个两个理论已经再熟悉不过了！&lt;/p>
&lt;p>我当年参加面试的时候，不夸张地说，只要问到分布式相关的内容，面试官几乎是必定会问这两个&lt;strong>分布式相关的理论&lt;/strong>。一是因为这两个分布式基础理论是学习分布式知识的&lt;strong>必备前置基础&lt;/strong>，二是因为很多面试官自己比较熟悉这两个理论（方便提问）。&lt;/p>
&lt;p>我们非常有必要将这两个理论搞懂，并且能够用自己的理解给别人讲出来。&lt;/p>
&lt;h2 id="cap-理论">CAP 理论&lt;a class="anchor" href="#cap-%e7%90%86%e8%ae%ba">#&lt;/a>&lt;/h2>
&lt;p>
&lt;a target="_blank" href="https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86">CAP 理论/定理&lt;/a>起源于 2000 年，由加州大学伯克利分校的 Eric Brewer 教授在分布式计算原理研讨会（PODC）上提出，因此 CAP 定理又被称作 &lt;strong>布鲁尔定理（Brewer’s theorem）&lt;/strong>&lt;/p>
&lt;p>2 年后，麻省理工学院的 Seth Gilbert 和 Nancy Lynch 发表了布鲁尔猜想的证明，CAP 理论正式成为&lt;strong>分布式领域的定理&lt;/strong>。&lt;/p>
&lt;h3 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h3>
&lt;blockquote class='book-hint '>
&lt;p>&lt;code>[kənˈsɪstənsi] consistency 一致性&lt;/code>&lt;br>
&lt;code>[əˌveɪlə'bɪləti] availability 可用性 &lt;/code>,&lt;br>
&lt;code>[pɑːˈtɪʃn] 分割 [ˈtɒlərəns] 容忍, &lt;/code>&lt;/p>&lt;/blockquote>&lt;p>&lt;strong>CAP&lt;/strong> 也就是 &lt;strong>Consistency（一致性）&lt;/strong>、&lt;strong>Availability（可用性）&lt;/strong>、&lt;strong>Partition Tolerance（分区容错性）&lt;/strong> 这三个单词首字母组合。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142027432.png" alt="ly-20241212142027432" />&lt;/p>
&lt;p>CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候，并没有详细定义 &lt;strong>Consistency&lt;/strong>、&lt;strong>Availability&lt;/strong>、&lt;strong>Partition Tolerance&lt;/strong> 三个单词的明确定义。&lt;/p>
&lt;p>因此，对于 CAP 的民间解读有很多，一般比较被大家推荐的是下面 👇 这种版本的解读。&lt;/p>
&lt;p>在理论计算机科学中，CAP 定理（CAP theorem）指出对于一个分布式系统来说，当设计&lt;strong>读写操作&lt;/strong>时，只能同时满足以下三点中的两个：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>一致性（Consistency）&lt;/strong> : 所有节点&lt;strong>访问同一份最新的数据副本&lt;/strong>&lt;/li>
&lt;li>&lt;strong>可用性（Availability）&lt;/strong>: &lt;strong>非故障的节点在合理的时间内返回合理的响应&lt;/strong>（不是错误或者超时的响应）。&lt;/li>
&lt;li>&lt;strong>分区容错性（Partition Tolerance）&lt;/strong> : 分布式系统出现网络分区的时候，&lt;strong>仍然能够对外提供服务&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>什么是网络分区？&lt;/strong>&lt;/p></description></item><item><title>Mybatis原理系列(3)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/principle/mybatis-principle3/</link><pubDate>Fri, 10 Feb 2023 11:27:48 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/principle/mybatis-principle3/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://www.jianshu.com/p/4e268828db48（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>&lt;strong>还没看完&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>在上篇文章中，我们讲解了MyBatis的启动流程，以及启动过程中涉及到的组件，在本篇文中，我们继续探索SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系。SqlSession作为MyBatis的核心组件，可以说MyBatis的所有操作都是围绕SqlSession来展开的。对&lt;strong>SqlSession理解透彻&lt;/strong>，才能全面掌握MyBatis。&lt;/p>&lt;/blockquote>&lt;h4 id="1-sqlsession初识">1. SqlSession初识&lt;a class="anchor" href="#1-sqlsession%e5%88%9d%e8%af%86">#&lt;/a>&lt;/h4>
&lt;p>SqlSession在一开始就介绍过是高级接口，类似于JDBC操作的connection对象，它包装了数据库连接，通过这个接口我们可以实现增删改查，提交/回滚事物，关闭连接，获取代理类等操作。SqlSession是个接口，其默认实现是DefaultSqlSession。SqlSession是线程不安全的，每个线程都会有自己唯一的SqlSession，不同线程间调用同一个SqlSession会出现问题，因此在使用完后需要close掉。&lt;/p>
&lt;p>&lt;img src="img/6aa056bf515cfd3c4bc96542f4b99250_MD5.webp" alt="img" />&lt;/p>
&lt;p>SqlSession的方法&lt;/p>
&lt;h4 id="2-sqlsession的创建">2. SqlSession的创建&lt;a class="anchor" href="#2-sqlsession%e7%9a%84%e5%88%9b%e5%bb%ba">#&lt;/a>&lt;/h4>
&lt;p>SqlSessionFactoryBuilder的build()方法使用建造者模式创建了SqlSessionFactory接口对象，SqlSessionFactory接口的默认实现是DefaultSqlSessionFactory。SqlSessionFactory使用实例工厂模式来创建SqlSession对象。SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系如下(图画得有点丑&amp;hellip;)：&lt;/p>
&lt;p>&lt;img src="img/dda341fb9f426f5b159a42798973cbba_MD5.webp" alt="img" />&lt;/p>
&lt;p>类图&lt;/p>
&lt;p>DefaultSqlSessionFactory中openSession是有两种方法一种是openSessionFromDataSource，另一种是openSessionFromConnection。这两种是什么区别呢？从字面意义上将，一种是从数据源中获取SqlSession对象，一种是由已有连接获取SqlSession。SqlSession实际是对数据库连接的一层包装，数据库连接是个珍贵的资源，如果频繁的创建销毁将会影响吞吐量，因此使用数据库连接池化技术就可以复用数据库连接了。因此openSessionFromDataSource会从数据库连接池中获取一个连接，然后包装成一个SqlSession对像。openSessionFromConnection则是直接包装已有的连接并返回SqlSession对像。&lt;/p>
&lt;p>openSessionFromDataSource 主要经历了以下几步：&lt;/p>
&lt;ol>
&lt;li>从获取configuration中获取Environment对象，Environment包含了数据库配置&lt;/li>
&lt;li>从Environment获取DataSource数据源&lt;/li>
&lt;li>从DataSource数据源中获取Connection连接对象&lt;/li>
&lt;li>从DataSource数据源中获取TransactionFactory事物工厂&lt;/li>
&lt;li>从TransactionFactory中创建事物Transaction对象&lt;/li>
&lt;li>创建Executor对象&lt;/li>
&lt;li>包装configuration和Executor对象成DefaultSqlSession对象&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> SqlSession &lt;span style="color:#a6e22e">openSessionFromDataSource&lt;/span>(ExecutorType execType, TransactionIsolationLevel level, &lt;span style="color:#66d9ef">boolean&lt;/span> autoCommit) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Transaction tx &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> Environment environment &lt;span style="color:#f92672">=&lt;/span> configuration.&lt;span style="color:#a6e22e">getEnvironment&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> TransactionFactory transactionFactory &lt;span style="color:#f92672">=&lt;/span> getTransactionFactoryFromEnvironment(environment);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tx &lt;span style="color:#f92672">=&lt;/span> transactionFactory.&lt;span style="color:#a6e22e">newTransaction&lt;/span>(environment.&lt;span style="color:#a6e22e">getDataSource&lt;/span>(), level, autoCommit);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> Executor executor &lt;span style="color:#f92672">=&lt;/span> configuration.&lt;span style="color:#a6e22e">newExecutor&lt;/span>(tx, execType);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> DefaultSqlSession(configuration, executor, autoCommit);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (Exception e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> closeTransaction(tx); &lt;span style="color:#75715e">// may have fetched a connection so lets call close()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> ExceptionFactory.&lt;span style="color:#a6e22e">wrapException&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Error opening session. Cause: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> e, e);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">finally&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ErrorContext.&lt;span style="color:#a6e22e">instance&lt;/span>().&lt;span style="color:#a6e22e">reset&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> SqlSession &lt;span style="color:#a6e22e">openSessionFromConnection&lt;/span>(ExecutorType execType, Connection connection) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> autoCommit;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoCommit &lt;span style="color:#f92672">=&lt;/span> connection.&lt;span style="color:#a6e22e">getAutoCommit&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (SQLException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Failover to true, as most poor drivers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// or databases won&amp;#39;t support transactions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoCommit &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> Environment environment &lt;span style="color:#f92672">=&lt;/span> configuration.&lt;span style="color:#a6e22e">getEnvironment&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> TransactionFactory transactionFactory &lt;span style="color:#f92672">=&lt;/span> getTransactionFactoryFromEnvironment(environment);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> Transaction tx &lt;span style="color:#f92672">=&lt;/span> transactionFactory.&lt;span style="color:#a6e22e">newTransaction&lt;/span>(connection);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> Executor executor &lt;span style="color:#f92672">=&lt;/span> configuration.&lt;span style="color:#a6e22e">newExecutor&lt;/span>(tx, execType);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> DefaultSqlSession(configuration, executor, autoCommit);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (Exception e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> ExceptionFactory.&lt;span style="color:#a6e22e">wrapException&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Error opening session. Cause: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> e, e);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">finally&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ErrorContext.&lt;span style="color:#a6e22e">instance&lt;/span>().&lt;span style="color:#a6e22e">reset&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="3-sqlsession的使用">3. SqlSession的使用&lt;a class="anchor" href="#3-sqlsession%e7%9a%84%e4%bd%bf%e7%94%a8">#&lt;/a>&lt;/h4>
&lt;p>SqlSession 获取成功后，我们就可以使用其中的方法了，比如直接使用SqlSession发送sql语句，或者通过mapper映射文件的方式来使用，在上两篇文章中我们都是通过mapper映射文件来使用的，接下来就介绍第一种，直接使用SqlSession发送sql语句。&lt;/p></description></item><item><title>Mybatis原理系列(2)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/principle/mybatis-principle2/</link><pubDate>Fri, 10 Feb 2023 11:04:31 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/principle/mybatis-principle2/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://www.jianshu.com/p/7d6b891180a3（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>在上篇文章中，我们举了一个例子如何使用MyBatis，但是对其中&lt;strong>dao层&lt;/strong>，&lt;strong>entity层&lt;/strong>，&lt;strong>mapper层&lt;/strong>间的关系不得而知，从此篇文章开始，笔者将&lt;strong>从MyBatis的启动流程&lt;/strong>着手，真正的开始研究MyBatis源码了。&lt;/p>&lt;/blockquote>&lt;h4 id="1-mybatis启动代码示例">1. MyBatis启动代码示例&lt;a class="anchor" href="#1-mybatis%e5%90%af%e5%8a%a8%e4%bb%a3%e7%a0%81%e7%a4%ba%e4%be%8b">#&lt;/a>&lt;/h4>
&lt;p>在上篇文章中，介绍了MyBatis的相关配置和各层代码编写，本文将以下代码展开描述和介绍MyBatis的启动流程，并简略的介绍各个模块的作用，&lt;strong>各个模块的细节部分将在其它文章中呈现&lt;/strong>。&lt;/p>
&lt;p>回顾下上文中使用mybatis的部分代码，包括七步。每步虽然都是一行代码，但是隐藏了很多细节。接下来我们将围绕这起步展开了解。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Slf4j&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">MyBatisBootStrap&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 1. 读取配置&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> InputStream inputStream &lt;span style="color:#f92672">=&lt;/span> Resources.&lt;span style="color:#a6e22e">getResourceAsStream&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;mybatis-config.xml&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 2. 创建SqlSessionFactory工厂&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SqlSessionFactory sqlSessionFactory &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> SqlSessionFactoryBuilder().&lt;span style="color:#a6e22e">build&lt;/span>(inputStream);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 3. 获取sqlSession&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SqlSession sqlSession &lt;span style="color:#f92672">=&lt;/span> sqlSessionFactory.&lt;span style="color:#a6e22e">openSession&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 4. 获取Mapper&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TTestUserMapper userMapper &lt;span style="color:#f92672">=&lt;/span> sqlSession.&lt;span style="color:#a6e22e">getMapper&lt;/span>(TTestUserMapper.&lt;span style="color:#a6e22e">class&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 5. 执行接口方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TTestUser userInfo &lt;span style="color:#f92672">=&lt;/span> userMapper.&lt;span style="color:#a6e22e">selectByPrimaryKey&lt;/span>(16L);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;userInfo = &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> JSONUtil.&lt;span style="color:#a6e22e">toJsonStr&lt;/span>(userInfo));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 6. 提交事物&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sqlSession.&lt;span style="color:#a6e22e">commit&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 7. 关闭资源&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sqlSession.&lt;span style="color:#a6e22e">close&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> inputStream.&lt;span style="color:#a6e22e">close&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (Exception e){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> log.&lt;span style="color:#a6e22e">error&lt;/span>(e.&lt;span style="color:#a6e22e">getMessage&lt;/span>(), e);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="2-读取配置">2. 读取配置&lt;a class="anchor" href="#2-%e8%af%bb%e5%8f%96%e9%85%8d%e7%bd%ae">#&lt;/a>&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 1. 读取配置
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>InputStream inputStream &lt;span style="color:#f92672">=&lt;/span> Resources.getResourceAsStream(&lt;span style="color:#e6db74">&amp;#34;mybatis-config.xml&amp;#34;&lt;/span>);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在&lt;strong>mybatis-config.xml&lt;/strong>中我们配置了&lt;strong>属性&lt;/strong>，&lt;strong>环境&lt;/strong>，&lt;strong>映射文件路径&lt;/strong>等，其实不仅可以配置以上内容，还可以配置&lt;strong>插件&lt;/strong>，&lt;strong>反射工厂&lt;/strong>，&lt;strong>类型处理器&lt;/strong>等等其它内容。在启动流程中的第一步我们就需要&lt;strong>读取这个配置文件&lt;/strong>，并获取一个输入流为下一步解析配置文件作准备。&lt;/p></description></item><item><title>Mybatis原理系列(1)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/principle/mybatis-principle1/</link><pubDate>Fri, 10 Feb 2023 08:54:26 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/principle/mybatis-principle1/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://www.jianshu.com/p/ada025f97a07（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>作为Java码农，无论在面试中，还是在工作中都会遇到MyBatis的相关问题。笔者从大学开始就接触MyBatis，到现在为止都是会用，知道怎么配置，怎么编写xml，但是不知道Mybatis核心原理，一遇到问题就复制错误信息百度解决。为了改变这种境地，鼓起勇气开始下定决心阅读MyBatis源码，并开始记录阅读过程，希望和大家分享。&lt;/p>&lt;/blockquote>&lt;h4 id="1-初识mybatis">1. 初识MyBatis&lt;a class="anchor" href="#1-%e5%88%9d%e8%af%86mybatis">#&lt;/a>&lt;/h4>
&lt;p>还记得当初接触MyBatis时，觉得要配置很多，而且sql要单独写在xml中，相比Hibernate来说简直不太友好，直到后来出现了复杂的业务需求，需要编写相应的复杂的sql，此时用Hibernate反而更加麻烦了，用MyBatis是真香了。因此笔者对MyBatis的第一印象就是&lt;strong>将业务关注的sql&lt;/strong>和&lt;strong>java代码&lt;/strong>进行了解耦，在业务复杂变化的时候，相应的数据库操作需要相应进行修改，&lt;strong>如果通过java代码构建操作数据逻辑&lt;/strong>，这不断变动的需求对程序员的耐心是极大的考验。如果&lt;strong>将sql统一的维护在一个文件&lt;/strong>里，java代码用接口定义，在需求变动时，&lt;strong>只用改相应的sql&lt;/strong>，从而&lt;strong>减少了修改量&lt;/strong>，&lt;strong>提高开发效率&lt;/strong>。以上也是经常在面试中经常问到的Hibernate和MyBatis间的区别一点。&lt;/p>
&lt;p>切到正题，Mybatis是什么呢？&lt;/p>
&lt;p>Mybatis SQL 映射框架使得一个&lt;strong>面向对象构建的应用程序&lt;/strong>去&lt;strong>访问一个关系型数据库&lt;/strong>变得更容易。MyBatis使用&lt;strong>XML描述符&lt;/strong>或&lt;strong>注解&lt;/strong>将&lt;strong>对象&lt;/strong>与&lt;strong>存储过程&lt;/strong>或&lt;strong>SQL语句耦合&lt;/strong>。与对象关系映射工具相比，简单性是MyBatis数据映射器的最大优势。&lt;/p>
&lt;p>以上是Mybatis的官方解释，其中“映射”，“面向对象”，“关系型”，“xml”等等都是Mybatis的关键词，也是我们了解了Mybatis原理后，会恍然大悟的地方。笔者现在不详述这些概念，在最后总结的时候再进行详述。我们只要知道Mybatis为我们操作数据库提供了很大的便捷。&lt;/p>
&lt;h4 id="2-源码下载">2. 源码下载&lt;a class="anchor" href="#2-%e6%ba%90%e7%a0%81%e4%b8%8b%e8%bd%bd">#&lt;/a>&lt;/h4>
&lt;blockquote class='book-hint '>
&lt;p>这里建议使用maven即可，在pom.xml添加以下依赖&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>mysql&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>mysql-connector-java&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>8.0.32&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.mybatis&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>mybatis&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>3.5.6&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!--这里还添加了一些辅助的依赖--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!--lombok--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.projectlombok&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>lombok&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>1.18.8&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!--日志模块--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.apache.logging.log4j&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>log4j-api&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>2.17.1&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependency&amp;gt;&lt;/span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependencies&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后在ExternalLibraries 的mybatis:3.5.6里找到，就能看到目录结构 ，随便找一个进去 idea右上角会出现DownloadSource之类的字样 ，点击即可&lt;br>
&lt;img src="img/ly-20241212142014144.png" alt="ly-20241212142014144" />&lt;/p>&lt;/blockquote>&lt;p>我们首先要从github上下载源码，
&lt;a target="_blank" href="https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fmybatis%2Fmybatis-3">仓库地址&lt;/a>，然后在IDEA中clone代码&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142014403.png" alt="img" />&lt;/p>
&lt;p>在打开中的IDEA中，选择vsc -&amp;gt; get from version control -&amp;gt; 复制刚才的地址&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142014541.png" alt="img" />&lt;/p>
&lt;p>image.png&lt;/p>
&lt;p>点击clone即可&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142014681.png" alt="img" />&lt;/p>
&lt;p>image.png&lt;/p></description></item><item><title>Mybatis面试</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/MyBatis-interview/</link><pubDate>Thu, 09 Feb 2023 16:34:39 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/MyBatis/MyBatis-interview/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>
&lt;p>部分疑问参考自 
&lt;a target="_blank" href="https://blog.csdn.net/Gherbirthday0916">https://blog.csdn.net/Gherbirthday0916&lt;/a> 感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="-和--的区别是什么">#{} 和 ${} 的区别是什么？&lt;a class="anchor" href="#-%e5%92%8c--%e7%9a%84%e5%8c%ba%e5%88%ab%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h3>
&lt;p>注：这道题是面试官面试我同事的。&lt;/p>
&lt;p>答：&lt;/p>
&lt;ul>
&lt;li>&lt;code>${}&lt;/code>是 Properties &lt;strong>文件中的变量占位符&lt;/strong>，它可以用于&lt;strong>标签属性值&lt;/strong>和 &lt;strong>sql 内部&lt;/strong>，属于&lt;strong>静态文本替换&lt;/strong>，比如${driver}会被静态替换为&lt;code>com.mysql.jdbc. Driver&lt;/code>。&lt;/li>
&lt;li>&lt;code>#{}&lt;/code>是 sql 的参数占位符，MyBatis 会&lt;strong>将 sql 中的&lt;code>#{}&lt;/code>&lt;strong>替换为&lt;/strong>? 号&lt;/strong>，在 sql 执行前会使用 &lt;strong>PreparedStatement 的参数设置&lt;/strong>方法，&lt;strong>按序给 sql 的? 号占位符设置参数值&lt;/strong>，比如 ps.setInt(0, parameterValue)，&lt;code>#{item.name}&lt;/code> 的取值方式为使用&lt;strong>反射从参数对象中获取 item 对象的 name 属性值&lt;/strong>，相当于 &lt;strong>&lt;code>param.getItem().getName()&lt;/code>&lt;/strong>。 [&lt;strong>这里用到了反射&lt;/strong>]&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>在&lt;strong>底层构造完整SQL&lt;/strong>语句时，MyBatis的两种&lt;strong>传参方式&lt;/strong>所采取的方式不同。&lt;code>#{Parameter}&lt;/code>采用&lt;strong>预编译&lt;/strong>的方式构造SQL，&lt;strong>避免了 SQL注入&lt;/strong> 的产生。而**&lt;code>${Parameter}&lt;/code>采用拼接&lt;strong>的方式构造SQL，在&lt;/strong>对用户输入过滤不严格**的前提下，此处很可能存在SQL注入&lt;/p>&lt;/blockquote>&lt;h3 id="xml-映射文件中除了常见的-selectinsertupdatedelete-标签之外还有哪些标签">xml 映射文件中，除了常见的 select、insert、update、delete 标签之外，还有哪些标签？&lt;a class="anchor" href="#xml-%e6%98%a0%e5%b0%84%e6%96%87%e4%bb%b6%e4%b8%ad%e9%99%a4%e4%ba%86%e5%b8%b8%e8%a7%81%e7%9a%84-selectinsertupdatedelete-%e6%a0%87%e7%ad%be%e4%b9%8b%e5%a4%96%e8%bf%98%e6%9c%89%e5%93%aa%e4%ba%9b%e6%a0%87%e7%ad%be">#&lt;/a>&lt;/h3>
&lt;p>注：这道题是京东面试官面试我时问的。&lt;/p>
&lt;p>答：还有很多其他的标签， &lt;strong>&lt;code>&amp;lt;resultMap&amp;gt;&lt;/code>&lt;/strong> 、 &lt;strong>&lt;code>&amp;lt;parameterMap&amp;gt;&lt;/code>&lt;/strong> 、 &lt;strong>&lt;code>&amp;lt;sql&amp;gt;&lt;/code>&lt;/strong> 、 &lt;strong>&lt;code>&amp;lt;include&amp;gt;&lt;/code>&lt;/strong> 、 &lt;strong>&lt;code>&amp;lt;selectKey&amp;gt;&lt;/code>&lt;/strong> ，加上动态 sql 的 9 个标签， &lt;strong>&lt;code>trim|where|set|foreach|if|choose|when|otherwise|bind&lt;/code>&lt;/strong> 等，其中 &lt;code>&amp;lt;sql&amp;gt;&lt;/code> 为 sql 片段标签，通过 &lt;code>&amp;lt;include&amp;gt;&lt;/code> 标签引入 sql 片段， &lt;code>&amp;lt;selectKey&amp;gt;&lt;/code> 为不支持自增的主键生成策略标签。&lt;/p></description></item><item><title>ConditionalOnClass实践</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/conditional_on_class/</link><pubDate>Thu, 09 Feb 2023 15:38:01 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/conditional_on_class/</guid><description>&lt;h1 id="两个测试方向">两个测试方向&lt;a class="anchor" href="#%e4%b8%a4%e4%b8%aa%e6%b5%8b%e8%af%95%e6%96%b9%e5%90%91">#&lt;/a>&lt;/h1>
&lt;h2 id="方向1两个maven项目">方向1：两个maven项目&lt;a class="anchor" href="#%e6%96%b9%e5%90%911%e4%b8%a4%e4%b8%aamaven%e9%a1%b9%e7%9b%ae">#&lt;/a>&lt;/h2>
&lt;blockquote class='book-hint '>
&lt;p>详见git上的 conditional_on_class_main 项目以及 conditional_on_class2 项目&lt;/p>&lt;/blockquote>&lt;ol>
&lt;li>
&lt;p>基础maven项目 conditional_on_class2&lt;br>
pom文件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;project&lt;/span> &lt;span style="color:#a6e22e">xmlns=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">xmlns:xsi=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">xsi:schemaLocation=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span style="color:#f92672">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.example&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>conditional_on_class_2&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>1.0-SNAPSHOT&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;/project&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>java类&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> com;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">LyReferenceImpl&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">sayWord&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#e6db74">&amp;#34;hello one&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>简单的SpringBoot项目 conditional_on_class_main&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&amp;lt;!--pom文件--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;project&lt;/span> &lt;span style="color:#a6e22e">xmlns=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">xmlns:xsi=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">xsi:schemaLocation=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span style="color:#f92672">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.example&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>conditional_on_class_main&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>1.0-SNAPSHOT&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-parent&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>2.7.8&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!--把1配置的bean引用进来--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.example&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>conditional_on_class_2&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;version&amp;gt;&lt;/span>1.0-SNAPSHOT&lt;span style="color:#f92672">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;scope&amp;gt;&lt;/span>provided&lt;span style="color:#f92672">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;optional&amp;gt;&lt;/span>true&lt;span style="color:#f92672">&amp;lt;/optional&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-web&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span style="color:#f92672">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-maven-plugin&lt;span style="color:#f92672">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;excludes&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!-- 默认会将conditional_on_class_2 打包进去,现在会配置SayExist
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">						如果放开注释,那么会配置SayNotExist--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">&amp;lt;!--&amp;lt;dependency&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> &amp;lt;groupId&amp;gt;org.example&amp;lt;/groupId&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> &amp;lt;artifactId&amp;gt;conditional_on_class_2&amp;lt;/artifactId&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> &amp;lt;/dependency&amp;gt;--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/excludes&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;jvmArguments&amp;gt;&lt;/span>-Dfile.encoding=UTF-8&lt;span style="color:#f92672">&amp;lt;/jvmArguments&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;executions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;execution&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;goals&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;goal&amp;gt;&lt;/span>repackage&lt;span style="color:#f92672">&amp;lt;/goal&amp;gt;&lt;/span>&lt;span style="color:#75715e">&amp;lt;!--可以把依赖的包都打包到生成的Jar包中 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/goals&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/execution&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/executions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;/build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;/project&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//两个配置类 &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//配置类1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> com.config;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> com.service.ISay;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> com.service.SayExist;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> org.springframework.context.annotation.Bean;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> org.springframework.context.annotation.Configuration;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//不要放在方法里面,否则会报错&amp;#34;java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@ConditionalOnClass&lt;/span>(value &lt;span style="color:#f92672">=&lt;/span> com.&lt;span style="color:#a6e22e">LyReferenceImpl&lt;/span>.&lt;span style="color:#a6e22e">class&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ExistConfiguration&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Bean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> ISay &lt;span style="color:#a6e22e">getISay1&lt;/span>(){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> SayExist();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//配置类2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> com.config;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> com.service.ISay;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> com.service.SayNotExist;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> org.springframework.context.annotation.Bean;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> org.springframework.context.annotation.Configuration;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@ConditionalOnMissingClass&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;com.LyReferenceImpl&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">NotExistConfiguration&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Bean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> ISay &lt;span style="color:#a6e22e">getISay1&lt;/span>(){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> SayNotExist();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h2 id="方向23个maven项目建议用这个理解">方向2：3个maven项目(建议用这个理解)&lt;a class="anchor" href="#%e6%96%b9%e5%90%9123%e4%b8%aamaven%e9%a1%b9%e7%9b%ae%e5%bb%ba%e8%ae%ae%e7%94%a8%e8%bf%99%e4%b8%aa%e7%90%86%e8%a7%a3">#&lt;/a>&lt;/h2>
&lt;blockquote class='book-hint '>
&lt;p>注意，这里可能还漏了一个问题，那就是 这个conditional_on_class1 的configuration之所以能够被自动装配，是因为和 conditional_on_class_main1的Application类是同一个包，所以不用特殊处理。如果是其他包名的话，那么是需要用到spring boot的自动装配机制的：在conditional_on_class1 工程的 resources 包下创建&lt;code>META-INF/spring.factories&lt;/code>，并写上Config类的全类名&lt;/p></description></item><item><title>SpringBoot自动装配原理</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly05ly_springboot-auto-assembly/</link><pubDate>Thu, 09 Feb 2023 10:37:36 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly05ly_springboot-auto-assembly/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>每次问到 Spring Boot， 面试官非常喜欢问这个问题：“讲述一下 SpringBoot 自动装配原理？”。&lt;/p>
&lt;p>我觉得我们可以从以下几个方面回答：&lt;/p>
&lt;ol>
&lt;li>什么是 &lt;strong>SpringBoot 自动装配&lt;/strong>？&lt;/li>
&lt;li>SpringBoot 是&lt;strong>如何实现自动装配&lt;/strong>的？&lt;strong>如何实现按需加载&lt;/strong>？&lt;/li>
&lt;li>&lt;strong>如何实现一个 Starter&lt;/strong>？&lt;/li>
&lt;/ol>
&lt;p>篇幅问题，这篇文章并没有深入，小伙伴们也可以直接使用 debug 的方式去看看 SpringBoot 自动装配部分的源代码。&lt;/p>
&lt;h2 id="前言">前言&lt;a class="anchor" href="#%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h2>
&lt;p>使用过 Spring 的小伙伴，一定有被 XML 配置统治的恐惧。即使 Spring 后面引入了基于注解的配置，我们在开启某些 Spring 特性或者引入第三方依赖的时候，还是需要用 XML 或 Java 进行显式配置。&lt;/p>
&lt;p>举个例子。没有 Spring Boot 的时候，我们写一个 RestFul Web 服务，还首先需要进行如下配置。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">RESTConfiguration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Bean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> View &lt;span style="color:#a6e22e">jsonTemplate&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MappingJackson2JsonView view &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> MappingJackson2JsonView();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> view.&lt;span style="color:#a6e22e">setPrettyPrint&lt;/span>(&lt;span style="color:#66d9ef">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> view;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Bean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> ViewResolver &lt;span style="color:#a6e22e">viewResolver&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> BeanNameViewResolver();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>spring&lt;span style="color:#f92672">-&lt;/span>servlet.&lt;span style="color:#a6e22e">xml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;lt;&lt;/span>beans xmlns&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.springframework.org/schema/beans&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> xmlns:xsi&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span> xmlns:context&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.springframework.org/schema/context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> xmlns:mvc&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.springframework.org/schema/mvc&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> xsi:schemaLocation&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd&amp;#34;&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>context:component&lt;span style="color:#f92672">-&lt;/span>scan base&lt;span style="color:#f92672">-&lt;/span>package&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;com.howtodoinjava.demo&amp;#34;&lt;/span> &lt;span style="color:#f92672">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>mvc:annotation&lt;span style="color:#f92672">-&lt;/span>driven &lt;span style="color:#f92672">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;!--&lt;/span> JSON Support &lt;span style="color:#f92672">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>bean name&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;viewResolver&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">=&amp;#34;&lt;/span>&lt;span style="color:#a6e22e">org&lt;/span>.&lt;span style="color:#a6e22e">springframework&lt;/span>.&lt;span style="color:#a6e22e">web&lt;/span>.&lt;span style="color:#a6e22e">servlet&lt;/span>.&lt;span style="color:#a6e22e">view&lt;/span>.&lt;span style="color:#a6e22e">BeanNameViewResolver&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;lt;bean name=&amp;#34;&lt;/span>jsonTemplate&lt;span style="color:#e6db74">&amp;#34; class=&amp;#34;&lt;/span>org.&lt;span style="color:#a6e22e">springframework&lt;/span>.&lt;span style="color:#a6e22e">web&lt;/span>.&lt;span style="color:#a6e22e">servlet&lt;/span>.&lt;span style="color:#a6e22e">view&lt;/span>.&lt;span style="color:#a6e22e">json&lt;/span>.&lt;span style="color:#a6e22e">MappingJackson2JsonView&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;lt;/beans&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但是，Spring Boot 项目，我们只需要添加相关依赖，无需配置，通过启动下面的 &lt;code>main&lt;/code> 方法即可。&lt;/p></description></item><item><title>spring 设计模式</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly04ly_spring-design-patterns/</link><pubDate>Wed, 08 Feb 2023 20:18:14 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly04ly_spring-design-patterns/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>“JDK 中用到了哪些设计模式? Spring 中用到了哪些设计模式? ”这两个问题，在面试中比较常见。&lt;/p>
&lt;p>我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律，而且大部分都年代久远。所以，花了几天时间自己总结了一下。&lt;/p>
&lt;p>由于我的个人能力有限，文中如有任何错误各位都可以指出。另外，文章篇幅有限，对于设计模式以及一些源码的解读我只是一笔带过，这篇文章的主要目的是回顾一下 Spring 中的设计模式。&lt;/p>
&lt;h2 id="控制反转ioc和依赖注入di">控制反转(IoC)和依赖注入(DI)&lt;a class="anchor" href="#%e6%8e%a7%e5%88%b6%e5%8f%8d%e8%bd%acioc%e5%92%8c%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5di">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>IoC(Inversion of Control,控制反转)&lt;/strong> 是 Spring 中一个非常非常重要的概念，它不是什么技术，而是一种&lt;strong>解耦的设计思想&lt;/strong>。IoC 的主要目的是借助于“第三方”(Spring 中的 IoC 容器) 实现&lt;strong>具有依赖关系的对象之间的解耦&lt;/strong>(IOC 容器管理对象，你只管使用即可)，从而降低代码之间的耦合度。&lt;/p>
&lt;p>&lt;strong>IoC 是一个原则，而不是一个模式，以下模式（但不限于）实现了 IoC 原则。&lt;/strong>&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142011328.jpg" alt="ioc-patterns" />&lt;/p>
&lt;p>&lt;strong>Spring IoC 容器就像是一个工厂一样，当我们需要创建一个对象的时候，只需要配置好配置文件/注解即可，完全不用考虑对象是如何被创建出来的。&lt;/strong> IoC 容器负责创建对象，将对象连接在一起，配置这些对象，并从创建中处理这些对象的整个生命周期，直到它们被完全销毁。&lt;/p>
&lt;p>在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层，我们需要实例化这个 Service，你可能要每次都要搞清这个 Service 所有底层类的构造函数，这可能会把人逼疯。如果利用 IOC 的话，你&lt;strong>只需要配置好，然后在需要的地方引用&lt;/strong>就行了，这大大增加了项目的可维护性且降低了开发难度。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>关于 Spring IOC 的理解，推荐看这一下知乎的一个回答：https://www.zhihu.com/question/23277575/answer/169698662 ，非常不错。&lt;/p>&lt;/blockquote>&lt;p>&lt;strong>控制反转怎么理解呢?&lt;/strong> 举个例子：&amp;quot;&lt;strong>对象 a 依赖了对象 b，当对象 a 需要使用 对象 b 的时候必须自己去创建。但是当系统引入了 IOC 容器后， 对象 a 和对象 b 之前就失去了直接的联系。这个时候，当对象 a 需要使用 对象 b 的时候， 我们可以指定 IOC 容器去创建一个对象 b 注入到对象 a 中&lt;/strong>&amp;quot;。 对象 a 获得依赖对象 b 的过程,由&lt;strong>主动&lt;/strong>行为变为了&lt;strong>被动&lt;/strong>行为，控制权反转，这就是控制反转名字的由来。&lt;/p></description></item><item><title>Spring事务详情</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly03ly_spring-transaction/</link><pubDate>Wed, 08 Feb 2023 16:08:16 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly03ly_spring-transaction/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>前段时间答应读者的 &lt;strong>Spring 事务&lt;/strong> 分析总结终于来了。这部分内容比较重要，不论是对于工作还是面试，但是网上比较好的参考资料比较少。&lt;/p>
&lt;h2 id="什么是事务">什么是事务？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e4%ba%8b%e5%8a%a1">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>事务是逻辑上的一组操作，要么都执行，要么都不执行。&lt;/strong>&lt;/p>
&lt;p>相信大家应该都能背上面这句话了，下面我结合我们日常的真实开发来谈一谈。&lt;/p>
&lt;p>我们系统的&lt;strong>每个业务方法&lt;/strong>可能包括了&lt;strong>多个原子性的数据库操作&lt;/strong>，比如下面的 &lt;code>savePerson()&lt;/code> 方法中就有&lt;strong>两个&lt;/strong>原子性的数据库操作。&lt;strong>这些&lt;/strong>原子性的数据库&lt;strong>操作&lt;/strong>是&lt;strong>有依赖&lt;/strong>的，它们要么&lt;strong>都执行&lt;/strong>，要不就&lt;strong>都不执行&lt;/strong>。&lt;/p>
&lt;pre tabindex="0">&lt;code>	public void savePerson() {
		personDao.save(person);
		personDetailDao.save(personDetail);
	}&lt;/code>&lt;/pre>&lt;p>另外，需要格外注意的是：&lt;strong>事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的 &lt;code>innodb&lt;/code>引擎。但是，如果把数据库引擎变为 &lt;code>myisam&lt;/code>，那么程序也就不再支持事务了！&lt;/strong>&lt;/p>
&lt;p>事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元，这个转账会涉及到两个关键操作就是：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ol>
&lt;li>将小明的余额减少 1000 元。&lt;/li>
&lt;li>将小红的余额增加 1000 元。&lt;/li>
&lt;/ol>&lt;/blockquote>&lt;p>万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障，导致小明余额减少而小红的余额没有增加，这样就不对了。事务就是保证这两个关键操作要么都成功，要么都要失败。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">OrdersService&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">private&lt;/span> AccountDao accountDao;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">setOrdersDao&lt;/span>(AccountDao accountDao) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">accountDao&lt;/span> &lt;span style="color:#f92672">=&lt;/span> accountDao;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Transactional&lt;/span>(propagation &lt;span style="color:#f92672">=&lt;/span> Propagation.&lt;span style="color:#a6e22e">REQUIRED&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> isolation &lt;span style="color:#f92672">=&lt;/span> Isolation.&lt;span style="color:#a6e22e">DEFAULT&lt;/span>, readOnly &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">false&lt;/span>, timeout &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>1)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">accountMoney&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//小红账户多1000&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		accountDao.&lt;span style="color:#a6e22e">addMoney&lt;/span>(1000,xiaohong);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//模拟突然出现的异常，比如银行中可能为突然停电等等&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//如果没有配置事务管理的话会造成，小红账户多了1000而小明账户没有少钱&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> 10 &lt;span style="color:#f92672">/&lt;/span> 0;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//小王账户少1000&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		accountDao.&lt;span style="color:#a6e22e">reduceMoney&lt;/span>(1000,xiaoming);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>另外，数据库事务的 &lt;strong>ACID&lt;/strong> 四大特性是事务的基础，下面简单来了解一下。&lt;/p></description></item><item><title>Spring/SpringBoot常用注解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly02ly_spring-annotations/</link><pubDate>Wed, 08 Feb 2023 14:56:32 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly02ly_spring-annotations/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="0前言">0.前言&lt;a class="anchor" href="#0%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h3>
&lt;p>可以毫不夸张地说，这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法，掌握搞懂，使用 SpringBoot 来开发项目基本没啥大问题了！&lt;/p>
&lt;p>&lt;strong>为什么要写这篇文章？&lt;/strong>&lt;/p>
&lt;p>最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多，我看了文章内容之后属实觉得质量有点低，并且有点会误导没有太多实际使用经验的人（这些人又占据了大多数）。所以，自己索性花了大概 两天时间简单总结一下了。&lt;/p>
&lt;p>&lt;strong>因为我个人的能力和精力有限，如果有任何不对或者需要完善的地方，请帮忙指出！Guide 哥感激不尽！&lt;/strong>&lt;/p>
&lt;h3 id="1-springbootapplication">1. &lt;code>@SpringBootApplication&lt;/code>&lt;a class="anchor" href="#1-springbootapplication">#&lt;/a>&lt;/h3>
&lt;p>这里先单独拎出&lt;code>@SpringBootApplication&lt;/code> 注解说一下，虽然我们一般不会主动去使用它。&lt;/p>
&lt;p>&lt;em>Guide 哥：这个注解是 Spring Boot 项目的基石，创建 SpringBoot 项目之后会默认在主类加上。&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@SpringBootApplication&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">SpringSecurityJwtGuideApplication&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(java.&lt;span style="color:#a6e22e">lang&lt;/span>.&lt;span style="color:#a6e22e">String&lt;/span>&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SpringApplication.&lt;span style="color:#a6e22e">run&lt;/span>(SpringSecurityJwtGuideApplication.&lt;span style="color:#a6e22e">class&lt;/span>, args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们可以把 &lt;code>@SpringBootApplication&lt;/code>看作是 &lt;code>@Configuration&lt;/code>、&lt;code>@EnableAutoConfiguration&lt;/code>、&lt;code>@ComponentScan&lt;/code> 注解的集合。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> org.springframework.boot.autoconfigure;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Target&lt;/span>(ElementType.&lt;span style="color:#a6e22e">TYPE&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Retention&lt;/span>(RetentionPolicy.&lt;span style="color:#a6e22e">RUNTIME&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Documented&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Inherited&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@SpringBootConfiguration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@EnableAutoConfiguration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@ComponentScan&lt;/span>(excludeFilters &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#a6e22e">@Filter&lt;/span>(type &lt;span style="color:#f92672">=&lt;/span> FilterType.&lt;span style="color:#a6e22e">CUSTOM&lt;/span>, classes &lt;span style="color:#f92672">=&lt;/span> TypeExcludeFilter.&lt;span style="color:#a6e22e">class&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#a6e22e">@Filter&lt;/span>(type &lt;span style="color:#f92672">=&lt;/span> FilterType.&lt;span style="color:#a6e22e">CUSTOM&lt;/span>, classes &lt;span style="color:#f92672">=&lt;/span> AutoConfigurationExcludeFilter.&lt;span style="color:#a6e22e">class&lt;/span>) })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">@interface&lt;/span> SpringBootApplication {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ......
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> org.springframework.boot;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Target&lt;/span>(ElementType.&lt;span style="color:#a6e22e">TYPE&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Retention&lt;/span>(RetentionPolicy.&lt;span style="color:#a6e22e">RUNTIME&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Documented&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">@interface&lt;/span> SpringBootConfiguration {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>根据 SpringBoot 官网，这三个注解的作用分别是：&lt;/p></description></item><item><title>spring 常见面试题总结</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly01ly_spring-knowledge-and-questions-summary/</link><pubDate>Tue, 07 Feb 2023 16:48:03 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lybly_framework/ly01ly_spring-knowledge-and-questions-summary/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>这篇文章主要是想通过一些问题，加深大家对于 Spring 的理解，所以不会涉及太多的代码！&lt;/p>
&lt;p>下面的很多问题我自己在使用 Spring 的过程中也并没有注意，自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章，我感觉大部分都是互相 copy，而且很多问题也不是很好，有些回答也存在问题。所以，自己花了一周的业余时间整理了一下，希望对大家有帮助。&lt;/p>
&lt;h2 id="spring-基础">Spring 基础&lt;a class="anchor" href="#spring-%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是-spring-框架">什么是 Spring 框架?&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-spring-%e6%a1%86%e6%9e%b6">#&lt;/a>&lt;/h3>
&lt;p>Spring 是一款&lt;strong>开源&lt;/strong>的&lt;strong>轻量级 Java 开发框架&lt;/strong>，旨在提高开发人员的&lt;strong>开发效率&lt;/strong>以及系统的&lt;strong>可维护性&lt;/strong>。&lt;/p>
&lt;p>我们一般说 Spring 框架指的都是 Spring Framework，它是很&lt;strong>多模块的集合&lt;/strong>，使用这些模块可以很方便地协助我们进行开发，比如说 Spring 支持 &lt;strong>IoC&lt;/strong>（&lt;strong>Inversion of Control:控制反转&lt;/strong>） 和 &lt;strong>AOP&lt;/strong>(&lt;strong>Aspect-Oriented Programming:面向切面编程&lt;/strong>)、可以很方便地&lt;strong>对数据库进行访问&lt;/strong>、可以很&lt;strong>方便地集成第三方组件&lt;/strong>（&lt;strong>电子邮件&lt;/strong>，&lt;strong>任务&lt;/strong>，&lt;strong>调度&lt;/strong>，缓存等等）、对&lt;strong>单元测试&lt;/strong>支持比较好、支持 &lt;strong>RESTful Java 应用程序&lt;/strong>的开发。&lt;/p>
&lt;p>[&lt;img src="img/ly-20241212142008049.jpg" alt="img" />&lt;/p>
&lt;p>Spring 最核心的思想就是不重新造轮子，&lt;strong>开箱即用&lt;/strong>，提高开发效率。&lt;/p>
&lt;p>Spring 翻译过来就是春天的意思，可见其目标和使命就是为 Java 程序员带来春天啊！感动！&lt;/p>
&lt;p>🤐 多提一嘴 ： &lt;strong>语言的流行通常需要一个杀手级的应用，Spring 就是 Java 生态的一个杀手级的应用框架。&lt;/strong>&lt;/p>
&lt;p>Spring 提供的核心功能主要是 &lt;strong>IoC&lt;/strong> 和 &lt;strong>AOP&lt;/strong>。学习 Spring ，一定要把 IoC 和 AOP 的核心思想搞懂！&lt;/p>
&lt;ul>
&lt;li>Spring 官网：https://spring.io/&lt;/li>
&lt;li>Github 地址： 
&lt;a target="_blank" href="https://github.com/spring-projects/spring-framework">https://github.com/spring-projects/spring-framework&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="spring-包含的模块有哪些">Spring 包含的模块有哪些？&lt;a class="anchor" href="#spring-%e5%8c%85%e5%90%ab%e7%9a%84%e6%a8%a1%e5%9d%97%e6%9c%89%e5%93%aa%e4%ba%9b">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>Spring4.x 版本&lt;/strong> ：&lt;/p></description></item><item><title>git</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyaly_dev_tools/git/</link><pubDate>Tue, 07 Feb 2023 16:14:19 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyaly_dev_tools/git/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="版本控制">版本控制&lt;a class="anchor" href="#%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是版本控制">什么是版本控制&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>版本控制&lt;/strong>是一种&lt;strong>记录一个&lt;/strong>或&lt;strong>若干文件内容变化&lt;/strong>，以便&lt;strong>将来查阅特定版本修订情况&lt;/strong>的系统。 除了&lt;strong>项目源代码&lt;/strong>，你还可以对&lt;strong>任何类型的文件&lt;/strong>进行版本控制。&lt;/p>
&lt;h3 id="为什么要版本控制">为什么要版本控制&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6">#&lt;/a>&lt;/h3>
&lt;p>有了它你就可以将某个文件&lt;strong>回溯到之前的状态&lt;/strong>，甚至将&lt;strong>整个项目都回退到过去某个时间点&lt;/strong>的状态，你可以&lt;strong>比较&lt;/strong>文件的变化细节，&lt;strong>查出最后&lt;/strong>是谁修改了哪个地方，从而&lt;strong>找出导致怪异问题&lt;/strong>出现的原因，又是&lt;strong>谁在何时报告了&lt;/strong>某个功能缺陷等等。&lt;/p>
&lt;h3 id="本地版本控制系统">本地版本控制系统&lt;a class="anchor" href="#%e6%9c%ac%e5%9c%b0%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6%e7%b3%bb%e7%bb%9f">#&lt;/a>&lt;/h3>
&lt;p>许多人习惯用&lt;strong>复制整个项目&lt;/strong>目录的方式来&lt;strong>保存不同的版本&lt;/strong>，或许还会&lt;strong>改名加上备份时间&lt;/strong>以示区别。 这么做唯一的好处就是&lt;strong>简单&lt;/strong>，但是特别容&lt;strong>易犯错&lt;/strong>。 有时候会&lt;strong>混淆&lt;/strong>所在的工作目录，一不小心会&lt;strong>写错&lt;/strong>文件或者&lt;strong>覆盖&lt;/strong>意想外的文件。&lt;/p>
&lt;p>为了解决这个问题，人们很久以前就开发了许多种&lt;strong>本地版本控制&lt;/strong>系统，大多都是采用某种&lt;strong>简单的数据库&lt;/strong>来&lt;strong>记录文件的历次更新&lt;/strong>差异。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142006392.png" alt="image.png" />&lt;/p>
&lt;h3 id="集中化的版本控制系统">集中化的版本控制系统&lt;a class="anchor" href="#%e9%9b%86%e4%b8%ad%e5%8c%96%e7%9a%84%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6%e7%b3%bb%e7%bb%9f">#&lt;/a>&lt;/h3>
&lt;p>接下来人们又遇到一个问题，如何让在&lt;strong>不同系统上的开发者协同&lt;/strong>工作？ 于是，&lt;strong>集中化的版本控制&lt;/strong>系统（&lt;strong>Centralized Version Control Systems&lt;/strong>，简称 &lt;strong>CVCS&lt;/strong>）应运而生。&lt;/p>
&lt;p>集中化的版本控制系统都有一个&lt;strong>单一的集中管理的服务器&lt;/strong>，&lt;strong>保存所有文件的修订版本&lt;/strong>，而协同工作的人们都通过客户端&lt;strong>连到这台服务器&lt;/strong>，取出&lt;strong>最新&lt;/strong>的文件或者&lt;strong>提交更新&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142006663.png" alt="image.png" />&lt;/p>
&lt;p>这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病，但也还是存在下面的问题：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>单点故障：&lt;/strong> &lt;strong>中央服务器宕机&lt;/strong>，则其他人无法使用；如果中心数据库磁盘损坏又没有进行备份，你将丢失所有数据。本地版本控制系统也存在类似问题，只要整个项目的历史记录被&lt;strong>保存在单一&lt;/strong>位置，就有&lt;strong>丢失所有历史更新记录&lt;/strong>的风险。&lt;/li>
&lt;li>&lt;strong>必须联网才能工作：&lt;/strong> &lt;strong>受网络&lt;/strong>状况、带宽影响。&lt;/li>
&lt;/ul>
&lt;h3 id="分布式版本控制系统">分布式版本控制系统&lt;a class="anchor" href="#%e5%88%86%e5%b8%83%e5%bc%8f%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6%e7%b3%bb%e7%bb%9f">#&lt;/a>&lt;/h3>
&lt;p>于是分布式版本控制系统（&lt;strong>Distributed Version Control System&lt;/strong>，简称 &lt;strong>DVCS&lt;/strong>）面世了。 Git 就是一个典型的分布式版本控制系统。&lt;/p>
&lt;p>这类系统，客户端并&lt;strong>不只提取最新版本的文件快照&lt;/strong>，而是把&lt;strong>代码仓库完整地镜像&lt;/strong>下来。 这么一来，任何一处协同工作用的服务器发生故障，事后都可以用&lt;strong>任何一个镜像出来的本地仓库&lt;/strong>恢复。 因为每一次的克隆操作，实际上都是一次对&lt;strong>代码仓库的完整备份&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142006803.png" alt="image.png" />&lt;/p>
&lt;p>分布式版本控制系统可以&lt;strong>不用联网就可以工作&lt;/strong>，因为每个人的电脑上都是完整的版本库，当你修改了某个文件后，你只需要&lt;strong>将自己的修改推送给别人&lt;/strong>就可以了。但是，在实际使用分布式版本控制系统的时候，很少会&lt;strong>直接进行推送&lt;/strong>修改，而是&lt;strong>使用一台充当“中央服务器”&lt;strong>的东西。这个服务器的作用仅仅是用来&lt;/strong>方便“交换”大家的修改&lt;/strong>，没有它大家也一样干活，只是交换修改不方便而已。&lt;/p>
&lt;p>分布式版本控制系统的优势不单是不必联网这么简单，后面我们还会看到 Git 极其&lt;strong>强大的分支管理&lt;/strong>等功能。&lt;/p>
&lt;h2 id="认识-git">认识 Git&lt;a class="anchor" href="#%e8%ae%a4%e8%af%86-git">#&lt;/a>&lt;/h2>
&lt;h3 id="git-简史">Git 简史&lt;a class="anchor" href="#git-%e7%ae%80%e5%8f%b2">#&lt;/a>&lt;/h3>
&lt;p>Linux 内核项目组当时使用分布式版本控制系统 &lt;strong>BitKeeper&lt;/strong> 来管理和维护代码。但是，后来开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束，他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 Linux 开源社区（特别是 Linux 的缔造者 &lt;strong>Linus Torvalds&lt;/strong>）基于使用 BitKeeper 时的经验教训，开发出自己的版本系统，而且对新的版本控制系统做了很多改进。&lt;/p></description></item><item><title>maven</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyaly_dev_tools/maven/</link><pubDate>Tue, 07 Feb 2023 13:28:19 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/lyaly_dev_tools/maven/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>这部分内容主要根据 Maven 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。&lt;/p>&lt;/blockquote>&lt;h2 id="maven-介绍">Maven 介绍&lt;a class="anchor" href="#maven-%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;p>
&lt;a target="_blank" href="https://github.com/apache/maven">Maven&lt;/a> 官方文档是这样介绍的 Maven 的：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project&amp;rsquo;s build, reporting and documentation from a central piece of information.&lt;/p>
&lt;p>Apache Maven 的本质是一个&lt;strong>软件项目管理&lt;/strong>和&lt;strong>理解&lt;/strong>工具。基于&lt;strong>项目对象模型&lt;/strong> (&lt;strong>Project Object Model&lt;/strong>，POM) 的概念，Maven 可以从&lt;strong>一条中心信息&lt;/strong> &lt;strong>管理&lt;/strong>项目的&lt;strong>构建&lt;/strong>、&lt;strong>报告&lt;/strong>和&lt;strong>文档&lt;/strong>。&lt;/p>&lt;/blockquote>&lt;p>&lt;strong>什么是 POM？&lt;/strong> 每一个 Maven 工程都有一个 &lt;strong>&lt;code>pom.xml&lt;/code>&lt;/strong> 文件，位于根目录中，包含项目&lt;strong>构建生命周期&lt;/strong>的详细信息。通过 &lt;code>pom.xml&lt;/code> 文件，我们可以定义&lt;strong>项目&lt;/strong>的&lt;strong>坐标&lt;/strong>、&lt;strong>项目依赖&lt;/strong>、&lt;strong>项目信息&lt;/strong>、&lt;strong>插件信息&lt;/strong>等等配置。&lt;/p>
&lt;p>对于开发者来说，Maven 的主要作用主要有 3 个：&lt;/p></description></item><item><title>Atomic预备知识</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly030301lyatomicpre/</link><pubDate>Fri, 03 Feb 2023 11:04:33 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly030301lyatomicpre/</guid><description>&lt;h3 id="java实现cas的原理非javaguide">Java实现CAS的原理[非javaguide]&lt;a class="anchor" href="#java%e5%ae%9e%e7%8e%b0cas%e7%9a%84%e5%8e%9f%e7%90%86%e9%9d%9ejavaguide">#&lt;/a>&lt;/h3>
&lt;p>i&lt;ins>是非线程安全的，因为**i&lt;/ins>不是原子&lt;strong>操作；可以使用&lt;/strong>synchronized和CAS实现加锁**&lt;/p>
&lt;p>&lt;strong>synchronized是悲观锁&lt;/strong>，一旦获得锁，其他线程进入后就会阻塞等待锁；而&lt;strong>CAS是乐观锁&lt;/strong>，执行时不会加锁，假设没有冲突，&lt;strong>如果因为冲突失败了就重试&lt;/strong>，直到成功&lt;/p>
&lt;ul>
&lt;li>
&lt;p>乐观锁和悲观锁&lt;/p>
&lt;ul>
&lt;li>这是一种分类方式&lt;/li>
&lt;li>&lt;strong>悲观锁&lt;/strong>，总是认为&lt;strong>每次访问共享资源会发生冲突&lt;/strong>，所以&lt;strong>必须对每次数据操作加锁&lt;/strong>，以&lt;strong>保证临界区的程序同一时间只能有一个线程&lt;/strong>在执行&lt;/li>
&lt;li>乐观锁，又称**“无锁”**，&lt;strong>假设对共享资源访问没有冲突&lt;/strong>，线程可以不停的执行，无需加锁无需等待；一旦发生冲突，通常是使用一种称为CAS的技术保证线程执行安全
&lt;ul>
&lt;li>无锁没有锁的存在，因此不可能发生死锁，即&lt;strong>乐观锁天生免疫死锁&lt;/strong>&lt;/li>
&lt;li>乐观锁用于**“读多写少”&lt;strong>的环境，&lt;strong>避免加锁频繁影响性能&lt;/strong>；悲观锁用于&lt;/strong>“写多读少”&lt;strong>，避免&lt;/strong>频繁失败及重试**影响性能&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>CAS概念，即CompareAndSwap ，比较和交换，CAS中，有三个值（概念上）&lt;br>
V：要更新的变量(var)；E：期望值（expected）；N：新值（new）
判断V是否等于E，如果等于，将V的值设置为N；如果不等，说明已经有其它线程更新了V，则当前线程放弃更新，什么都不做。
一般来说，预期值E本质上指的是“旧值”（判断是否修改了）&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ol>
&lt;li>如果有一个多个线程共享的变量&lt;code>i&lt;/code>原本等于5，我现在在线程A中，想把它设置为新的值6;&lt;/li>
&lt;li>我们使用CAS来做这个事情；&lt;/li>
&lt;li>（首先要把原来的值5在线程中保存起来）&lt;/li>
&lt;li>接下来是原子操作：首先我们&lt;strong>用（现在的i）去与5对比，发现它等于5，说明没有被其它线程改过，那我就把它设置为新的值6&lt;/strong>，此次CAS成功，&lt;code>i&lt;/code>的值被设置成了6；&lt;/li>
&lt;li>如果不等于5，说明&lt;code>i&lt;/code>被其它线程改过了（比如现在&lt;code>i&lt;/code>的值为2），那么我就什么也不做，此次CAS失败，&lt;code>i&lt;/code>的值仍然为2。&lt;/li>
&lt;/ol>&lt;/blockquote>&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>其中i为V，5为E，6为N&lt;/p>&lt;/blockquote>&lt;p>CAS是一种原子操作，它是一种系统原语，是一条CPU原子指令，从CPU层面保证它的原子性（&lt;strong>不可能出现说，判断了对比了i为5之后，正准备更新它的值，此时该值被其他线程改了&lt;/strong>）&lt;/p>
&lt;p>当&lt;strong>多个线程同时使用CAS操作一个变量&lt;/strong>时，&lt;strong>只有一个会胜出，并成功更新&lt;/strong>，&lt;strong>其余均会失败&lt;/strong>，但&lt;strong>失败的线程并不会被挂起&lt;/strong>，仅是&lt;strong>被告知失败，并且允许再次尝试&lt;/strong>，当然也&lt;strong>允许失败的线程放弃&lt;/strong>操作。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Java实现CAS的原理 - Unsafe类&lt;/p>
&lt;ul>
&lt;li>
&lt;p>在Java中，如果一个方法是native的，那Java就不负责具体实现它，而是交给底层的JVM使用c或者c++去实现&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java中有一个Unsafe类，在sun.misc包中，里面有一些native方法，其中包括：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">boolean&lt;/span> &lt;span style="color:#a6e22e">compareAndSwapObject&lt;/span>(Object o, &lt;span style="color:#66d9ef">long&lt;/span> offset,Object expected, Object x);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">boolean&lt;/span> &lt;span style="color:#a6e22e">compareAndSwapInt&lt;/span>(Object o, &lt;span style="color:#66d9ef">long&lt;/span> offset,&lt;span style="color:#66d9ef">int&lt;/span> expected,&lt;span style="color:#66d9ef">int&lt;/span> x);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">boolean&lt;/span> &lt;span style="color:#a6e22e">compareAndSwapLong&lt;/span>(Object o, &lt;span style="color:#66d9ef">long&lt;/span> offset,&lt;span style="color:#66d9ef">long&lt;/span> expected,&lt;span style="color:#66d9ef">long&lt;/span> x);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//------&amp;gt;AtomicInteger.class&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">AtomicInteger&lt;/span> &lt;span style="color:#66d9ef">extends&lt;/span> Number &lt;span style="color:#66d9ef">implements&lt;/span> java.&lt;span style="color:#a6e22e">io&lt;/span>.&lt;span style="color:#a6e22e">Serializable&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> serialVersionUID &lt;span style="color:#f92672">=&lt;/span> 6214790243416807050L;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// setup to use Unsafe.compareAndSwapInt for updates&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> Unsafe unsafe &lt;span style="color:#f92672">=&lt;/span> Unsafe.&lt;span style="color:#a6e22e">getUnsafe&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> valueOffset;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> valueOffset &lt;span style="color:#f92672">=&lt;/span> unsafe.&lt;span style="color:#a6e22e">objectFieldOffset&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (AtomicInteger.&lt;span style="color:#a6e22e">class&lt;/span>.&lt;span style="color:#a6e22e">getDeclaredField&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;value&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (Exception ex) { &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Error(ex); }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">volatile&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">getAndIncrement&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> unsafe.&lt;span style="color:#a6e22e">getAndAddInt&lt;/span>(&lt;span style="color:#66d9ef">this&lt;/span>, valueOffset, 1);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;pre>&lt;code>}
&lt;/code>&lt;/pre>
&lt;p>}&lt;/p></description></item><item><title>MySQL高性能优化规范建议总结</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0611lymysql-high-performance-optimization-specification-recommendations/</link><pubDate>Fri, 20 Jan 2023 11:36:34 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0611lymysql-high-performance-optimization-specification-recommendations/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>索引优化相关&lt;/p>
&lt;ol>
&lt;li>in 代替 or&lt;/li>
&lt;li>not exist 代替 not in&lt;/li>
&lt;/ol>&lt;/blockquote>&lt;h1 id="数据库命名规范">数据库命名规范&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%91%bd%e5%90%8d%e8%a7%84%e8%8c%83">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>所有&lt;strong>数据库对象&lt;/strong>名称必须使用&lt;strong>小写字母&lt;/strong>并用&lt;strong>下划线分割&lt;/strong>&lt;/li>
&lt;li>所有数据库对象名称&lt;strong>禁止使用 MySQL 保留关键字&lt;/strong>（如果表名中包含关键字查询时，需要将其用单引号括起来）&lt;/li>
&lt;li>数据库对象的命名要能做到&lt;strong>见名识意&lt;/strong>，并且最好&lt;strong>不要超过 32 个&lt;/strong>字符&lt;/li>
&lt;li>&lt;strong>临时库表&lt;/strong>必须&lt;strong>以 &lt;code>tmp_&lt;/code> 为前缀&lt;/strong>并以日期为后缀，&lt;strong>备份表&lt;/strong>必须&lt;strong>以 &lt;code>bak_&lt;/code> 为前缀&lt;/strong>并&lt;strong>以日期 (时间戳) 为后缀&lt;/strong>&lt;/li>
&lt;li>所有&lt;strong>存储相同数据的列名和列类型&lt;/strong>必须一致（&lt;strong>一般作为关联列&lt;/strong>，如果查询时关联列&lt;strong>类型不一致会自动进行数据类型隐式转换&lt;/strong>，会&lt;strong>造成列上的索引失效&lt;/strong>，导致查询效率降低）&lt;/li>
&lt;/ul>
&lt;h1 id="数据库基本设计规范">数据库基本设计规范&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%9f%ba%e6%9c%ac%e8%ae%be%e8%ae%a1%e8%a7%84%e8%8c%83">#&lt;/a>&lt;/h1>
&lt;h2 id="所有表必须使用innodb存储引擎">所有表必须使用InnoDB存储引擎&lt;a class="anchor" href="#%e6%89%80%e6%9c%89%e8%a1%a8%e5%bf%85%e9%a1%bb%e4%bd%bf%e7%94%a8innodb%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>没有特殊要求（即 InnoDB 无法满足的功能如：列存储，存储空间数据等）的情况下，所有表必须使用 InnoDB 存储引擎（MySQL5.5 之前默认使用 Myisam，5.6 以后默认的为 InnoDB）。&lt;/li>
&lt;li>InnoDB 支持&lt;strong>事务&lt;/strong>，支持&lt;strong>行级锁&lt;/strong>，更好的&lt;strong>恢复性&lt;/strong>，&lt;strong>高并发&lt;/strong>下性能更好&lt;/li>
&lt;/ul>
&lt;h2 id="数据库和表的字符集统一使用utf-8">数据库和表的字符集统一使用UTF-8&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%92%8c%e8%a1%a8%e7%9a%84%e5%ad%97%e7%ac%a6%e9%9b%86%e7%bb%9f%e4%b8%80%e4%bd%bf%e7%94%a8utf-8">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>兼容性&lt;/strong>更好，&lt;strong>统一字符集&lt;/strong>可以避免由于字符集转换产生的&lt;strong>乱码&lt;/strong>，不同的字符集进行比较前需要进行转换会造成&lt;strong>索引失效&lt;/strong>，如果数据库中有&lt;strong>存储 emoji 表情&lt;/strong>的需要，字符集需要采用 &lt;strong>utf8mb4&lt;/strong> 字符集。&lt;/p>
&lt;p>参考文章：&lt;/p>
&lt;ul>
&lt;li>
&lt;a target="_blank" href="https://blog.csdn.net/horses/article/details/107243447">MySQL 字符集不一致导致索引失效的一个真实案例open in new window&lt;/a>&lt;/li>
&lt;li>[MySQL 字符集详解&lt;/li>
&lt;/ul>
&lt;h2 id="所有表和字段都需要添加注释">所有表和字段都需要添加注释&lt;a class="anchor" href="#%e6%89%80%e6%9c%89%e8%a1%a8%e5%92%8c%e5%ad%97%e6%ae%b5%e9%83%bd%e9%9c%80%e8%a6%81%e6%b7%bb%e5%8a%a0%e6%b3%a8%e9%87%8a">#&lt;/a>&lt;/h2>
&lt;p>使用 &lt;strong>comment 从句添加表&lt;/strong>和&lt;strong>列的备注&lt;/strong>，从一开始就进行数据字典的维护&lt;/p>
&lt;h2 id="尽量控制单表数据量的大小建议控制在500万以内">尽量控制单表数据量的大小，建议控制在500万以内&lt;a class="anchor" href="#%e5%b0%bd%e9%87%8f%e6%8e%a7%e5%88%b6%e5%8d%95%e8%a1%a8%e6%95%b0%e6%8d%ae%e9%87%8f%e7%9a%84%e5%a4%a7%e5%b0%8f%e5%bb%ba%e8%ae%ae%e6%8e%a7%e5%88%b6%e5%9c%a8500%e4%b8%87%e4%bb%a5%e5%86%85">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>500 万并不是 MySQL 数据库的限制，&lt;strong>过大会造成修改表结构&lt;/strong>，&lt;strong>备份&lt;/strong>，&lt;strong>恢复&lt;/strong>都会有很大的问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>可以用&lt;strong>历史数据归档（应用于日志数据）&lt;/strong>，**分库分表（应用于业务数据）**等手段来控制数据量大小&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="谨慎使用mysql分区表">谨慎使用MySQL分区表&lt;a class="anchor" href="#%e8%b0%a8%e6%85%8e%e4%bd%bf%e7%94%a8mysql%e5%88%86%e5%8c%ba%e8%a1%a8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>分区表在&lt;strong>物理上表现为多个文件&lt;/strong>，在&lt;strong>逻辑上表现为一个表&lt;/strong>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>谨慎选择分区键，&lt;strong>跨分区查询效率可能更低&lt;/strong>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>建议采用&lt;strong>物理分表的方式管理大数据&lt;/strong>。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="经常一起使用的列放到一个表中">经常一起使用的列放到一个表中&lt;a class="anchor" href="#%e7%bb%8f%e5%b8%b8%e4%b8%80%e8%b5%b7%e4%bd%bf%e7%94%a8%e7%9a%84%e5%88%97%e6%94%be%e5%88%b0%e4%b8%80%e4%b8%aa%e8%a1%a8%e4%b8%ad">#&lt;/a>&lt;/h2>
&lt;p>避免更多的关联操作。&lt;/p></description></item><item><title>MySQL常见面试题总结</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0610lymysql-questions-01/</link><pubDate>Fri, 20 Jan 2023 11:36:06 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0610lymysql-questions-01/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!====&lt;/p>&lt;/blockquote>&lt;h1 id="mysql基础">MySQL基础&lt;a class="anchor" href="#mysql%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h1>
&lt;h2 id="关系型数据库介绍">关系型数据库介绍&lt;a class="anchor" href="#%e5%85%b3%e7%b3%bb%e5%9e%8b%e6%95%b0%e6%8d%ae%e5%ba%93%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>关系型数据库&lt;/strong>，建立在&lt;strong>关系模型&lt;/strong>的基础上的数据库。表明数据库中所&lt;strong>存储&lt;/strong>的数据之间的&lt;strong>联系&lt;/strong>（一对一、一对多、多对多）&lt;/li>
&lt;li>关系型数据库中，我们的数据都被&lt;strong>存放在各种表&lt;/strong>中（比如用户表），表中的&lt;strong>每一行&lt;/strong>存放着&lt;strong>一条数据（比如一个用户的信息）&lt;/strong>
&lt;img src="img/ly-20241212141910786.png" alt="关系型数据库表关系" />&lt;/li>
&lt;li>大部分关系型数据库都使用&lt;strong>SQL&lt;/strong>来操作数据库中的数据，并且大部分&lt;strong>关系型数据库&lt;/strong>都支持&lt;strong>事务&lt;/strong>的&lt;strong>四大特性（ACID）&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>常见的关系型数据库&lt;/strong>&lt;br>
&lt;strong>MySQL&lt;/strong>、&lt;strong>PostgreSQL&lt;/strong>、&lt;strong>Oracle&lt;/strong>、&lt;strong>SQL Server&lt;/strong>、&lt;strong>SQLite&lt;/strong>（&lt;strong>微信本地的聊天记录&lt;/strong>的存储就是用的 SQLite） &amp;hellip;&amp;hellip;&lt;/p>
&lt;h2 id="mysql介绍">MySQL介绍&lt;a class="anchor" href="#mysql%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212141911084.png" alt="img" />&lt;/p>
&lt;ul>
&lt;li>
&lt;p>MySQL是一种&lt;strong>关系型数据库&lt;/strong>，主要用于&lt;strong>持久化存储&lt;/strong>我们系统中的一些数据比如&lt;strong>用户信息&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>由于 MySQL 是&lt;strong>开源&lt;/strong>免费并且比较&lt;strong>成熟&lt;/strong>的数据库，因此，MySQL 被大量使用在各种系统中。任何人都可以在 &lt;strong>GPL(General Public License 通用性公开许可证)&lt;/strong> 的许可下下载并根据&lt;strong>个性化的需要&lt;/strong>对其进行&lt;strong>修改&lt;/strong>。MySQL 的默认端口号是&lt;strong>3306&lt;/strong>。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="mysql基础架构">MySQL基础架构&lt;a class="anchor" href="#mysql%e5%9f%ba%e7%a1%80%e6%9e%b6%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>MySQL的一个&lt;strong>简要机构图&lt;/strong>，客户端的一条&lt;strong>SQL语句&lt;/strong>在MySQL内部如何执行
&lt;img src="img/ly-20241212141911246.png" alt="ly-20241212141911246" />&lt;/li>
&lt;li>MySQL主要由几部分构成
&lt;ol>
&lt;li>&lt;strong>连接器&lt;/strong>：&lt;strong>身份认证&lt;/strong>和&lt;strong>权限相关&lt;/strong>（登录MySQL的时候）&lt;/li>
&lt;li>&lt;strong>查询缓存&lt;/strong>：执行&lt;strong>查询&lt;/strong>语句的时候，会先&lt;strong>查询缓存&lt;/strong>（MySQL8.0版本后&lt;strong>移除&lt;/strong>，因为这个功能不太实用）&lt;/li>
&lt;li>&lt;strong>分析器&lt;/strong>：&lt;strong>没有命中缓存&lt;/strong>的话，SQL语句就会经过分析器，分析器说白了就是要先看你的SQL语句&lt;strong>要干嘛&lt;/strong>，再检查你的&lt;strong>SQL语句语法&lt;/strong>是否正确&lt;/li>
&lt;li>&lt;strong>优化器&lt;/strong>：按照&lt;strong>MySQL认为最优的方案&lt;/strong>去执行&lt;/li>
&lt;li>&lt;strong>执行器&lt;/strong>：&lt;strong>执行&lt;/strong>语句，然后从&lt;strong>存储引擎返回&lt;/strong>数据。执行语句之前会&lt;strong>先判断是否有权限&lt;/strong>，如果没有权限，就会报错&lt;/li>
&lt;li>&lt;strong>插件式存储引擎&lt;/strong>：主要负责&lt;strong>数据&lt;/strong>的&lt;strong>存储&lt;/strong>和&lt;strong>读取&lt;/strong>，采用的是&lt;strong>插件式架构&lt;/strong>，支持&lt;strong>InnoDB&lt;/strong>、&lt;strong>MyISAM&lt;/strong>、&lt;strong>Memory&lt;/strong>等多种存储引擎&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h1 id="mysql存储引擎">MySQL存储引擎&lt;a class="anchor" href="#mysql%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e">#&lt;/a>&lt;/h1>
&lt;p>MySQL&lt;strong>核心&lt;/strong>在于&lt;strong>存储引擎&lt;/strong>&lt;/p>
&lt;h2 id="mysql支持哪些存储引擎默认使用哪个">MySQL支持哪些存储引擎？默认使用哪个？&lt;a class="anchor" href="#mysql%e6%94%af%e6%8c%81%e5%93%aa%e4%ba%9b%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e9%bb%98%e8%ae%a4%e4%bd%bf%e7%94%a8%e5%93%aa%e4%b8%aa">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>MySQL支持&lt;strong>多种存储引擎&lt;/strong>，可以通过&lt;code>show engines&lt;/code>命令来&lt;strong>查看MySQL支持的所有存储引擎&lt;/strong>
&lt;img src="img/ly-20241212141911409.png" alt="查看 MySQL 提供的所有存储引擎" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>默认&lt;/strong>存储引擎为InnoDB，并且，所有存储引擎中&lt;strong>只有InnoDB是事务性存储引擎&lt;/strong>，也就是说&lt;strong>只有InnoDB支持事务&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>这里使用MySQL 8.x&lt;/strong>
MySQL 5.5.5之前，MyISAM是MySQL的默认存储引擎；5.5.5之后，InnoDB是MySQL的默认存储引擎，可以通过&lt;code>select version()&lt;/code>命令查看你的MySQL版本&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>mysql&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">select&lt;/span> &lt;span style="color:#a6e22e">version&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">+-----------+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">|&lt;/span> &lt;span style="color:#a6e22e">version&lt;/span>() &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">+-----------+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">|&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>.&lt;span style="color:#ae81ff">0&lt;/span>.&lt;span style="color:#ae81ff">27&lt;/span> &lt;span style="color:#f92672">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">+-----------+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">1&lt;/span> row &lt;span style="color:#66d9ef">in&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> (&lt;span style="color:#ae81ff">0&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span> sec) &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用&lt;code>show variables like %storage_engine%&lt;/code>命令直接查看MySQL&lt;strong>当前默认的存储引擎&lt;/strong> &lt;br>
&lt;img src="img/ly-20241212141911580.png" alt="查看 MySQL 当前默认的存储引擎" />&lt;/p>
&lt;p>如果只想&lt;strong>查看数据库中某个表使用的存储引擎&lt;/strong>的话，可以使用&lt;code>show table status from db_name where name = 'table_name'&lt;/code>命令&lt;br>
&lt;img src="img/ly-20241212141911743.png" alt="查看表的存储引擎" />&lt;/p></description></item><item><title>MySQL中的隐式转换造成的索引失效</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0609lyindex-invalidation-caused-by-implicit-conversion/</link><pubDate>Thu, 19 Jan 2023 17:10:48 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0609lyindex-invalidation-caused-by-implicit-conversion/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>
&lt;p>本篇文章基于MySQL 5.7.26，原文：https://www.guitu18.com/post/2019/11/24/61.html&lt;/p>&lt;/blockquote>&lt;h1 id="前言">前言&lt;a class="anchor" href="#%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>关于&lt;strong>数据库优化&lt;/strong>，最常见的莫过于&lt;strong>索引失效&lt;/strong>，数据量多的时候比较明显，处理不及时会造成&lt;strong>雪球效应&lt;/strong>，最终导致&lt;strong>数据库卡死甚至瘫痪&lt;/strong>。&lt;/li>
&lt;li>这里说的是&lt;strong>隐式转换造成的索引失效&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h1 id="数据准备">数据准备&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%87%86%e5%a4%87">#&lt;/a>&lt;/h1>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 创建测试数据表
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> test1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#f92672">`&lt;/span>test1&lt;span style="color:#f92672">`&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>(&lt;span style="color:#ae81ff">11&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>num1&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>(&lt;span style="color:#ae81ff">11&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>num2&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">varchar&lt;/span>(&lt;span style="color:#ae81ff">11&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>type1&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>(&lt;span style="color:#ae81ff">4&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>type2&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>(&lt;span style="color:#ae81ff">4&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>str1&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">varchar&lt;/span>(&lt;span style="color:#ae81ff">100&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>str2&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">varchar&lt;/span>(&lt;span style="color:#ae81ff">100&lt;/span>) &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">PRIMARY&lt;/span> &lt;span style="color:#66d9ef">KEY&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">KEY&lt;/span> &lt;span style="color:#f92672">`&lt;/span>num1&lt;span style="color:#f92672">`&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>num1&lt;span style="color:#f92672">`&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">KEY&lt;/span> &lt;span style="color:#f92672">`&lt;/span>num2&lt;span style="color:#f92672">`&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>num2&lt;span style="color:#f92672">`&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">KEY&lt;/span> &lt;span style="color:#f92672">`&lt;/span>type1&lt;span style="color:#f92672">`&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>type1&lt;span style="color:#f92672">`&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">KEY&lt;/span> &lt;span style="color:#f92672">`&lt;/span>str1&lt;span style="color:#f92672">`&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>str1&lt;span style="color:#f92672">`&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">KEY&lt;/span> &lt;span style="color:#f92672">`&lt;/span>str2&lt;span style="color:#f92672">`&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>str2&lt;span style="color:#f92672">`&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">ENGINE&lt;/span>&lt;span style="color:#f92672">=&lt;/span>InnoDB &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">CHARSET&lt;/span>&lt;span style="color:#f92672">=&lt;/span>utf8;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 创建存储过程
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">DROP&lt;/span> &lt;span style="color:#66d9ef">PROCEDURE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> pre_test1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DELIMITER &lt;span style="color:#f92672">//&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">PROCEDURE&lt;/span> &lt;span style="color:#f92672">`&lt;/span>pre_test1&lt;span style="color:#f92672">`&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>BEGIN
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">DECLARE&lt;/span> i &lt;span style="color:#66d9ef">INT&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> autocommit &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHILE&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">10000000&lt;/span> DO
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> &lt;span style="color:#f92672">@&lt;/span>str1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">SUBSTRING&lt;/span>(&lt;span style="color:#a6e22e">MD5&lt;/span>(&lt;span style="color:#a6e22e">RAND&lt;/span>()),&lt;span style="color:#ae81ff">1&lt;/span>,&lt;span style="color:#ae81ff">20&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- 每100条数据str2产生一个null值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> i &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> &lt;span style="color:#f92672">@&lt;/span>str2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> &lt;span style="color:#f92672">@&lt;/span>str2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">@&lt;/span>str1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> END &lt;span style="color:#66d9ef">IF&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#a6e22e">test1&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span>, &lt;span style="color:#f92672">`&lt;/span>num1&lt;span style="color:#f92672">`&lt;/span>, &lt;span style="color:#f92672">`&lt;/span>num2&lt;span style="color:#f92672">`&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>type1&lt;span style="color:#f92672">`&lt;/span>, &lt;span style="color:#f92672">`&lt;/span>type2&lt;span style="color:#f92672">`&lt;/span>, &lt;span style="color:#f92672">`&lt;/span>str1&lt;span style="color:#f92672">`&lt;/span>, &lt;span style="color:#f92672">`&lt;/span>str2&lt;span style="color:#f92672">`&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">VALUES&lt;/span> (&lt;span style="color:#a6e22e">CONCAT&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>, i), &lt;span style="color:#a6e22e">CONCAT&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>, i),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">CONCAT&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>, i), i&lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>, i&lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#f92672">@&lt;/span>str1, &lt;span style="color:#f92672">@&lt;/span>str2);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- 事务优化，每一万条数据提交一次事务
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> i &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">10000&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">THEN&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> COMMIT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> END &lt;span style="color:#66d9ef">IF&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> END &lt;span style="color:#66d9ef">WHILE&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>END;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">//&lt;/span> DELIMITER ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 执行存储过程
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CALL&lt;/span> &lt;span style="color:#a6e22e">pre_test1&lt;/span>(); &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中，七个字段，首先使用存储过程生成 1000 万条测试数据， 测试表一共建立了 7 个字段（包括主键），&lt;strong>&lt;code>num1&lt;/code>&lt;strong>和&lt;/strong>&lt;code>num2&lt;/code>&lt;strong>保存的是和&lt;code>ID&lt;/code>一样的顺序数字，其中&lt;/strong>&lt;code>num2&lt;/code>是字符串类型&lt;/strong>。 &lt;strong>&lt;code>type1&lt;/code>&lt;strong>和&lt;/strong>&lt;code>type2&lt;/code>&lt;strong>保存的都是主键对 5 的取模，目的是模拟实际应用中常用&lt;/strong>类似 type 类型&lt;/strong>的数据，但是**&lt;code>type2&lt;/code>是没有建立索引&lt;strong>的。 &lt;code>str1&lt;/code>和&lt;code>str2&lt;/code>都是保存了一个 &lt;strong>20 位长度的随机字符串&lt;/strong>，&lt;/strong>&lt;code>str1&lt;/code>不能为&lt;code>NULL&lt;/code>，&lt;code>str2&lt;/code>允许为&lt;code>NULL&lt;/code>&lt;strong>，相应的生成测试数据的时候我也会&lt;/strong>在&lt;code>str2&lt;/code>字段生产少量&lt;code>NULL&lt;/code>值**（每 100 条数据产生一个&lt;code>NULL&lt;/code>值）。&lt;/p></description></item><item><title>MySQL数据库时间类型数据存储建议</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0608lysome-thoughts-on-database-storage-time/</link><pubDate>Thu, 19 Jan 2023 17:10:06 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0608lysome-thoughts-on-database-storage-time/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="不要用字符串存储日期">不要用字符串存储日期&lt;a class="anchor" href="#%e4%b8%8d%e8%a6%81%e7%94%a8%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%ad%98%e5%82%a8%e6%97%a5%e6%9c%9f">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>优点：简单直白&lt;/li>
&lt;li>缺点
&lt;ol>
&lt;li>字符串&lt;strong>占有的空间更大&lt;/strong>&lt;/li>
&lt;li>字符串存储的日期&lt;strong>效率比较低&lt;/strong>（逐个字符进行比较），&lt;strong>无法用日期相关的API&lt;/strong>进行计算和比较&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h1 id="datetime和timestamp之间抉择">Datetime和Timestamp之间抉择&lt;a class="anchor" href="#datetime%e5%92%8ctimestamp%e4%b9%8b%e9%97%b4%e6%8a%89%e6%8b%a9">#&lt;/a>&lt;/h1>
&lt;p>Datetime 和 Timestamp 是 MySQL 提供的两种&lt;strong>比较相似&lt;/strong>的保存时间的数据类型。他们两者究竟该如何选择呢？&lt;/p>
&lt;p>&lt;strong>通常我们都会首选 Timestamp&lt;/strong>&lt;/p>
&lt;h2 id="datetime类型没有时区信息">Datetime类型没有时区信息&lt;a class="anchor" href="#datetime%e7%b1%bb%e5%9e%8b%e6%b2%a1%e6%9c%89%e6%97%b6%e5%8c%ba%e4%bf%a1%e6%81%af">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>DateTime 类型是没有时区信息的（时区无关）&lt;/strong> ，DateTime 类型保存的时间都是&lt;strong>当前会话所设置的时区&lt;/strong>对应的时间。这样就会有什么问题呢？当你的时区更换之后，比如你的服务器更换地址或者更换客户端连接时区设置的话，就会导致你从数据库中读出的时间错误。不要小看这个问题，很多系统就是因为这个问题闹出了很多笑话。&lt;/li>
&lt;li>&lt;strong>Timestamp 和时区有关&lt;/strong>。Timestamp 类型字段的值会随着服务器时区的变化而变化，自动换算成相应的时间，说简单点就是&lt;strong>在不同时区&lt;/strong>，&lt;strong>查询到同一个条记录此字段的值会不一样&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>案例&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 建表
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#f92672">`&lt;/span>time_zone_test&lt;span style="color:#f92672">`&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">bigint&lt;/span>(&lt;span style="color:#ae81ff">20&lt;/span>) &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">AUTO_INCREMENT&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>date_time&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">datetime&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>time_stamp&lt;span style="color:#f92672">`&lt;/span> &lt;span style="color:#66d9ef">timestamp&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span> &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">UPDATE&lt;/span> &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">PRIMARY&lt;/span> &lt;span style="color:#66d9ef">KEY&lt;/span> (&lt;span style="color:#f92672">`&lt;/span>id&lt;span style="color:#f92672">`&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">ENGINE&lt;/span>&lt;span style="color:#f92672">=&lt;/span>InnoDB &lt;span style="color:#66d9ef">DEFAULT&lt;/span> &lt;span style="color:#66d9ef">CHARSET&lt;/span>&lt;span style="color:#f92672">=&lt;/span>utf8; 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 插入数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">INSERT&lt;/span> &lt;span style="color:#66d9ef">INTO&lt;/span> &lt;span style="color:#a6e22e">time_zone_test&lt;/span>(date_time,time_stamp) &lt;span style="color:#66d9ef">VALUES&lt;/span>(&lt;span style="color:#a6e22e">NOW&lt;/span>(),&lt;span style="color:#a6e22e">NOW&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 查看数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">select&lt;/span> date_time,time_stamp &lt;span style="color:#66d9ef">from&lt;/span> time_zone_test;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- 结果
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> +---------------------+---------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">| date_time | time_stamp |
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">+---------------------+---------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">| 2020-01-11 09:53:32 | 2020-01-11 09:53:32 |
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">+---------------------+---------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">------ 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>修改时区并查看数据&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-mysql" data-lang="mysql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> time_zone&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;+8:00&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">+---------------------+---------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">| date_time | time_stamp |
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">+---------------------+---------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">| 2020-01-11 09:53:32 | 2020-01-11 17:53:32 |
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">+---------------------+---------------------+
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">------ 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>关于MySQL时区设置的一个&lt;strong>常用sql命令&lt;/strong>&lt;/p></description></item><item><title>SQL语句在MySQL中的执行过程</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0605lyhow-sql-executed-in-mysql/</link><pubDate>Thu, 19 Jan 2023 10:20:57 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0605lyhow-sql-executed-in-mysql/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>
&lt;p>原文 
&lt;a target="_blank" href="https://github.com/kinglaw1204">https://github.com/kinglaw1204&lt;/a> 感谢作者&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>本篇文章会分析&lt;strong>一个SQL语句&lt;/strong>在MySQL的&lt;strong>执行流程&lt;/strong>，包括&lt;strong>SQL的查询&lt;/strong>在MySQL内部会怎么&lt;strong>流转&lt;/strong>，&lt;strong>SQL语句的更新&lt;/strong>是怎么完成的&lt;/li>
&lt;li>分析之前先看看&lt;strong>MySQL的基础架构&lt;/strong>，知道了MySQL由&lt;strong>哪些组件&lt;/strong>组成以及&lt;strong>这些组件的作用&lt;/strong>是什么，可以帮助我们&lt;strong>理解&lt;/strong>和&lt;strong>解决&lt;/strong>这些问题&lt;/li>
&lt;/ul>
&lt;h1 id="mysql基础架构分析">MySQL基础架构分析&lt;a class="anchor" href="#mysql%e5%9f%ba%e7%a1%80%e6%9e%b6%e6%9e%84%e5%88%86%e6%9e%90">#&lt;/a>&lt;/h1>
&lt;p>&lt;img src="img/ly-20241212141908611.png" alt="ly-20241212141908611" />&lt;/p>
&lt;h2 id="mysql基本架构概览">MySQL基本架构概览&lt;a class="anchor" href="#mysql%e5%9f%ba%e6%9c%ac%e6%9e%b6%e6%9e%84%e6%a6%82%e8%a7%88">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>下图是MySQL的简要架构图，从下图可以看到&lt;strong>用户的SQL语句&lt;/strong>在MySQL内部是&lt;strong>如何执行的&lt;/strong>&lt;/li>
&lt;li>先简单介绍一个下图涉及的一些组件的基本作用
&lt;img src="img/ly-20241212141908907.png" alt="ly-20241212141908907" />
&lt;ol>
&lt;li>&lt;strong>连接器&lt;/strong>： &lt;strong>身份认证&lt;/strong>和&lt;strong>权限相关&lt;/strong>（登录MySQL的时候）&lt;/li>
&lt;li>&lt;strong>查询缓存&lt;/strong>：执行查询语句的时候，会&lt;strong>先查询缓存&lt;/strong>（MySQL8.0版本后移除，因为这个功能不太实用）&lt;/li>
&lt;li>&lt;strong>分析器&lt;/strong>：&lt;strong>没有命中&lt;/strong>缓存的话，SQL语句就会经过分析器，分析器说白了就是要&lt;strong>先看&lt;/strong>你的SQL语句干嘛，再&lt;strong>检查&lt;/strong>你的SQL语句&lt;strong>语法&lt;/strong>是否正确&lt;/li>
&lt;li>&lt;strong>优化器&lt;/strong>：按照&lt;strong>MySQL认为最优的方案&lt;/strong>去执行&lt;/li>
&lt;li>&lt;strong>执行器&lt;/strong>：&lt;strong>执行语句&lt;/strong>，然后从&lt;strong>存储引擎&lt;/strong>返回数据&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>简单来说 MySQL 主要分为 Server 层和存储引擎层：
&lt;ul>
&lt;li>&lt;strong>Server 层&lt;/strong>：主要包括&lt;strong>连接器&lt;/strong>、&lt;strong>查询缓存&lt;/strong>、&lt;strong>分析器&lt;/strong>、&lt;strong>优化器&lt;/strong>、&lt;strong>执行器&lt;/strong>等，所有&lt;strong>跨存储引擎&lt;/strong>的功能都在这一层实现，比如&lt;strong>存储过程&lt;/strong>、&lt;strong>触发器&lt;/strong>、&lt;strong>视图&lt;/strong>，&lt;strong>函数&lt;/strong>等，还有一个&lt;strong>通用的日志模块 binlog 日志模块&lt;/strong>。&lt;/li>
&lt;li>&lt;strong>存储引擎&lt;/strong>： 主要负责数据的&lt;strong>存储&lt;/strong>和&lt;strong>读取&lt;/strong>，采用&lt;strong>可以替换的插件式架构&lt;/strong>，支持 InnoDB、MyISAM、Memory 等多个存储引擎，其中 InnoDB 引擎有自有的日志模块 &lt;strong>redolog 模块&lt;/strong>。&lt;strong>现在最常用的存储引擎是 InnoDB，它从 MySQL 5.5 版本开始就被当做默认存储引擎了&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="server层基本组件介绍">Server层基本组件介绍&lt;a class="anchor" href="#server%e5%b1%82%e5%9f%ba%e6%9c%ac%e7%bb%84%e4%bb%b6%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212141909073.png" alt="ly-20241212141909073" />&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>连接器&lt;/strong>
连接器主要和&lt;strong>身份认证&lt;/strong>和&lt;strong>权限相关&lt;/strong>的功能相关，就好比一个级别很高的门卫一样&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>主要负责用户登录数据库，进行用户的身份认证，包括校验账户密码，权限等操作，如果用户&lt;strong>账户密码&lt;/strong>已通过，连接器会到&lt;strong>权限表&lt;/strong>中查询&lt;strong>该用户的所有权限&lt;/strong>，之后在这个连接里的权限逻辑判断都是会依赖&lt;strong>此时读取到的权限数据&lt;/strong>，也就是说，&lt;strong>后续只要这个连接不断开&lt;/strong>，&lt;strong>即使&lt;/strong>管理员&lt;strong>修改&lt;/strong>了该用户的权限，该用户也是不受影响的。&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>&lt;strong>查询缓存（MySQL8.0 版本后移除）&lt;/strong>&lt;br>
查询缓存&lt;strong>主要用来缓存&lt;/strong>我们所执行的 &lt;strong>SELECT 语句&lt;/strong>以及该&lt;strong>语句的结果集&lt;/strong>。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ul>
&lt;li>
&lt;p>连接建立后，执行查询语句的时候，会先查询缓存，MySQL 会先校验这个 SQL 是否执行过，以 &lt;strong>Key-Value&lt;/strong> 的形式缓存在内存中，Key 是查询预计，Value 是结果集。如果缓存 key 被命中，就会直接返回给客户端，如果没有命中，就会执行后续的操作，完成后也会把结果缓存起来，方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限，是否有该表的查询条件。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>MySQL 查询不建议使用缓存，因为查询缓存失效在实际业务场景中可能会非常频繁，假如&lt;strong>你对一个表更新&lt;/strong>的话，&lt;strong>这个表上的所有的查询缓存都会被清空&lt;/strong>。对于&lt;strong>不经常更新的数据&lt;/strong>来说，&lt;strong>使用缓存还是可以&lt;/strong>的。所以，一般在大多数情况下我们都是不推荐去使用查询缓存的。&lt;/p>
&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;p>MySQL &lt;strong>8.0 版本后删除了缓存&lt;/strong>的功能，官方也是认为该功能在实际的应用场景比较少，所以干脆直接删掉了&lt;/p></description></item><item><title>innodb引擎对MVCC的实现</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0604lyinnodb-implementation-of-mvcc/</link><pubDate>Mon, 16 Jan 2023 19:23:55 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0604lyinnodb-implementation-of-mvcc/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="一致性非锁定读和锁定读">一致性非锁定读和锁定读&lt;a class="anchor" href="#%e4%b8%80%e8%87%b4%e6%80%a7%e9%9d%9e%e9%94%81%e5%ae%9a%e8%af%bb%e5%92%8c%e9%94%81%e5%ae%9a%e8%af%bb">#&lt;/a>&lt;/h1>
&lt;h2 id="一致性非锁定读">一致性非锁定读&lt;a class="anchor" href="#%e4%b8%80%e8%87%b4%e6%80%a7%e9%9d%9e%e9%94%81%e5%ae%9a%e8%af%bb">#&lt;/a>&lt;/h2>
&lt;p>★★非锁定★★&lt;/p>
&lt;ul>
&lt;li>对于&lt;strong>一致性非锁定读（Consistent Nonlocking Reads）&lt;strong>的实现，通常做法是&lt;/strong>加一个版本号&lt;/strong>或者&lt;strong>时间戳&lt;/strong>字段，在更新数据的同时&lt;strong>版本号+1&lt;/strong>或者&lt;strong>更新时间戳&lt;/strong>。查询时，将&lt;strong>当前可见的版本号&lt;/strong>与&lt;strong>对应记录的版本号&lt;/strong>进行比对，如果&lt;strong>记录的版本&lt;/strong>小于&lt;strong>可见版本&lt;/strong>，则表示&lt;strong>该记录可见&lt;/strong>&lt;/li>
&lt;li>&lt;strong>InnoDB&lt;/strong>存储引擎中，&lt;strong>多版本控制（multi versioning）&lt;strong>即是非锁定读的实现。如果读取的行&lt;/strong>正在执行DELETE&lt;/strong>或&lt;strong>UPDATE&lt;/strong>操作，这时读取操作&lt;strong>不会去等待行上&lt;/strong> &lt;strong>锁的释放&lt;/strong>.相反地，Inn哦DB存储引擎会去读取&lt;strong>行的一个快照数据&lt;/strong>，对于这种&lt;strong>读取历史数据&lt;/strong>的方式，我们叫它&lt;strong>快照读（snapshot read）&lt;/strong>。&lt;/li>
&lt;li>在 &lt;strong>&lt;code>Repeatable Read&lt;/code>&lt;/strong> 和 &lt;strong>&lt;code>Read Committed&lt;/code>&lt;/strong> 两个隔离级别下，如果是执行普通的 &lt;code>select&lt;/code> 语句（&lt;strong>不包括 &lt;code>select ... lock in share mode&lt;/code> ,&lt;code>select ... for update&lt;/code>&lt;/strong>）则会使用 &lt;strong>&lt;code>一致性非锁定读（MVCC）&lt;/code>&lt;/strong>。并且在 &lt;strong>&lt;code>Repeatable Read&lt;/code> 下 &lt;code>MVCC&lt;/code> 实现了可重复读和防止部分幻读&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="锁定读">锁定读&lt;a class="anchor" href="#%e9%94%81%e5%ae%9a%e8%af%bb">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>如果执行的是下列语句，就是&lt;strong>锁定读（Locking Reads）&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;code>select ... lock in share&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>select ... for update&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>insert &lt;/code>、&lt;code>upate&lt;/code>、&lt;code>delete&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>锁定读下，读取的是数据的最新版本，这种读也被称为&lt;strong>当前读current read&lt;/strong>。&lt;strong>锁定读&lt;/strong>会对读取到的记录加锁&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>select ... lock in share mode &lt;/code>：对(读取到的)记录加&lt;strong>S锁&lt;/strong>，其他事务也可以加S锁，如果加X锁则会被阻塞&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>select ... for update&lt;/code>、&lt;code>insert&lt;/code>、&lt;code>update&lt;/code>、&lt;code>delete&lt;/code>：对记录加&lt;strong>X锁&lt;/strong>，且其他事务不能加任何锁&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>在一致性非锁定读下，即使读取的记录&lt;strong>已被其他事务加上X锁&lt;/strong>，这时记录也是可以被读取的，即读取的&lt;strong>快照数据&lt;/strong>。&lt;/p>
&lt;ol>
&lt;li>在&lt;strong>RepeatableRead&lt;/strong>下MVCC&lt;strong>防止了部分幻读&lt;/strong>，这边的“&lt;strong>部分”&lt;strong>是指在&lt;/strong>一致性非锁定读&lt;/strong>情况下，只能读取到第一次查询之前所插入的数据（&lt;strong>根据ReadView判断数据可见性，ReadView在第一次查询时生成&lt;/strong>），但如果是&lt;strong>当前读&lt;/strong>，每次读取的都是&lt;strong>最新数据&lt;/strong>，这时如果两次查询中间有其他事务插入数据，就会&lt;strong>产生幻读&lt;/strong>&lt;/li>
&lt;li>所以，InnoDB在实现RepeatableRead时，如果执行的是&lt;strong>当前读&lt;/strong>，则会对读取的记录使用&lt;strong>Next-key Lock&lt;/strong>，来防止其他事务在&lt;strong>间隙间插入数据&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;blockquote class='book-hint '>
&lt;p>&lt;strong>RR产生幻读的另一个场景&lt;/strong>&lt;/p></description></item><item><title>MySQL事务隔离级别详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0603lytransaction-isolation-level/</link><pubDate>Mon, 16 Jan 2023 01:00:44 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0603lytransaction-isolation-level/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="事务隔离级别总结">事务隔离级别总结&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1%e9%9a%94%e7%a6%bb%e7%ba%a7%e5%88%ab%e6%80%bb%e7%bb%93">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>SQL标准定义了&lt;strong>四个隔离级别&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>READ-UNCOMMITTED(读取未提交)&lt;/strong>：&lt;strong>最低&lt;/strong>的隔离级别，允许读取&lt;strong>尚未提交的数据&lt;/strong>变更，可能会导致&lt;strong>脏读、幻读或不可重复读&lt;/strong>&lt;/li>
&lt;li>&lt;strong>READ-COMMITED(读取已提交)&lt;/strong>：允许读取&lt;strong>并发事务&lt;/strong> &lt;strong>已经提交&lt;/strong>的数据，可以阻止&lt;strong>脏读&lt;/strong>，但是&lt;strong>幻读&lt;/strong>或&lt;strong>不可重复读&lt;/strong>仍有可能发生&lt;/li>
&lt;li>&lt;strong>REPEATABLE-READ(可重复读)&lt;/strong>：对&lt;strong>同一字段的多次读取&lt;/strong>结果都是一致的，除非数据是被&lt;strong>本身事务自己&lt;/strong>所修改，可以&lt;strong>阻止脏读&lt;/strong>和&lt;strong>不可重复读&lt;/strong>，但&lt;strong>幻读&lt;/strong>仍有可能发生&lt;/li>
&lt;li>&lt;strong>SERIALIZABLE(可串行化)&lt;/strong>：&lt;strong>最高&lt;/strong>的隔离级别，&lt;strong>完全服从ACID&lt;/strong>的隔离级别。所有的&lt;strong>事务依次逐个&lt;/strong>执行，这样事务之间就&lt;strong>完全不可能产生干扰&lt;/strong>，也就是说，该级别可以防止&lt;strong>脏读&lt;/strong>、&lt;strong>不可重复读&lt;/strong>以及&lt;strong>幻读&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th style="text-align: center">隔离级别&lt;/th>
 &lt;th style="text-align: center">脏读&lt;/th>
 &lt;th style="text-align: center">不可重复读&lt;/th>
 &lt;th style="text-align: center">幻读&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td style="text-align: center">READ-UNCOMMITTED&lt;/td>
 &lt;td style="text-align: center">√&lt;/td>
 &lt;td style="text-align: center">√&lt;/td>
 &lt;td style="text-align: center">√&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">READ-COMMITTED&lt;/td>
 &lt;td style="text-align: center">×&lt;/td>
 &lt;td style="text-align: center">√&lt;/td>
 &lt;td style="text-align: center">√&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">REPEATABLE-READ&lt;/td>
 &lt;td style="text-align: center">×&lt;/td>
 &lt;td style="text-align: center">×&lt;/td>
 &lt;td style="text-align: center">√&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">SERIALIZABLE&lt;/td>
 &lt;td style="text-align: center">×&lt;/td>
 &lt;td style="text-align: center">×&lt;/td>
 &lt;td style="text-align: center">×&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;/li>
&lt;li>
&lt;p>MySQL InnoDB 存储引擎的默认支持的隔离级别是 &lt;strong>REPEATABLE-READ（可重读）&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>使用命令查看，通过&lt;code>SELECT @@tx_isolation;&lt;/code>。&lt;br>
MySQL 8.0 该命令改为&lt;code>SELECT @@transaction_isolation;&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>MySQL&amp;gt; SELECT @@tx_isolation;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+-----------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| @@tx_isolation |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+-----------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| REPEATABLE-READ |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+-----------------+ &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>从上面对SQL标准定义了&lt;strong>四个隔离级别&lt;/strong>的介绍可以看出，标准的SQL隔离级别里，&lt;strong>REPEATABLE-READ(可重复读)&lt;strong>是不可以防止幻读的。但是，&lt;strong>InnoDB实现的REPEATABLE-READ&lt;/strong> 隔离级别其实是可以&lt;/strong>解决幻读&lt;/strong>问题发生的，分两种情况&lt;/p>
&lt;ol>
&lt;li>&lt;strong>快照读&lt;/strong>：由&lt;strong>MVCC&lt;/strong>机制来保证不出现幻读&lt;/li>
&lt;li>&lt;strong>当前读&lt;/strong>：使用&lt;strong>Next-Key Lock&lt;/strong>进行&lt;strong>加锁&lt;/strong>来保证不出现幻读，Next-Key Lock是&lt;strong>行锁（Record Lock ）和间隙锁（Gap Lock）的结合&lt;/strong>，行锁只能锁住已经存在的行，为了避免插入新行，需要依赖&lt;strong>间隙锁&lt;/strong> (&lt;strong>只用间隙锁不行，因为间隙锁是 &amp;gt; 或 &amp;lt; ，不包括等于，所以再可重复读下原记录可能会被删掉&lt;/strong>)&lt;/li>
&lt;/ol>
&lt;p>因为&lt;strong>隔离级别越低，事务请求的锁越少&lt;/strong>，所以大部分数据库系统的隔离级别都是 &lt;strong>READ-COMMITTED&lt;/strong> ，但是你要知道的是 InnoDB 存储引擎默认使用 &lt;strong>REPEATABLE-READ&lt;/strong> 并不会有任何性能损失。&lt;/p></description></item><item><title>日志</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0602lymysql-logs/</link><pubDate>Sat, 14 Jan 2023 17:31:53 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0602lymysql-logs/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="前言">前言&lt;a class="anchor" href="#%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>首先要了解一个东西 ：WAL，全称 Write-Ahead Logging&lt;code>，它的关键点就是&lt;/code>先写日志，再写磁盘&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>在概念上，innodb通过***force log at commit***机制实现事务的持久性，即在事务提交的时候，必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;ol>
&lt;li>
&lt;p>WAL 机制的原理也很简单：&lt;strong>修改并不直接写入到数据库文件中，而是写入到另外一个称为 WAL 的文件中；如果事务失败，WAL 中的记录会被忽略，撤销修改；如果事务成功，它将在随后的某个时间被写回到数据库文件中，提交修改&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用 WAL 的数据库系统不会再每新增一条 WAL 日志就将其刷入数据库文件中，一般&lt;strong>积累一定的量然后批量写入，通常使用页为单位，这是磁盘的写入单位&lt;/strong>。 同步 &lt;strong>WAL 文件和数据库文件的行为被称为 checkpoint（检查点）&lt;/strong>，一般在 WAL 文件积累到一定页数修改的时候；当然，有些系统也可以手动执行 checkpoint。执行 checkpoint 之后，WAL 文件可以被清空，这样可以保证 WAL 文件不会因为太大而性能下降。&lt;/p>
&lt;p>有些数据库系统读取请求也可以使用 WAL，通过读取 WAL 最新日志就可以获取到数据的最新状态&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>关于checkpoint：https://www.cnblogs.com/chenpingzhao/p/5107480.html&lt;strong>思考一下这个场景&lt;/strong>：如果重做日志可以无限地增大，同时缓冲池也足够大 ，那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时，完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。但是这需要两个前提条件：1、缓冲池可以缓存数据库中所有的数据；2、重做日志可以无限增大&lt;/p>
&lt;p>因此Checkpoint（检查点）技术就诞生了，目的是解决以下几个问题：1、&lt;strong>缩短数据库的恢复时间&lt;/strong>；2、&lt;strong>缓冲池不够用时，将脏页刷新到磁盘&lt;/strong>；3、&lt;strong>重做日志不可用时，刷新脏页&lt;/strong>。&lt;/p>
&lt;ul>
&lt;li>当数据库发生宕机时，数据库&lt;strong>不需要重做所有的日志，因为Checkpoint之前的页都已经刷新回磁盘&lt;/strong>。数据库只需对Checkpoint后的重做日志进行恢复，这样就大大缩短了恢复的时间。&lt;/li>
&lt;li>当缓冲池不够用时，根据LRU算法会溢出最近最少使用的页，若此页为脏页，那么需要强制执行Checkpoint，将脏页也就是页的新版本刷回磁盘。&lt;/li>
&lt;li>当重做日志出现不可用时，因为当前事务数据库系统对重做日志的设计都是循环使用的，并不是让其无限增大的，重做日志可以被重用的部分是指这些重做日志已经不再需要，当数据库发生宕机时，数据库恢复操作不需要这部分的重做日志，因此这部分就可以被覆盖重用。如果重做日志还需要使用，那么必须强制Checkpoint，将缓冲池中的页至少刷新到当前重做日志的位置。&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>mysql 的 WAL，大家可能都比较熟悉。mysql 通过 redo、undo 日志实现 WAL。redo log 称为重做日志，每当有操作时，在&lt;strong>数据变更之前将操作写入 redo log&lt;/strong>，这样当发生掉电之类的情况时系统可以在重启后继续操作。undo log 称为撤销日志，当一些变更执行到一半无法完成时，可以根据撤销日志恢复到变更之间的状态。mysql 中用 redo log 来在系统 Crash 重启之类的情况时修复数据（事务的持久性），而 undo log 来保证事务的原子性。&lt;/p></description></item><item><title>索引</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0601lymysql-index/</link><pubDate>Tue, 10 Jan 2023 22:09:25 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/MySQL/ly0601lymysql-index/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="补充索引基础知识引自b站sgg视频">补充索引基础知识(引自b站sgg视频)&lt;a class="anchor" href="#%e8%a1%a5%e5%85%85%e7%b4%a2%e5%bc%95%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86%e5%bc%95%e8%87%aab%e7%ab%99sgg%e8%a7%86%e9%a2%91">#&lt;/a>&lt;/h1>
&lt;ol>
&lt;li>存储引擎，数据的基本单位是&lt;strong>页&lt;/strong>，如果数据很少，只有一页，那就简单，是直接二分查找(不涉及磁盘IO)；如果数据很多，有好几个页，那么需要对页建立一种数据结构，能够最快定位到哪一页，然后减少磁盘IO&lt;/li>
&lt;/ol>
&lt;h1 id="索引介绍">索引介绍&lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>索引是一种用于&lt;strong>快速查询&lt;/strong>和&lt;strong>检索数据&lt;/strong>的数据结构，其本质可以看成是一种&lt;strong>排序好&lt;/strong>的数据结构&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>索引的作用就相当于书的目录。打个比方: 我们在查字典的时候，如果没有目录，那我们就只能一页一页的去找我们需要查的那个字，速度很慢。如果有目录了，我们只需要先去目录里查找字的位置，然后直接翻到那一页就行了&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>索引底层数据结构存在很多种类型，常见的索引结构有：&lt;strong>B树&lt;/strong>，&lt;strong>B+树&lt;/strong>和&lt;strong>Hash&lt;/strong>、&lt;strong>红黑树&lt;/strong>。在MySQL中，无论是Innodb还是MyIsam，都使用了B+树作为索引结构&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="索引的优缺点">索引的优缺点&lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95%e7%9a%84%e4%bc%98%e7%bc%ba%e7%82%b9">#&lt;/a>&lt;/h1>
&lt;p>&lt;strong>优点：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>使用索引可以大大&lt;strong>加快&lt;/strong> 数据的检索速度（大大&lt;strong>减少检索的数据量&lt;/strong>）, 这也是创建索引的最主要的原因。&lt;/li>
&lt;li>通过创建&lt;strong>唯一性索引&lt;/strong>，可以保证数据库表中&lt;strong>每一行数据的唯一性&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>缺点：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>创建索引&lt;/strong>和&lt;strong>维护索引&lt;/strong>需要&lt;strong>耗费许多&lt;/strong>时间。当对表中的数据进行&lt;strong>增删改&lt;/strong>的时候，如果数据有索引，那么&lt;strong>索引也需要动态的修改&lt;/strong>，会&lt;strong>降低 SQL 执行效率&lt;/strong>。&lt;/li>
&lt;li>索引需要使用&lt;strong>物理文件存储&lt;/strong>，也会&lt;strong>耗费一定空间&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>索引一定会提高查询性能吗&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>多数情况下，&lt;strong>索引查询&lt;/strong>都是比&lt;strong>全表扫描&lt;/strong>要快的。但是如果数据库的&lt;strong>数据量不大&lt;/strong>，那么使用&lt;strong>索引也不一定能够带来很大提升&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h1 id="索引的底层数据结构">索引的底层数据结构&lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95%e7%9a%84%e5%ba%95%e5%b1%82%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;h2 id="hash表">Hash表&lt;a class="anchor" href="#hash%e8%a1%a8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>哈希表是键值对的集合，通过&lt;strong>键（key）&lt;strong>即可&lt;/strong>快速取出对应的值（value）&lt;/strong>，因此哈希表可以&lt;strong>快速检索数据（接近O(1))&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>为何能够通过key快速取出value呢？原因在于&lt;strong>哈希算法（也叫散列算法）&lt;/strong>。通过哈希算法，我们可以&lt;strong>快速找到key对应的index&lt;/strong>，找到了index也就找到了对应的value&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>hash &lt;span style="color:#f92672">=&lt;/span> hashfunc(key)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>index &lt;span style="color:#f92672">=&lt;/span> hash &lt;span style="color:#f92672">%&lt;/span> array_size&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意，图中keys[天蓝色]是&lt;strong>字符串&lt;/strong>，&lt;strong>不是什么莫名其妙的人&lt;/strong>
&lt;img src="img/ly-20241212141858665.png" alt="ly-20241212141858665" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>哈希算法有个 &lt;strong>Hash 冲突&lt;/strong> 问题，也就是说&lt;strong>多个不同的 key 最后得到的 index 相同&lt;/strong>。通常情况下，我们常用的解决办法是 &lt;strong>链地址法&lt;/strong>。链地址法就是&lt;strong>将哈希冲突数据存放在链表中&lt;/strong>。就比如 JDK1.8 之前 &lt;code>HashMap&lt;/code> 就是通过&lt;strong>链地址法来解决哈希冲突&lt;/strong>的。不过，JDK1.8 以后&lt;code>HashMap&lt;/code>&lt;strong>为了减少链表过长&lt;/strong>的时候搜索时间过长引入了&lt;strong>红黑树&lt;/strong>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>为了减&lt;strong>少 Hash 冲突&lt;/strong>的发生，一个&lt;strong>好的哈希函数&lt;/strong>应该**“均匀地”将数据分布**在整个可能的哈希值集合中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>由于&lt;strong>Hash索引&lt;/strong>不支持&lt;strong>顺序&lt;/strong>和&lt;strong>范围查询&lt;/strong>，假如要对表中的数据进行&lt;strong>排序&lt;/strong>或者&lt;strong>进行范围查询&lt;/strong>，那&lt;strong>Hash索引&lt;/strong>就不行了，并且，&lt;strong>每次IO&lt;/strong>只能取一个&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>例如： &lt;code>SELECT * FROM tb1 WHERE id &amp;lt; 500 ; &lt;/code>&lt;/p>
&lt;ul>
&lt;li>这种范围查询中，B+树 优势非常大 直接遍历比500小的&lt;strong>叶子&lt;/strong>节点即可&lt;/li>
&lt;li>如果使用Hash索引，由于&lt;strong>Hash索引&lt;/strong>是根据hash算法来定位的，难不成把1 ~499 （小于500）的数据都进行一次hash计算来定位吗？这就是Hash最大的缺点&lt;/li>
&lt;/ul>
&lt;p>这里其实说的是已经找到了索引，但是索引没有数据的情形。要么通过&lt;strong>hash&lt;/strong>一个个取数据，要么利用&lt;strong>B+树&lt;/strong>的特性（叶子节点有完整数据）&lt;/p></description></item><item><title>字符集详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0502lycharactor-set/</link><pubDate>Tue, 10 Jan 2023 16:48:22 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0502lycharactor-set/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>图示总结&lt;br>
&lt;img src="img/ly-20241212141855666.png" alt="ly-20241212141855666" />&lt;/p>
&lt;ul>
&lt;li>MySQL字符编码集有&lt;strong>两套UTF-8&lt;/strong>编码实现：&lt;strong>utf-8&lt;/strong> 和 &lt;strong>utf8mb4&lt;/strong>&lt;br>
而其中，utf-8 &lt;strong>不支持存储&lt;/strong>emoji符号和一些比较复杂的汉字、繁体字，会出错&lt;/li>
&lt;/ul>
&lt;h1 id="何为字符集">何为字符集&lt;a class="anchor" href="#%e4%bd%95%e4%b8%ba%e5%ad%97%e7%ac%a6%e9%9b%86">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>字符是&lt;strong>各种文字&lt;/strong>和&lt;strong>符号&lt;/strong>的统称，包括&lt;strong>各个国家文字&lt;/strong>、&lt;strong>标点符号&lt;/strong>、&lt;strong>表情&lt;/strong>、&lt;strong>数字&lt;/strong>等等&lt;/p>
&lt;ul>
&lt;li>字符集就是&lt;strong>一系列字符&lt;/strong>的集合，字符集的种类较多，每个字符集可以表示的&lt;strong>字符范围&lt;/strong>通常不同，就比如说&lt;strong>有些字符集&lt;/strong>无法表示汉字&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>计算机只能存储&lt;strong>二进制&lt;/strong>的数据，那&lt;strong>英文&lt;/strong>、&lt;strong>汉字&lt;/strong>、&lt;strong>表情&lt;/strong>等字符应该如何存储呢&lt;/p>
&lt;ul>
&lt;li>
&lt;p>我们要将这些&lt;strong>字符和二进制的数据一一对应&lt;/strong>起来，比如说字符“a”对应“01100001”，反之，“01100001”对应 “a”。我们将字符对应二进制数据的过程称为&amp;quot;&lt;strong>字符编码&lt;/strong>&amp;quot;，反之，二进制数据解析成字符的过程称为“&lt;strong>字符解码&lt;/strong>”。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141855958.png" alt="ly-20241212141855958" />&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="有哪些常见的字符集">有哪些常见的字符集&lt;a class="anchor" href="#%e6%9c%89%e5%93%aa%e4%ba%9b%e5%b8%b8%e8%a7%81%e7%9a%84%e5%ad%97%e7%ac%a6%e9%9b%86">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>常见的字符集有&lt;strong>ASCLL&lt;/strong>、&lt;strong>GB2312&lt;/strong>、&lt;strong>GBK&lt;/strong>、&lt;strong>UTF-8&lt;/strong>&lt;/li>
&lt;li>不同的字符集的主要区别在于
&lt;ol>
&lt;li>可以表示的&lt;strong>字符范围&lt;/strong>&lt;/li>
&lt;li>&lt;strong>编码方式&lt;/strong>&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h2 id="ascll">ASCLL&lt;a class="anchor" href="#ascll">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>ASCII&lt;/strong> (&lt;strong>A&lt;/strong>merican &lt;strong>S&lt;/strong>tandard &lt;strong>C&lt;/strong>ode for &lt;strong>I&lt;/strong>nformation &lt;strong>I&lt;/strong>nterchange，美国信息交换标准代码) 是一套主要用于现代美国英语的字符集（这也是 ASCII 字符集的局限性所在）&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>&lt;strong>为什么 ASCII 字符集没有考虑到中文等其他字符呢？&lt;/strong> 因为计算机是美国人发明的，当时，计算机的发展还处于比较雏形的时代，还未在其他国家大规模使用。因此，美国发布 ASCII 字符集的时候没有考虑兼容其他国家的语言&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>ASCII 字符集至今为止共定义了 128 个字符，其中有 33 个控制字符（比如回车、删除）无法显示&lt;/p>
&lt;/li>
&lt;li>
&lt;p>一个 ASCII 码长度是一个字节也就是 8 个 bit，比如“a”对应的 ASCII 码是“01100001”。不过，最高位是 0 仅仅作为校验位，其余 7 位使用 0 和 1 进行组合，所以，ASCII 字符集可以定义 128（2^7）个字符&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>由于，ASCII 码可以表示的字符实在是太少了。后来，人们对其进行了扩展得到了 &lt;strong>ASCII 扩展字符集&lt;/strong> 。ASCII 扩展字符集使用 8 位（bits）表示一个字符，所以，ASCII 扩展字符集可以定义 256（2^8）个字符&lt;/p></description></item><item><title>树</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/tree/</link><pubDate>Mon, 09 Jan 2023 15:52:34 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/tree/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>
&lt;p>树是一种类似现实生活中的树的数据结构（&lt;strong>倒置的树&lt;/strong>）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>任何一颗非空树&lt;strong>只有一个根节点&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>一棵树具有以下特点：&lt;/p>
&lt;ol>
&lt;li>一棵树中的任何两个节点有且仅有唯一的一条路相通 （因为每个结点只会有一个父节点）&lt;/li>
&lt;li>一棵树如果有n个节点，那么它一定恰好有n-1条边&lt;/li>
&lt;li>一棵树不包括回路&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>下面是一颗&lt;strong>二叉树&lt;/strong>
&lt;img src="img/ly-20241212141852140.png" alt="ly-20241212141852140" />
深度和高度是对应的；根节点所在层为1层&lt;/p>
&lt;/li>
&lt;li>
&lt;p>常用概念&lt;/p>
&lt;ol>
&lt;li>
&lt;p>节点：树中每个元素都可以统称为节点&lt;/p>
&lt;/li>
&lt;li>
&lt;p>根节点：顶层节点，或者说没有父节点的节点。上图中A节点就是根节点&lt;/p>
&lt;/li>
&lt;li>
&lt;p>父节点：若一个节点含有子节点，则这个节点称为其子节点的父节点。上图中的 B 节点是 D 节点、E 节点的父节点&lt;/p>
&lt;/li>
&lt;li>
&lt;p>兄弟节点：具有&lt;strong>相同父节点的节点&lt;/strong>互称为兄弟节点。上图中 D 节点、E 节点的共同父节点是 B 节点，故 D 和 E 为兄弟节点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>叶子节点：&lt;strong>没有子节点&lt;/strong>的节点。上图中的 D、F、H、I 都是叶子节点&lt;/p>
&lt;/li>
&lt;li>
&lt;p>节点的高度**（跟叶子节点有关，同一层不一定一样）&lt;strong>：&lt;strong>该节点到叶子节点&lt;/strong>的&lt;/strong>最长&lt;strong>路径&lt;/strong>所包含的&lt;strong>边数&lt;/strong>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>节点的深度**（跟根节点有关，同一层是一样的）&lt;strong>：&lt;strong>根节点到该节点&lt;/strong>的路径所包含的&lt;/strong>边数**&lt;/p>
&lt;/li>
&lt;li>
&lt;p>节点的层数：节点的深度+1&lt;/p>
&lt;/li>
&lt;li>
&lt;p>树的高度：根节点的高度&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h1 id="二叉树的分类">二叉树的分类&lt;a class="anchor" href="#%e4%ba%8c%e5%8f%89%e6%a0%91%e7%9a%84%e5%88%86%e7%b1%bb">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>**二叉树（Binary tree）**是每个节点最多只有两个分支（即不存在分支度大于2的节点）的树结构&lt;/li>
&lt;li>&lt;strong>二叉树&lt;/strong>的分支，通常被称为&lt;strong>左子树&lt;/strong>或&lt;strong>右子树&lt;/strong>，并且，&lt;strong>二叉树&lt;/strong>的分支具有左右次序，不能随意颠倒&lt;/li>
&lt;li>&lt;strong>二叉树&lt;/strong>的第i层至多拥有&lt;strong>2^(i-1)&lt;/strong> 个节点&lt;br>
深度为&lt;strong>k&lt;/strong>的二叉树至多总共有 &lt;strong>2^(k+1) -1&lt;/strong> 个节点 （深度为k，最多k + 1 层，最多为满二叉树的情况）&lt;br>
至少有2^(k) 个节点，即 深度为k-1的二叉树的最多的节点再加1&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>（关于节点的深度的定义国内争议比较多，我个人比较认可维基百科对
&lt;a target="_blank" href="https://zh.wikipedia.org/wiki/%E6%A0%91_%28%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%29#/%E6%9C%AF%E8%AF%AD">节点深度的定义open in new window&lt;/a>）。
&lt;img src="img/ly-20241212141852434.png" alt="ly-20241212141852434" />&lt;/p>&lt;/blockquote>&lt;h2 id="满二叉树">满二叉树&lt;a class="anchor" href="#%e6%bb%a1%e4%ba%8c%e5%8f%89%e6%a0%91">#&lt;/a>&lt;/h2>
&lt;p>一个二叉树，如果每一个&lt;strong>层&lt;/strong>的结点数都达到最大值，则这个二叉树就是 &lt;strong>满二叉树&lt;/strong>。也就是说，如果一个&lt;strong>二叉树的层数&lt;/strong>为 K，且结点总数是(2^k) -1 ，则它就是 &lt;strong>满二叉树&lt;/strong>。
&lt;img src="img/ly-20241212141852640.png" alt="ly-20241212141852640" />&lt;/p></description></item><item><title>堆</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/heap/</link><pubDate>Fri, 06 Jan 2023 10:46:19 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/heap/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="什么是堆">什么是堆&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%a0%86">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>堆是&lt;strong>满足以下条件&lt;/strong>的树
堆中每一个节点值都&lt;strong>大于等于（或小于等于）子树中所有节点&lt;/strong>。或者说，任意一个节点的值**都大于等于（或小于等于）**所有子节点的值&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>大家可以把堆(最大堆)理解为一个公司,这个公司很公平,谁能力强谁就当老大,不存在弱的人当老大,老大手底下的人一定不会比他强。这样有助于理解后续堆的操作。&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>&lt;strong>堆不一定是完全二叉树&lt;/strong>，为了方便&lt;strong>存储&lt;/strong>和&lt;strong>索引&lt;/strong>，我们通常用完全二叉树的形式来表示堆&lt;br>
广为人知的&lt;strong>斐波那契堆&lt;/strong>和&lt;strong>二项堆&lt;/strong>就不是完全二叉树，它们甚至都&lt;strong>不是二叉树&lt;/strong>&lt;/li>
&lt;li>(二叉)堆是一个数组，它可以被看成是一个&lt;strong>近似的完全二叉树&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>下面给出的图是否是堆（通过定义）&lt;/p>
&lt;p>1，2是。
3不是。
&lt;img src="img/ly-20241212141845610.png" alt="ly-20241212141845610" />&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="堆的用途">堆的用途&lt;a class="anchor" href="#%e5%a0%86%e7%9a%84%e7%94%a8%e9%80%94">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>当我们&lt;strong>只关心&lt;/strong>所有数据中的&lt;strong>最大值&lt;/strong>或者&lt;strong>最小值&lt;/strong>，存在&lt;strong>多次获取最大值&lt;/strong>或者&lt;strong>最小值&lt;/strong>，多次插入或删除数据时，就可以使用堆。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>有小伙伴可能会想到用&lt;strong>有序数组&lt;/strong>，初始化一个有序数组时间复杂度是 &lt;code>O(nlog(n))&lt;/code>&lt;strong>[也就是将一堆数字乱序排序，最快是O(nlog(n))]&lt;/strong>，查找最大值或者最小值时间复杂度都是 &lt;code>O(1)&lt;/code>，但是，涉及到更新（插入或删除）数据时，时间复杂度为 &lt;code>O(n)&lt;/code>，即使是使用复杂度为 &lt;code>O(log(n))&lt;/code> 的二分法找到要插入或者删除的数据，在移动数据时也需要 &lt;code>O(n)&lt;/code> 的时间复杂度。&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>相对于有序数组而言，堆的主要优势在于更新数据效率较高&lt;/p>
&lt;ul>
&lt;li>堆的&lt;strong>初始化时间复杂度&lt;/strong>为O(nlog(n))，堆可以做到&lt;strong>O(1)&lt;strong>的时间复杂度取出&lt;/strong>最大值&lt;/strong>或者&lt;strong>最小值&lt;/strong>，&lt;strong>O(log(n))&lt;strong>的时间复杂度&lt;/strong>插入或者删除&lt;/strong>数据&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="堆的分类">堆的分类&lt;a class="anchor" href="#%e5%a0%86%e7%9a%84%e5%88%86%e7%b1%bb">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>堆分为&lt;strong>最大堆&lt;/strong>和&lt;strong>最小堆&lt;/strong>，二者的区别在于节点的&lt;strong>排序方式&lt;/strong>
&lt;ul>
&lt;li>最大堆：堆中的每一个节点的值&lt;strong>都大于&lt;/strong>子树中&lt;strong>所有节点&lt;/strong>的值&lt;/li>
&lt;li>最小堆：堆中的每一个节点的值&lt;strong>都小于&lt;/strong>子树中&lt;strong>所有节点&lt;/strong>的值&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>如图，图1是最大堆，图2是最小堆
&lt;img src="img/ly-20241212141845906.png" alt="ly-20241212141845906" />&lt;/li>
&lt;/ul>
&lt;h1 id="堆的存储">堆的存储&lt;a class="anchor" href="#%e5%a0%86%e7%9a%84%e5%ad%98%e5%82%a8">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>由于&lt;strong>完全二叉树的优秀性质&lt;/strong>，&lt;strong>利用数组存储二叉树&lt;/strong>即节省空间，又&lt;strong>方便索引&lt;/strong>（若根结点的序号为1，那么对于树中任意节点i，其左子节点序号为 &lt;code>2*i&lt;/code>，右子节点序号为 &lt;code>2*i+1&lt;/code>）。&lt;/li>
&lt;li>为了&lt;strong>方便存储&lt;/strong>和&lt;strong>索引&lt;/strong>，&lt;strong>（二叉）堆&lt;/strong>可以用&lt;strong>完全二叉树&lt;/strong>的形式进行存储。存储的方式如下图所示
&lt;img src="img/ly-20241212141846083.png" alt="ly-20241212141846083" />&lt;/li>
&lt;/ul>
&lt;h1 id="堆的操作">堆的操作&lt;a class="anchor" href="#%e5%a0%86%e7%9a%84%e6%93%8d%e4%bd%9c">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>堆的更新操作主要包括两种：&lt;strong>插入元素&lt;/strong>和&lt;strong>删除堆顶元素&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>堆是一个公平的公司，&lt;strong>有能力的人&lt;/strong>自然会走到与他能力所匹配的位置&lt;/p>&lt;/blockquote>&lt;/li>
&lt;/ul>
&lt;h2 id="插入元素">插入元素&lt;a class="anchor" href="#%e6%8f%92%e5%85%a5%e5%85%83%e7%b4%a0">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>将要插入的元素放到&lt;strong>最后&lt;/strong>
&lt;img src="img/ly-20241212141846259.png" alt="ly-20241212141846259" />&lt;/li>
&lt;li>从底向上，如果&lt;strong>父节点&lt;/strong>比&lt;strong>该元素小&lt;/strong>，则该节点和父节点交换（其实就是一棵树有3个（最多）节点，与树上最大的节点比较）
直到无法交换（已经与根节点比较过）
&lt;img src="img/ly-20241212141846433.png" alt="ly-20241212141846433" />
&lt;img src="img/ly-20241212141846614.png" alt="ly-20241212141846614" />&lt;/li>
&lt;/ol>
&lt;h2 id="删除堆顶元素">删除堆顶元素&lt;a class="anchor" href="#%e5%88%a0%e9%99%a4%e5%a0%86%e9%a1%b6%e5%85%83%e7%b4%a0">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>根据堆的性质可知，&lt;strong>最大堆&lt;/strong>的堆盯元素为所有元素中最大的，&lt;strong>最小堆&lt;/strong>的堆顶元素是所有元素中最小的&lt;/p>
&lt;/li>
&lt;li>
&lt;p>当我们需要多次查找&lt;strong>最大元素&lt;/strong>或者&lt;strong>最小元素&lt;/strong>的时候，可以利用堆来实现&lt;/p>
&lt;/li>
&lt;li>
&lt;p>删除堆顶元素后，为了保持&lt;strong>堆的性质&lt;/strong>，需要对堆的结构进行调整，我们可以将这个过程称之为&lt;strong>堆化&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>自底向上&lt;/strong>的堆化，上述的&lt;strong>插入元素&lt;/strong>所使用的，就是自顶向上的&lt;strong>堆化&lt;/strong>，元素从最底部向上移动&lt;/li>
&lt;li>&lt;strong>自顶向下&lt;/strong>的堆化，元素由&lt;strong>顶部向下&lt;/strong>移动。在讲解删除堆顶元素的方法时，我将阐述这&lt;strong>两种操作的过程&lt;/strong>&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>自底向上堆化&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>在堆这个公司中，会出现老大离职的现象，老大离职之后，它的位置就空出来了&lt;/p>&lt;/blockquote>&lt;ol>
&lt;li>
&lt;p>首先删除堆顶元素，使得数组中下标为1的位置空出
&lt;img src="img/ly-20241212141846789.png" alt="ly-20241212141846789" />&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>那么他的位置由谁来接替呢，当然是他的直接下属了，谁能力强就让谁上&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>比较&lt;strong>根节点（当前节点）&lt;strong>的&lt;/strong>左子节点&lt;/strong>和&lt;strong>右子节点&lt;/strong>，也就是下标为 2 ，3 的数组元素，将较大的元素填充到**根节点（下标为1）（当前遍历节点）**的位置
&lt;img src="img/ly-20241212141846962.png" alt="ly-20241212141846962" />&lt;/p></description></item><item><title>图</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/graph/</link><pubDate>Mon, 26 Dec 2022 08:47:25 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/graph/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>图是一种较为复杂的&lt;strong>非线性&lt;/strong>结构&lt;/li>
&lt;li>&lt;strong>线性&lt;/strong>数据结构的元素满足唯一的线性关系，&lt;strong>每个元素&lt;/strong>（除第一个和最后一个外）只有一个&lt;strong>直接前驱&lt;/strong>和一个&lt;strong>直接后继&lt;/strong>&lt;/li>
&lt;li>&lt;strong>树形&lt;/strong>数据结构的元素之间有着明显的&lt;strong>层级关系&lt;/strong>&lt;/li>
&lt;li>&lt;strong>图形&lt;/strong>结构的元素之间的关系是任意的
&lt;ul>
&lt;li>图就是由&lt;strong>顶点&lt;/strong>的&lt;strong>有穷非空集合&lt;/strong>和顶点之间的&lt;strong>边&lt;/strong>组成的集合，通常表示为：&lt;strong>G（V，E）&lt;/strong>，其中，G表示一个图，V表示顶点的集合，E表示边的集合&lt;/li>
&lt;li>下面显示的即&lt;strong>图&lt;/strong>这种数据结构，而且还是一张&lt;strong>有向图&lt;/strong>
&lt;img src="img/ly-20241212141841748.png" alt="ly-20241212141841748" />&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="图的基本概念">图的基本概念&lt;a class="anchor" href="#%e5%9b%be%e7%9a%84%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5">#&lt;/a>&lt;/h1>
&lt;h2 id="顶点">顶点&lt;a class="anchor" href="#%e9%a1%b6%e7%82%b9">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>图中的&lt;strong>数据元素&lt;/strong>，我们称之为&lt;strong>顶点&lt;/strong>，图至少有一个&lt;strong>顶点&lt;/strong>（&lt;strong>有穷非空&lt;/strong>集合）&lt;/li>
&lt;li>对应到好友关系图，&lt;strong>每一个用户&lt;/strong>就代表&lt;strong>一个顶点&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="边">边&lt;a class="anchor" href="#%e8%be%b9">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>顶点之间的&lt;strong>关系&lt;/strong>用&lt;strong>边&lt;/strong>表示&lt;/li>
&lt;li>对应到好友关系图，两个用户是好友的话，那两者之间就存在一条&lt;strong>边&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="度">度&lt;a class="anchor" href="#%e5%ba%a6">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>度表示一个顶点包含多少条边&lt;/li>
&lt;li>有向图中，分为&lt;strong>出度&lt;/strong>和&lt;strong>入度&lt;/strong>，出度表示&lt;strong>从该顶点出去的边&lt;/strong>的条数，入度表示&lt;strong>从进入该顶点的边&lt;/strong>的条数&lt;/li>
&lt;li>对应到好友关系图，度就代表了某个人的&lt;strong>好友数量&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="无向图和有向图">无向图和有向图&lt;a class="anchor" href="#%e6%97%a0%e5%90%91%e5%9b%be%e5%92%8c%e6%9c%89%e5%90%91%e5%9b%be">#&lt;/a>&lt;/h2>
&lt;p>边表示的是顶点之间的关系，有的关系是双向的，比如同学关系，A是B的同学，那么B也肯定是A的同学，那么在表示A和B的关系时，就不用关注方向，用&lt;strong>不带箭头的边&lt;/strong>表示，这样的图就是&lt;strong>无向图&lt;/strong>。&lt;/p>
&lt;p>有的关系是有方向的，比如父子关系，师生关系，微博的关注关系，A是B的爸爸，但B肯定不是A的爸爸，A关注B，B不一定关注A。在这种情况下，我们就用&lt;strong>带箭头的边&lt;/strong>表示二者的关系，这样的图就是&lt;strong>有向图&lt;/strong>。&lt;/p>
&lt;h2 id="无权图和带权图">无权图和带权图&lt;a class="anchor" href="#%e6%97%a0%e6%9d%83%e5%9b%be%e5%92%8c%e5%b8%a6%e6%9d%83%e5%9b%be">#&lt;/a>&lt;/h2>
&lt;p>对于一个关系，如果我们只关心关系的有无，而&lt;strong>不关心关系有多强&lt;/strong>，那么就可以用&lt;strong>无权图&lt;/strong>表示二者的关系。&lt;/p>
&lt;p>对于一个关系，如果我们既&lt;strong>关心关系的有无&lt;/strong>，也关心&lt;strong>关系的强度&lt;/strong>，比如描述地图上&lt;strong>两个城市的关系&lt;/strong>，需要用到&lt;strong>距离&lt;/strong>，那么就用&lt;strong>带权图&lt;/strong>来表示，&lt;strong>带权图中的每一条边一个数值表示权值&lt;/strong>，代表&lt;strong>关系的强度&lt;/strong>。&lt;/p>
&lt;p>下图就是一个&lt;strong>带权有向图&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141842057.png" alt="ly-20241212141842057" />&lt;/p>
&lt;h1 id="图的存储">图的存储&lt;a class="anchor" href="#%e5%9b%be%e7%9a%84%e5%ad%98%e5%82%a8">#&lt;/a>&lt;/h1>
&lt;h2 id="邻接矩阵存储">邻接矩阵存储&lt;a class="anchor" href="#%e9%82%bb%e6%8e%a5%e7%9f%a9%e9%98%b5%e5%ad%98%e5%82%a8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>邻接矩阵将图用&lt;strong>二维矩阵&lt;/strong>存储，是一种比较&lt;strong>直观&lt;/strong>的表示方式&lt;/li>
&lt;li>如果第i个顶点和第j个顶点&lt;strong>有关系&lt;/strong>，且&lt;strong>关系权值&lt;/strong>为n，则A[i] [j] = n&lt;/li>
&lt;li>在无向图中，我们只关心关系的有无，所以当&lt;strong>顶点i&lt;/strong>和&lt;strong>顶点j&lt;/strong>有关系时，A[i] [j]=1 ; 当顶点i和顶点j没有关系时，A[i] [j] = 0 ，如下图所示&lt;br>
&lt;img src="img/ly-20241212141842233.png" alt="ly-20241212141842233" />
无向图的邻接矩阵是一个&lt;strong>对称&lt;/strong>矩阵，因为在无向图中，&lt;strong>顶点i&lt;/strong>和&lt;strong>顶点j&lt;/strong>有关系，则&lt;strong>顶点j&lt;/strong>和&lt;strong>顶点i&lt;/strong>必有关系&lt;/li>
&lt;li>有向图的邻接矩阵存储
&lt;img src="img/ly-20241212141842414.png" alt="ly-20241212141842414" />
邻接矩阵存储的方式优点是&lt;strong>简单直接（直接使用一个二维数组即可）&lt;/strong>，并且在获取两个顶点之间的关系的时候也非常高效*直接获取指定位置的&lt;strong>数组&lt;/strong>元素。但是这种存储方式的确定啊也比较明显&lt;strong>即 比较浪费空间&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="邻接表存储">邻接表存储&lt;a class="anchor" href="#%e9%82%bb%e6%8e%a5%e8%a1%a8%e5%ad%98%e5%82%a8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>针对上面邻接矩阵比较浪费内存空间的问题，诞生了图的另一种存储方法&amp;ndash;&lt;strong>邻接表&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>邻接链表使用一个&lt;strong>链表&lt;/strong>来存储某个顶点的&lt;strong>所有后继相邻顶点&lt;/strong>。对于图中每个顶点Vi ，把所有邻接于Vi 的顶点Vj 链接成一个&lt;strong>单链表&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>无向图的邻接表存储
&lt;img src="img/ly-20241212141842597.png" alt="ly-20241212141842597" />&lt;/li>
&lt;li>有向图的邻接表存储
&lt;img src="img/ly-20241212141842773.png" alt="ly-20241212141842773" />&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>邻接表中存储的元素的个数（顶点数）以及图中&lt;strong>边的条数&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>无向图中，&lt;strong>邻接表&lt;/strong>的元素个数等于&lt;strong>边的条数&lt;/strong>的两倍，如下图
7条边，邻接表存储的元素个数为14 （即&lt;strong>每条边存储了两次&lt;/strong>）&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141842952.png" alt="ly-20241212141842952" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有向图中，邻接表元素个数等于边的条数，如图所示的有向图中，边的条数为8，邻接表
&lt;img src="img/ly-20241212141843127.png" alt="ly-20241212141843127" />&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="图的搜索">图的搜索&lt;a class="anchor" href="#%e5%9b%be%e7%9a%84%e6%90%9c%e7%b4%a2">#&lt;/a>&lt;/h1>
&lt;h2 id="广度优先搜索">广度优先搜索&lt;a class="anchor" href="#%e5%b9%bf%e5%ba%a6%e4%bc%98%e5%85%88%e6%90%9c%e7%b4%a2">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>广度优先搜索：像水面上的波纹一样，一层一层向外扩展，如图
&lt;img src="img/ly-20241212141843300.png" alt="ly-20241212141843300" />&lt;/p></description></item><item><title>线性数据结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/linear-data-structure/</link><pubDate>Tue, 20 Dec 2022 13:34:59 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/cs_basics/data-structure/linear-data-structure/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="数组">数组&lt;a class="anchor" href="#%e6%95%b0%e7%bb%84">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>数组（Array）是一种常见数据结构，由&lt;strong>相同类型的元素（element）&lt;strong>组成，并且是使用一块&lt;/strong>连续的内存&lt;/strong>来存储&lt;/li>
&lt;li>直接可以利用元素的**索引（index）**可以计算出该元素对应的存储地址&lt;/li>
&lt;li>数组的特点是：提供&lt;strong>随机访问&lt;/strong>并且&lt;strong>容量有限&lt;/strong>&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>假设数组长度为n：&lt;br>
访问：O(1) //访问特定位置的元素&lt;/p>
&lt;p>插入：O(n) //最坏的情况插入在数组的&lt;strong>首部&lt;/strong>并需要&lt;strong>移动所有元素&lt;/strong>时&lt;/p>
&lt;p>删除：O(n) //最坏的情况发生在删除数组的&lt;strong>开头&lt;/strong>并需要移动&lt;strong>第一元素&lt;/strong>后面所有的元素时&lt;/p>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212141849981.png" alt="ly-20241212141849981" />&lt;/p>
&lt;h1 id="链表">链表&lt;a class="anchor" href="#%e9%93%be%e8%a1%a8">#&lt;/a>&lt;/h1>
&lt;h2 id="链表简介">链表简介&lt;a class="anchor" href="#%e9%93%be%e8%a1%a8%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>链表（LinkedList）虽然是一种&lt;strong>线性表&lt;/strong>，但是并&lt;strong>不会按线性&lt;/strong>的顺序&lt;strong>存储&lt;/strong>数据，使用的&lt;strong>不是&lt;/strong>连续的内存空间来&lt;strong>存储数据&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>链表的&lt;strong>插入&lt;/strong>和&lt;strong>删除&lt;/strong>操作的复杂度为O(1)，只需要直到目标位置元素的&lt;strong>上一个元素&lt;/strong>即可。但是，在&lt;strong>查找一个节点&lt;/strong>或者&lt;strong>访问特定位置&lt;/strong>的节点的时候复杂度为&lt;strong>O(n)&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用链表结构可以&lt;strong>克服数组需要预先知道数据大小&lt;/strong>的缺点，链表结构可以充分利用&lt;strong>计算机内存空间&lt;/strong>，实现灵活的&lt;strong>内存动态管理&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>但链表&lt;strong>不会节省空间&lt;/strong>，相比于数组会&lt;strong>占用&lt;/strong>更多空间，因为链表中&lt;strong>每个节点&lt;/strong>存放的还有&lt;strong>指向其他节点&lt;/strong>的指针。除此之外，链表不具有&lt;strong>数组随机读取&lt;/strong>的优点&lt;/p>&lt;/blockquote>&lt;/li>
&lt;/ul>
&lt;h2 id="链表分类">链表分类&lt;a class="anchor" href="#%e9%93%be%e8%a1%a8%e5%88%86%e7%b1%bb">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>单链表&lt;/strong>、&lt;strong>双向链表&lt;/strong>、&lt;strong>循环链表&lt;/strong>、&lt;strong>双向循环链表&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>假设链表中有&lt;strong>n个元素&lt;/strong>&lt;br>
访问：O(n) //访问特地给位置的元素&lt;/p>
&lt;p>插入删除：O(1) //必须要知道插入元素的位置&lt;/p>&lt;/blockquote>&lt;h3 id="单链表">单链表&lt;a class="anchor" href="#%e5%8d%95%e9%93%be%e8%a1%a8">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>单链表&lt;/strong>只有一个方向，结点&lt;strong>只有一个后继指针next&lt;/strong>指向后面的节点。因此，链表这种数据结构通常在&lt;strong>物理内存&lt;/strong>上是&lt;strong>不连续&lt;/strong>的&lt;/li>
&lt;li>我们习惯性地把&lt;strong>第一个结点&lt;/strong>叫做&lt;strong>头结点&lt;/strong>，链表通常有一个&lt;strong>不保存任何值的head节点&lt;/strong>（头结点），通过头结点我们可以&lt;strong>遍历整个链表&lt;/strong>，尾结点通常&lt;strong>指向null&lt;/strong>&lt;/li>
&lt;li>如下图
&lt;img src="img/ly-20241212141850281.png" alt="ly-20241212141850281" />&lt;/li>
&lt;/ul>
&lt;h3 id="循环链表">循环链表&lt;a class="anchor" href="#%e5%be%aa%e7%8e%af%e9%93%be%e8%a1%a8">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>循环链表是一种&lt;strong>特殊的单链表&lt;/strong>，和单链表不同的是&lt;strong>循环链表的尾结点&lt;/strong>不是指向null，而是&lt;strong>指向链表的头结点&lt;/strong>&lt;/li>
&lt;li>如图
&lt;img src="img/ly-20241212141850545.png" alt="ly-20241212141850545" />&lt;/li>
&lt;/ul>
&lt;h3 id="双向链表">双向链表&lt;a class="anchor" href="#%e5%8f%8c%e5%90%91%e9%93%be%e8%a1%a8">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>双向链表包含&lt;strong>两个指针&lt;/strong>，一个&lt;strong>prev&lt;/strong>指向&lt;strong>前一个节点&lt;/strong>，另一个&lt;strong>next&lt;/strong>指向&lt;/li>
&lt;li>如图
&lt;img src="img/ly-20241212141850726.png" alt="ly-20241212141850726" />&lt;/li>
&lt;/ul>
&lt;h3 id="双向循环链表">双向循环链表&lt;a class="anchor" href="#%e5%8f%8c%e5%90%91%e5%be%aa%e7%8e%af%e9%93%be%e8%a1%a8">#&lt;/a>&lt;/h3>
&lt;p>双向循环链表的&lt;strong>最后一个节点的next&lt;/strong>指向head，而head的&lt;strong>prev&lt;/strong>指向最后一个节点，构成一个环&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141850919.png" alt="ly-20241212141850919" />&lt;/p>
&lt;h2 id="应用场景">应用场景&lt;a class="anchor" href="#%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>如果需要支持&lt;strong>随机访问&lt;/strong>的话，链表无法做到&lt;/li>
&lt;li>如果需要&lt;strong>存储的数据元素个数不确定&lt;/strong>，并且需要经常&lt;strong>添加&lt;/strong>和&lt;strong>删除&lt;/strong>数据的话，使用&lt;strong>链表&lt;/strong>比较合适&lt;/li>
&lt;li>如果需要&lt;strong>存储的数据元素&lt;/strong>的个数确定，并且不需要&lt;strong>经常添加&lt;/strong>和&lt;strong>删除&lt;/strong>数据的话，使用数组比较合适&lt;/li>
&lt;/ul>
&lt;h2 id="数组-vs-链表">数组 vs 链表&lt;a class="anchor" href="#%e6%95%b0%e7%bb%84-vs-%e9%93%be%e8%a1%a8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>数组支持&lt;strong>随机访问&lt;/strong>，链表不支持&lt;/li>
&lt;li>数组使用的是&lt;strong>连续内存空间&lt;/strong> &lt;strong>对CPU缓存机制&lt;/strong>友好，链表则&lt;strong>相反&lt;/strong>&lt;/li>
&lt;li>数组的&lt;strong>大小固定&lt;/strong>，而链表则&lt;strong>天然支持动态扩容&lt;/strong>。如果生命的数组过小，需要另外申请一个&lt;strong>更大的内存空间&lt;/strong>存放数组元素，然后将&lt;strong>原数组拷贝进去&lt;/strong>，这个操作比较耗时&lt;/li>
&lt;/ul>
&lt;h1 id="栈">栈&lt;a class="anchor" href="#%e6%a0%88">#&lt;/a>&lt;/h1>
&lt;h2 id="栈简介">栈简介&lt;a class="anchor" href="#%e6%a0%88%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>栈（stack）只允许在&lt;strong>有序的线性数据集合&lt;/strong>的&lt;strong>一端&lt;/strong>（称为栈顶top）进行&lt;strong>加入数据（push）&lt;strong>和&lt;/strong>移除数据（pop）&lt;/strong>。因而按照**后进先出（LIFO，Last In First Out）**的原理运作。&lt;/li>
&lt;li>栈中，&lt;strong>push&lt;/strong>和&lt;strong>pop&lt;/strong>的操作都发生在栈顶&lt;/li>
&lt;li>栈常用&lt;strong>一维数组&lt;/strong>或&lt;strong>链表&lt;/strong>来实现，用数组实现的叫&lt;strong>顺序栈&lt;/strong>，用链表实现的叫做&lt;strong>链式栈&lt;/strong>&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>假设堆栈中有n个元素。
访问：O（n）//最坏情况
插入删除：O（1）//顶端插入和删除元素&lt;/p></description></item><item><title>数据库基础</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0501lybasis/</link><pubDate>Tue, 20 Dec 2022 11:19:14 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/database/ly0501lybasis/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>这部分内容由于涉及太多概念性内容，所以参考了维基百科和百度百科相应的介绍。&lt;/p>&lt;/blockquote>&lt;h1 id="什么是数据库数据库管理系统数据库系统数据库管理员">什么是数据库，数据库管理系统，数据库系统，数据库管理员&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e6%95%b0%e6%8d%ae%e5%ba%93%e6%95%b0%e6%8d%ae%e5%ba%93%e7%ae%a1%e7%90%86%e7%b3%bb%e7%bb%9f%e6%95%b0%e6%8d%ae%e5%ba%93%e7%b3%bb%e7%bb%9f%e6%95%b0%e6%8d%ae%e5%ba%93%e7%ae%a1%e7%90%86%e5%91%98">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;strong>数据库&lt;/strong>：数据库（&lt;strong>DataBase 简称DB&lt;/strong>）就是&lt;strong>信息的集合&lt;/strong>或者说&lt;strong>数据库管理系统管理的数据的集合&lt;/strong>。&lt;/li>
&lt;li>&lt;strong>数据库管理系统&lt;/strong>：数据库管理系统（&lt;strong>Database Management System 简称DBMS&lt;/strong>）是一种&lt;strong>操纵和管理数据库&lt;/strong>的大型软件，通常用于建立、使用和维护 &lt;strong>数据库&lt;/strong>。&lt;/li>
&lt;li>&lt;strong>数据库系统（范围最大）&lt;/strong>：数据库系统（Data Base System，简称DBS）通常由**软件、数据和数据管理员（DBA）**组成。&lt;/li>
&lt;li>&lt;strong>数据库管理员&lt;/strong>：数据库管理员（Database Adminitrator，简称DBA）负责全面&lt;strong>管理和控制&lt;/strong>数据库系统 &lt;strong>(是一个人)&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>数据库系统基本构成&lt;/strong>如下图所示&lt;br>
&lt;img src="img/ly-20241212141854630.png" alt="ly-20241212141854630" />&lt;/p>
&lt;h1 id="什么是元组码候选码主码外码主属性非主属性">什么是元组，码，候选码，主码，外码，主属性，非主属性&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%85%83%e7%bb%84%e7%a0%81%e5%80%99%e9%80%89%e7%a0%81%e4%b8%bb%e7%a0%81%e5%a4%96%e7%a0%81%e4%b8%bb%e5%b1%9e%e6%80%a7%e9%9d%9e%e4%b8%bb%e5%b1%9e%e6%80%a7">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;strong>元组&lt;/strong>：元组（tuple）是&lt;strong>关系数据库&lt;/strong>中的&lt;strong>基本概念&lt;/strong>，&lt;strong>关系&lt;/strong>是一张表，表中的&lt;strong>每行&lt;/strong>（即数据库中的每条&lt;strong>记录&lt;/strong>）就是一个元组，每列就是一个属性。在&lt;strong>二维表&lt;/strong>里，元组也成为&lt;strong>行&lt;/strong>&lt;/li>
&lt;li>&lt;strong>码&lt;/strong>：码就是能&lt;strong>唯一标识实体&lt;/strong>的属性，对应表中的&lt;strong>列&lt;/strong>&lt;/li>
&lt;li>&lt;strong>候选码&lt;/strong>：若关系中的&lt;strong>某一属性&lt;/strong>或&lt;strong>属性组的值&lt;/strong>能&lt;strong>唯一的标识一个元组&lt;/strong>，而&lt;strong>其任何、子集都不能再标识&lt;/strong>，则称该&lt;strong>属性组&lt;/strong>为&lt;strong>候选码&lt;/strong>。例如：在学生实体中，&lt;strong>“学号”&lt;strong>是能唯一的区分学生实体的，同时又假设&lt;/strong>“姓名”、“班级”的属性组合&lt;/strong>足以区分学生实体，那么**{学号}&lt;strong>和&lt;/strong>{姓名，班级}&lt;strong>都是&lt;/strong>候选码**。&lt;/li>
&lt;li>&lt;strong>主码&lt;/strong>：主码也叫&lt;strong>主键&lt;/strong>，主码是&lt;strong>从候选码&lt;/strong>中选出来的。一个实体集中只能有&lt;strong>一个主码&lt;/strong>，但可以有&lt;strong>多个候选码&lt;/strong>&lt;/li>
&lt;li>&lt;strong>外码&lt;/strong>：外码也叫&lt;strong>外键&lt;/strong>。如果&lt;strong>一个关系中的一个属性&lt;/strong>是&lt;strong>另外一个关系中的主码&lt;/strong>则这个属性为外码。&lt;/li>
&lt;li>&lt;strong>主属性&lt;/strong> ： &lt;strong>候选码中出现过的属性&lt;/strong>称为主属性(&lt;strong>这里强调单个&lt;/strong>）。比如关系 工人（工号，身份证号，姓名，性别，部门）. 显然&lt;strong>工号和身份证号&lt;/strong>都能够唯一标示这个关系，所以都是候选码。&lt;strong>工号、身份证号这两个属性就是主属性&lt;/strong>。如果主码是一个属性组，那么属性组中的属性都是主属性。&lt;/li>
&lt;li>&lt;strong>非主属性：&lt;/strong> &lt;strong>不包含在任何一个候选码中的属性&lt;/strong>称为非主属性。比如在关系——学生（学号，姓名，年龄，性别，班级）中，主码是“学号”，那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。&lt;/li>
&lt;/ul>
&lt;h1 id="主键和外键有什么区别">主键和外键有什么区别&lt;a class="anchor" href="#%e4%b8%bb%e9%94%ae%e5%92%8c%e5%a4%96%e9%94%ae%e6%9c%89%e4%bb%80%e4%b9%88%e5%8c%ba%e5%88%ab">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;strong>主键(主码)&lt;/strong> ：主键用于&lt;strong>唯一标识一个元组&lt;/strong>，不能有重复，不允许为空。一个表只能有一个主键。&lt;/li>
&lt;li>&lt;strong>外键(外码)&lt;/strong> ：外键用来和其他表建立联系用，&lt;strong>外键是另一表的主键&lt;/strong>，外键是可以有重复的，可以是空值。一个表可以有多个外键&lt;/li>
&lt;/ul>
&lt;h1 id="为什么不推荐使用外键与级联">为什么不推荐使用外键与级联&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e6%8e%a8%e8%8d%90%e4%bd%bf%e7%94%a8%e5%a4%96%e9%94%ae%e4%b8%8e%e7%ba%a7%e8%81%94">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>对于外键和级联，阿里巴巴开发手册这样说道&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>【强制】不得使用外键与级联，一切&lt;strong>外键概念&lt;/strong>必须在应用层解决。&lt;/p>
&lt;p>说明: 以学生和成绩的关系为例，学生表中的 student_id 是主键，那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id，同时触发成绩表中的 student_id 更新，即为级联更新。&lt;/p>
&lt;p>缺点： &lt;strong>外键与级联更新适用于单机低并发，不适合分布式、高并发集群; 级联更新是强阻塞，存在数据库更新风暴的风 险; 外键影响数据库的插入速度&lt;/strong>&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>为什么不要使用外键&lt;/p>
&lt;ol>
&lt;li>
&lt;p>增加了复杂性&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>a. 每次做DELETE 或者UPDATE都必须考虑外键约束，会导致开发的时候很痛苦, &lt;strong>测试数据极为不方便&lt;/strong>; b. 外键的主从关系是定的，假如那天需求有变化，&lt;strong>数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦&lt;/strong>。&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>增加了额外操作&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>数据库需要增加维护外键的工作，比如当我们做一些涉及外键字段的增，删，更新操作之后，需要触发相关操作去检查，保证数据的的一致性和正确性，这样会不得不消耗资源；（&lt;strong>个人觉得这个不是不用外键的原因&lt;/strong>，因为即使你不使用外键，你在应用层面也还是要保证的。所以，我觉得这个影响可以忽略不计。）&lt;/p></description></item><item><title>jvm监控和故障处理工具 总结</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0408lyjdk-monitoring-and-troubleshooting-tools/</link><pubDate>Mon, 19 Dec 2022 16:04:34 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0408lyjdk-monitoring-and-troubleshooting-tools/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="jdk-命令行工具">JDK 命令行工具&lt;a class="anchor" href="#jdk-%e5%91%bd%e4%bb%a4%e8%a1%8c%e5%b7%a5%e5%85%b7">#&lt;/a>&lt;/h1>
&lt;p>这些命令在 JDK 安装目录下的 bin 目录下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>jps&lt;/code>&lt;/strong> (JVM Process Status）: 类似 UNIX 的 &lt;code>ps&lt;/code> 命令。用于查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息；&lt;/li>
&lt;li>&lt;strong>&lt;code>jstat&lt;/code>&lt;/strong>（JVM Statistics Monitoring Tool）: 用于收集 HotSpot 虚拟机各方面的运行数据;&lt;/li>
&lt;li>&lt;strong>&lt;code>jinfo&lt;/code>&lt;/strong> (Configuration Info for Java) : Configuration Info for Java,显示虚拟机配置信息;&lt;/li>
&lt;li>&lt;strong>&lt;code>jmap&lt;/code>&lt;/strong> (Memory Map for Java) : 生成堆转储快照;&lt;/li>
&lt;li>&lt;strong>&lt;code>jhat&lt;/code>&lt;/strong> (JVM Heap Dump Browser) : 用于分析 heapdump 文件，它会建立一个 HTTP/HTML 服务器，让用户可以在浏览器上查看分析结果;&lt;/li>
&lt;li>&lt;strong>&lt;code>jstack&lt;/code>&lt;/strong> (Stack Trace for Java) : 生成虚拟机当前时刻的线程快照，线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。&lt;/li>
&lt;/ul>
&lt;h2 id="jps-查看所有-java-进程">jps: 查看所有 Java 进程&lt;a class="anchor" href="#jps-%e6%9f%a5%e7%9c%8b%e6%89%80%e6%9c%89-java-%e8%bf%9b%e7%a8%8b">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>jps&lt;/code>(JVM Process Status) 命令类似 UNIX 的 &lt;code>ps&lt;/code> 命令。&lt;/p></description></item><item><title>jvm参数</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0406lyjvm-params/</link><pubDate>Mon, 19 Dec 2022 15:24:01 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0406lyjvm-params/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>本文由 JavaGuide 翻译自 
&lt;a target="_blank" href="https://www.baeldung.com/jvm-parameters">https://www.baeldung.com/jvm-parametersopen in new window&lt;/a>，并对文章进行了大量的完善补充。翻译不易，如需转载请注明出处，作者：
&lt;a target="_blank" href="https://www.baeldung.com/author/baeldung/">baeldungopen in new window&lt;/a> 。&lt;/p>&lt;/blockquote>&lt;h1 id="概述">概述&lt;a class="anchor" href="#%e6%a6%82%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;p>本篇文章中，将掌握最常用的&lt;strong>JVM参数配置&lt;/strong>。下面提到了一些概念，&lt;strong>堆&lt;/strong>、&lt;strong>方法区&lt;/strong>、&lt;strong>垃圾回收&lt;/strong>等。&lt;/p>
&lt;h1 id="堆内存相关">堆内存相关&lt;a class="anchor" href="#%e5%a0%86%e5%86%85%e5%ad%98%e7%9b%b8%e5%85%b3">#&lt;/a>&lt;/h1>
&lt;p>Java 虚拟机所管理的&lt;strong>内存中最大的一块&lt;/strong>，&lt;strong>Java 堆&lt;/strong>是&lt;strong>所有线程共享的一块内存区域&lt;/strong>，在虚拟机&lt;strong>启动时创建&lt;/strong>。此内存区域的&lt;strong>唯一目的就是存放对象实例&lt;/strong>，&lt;strong>几乎&lt;/strong> &lt;strong>所有的对象实例&lt;/strong>以及&lt;strong>数组&lt;/strong>都在这里分配内存。&lt;br>
&lt;img src="img/ly-20241212142002639.png" alt="ly-20241212142002639" />&lt;/p>
&lt;h2 id="显式指定堆内存-xms和-xmx">显式指定堆内存-Xms和-Xmx&lt;a class="anchor" href="#%e6%98%be%e5%bc%8f%e6%8c%87%e5%ae%9a%e5%a0%86%e5%86%85%e5%ad%98-xms%e5%92%8c-xmx">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>与&lt;strong>性能相关&lt;/strong>的最常见实践之一是根据应用程序要求&lt;strong>初始化堆内存&lt;/strong>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果我们需要指定&lt;strong>最小&lt;/strong>和&lt;strong>最大堆&lt;/strong>大小（推荐显示指定大小）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>-Xms&amp;lt;heap size&amp;gt;&lt;span style="color:#f92672">[&lt;/span>unit&lt;span style="color:#f92672">]&lt;/span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-Xmx&amp;lt;heap size&amp;gt;&lt;span style="color:#f92672">[&lt;/span>unit&lt;span style="color:#f92672">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>heap size&lt;/strong> 表示要初始化内存的具体大小。&lt;/li>
&lt;li>&lt;strong>unit&lt;/strong> 表示要初始化内存的单位。单位为***“ g”*** (GB) 、&lt;em>&lt;strong>“ m”&lt;/strong>&lt;/em>（MB）、&lt;em>&lt;strong>“ k”&lt;/strong>&lt;/em>（KB）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>举例，为JVM分配最小2GB和最大5GB的堆内存大小&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>-Xms2G -Xmx5G&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h2 id="显示新生代内存young-generation">显示新生代内存（Young Generation）&lt;a class="anchor" href="#%e6%98%be%e7%a4%ba%e6%96%b0%e7%94%9f%e4%bb%a3%e5%86%85%e5%ad%98young-generation">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>在堆总可用内存配置完成之后，第二大影响因素是为 &lt;strong>&lt;code>Young Generation&lt;/code>&lt;/strong> 在堆内存所占的比例。默认情况下，&lt;strong>YG 的最小大小为 1310 &lt;em>MB&lt;/em>&lt;/strong>，最大大小为&lt;em>无限制&lt;/em>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>两种指定 &lt;strong>新生代内存(Young Generation)&lt;/strong> 大小的方法&lt;/p>
&lt;ol>
&lt;li>
&lt;p>通过 &lt;code>-XX:NewSize&lt;/code> 和 &lt;code>-XX:MaxNewSize &lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">-&lt;/span>XX:NewSize&lt;span style="color:#f92672">=&amp;lt;&lt;/span>young size&lt;span style="color:#f92672">&amp;gt;[&lt;/span>unit&lt;span style="color:#f92672">]&lt;/span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">-&lt;/span>XX:MaxNewSize&lt;span style="color:#f92672">=&amp;lt;&lt;/span>young size&lt;span style="color:#f92672">&amp;gt;[&lt;/span>unit&lt;span style="color:#f92672">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如，为新生代分配&lt;strong>最小256m&lt;/strong>的内存，&lt;strong>最大1024m&lt;/strong>的内存我们的参数为：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">-&lt;/span>XX:NewSize&lt;span style="color:#f92672">=&lt;/span>256m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">-&lt;/span>XX:MaxNewSize&lt;span style="color:#f92672">=&lt;/span>1024m&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>通过&lt;code>-Xmn&amp;lt;young size&amp;gt;[unit] &lt;/code>指定
举例，为新生代分配256m的内存（NewSize与MaxNewSize设为一致）
&lt;code>-Xmn256m &lt;/code>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>将新对象预留在新生代，由于 &lt;strong>Full GC 的成本远高于 Minor GC&lt;/strong>，因此尽可能&lt;strong>将对象分配在新生代&lt;/strong>是明智的做法，实际项目中&lt;strong>根据 GC 日志分析新生代空间大小分配是否合理&lt;/strong>，适当通过“-Xmn”命令调节新生代大小，&lt;strong>最大限度降低新对象直接进入老年代&lt;/strong>的情况。&lt;/p></description></item><item><title>类文件结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0403lyclass-structure/</link><pubDate>Sun, 18 Dec 2022 08:24:36 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0403lyclass-structure/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="概述">概述&lt;a class="anchor" href="#%e6%a6%82%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>Java中，JVM可以理解的代码就叫做&lt;strong>字节码&lt;/strong>（即扩展名为.class的文件），它不面向任何特定的处理器，只&lt;strong>面向虚拟机&lt;/strong>&lt;/li>
&lt;li>Java语言通过&lt;strong>字节码&lt;/strong>的方式，在一定程度上解决了&lt;strong>传统解释型语言执行效率低&lt;/strong>的问题，同时又保留了&lt;strong>解释型语言&lt;/strong>可移植的特点。所以Java程序运行时&lt;strong>效率极高&lt;/strong>，且由于字节码并不针对一种特定的&lt;strong>机器&lt;/strong>。因此，Java程序无需重新编译便可在&lt;strong>多种不通操作系统的计算机&lt;/strong>运行&lt;/li>
&lt;li>Clojure（Lisp 语言的一种方言）、Groovy、Scala 等语言都是运行在 Java 虚拟机之上。下图展示了&lt;strong>不同的语言被不同的编译器&lt;/strong>编译&lt;strong>成&lt;code>.class&lt;/code>&lt;strong>文件&lt;/strong>最终运行在 Java 虚拟机&lt;/strong>之上。&lt;strong>&lt;code>.class&lt;/code>文件的二进制格式&lt;/strong>可以使用 
&lt;a target="_blank" href="https://www.x-ways.net/winhex/">WinHexopen in new window&lt;/a> 查看。&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="img/ly-20241212141957797.png" alt="ly-20241212141957797" />&lt;/p>
&lt;p>.class文件是不同语言在&lt;strong>Java虚拟机&lt;/strong>之间的重要桥梁，同时也是&lt;strong>支持Java跨平台&lt;/strong>很重要的一个原因&lt;/p>
&lt;h1 id="class文件结构总结">Class文件结构总结&lt;a class="anchor" href="#class%e6%96%87%e4%bb%b6%e7%bb%93%e6%9e%84%e6%80%bb%e7%bb%93">#&lt;/a>&lt;/h1>
&lt;p>根据Java虚拟机规范，Class文件通过&lt;strong>ClassFile&lt;/strong>定义，有点类似C语言的&lt;strong>结构体&lt;/strong>&lt;/p>
&lt;p>ClassFile的结构如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>ClassFile {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u4 magic; &lt;span style="color:#75715e">//Class 文件的标志&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 minor_version;&lt;span style="color:#75715e">//Class 的小版本号&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 major_version;&lt;span style="color:#75715e">//Class 的大版本号&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 constant_pool_count;&lt;span style="color:#75715e">//常量池的数量&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cp_info constant_pool&lt;span style="color:#f92672">[&lt;/span>constant_pool_count&lt;span style="color:#f92672">-&lt;/span>1&lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#75715e">//常量池&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 access_flags;&lt;span style="color:#75715e">//Class 的访问标记&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 this_class;&lt;span style="color:#75715e">//当前类&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 super_class;&lt;span style="color:#75715e">//父类&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 interfaces_count;&lt;span style="color:#75715e">//接口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 interfaces&lt;span style="color:#f92672">[&lt;/span>interfaces_count&lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#75715e">//一个类可以实现多个接口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 fields_count;&lt;span style="color:#75715e">//Class 文件的字段属性&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> field_info fields&lt;span style="color:#f92672">[&lt;/span>fields_count&lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#75715e">//一个类可以有多个字段&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 methods_count;&lt;span style="color:#75715e">//Class 文件的方法数量&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> method_info methods&lt;span style="color:#f92672">[&lt;/span>methods_count&lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#75715e">//一个类可以有个多个方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u2 attributes_count;&lt;span style="color:#75715e">//此类的属性表中的属性数&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> attribute_info attributes&lt;span style="color:#f92672">[&lt;/span>attributes_count&lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#75715e">//属性表集合&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="img/ly-20241212141958065.png" alt="ly-20241212141958065" />&lt;/p></description></item><item><title>类加载器详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0405lyclassloader-detail/</link><pubDate>Sat, 17 Dec 2022 22:39:21 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0405lyclassloader-detail/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="回顾一下类加载过程">回顾一下类加载过程&lt;a class="anchor" href="#%e5%9b%9e%e9%a1%be%e4%b8%80%e4%b8%8b%e7%b1%bb%e5%8a%a0%e8%bd%bd%e8%bf%87%e7%a8%8b">#&lt;/a>&lt;/h2>
&lt;p>开始介绍类加载器和双亲委派模型之前，简单回顾一下类加载过程。&lt;/p>
&lt;ul>
&lt;li>类加载过程：&lt;strong>加载-&amp;gt;连接-&amp;gt;初始化&lt;/strong>。&lt;/li>
&lt;li>连接过程又可分为三步：&lt;strong>验证-&amp;gt;准备-&amp;gt;解析&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;p>[&lt;img src="img/ly-20241212142001796.jpg" alt="类加载过程" />&lt;/p>
&lt;p>加载是类加载过程的第一步，主要完成下面 3 件事情：&lt;/p>
&lt;ol>
&lt;li>通过&lt;strong>全类名&lt;/strong>获取定义此类的&lt;strong>二进制字节流&lt;/strong>&lt;/li>
&lt;li>将字节流所代表的静态存储结构&lt;strong>转换为方法区的运行时数据结构&lt;/strong>&lt;/li>
&lt;li>在&lt;strong>内存&lt;/strong>中生成一个代表该类的 &lt;strong>&lt;code>Class&lt;/code> 对象&lt;/strong>，作为&lt;strong>方法区这些数据的访问入口&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h2 id="类加载器">类加载器&lt;a class="anchor" href="#%e7%b1%bb%e5%8a%a0%e8%bd%bd%e5%99%a8">#&lt;/a>&lt;/h2>
&lt;h3 id="类加载器介绍">类加载器介绍&lt;a class="anchor" href="#%e7%b1%bb%e5%8a%a0%e8%bd%bd%e5%99%a8%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h3>
&lt;p>类加载器从 JDK 1.0 就出现了，最初只是为了满足 Java Applet（已经被淘汰） 的需要。后来，慢慢成为 Java 程序中的一个重要组成部分，赋予了 &lt;strong>Java 类可以被动态加载到 JVM&lt;/strong> 中并执行的能力。&lt;/p>
&lt;p>根据官方 API 文档的介绍：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a &amp;ldquo;class file&amp;rdquo; of that name from a file system.&lt;/p></description></item><item><title>类加载过程</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0404lyclassloader-process/</link><pubDate>Fri, 16 Dec 2022 10:06:50 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0404lyclassloader-process/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="类的声明周期">类的声明周期&lt;a class="anchor" href="#%e7%b1%bb%e7%9a%84%e5%a3%b0%e6%98%8e%e5%91%a8%e6%9c%9f">#&lt;/a>&lt;/h1>
&lt;p>&lt;img src="img/ly-20241212141959924.png" alt="ly-20241212141959924" />&lt;/p>
&lt;h1 id="类加载过程">类加载过程&lt;a class="anchor" href="#%e7%b1%bb%e5%8a%a0%e8%bd%bd%e8%bf%87%e7%a8%8b">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>Class文件，需要&lt;strong>加载到虚拟机中&lt;/strong>之后才能运行和使用，那么虚拟机是&lt;strong>如何加载&lt;/strong>这些Class文件呢&lt;/li>
&lt;li>系统加载Class类文件需要三步：&lt;strong>加载&lt;/strong>-&amp;gt;&lt;strong>连接&lt;/strong>-&amp;gt;&lt;strong>初始化&lt;/strong>。连接过程又分为三步：&lt;strong>验证&lt;/strong>-&amp;gt;&lt;strong>准备&lt;/strong>-&amp;gt;&lt;strong>解析&lt;/strong>&lt;br>
&lt;img src="img/ly-20241212142000194.png" alt="ly-20241212142000194" />&lt;/li>
&lt;/ul>
&lt;h2 id="加载">加载&lt;a class="anchor" href="#%e5%8a%a0%e8%bd%bd">#&lt;/a>&lt;/h2>
&lt;p>类加载的第一步，主要完成3件事情&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>&lt;strong>构造与类相关联的方法表&lt;/strong>&lt;/p>&lt;/blockquote>&lt;ol>
&lt;li>&lt;strong>通过全类名&lt;/strong>获取定义此类的&lt;strong>二进制字节流&lt;/strong>&lt;/li>
&lt;li>将&lt;strong>字节流&lt;/strong>所代表的&lt;strong>静态存储结构&lt;/strong>，转换为&lt;strong>方法区&lt;/strong>的&lt;strong>运行时数据结构&lt;/strong>&lt;/li>
&lt;li>在内存中生成一个该类的&lt;strong>Class对象&lt;/strong>，作为&lt;strong>方法区&lt;/strong>这些数据的访问入口&lt;/li>
&lt;/ol>
&lt;blockquote class='book-hint '>
&lt;p>虚拟机规范对上面3点不具体，比较灵活&lt;/p>
&lt;ol>
&lt;li>对于1 没有具体指明从哪里获取、怎样获取。可以从ZIP包读取 （JAR/EAR/WAR格式的基础）、其他文件生成（JSP）等&lt;/li>
&lt;/ol>&lt;/blockquote>&lt;ul>
&lt;li>非数组类的加载阶段（加载阶段&lt;strong>获取类的二进制字节流&lt;/strong>的动作）是可控性最强的阶段，这一步我们可以去完成还可以&lt;strong>自定义类加载器&lt;/strong>去&lt;strong>控制字节流&lt;/strong>的获取方式（重写一个类加载器的**loadClass()**方法&lt;/li>
&lt;li>数组类型&lt;strong>不通过&lt;/strong>类加载器创建，它由&lt;strong>Java虚拟机&lt;/strong>直接创建&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>加载阶段&lt;/strong>和&lt;strong>连接阶段&lt;/strong>的部分内容是&lt;strong>交叉执行&lt;/strong>的，即加载阶段尚未结束，连接阶段就可能已经开始了&lt;/p>
&lt;h2 id="验证">验证&lt;a class="anchor" href="#%e9%aa%8c%e8%af%81">#&lt;/a>&lt;/h2>
&lt;p>验证是连接阶段的第一步，这一阶段的目的是确保 Class 文件的&lt;strong>字节流&lt;/strong>中包含的信息&lt;strong>符合《Java 虚拟机规范》&lt;strong>的全部约束要求，保证这些信息被当作代码运行后&lt;/strong>不会危害虚拟机自身的安全&lt;/strong>。&lt;/p>
&lt;p>验证阶段主要由&lt;strong>四个&lt;/strong>检验阶段组成：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>文件格式&lt;/strong>验证（Class 文件格式检查）&lt;/li>
&lt;li>&lt;strong>元数据&lt;/strong>验证（字节码语义检查）&lt;/li>
&lt;li>&lt;strong>字节码&lt;/strong>验证（程序语义检查）&lt;/li>
&lt;li>&lt;strong>符号引用&lt;/strong>验证（类的正确性检查）&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="img/ly-20241212142000337.png" alt="ly-20241212142000337" />&lt;/p>
&lt;h2 id="准备">准备&lt;a class="anchor" href="#%e5%87%86%e5%a4%87">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>准备阶段是正式&lt;strong>为类变量分配内存&lt;/strong>并&lt;strong>设置类变量初始值&lt;/strong>的阶段，这些内存都将在&lt;strong>方法区&lt;/strong>中分配，注意：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>这时候进行内存分配的&lt;strong>仅&lt;/strong>包括&lt;strong>类变量&lt;/strong>（&lt;strong>ClassVariables&lt;/strong>，即&lt;strong>静态变量&lt;/strong>：被&lt;code>static&lt;/code>关键字修饰的变量，&lt;strong>只与类相关&lt;/strong>，因此被称为&lt;strong>类变量&lt;/strong>），而&lt;strong>不包括&lt;/strong>实例变量。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>实例变量会在&lt;strong>对象实例化&lt;/strong>时，随着对象一块分配到&lt;strong>Java堆&lt;/strong>中&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>从概念上讲，类变量所使用的内存都应当在 &lt;strong>方法区&lt;/strong> 中进行分配。不过有一点需要注意的是：JDK 7 之前，HotSpot 使用永久代来实现方法区的时候，实现是完全符合这种逻辑概念的。 而在 &lt;strong>JDK 7 及之后&lt;/strong>，HotSpot 已经把&lt;strong>原本放在永久代&lt;/strong>的&lt;strong>字符串常量池&lt;/strong>、&lt;strong>静态变量&lt;/strong>等移动到&lt;strong>堆&lt;/strong>中，这个时候&lt;strong>类变量&lt;/strong>则会随着 **Class 对象（上面有提到，内存区生成Class对象）**一起存放在 Java 堆中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这里所设置的初始值**&amp;ldquo;通常情况&amp;rdquo;&lt;strong>下是&lt;/strong>数据类型默认的零值（如 0、0L、null、false 等**），比如我们定义了**&lt;code>public static int value=111&lt;/code>** ，那么 value 变量在准备阶段的&lt;strong>初始值就是 0 而不是 111&lt;/strong>（&lt;strong>初始化&lt;/strong>阶段才会赋值）。&lt;strong>特殊情况&lt;/strong>：比如给 &lt;strong>value 变量加上了 final&lt;/strong> 关键字&lt;code>public static final int value=111&lt;/code> ，那么准备阶段 value 的值就被赋值为 111&lt;/p></description></item><item><title>java垃圾回收</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0402lygarbage-collection/</link><pubDate>Mon, 12 Dec 2022 15:58:48 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0402lygarbage-collection/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h1 id="前言">前言&lt;a class="anchor" href="#%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h1>
&lt;p>当&lt;strong>需要排查各种内存溢出问题&lt;/strong>、当&lt;strong>垃圾收集&lt;/strong>成为系统达到更高并发的瓶颈时，我们就需要对这些**“自动化”&lt;strong>的技术实施必要的&lt;/strong>监控&lt;strong>和&lt;/strong>调节**&lt;/p>
&lt;h1 id="堆空间的基本结构">堆空间的基本结构&lt;a class="anchor" href="#%e5%a0%86%e7%a9%ba%e9%97%b4%e7%9a%84%e5%9f%ba%e6%9c%ac%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>Java的&lt;strong>自动内存管理&lt;/strong>主要是针对对象内存的&lt;strong>回收&lt;/strong>和对象内存的&lt;strong>分配&lt;/strong>。且Java自动内存管理最核心的功能是&lt;strong>堆&lt;/strong>内存中的对象&lt;strong>分配&lt;/strong>和&lt;strong>回收&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java堆是垃圾收集器管理的主要区域，因此也被称作&lt;strong>GC堆（Garbage Collected Heap）&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>从&lt;strong>垃圾回收的角度&lt;/strong>来说，由于现在收集器基本都采用&lt;strong>分代垃圾收集算法&lt;/strong>，所以Java堆被划分为了几个不同的区域，这样我们就可以&lt;strong>根据各个区域的特点&lt;/strong>选择&lt;strong>合适的垃圾收集算法&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>JDK7版本及JDK7版本之前，堆内存被通常分为下面三部分：&lt;/p>
&lt;ol>
&lt;li>新生代内存（Young Generation）&lt;/li>
&lt;li>老生代（Old Generation）&lt;/li>
&lt;li>永久代（Permanent Generation）&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://javaguide.cn/assets/hotspot-heap-structure.41533631.png" alt="hotspot-heap-structure" />&lt;/p>
&lt;p>JDK8版本之后PermGen（永久）已被Metaspace（元空间）取代，且已经不在堆里面了，元空间使用的是&lt;strong>直接内存&lt;/strong>。&lt;/p>
&lt;h1 id="内存分配和回收原则">内存分配和回收原则&lt;a class="anchor" href="#%e5%86%85%e5%ad%98%e5%88%86%e9%85%8d%e5%92%8c%e5%9b%9e%e6%94%b6%e5%8e%9f%e5%88%99">#&lt;/a>&lt;/h1>
&lt;h2 id="对象优先在eden区分配">对象优先在Eden区分配&lt;a class="anchor" href="#%e5%af%b9%e8%b1%a1%e4%bc%98%e5%85%88%e5%9c%a8eden%e5%8c%ba%e5%88%86%e9%85%8d">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>多数情况下，对象在&lt;strong>新生代中Eden区&lt;/strong>分配。当Eden区没有足够空间进行分配时，会触发一次MinorGC
首先，先添加一下参数打印GC详情：&lt;code>-XX:+PrintGCDetails&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">GCTest&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[]&lt;/span> allocation1, allocation2;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		allocation1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>30900&lt;span style="color:#f92672">*&lt;/span>1024&lt;span style="color:#f92672">]&lt;/span>;&lt;span style="color:#75715e">//会用掉3万多K&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行后的结果（这里应该是配过xms和xmx了，即堆内存大小）
&lt;img src="img/ly-20241212141955776.jpg" alt="img" />
如上，&lt;strong>Eden区内存几乎被分配完全&lt;/strong>（即使程序什么都不做，新生代也会使用2000多K）&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>注： PSYoungGen 为 38400K ，= 33280K + 5120K （Survivor区总会有一个是空的，所以只加了一个5120K ）&lt;/p>&lt;/blockquote>&lt;p>假如我们再为allocation2分配内存会怎么样(不处理的话，年轻代会溢出)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>allocation2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>900 &lt;span style="color:#f92672">*&lt;/span> 1024&lt;span style="color:#f92672">]&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="img/ly-20241212141956049.jpg" alt="img" />
在给allocation2分配内存之前，Eden区内存几乎已经被分配完。所以当&lt;strong>Eden区没有足够空间进行分配时&lt;/strong>，虚拟机将发起一次MinorGC。GC期间虚拟机又发现&lt;strong>allocation1无法存入空间&lt;/strong>，所以只好通过&lt;strong>分配担保机制&lt;/strong>，把&lt;strong>新生代的对象&lt;/strong>，&lt;strong>提前转移到老年代&lt;/strong>去，老年代的空间足够存放allocation1，&lt;strong>所以不会出现Full GC（这里可能是之前的说法，可能只是要表达老年代的GC，而不是Full GC(整堆GC) ）&lt;/strong>　　&lt;/p>
&lt;p>执行MinorGC后，&lt;strong>后面分配的对象如果能够存在Eden区&lt;/strong>的话，还是会在Eden区分配内存&lt;br>
执行如下代码验证：&lt;/p>
&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">GCTest&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 	&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 		&lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[]&lt;/span> allocation1, allocation2,allocation3,allocation4,allocation5;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 		allocation1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>32000&lt;span style="color:#f92672">*&lt;/span>1024&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 		allocation2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>1000&lt;span style="color:#f92672">*&lt;/span>1024&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 		allocation3 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>1000&lt;span style="color:#f92672">*&lt;/span>1024&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 		allocation4 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>1000&lt;span style="color:#f92672">*&lt;/span>1024&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 		allocation5 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>1000&lt;span style="color:#f92672">*&lt;/span>1024&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="大对象直接进入老年代">大对象直接进入老年代&lt;a class="anchor" href="#%e5%a4%a7%e5%af%b9%e8%b1%a1%e7%9b%b4%e6%8e%a5%e8%bf%9b%e5%85%a5%e8%80%81%e5%b9%b4%e4%bb%a3">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>大对象就是需要连续空间的对象（&lt;strong>字符串&lt;/strong>、&lt;strong>数组&lt;/strong>等）&lt;/li>
&lt;li>大对象直接进入老年代，主要是为了避免为&lt;strong>大对象分配内存时&lt;/strong>，由于&lt;strong>分配担保机制(这好像跟分配担保机制没有太大关系)&lt;strong>带来的复制而&lt;/strong>降低效率&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;ul>
&lt;li>
&lt;p>假设大对象最后会晋升老年代，而新生代是基于复制算法来回收垃圾的，由两个Survivor区域配合完成复制算法，如果新生代中出现大对象且能屡次躲过GC，那这个对象就会在两个Survivor区域中来回复制，直至最后升入老年代，而大对象在内存里来回复制移动，就会消耗更多的时间。&lt;/p></description></item><item><title>jvm-intro</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0407lyjvm-intro/</link><pubDate>Fri, 09 Dec 2022 08:48:22 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0407lyjvm-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide（添加小部分笔记）感谢作者!&lt;/p>
&lt;p>原文地址： 
&lt;a target="_blank" href="https://juejin.im/post/5e1505d0f265da5d5d744050#heading-28">https://juejin.im/post/5e1505d0f265da5d5d744050#heading-28&lt;/a> 感谢原作者分享！！&lt;/p>&lt;/blockquote>&lt;h1 id="jvm的基本介绍">JVM的基本介绍&lt;a class="anchor" href="#jvm%e7%9a%84%e5%9f%ba%e6%9c%ac%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>JVM，JavaVirtualMachine的缩写，虚拟出来的计算机，通过在实际的计算机上&lt;strong>仿真模拟&lt;/strong>各类计算机功能实现&lt;/li>
&lt;li>JVM类似一台小电脑，运行在windows或者linux这些&lt;strong>真实操作系统环境下&lt;/strong>，&lt;strong>直接&lt;/strong>和操作系统交互，&lt;strong>与硬件不直接交互&lt;/strong>，操作系统帮我们完成和硬件交互的工作&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="img/ly-20241212142002924.png" alt="img" />&lt;/p>
&lt;h2 id="java文件是如何运行的">Java文件是如何运行的&lt;a class="anchor" href="#java%e6%96%87%e4%bb%b6%e6%98%af%e5%a6%82%e4%bd%95%e8%bf%90%e8%a1%8c%e7%9a%84">#&lt;/a>&lt;/h2>
&lt;p>场景假设：我们写了一个HelloWorld.java，这是一个文本文件。JVM不认识文本文件，所以&lt;strong>需要一个编译&lt;/strong>，让其(xxx.java)成为一个&lt;strong>JVM会读的二进制文件&amp;mdash;&amp;gt; HelloWorld.class&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>类加载器
如果JVM想要执行这个.class文件，需要将其**(这里应该指的二进制文件)&lt;strong>装进&lt;/strong>类加载器**中，它就像一个搬运工一样，会把所有的.class文件全部搬进JVM里面
&lt;img src="img/ly-20241212142003190.png" alt="img" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>方法区&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>类加载器将.class文件搬过来，就是先丢到这一块上&lt;/p>&lt;/blockquote>&lt;p>方法区是用于&lt;strong>存放类似于元数据信息方面的数据&lt;/strong>的，比如&lt;strong>类信息&lt;/strong>、&lt;strong>常量&lt;/strong>、&lt;strong>静态变量&lt;/strong>、&lt;strong>编译后代码&lt;/strong>&amp;hellip;等&lt;/p>
&lt;/li>
&lt;li>
&lt;p>堆
堆主要放一些&lt;strong>存储的数据&lt;/strong>，比如&lt;strong>对象实例&lt;/strong>、&lt;strong>数组&lt;/strong>&amp;hellip;等，它和&lt;strong>方法区&lt;/strong>都同属于&lt;strong>线程共享区域&lt;/strong>，即它们都是&lt;strong>线程不安全&lt;/strong>的&lt;/p>
&lt;/li>
&lt;li>
&lt;p>栈&lt;/p>
&lt;p>线程独享&lt;br>
栈是我们&lt;strong>代码运行空间&lt;/strong>，我们编写的&lt;strong>每一个方法&lt;/strong>都会放到&lt;strong>栈&lt;/strong>里面运行。&lt;br>
名词：&lt;strong>本地方法栈&lt;/strong>或&lt;strong>本地方法接口&lt;/strong>，不过我们基本不会涉及这两块内容，这&lt;strong>两底层使用C&lt;/strong>进行工作，&lt;strong>和Java没有太大关系&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>程序计数器
主要就是完成一个加载工作，类似于一个指针一样的，&lt;strong>指向下一行我们需要执行的代码&lt;/strong>。和栈一样，都是&lt;strong>线程独享&lt;/strong>的，就是&lt;strong>每一个线程都会自己对应的一块区域&lt;/strong>而不会存在并发和多线程问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>小总结
&lt;img src="img/ly-20241212142003329.png" alt="img" />&lt;/p>
&lt;ol>
&lt;li>Java文件经过编译后编程.class字节码文件&lt;/li>
&lt;li>字节码文件通过类加载器被搬运到 JVM虚拟机中&lt;/li>
&lt;li>虚拟机主要的5大块：&lt;strong>方法区、堆&lt;/strong> 都为&lt;strong>线程共享&lt;/strong>区域，有线程安全问题；&lt;strong>栈&lt;/strong>和&lt;strong>本地方法栈&lt;/strong>和&lt;strong>计数器&lt;/strong>都是&lt;strong>独享&lt;/strong>区域，不存在线程安全问题，而JVM的调优主要就是围绕&lt;strong>堆&lt;/strong>、&lt;strong>栈&lt;/strong>两大块进行&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;h2 id="简单的代码例子">简单的代码例子&lt;a class="anchor" href="#%e7%ae%80%e5%8d%95%e7%9a%84%e4%bb%a3%e7%a0%81%e4%be%8b%e5%ad%90">#&lt;/a>&lt;/h2>
&lt;p>一个简单的学生类及main方法:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Student&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">Student&lt;/span>(String name) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">name&lt;/span> &lt;span style="color:#f92672">=&lt;/span> name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">sayName&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;student&amp;#39;s name is : &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>main方法：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">App&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Student student &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Student(&lt;span style="color:#e6db74">&amp;#34;tellUrDream&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> student.&lt;span style="color:#a6e22e">sayName&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>★★ 执行main方法的步骤如下&lt;/p></description></item><item><title>memory-area</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0401lymemory-area/</link><pubDate>Wed, 07 Dec 2022 13:49:39 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/JVM/ly0401lymemory-area/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>
&lt;p>如果没有特殊说明，针对的都是HotSpot虚拟机&lt;/p>&lt;/blockquote>&lt;h1 id="前言">前言&lt;a class="anchor" href="#%e5%89%8d%e8%a8%80">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>对于Java程序员，虚拟机自动管理机制，不需要像C/C++程序员为每一个new 操作去写对应的delete/free 操作，不容易出现&lt;strong>内存泄漏&lt;/strong> 和 &lt;strong>内存溢出&lt;/strong>问题&lt;/li>
&lt;li>但由于内存控制权交给Java虚拟机，一旦出现&lt;strong>内存泄漏和溢出&lt;/strong>方面问题，如果不了解虚拟机是怎么样使用内存，那么很难&lt;strong>排查任务&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h1 id="运行时数据区域">运行时数据区域&lt;a class="anchor" href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e6%95%b0%e6%8d%ae%e5%8c%ba%e5%9f%9f">#&lt;/a>&lt;/h1>
&lt;p>&lt;strong>Java虚拟机&lt;/strong>在执行Java程序的过程中，会把它管理的内存，&lt;strong>划分成若干个&lt;/strong>不同的数据区域&lt;/p>
&lt;p>JDK1.8之前：&lt;/p>
&lt;ol>
&lt;li>线程共享
堆，方法区【永久代】(包括运行时常量池)&lt;/li>
&lt;li>线程私有
虚拟机栈、本地方法栈、程序计数器&lt;/li>
&lt;li>本地内存(包括直接内存)&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="img/ly-20241212141952681.png" alt="ly-20241212141952681" />&lt;/p>
&lt;p>JDK1.8之后：&lt;br>
&lt;img src="img/ly-20241212141952951.png" alt="Java 运行时数据区域（JDK1.8 之后）" />
1.8之后整个永久代改名叫&amp;quot;元空间&amp;quot;，且移到了本地内存中&lt;/p>
&lt;p>规范（概括）：&lt;br>
&lt;strong>线程私有&lt;/strong>：程序计数器，虚拟机栈，本地方法栈&lt;/p>
&lt;p>&lt;strong>线程共享&lt;/strong>：堆，方法区，直接内存（非运行时数据区的一部分）&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>Java虚拟机规范对于运行时数据区域的规定是相当宽松的，以堆为例：&lt;/p>
&lt;ol>
&lt;li>堆可以是连续，也可以不连续&lt;/li>
&lt;li>大小可以固定，也可以运行时按需扩展&lt;/li>
&lt;li>虚拟机实现者可以使用任何&lt;strong>垃圾回收算法管理堆&lt;/strong>，设置不进行垃圾收集&lt;/li>
&lt;/ol>&lt;/blockquote>&lt;h2 id="程序计数器">程序计数器&lt;a class="anchor" href="#%e7%a8%8b%e5%ba%8f%e8%ae%a1%e6%95%b0%e5%99%a8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>是一块较小内存空间，看作是&lt;strong>当前线程所执行的字节码&lt;/strong>的&lt;strong>行号指示器&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>java程序流程&lt;br>
&lt;img src="img/ly-20241212141953092.png" alt="ly-20241212141953092" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>字节码解释器，工作时通过&lt;strong>改变这个计数器的值&lt;/strong>来选取下一条需要执行的&lt;strong>字节码指令&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>而且，为了&lt;strong>线程切换后恢复到正确执行位置&lt;/strong>，每条线程需要一个独立程序计数器，各线程计数器互不影响，独立存储，我们称这类内存区域为**&amp;ldquo;线程私有&amp;rdquo;**的内存&lt;/p>
&lt;/li>
&lt;li>
&lt;p>总结，&lt;strong>程序计数器&lt;/strong>的作用&lt;/p>
&lt;ul>
&lt;li>字节码解释器通过&lt;strong>改变程序计数器来依次读取指令&lt;/strong>，从而实现代码的流程控制&lt;/li>
&lt;li>多线程情况下，&lt;strong>程序计数器用于记录当前线程执行的位置&lt;/strong>，从而当线程被切回来的时候能够知道该线程上次运行到哪&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>程序计数器是唯一一个不会出现OutOfMemoryError的内存区域，它的生命周期随线程创建而创建，线程结束而死亡&lt;/p>&lt;/blockquote>&lt;/li>
&lt;/ul>
&lt;h2 id="java虚拟机栈">Java虚拟机栈&lt;a class="anchor" href="#java%e8%99%9a%e6%8b%9f%e6%9c%ba%e6%a0%88">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>Java虚拟机栈，&lt;strong>简称&amp;quot;栈&amp;quot;&lt;/strong>，也是线程私有的，生命周期和线程相同，随线程创建而创建，线程死亡而死亡&lt;/li>
&lt;li>除了&lt;strong>Native方法&lt;/strong>调用的是&lt;strong>通过本地方法栈实现&lt;/strong>的，其他所有的Java方法调用都是通过&lt;strong>栈&lt;/strong>来实现的（需要和其他运行时数据区域比如&lt;strong>程序计数器&lt;/strong>配合）&lt;/li>
&lt;li>&lt;strong>方法调用的数据&lt;/strong>需要通过栈进行&lt;strong>传递&lt;/strong>，&lt;strong>每一次方法调用&lt;/strong>都会有一个对应的&lt;strong>栈帧被压入栈&lt;/strong>，每一个&lt;strong>方法调用结束后&lt;/strong>，都会有一个&lt;strong>栈帧被弹出&lt;/strong>。&lt;/li>
&lt;li>栈由一个个&lt;strong>栈帧&lt;/strong>组成，每个栈帧包括&lt;strong>局部变量表&lt;/strong>、&lt;strong>操作数栈&lt;/strong>、&lt;strong>动态链接&lt;/strong>、&lt;strong>方法返回地址&lt;/strong>。
栈为&lt;strong>先进后出&lt;/strong>，且只支持&lt;strong>出栈&lt;/strong>和&lt;strong>入栈&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="img/ly-20241212141953237.png" alt="Java 虚拟机栈" />&lt;/p>
&lt;ul>
&lt;li>
&lt;p>局部变量表：存放&lt;strong>编译器可知&lt;/strong>的各种&lt;strong>数据类型&lt;/strong>(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型，不同于对象本身，可能是一个指向对象起始地址的引用指针，也可能是一个&lt;strong>指向一个代表对象的句柄或其他与此对象相关的位置&lt;/strong>)
&lt;img src="img/ly-20241212141953378.png" alt="局部变量表" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>操作数栈 作为方法调用的&lt;strong>中转站&lt;/strong>使用，用于存放方法执行过程中产生的&lt;strong>中间计算结果&lt;/strong>。计算过程中产生的临时变量也放在操作数栈中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>动态链接 主要服务&lt;strong>一个方法需要调用其他方法&lt;/strong>的场景。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>在 Java 源文件被编译成字节码文件时，所有的变量和方法引用都作为符号引用（Symbilic Reference）保存在 Class 文件的常量池里。当一个方法要调用其他方法，需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了&lt;strong>将符号引用转换为调用方法的直接引用&lt;/strong>。&lt;/p></description></item><item><title>completablefuture-intro</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0311lycompletablefuture-intro/</link><pubDate>Tue, 06 Dec 2022 17:13:41 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0311lycompletablefuture-intro/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>Java8被引入的一个非常有用的用于异步编程的类【&lt;strong>没看&lt;/strong>】&lt;/p>
&lt;h1 id="简单介绍">简单介绍&lt;a class="anchor" href="#%e7%ae%80%e5%8d%95%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h1>
&lt;p>CompletableFuture同时实现了&lt;strong>Future&lt;/strong>和&lt;strong>CompletionStage&lt;/strong>接口&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">CompletableFuture&lt;/span>&lt;span style="color:#f92672">&amp;lt;&lt;/span>T&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> Future&lt;span style="color:#f92672">&amp;lt;&lt;/span>T&lt;span style="color:#f92672">&amp;gt;&lt;/span>, CompletionStage&lt;span style="color:#f92672">&amp;lt;&lt;/span>T&lt;span style="color:#f92672">&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>CompletableFuture&lt;/code> 除了提供了更为好用和强大的 &lt;code>Future&lt;/code> 特性之外，还提供了函数式编程的能力。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141948027.png" alt="img" />&lt;/p>
&lt;p>Future接口有5个方法：&lt;/p>
&lt;ul>
&lt;li>&lt;code>boolean cancel(boolean mayInterruptIfRunning)&lt;/code> ：尝试取消执行任务。&lt;/li>
&lt;li>&lt;code>boolean isCancelled()&lt;/code> ：判断任务是否被取消。&lt;/li>
&lt;li>&lt;code>boolean isDone()&lt;/code> ： 判断任务是否已经被执行完成。&lt;/li>
&lt;li>&lt;code>get()&lt;/code> ：等待任务执行完成并获取运算结果。&lt;/li>
&lt;li>&lt;code>get(long timeout, TimeUnit unit)&lt;/code> ：多了一个超时时间。&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="img/ly-20241212141948313.png" alt="img" />&lt;/p>
&lt;p>&lt;code>CompletionStage&amp;lt;T&amp;gt; &lt;/code>接口中的方法比较多，CompoletableFuture的函数式能力就是这个接口赋予的，大量使用&lt;strong>Java8引入的函数式编程&lt;/strong>&lt;/p>
&lt;h1 id="常见操作">常见操作&lt;a class="anchor" href="#%e5%b8%b8%e8%a7%81%e6%93%8d%e4%bd%9c">#&lt;/a>&lt;/h1>
&lt;h2 id="创建completablefuture">创建CompletableFuture&lt;a class="anchor" href="#%e5%88%9b%e5%bb%bacompletablefuture">#&lt;/a>&lt;/h2>
&lt;p>两种方法：new关键字或 CompletableFuture自带的静态工厂方法 &lt;code>runAysnc()&lt;/code>或&lt;code>supplyAsync()&lt;/code>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>通过new关键字
这个方式，可以看作是将&lt;strong>CompletableFuture当作Future&lt;/strong>来使用，如下：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>我们通过创建了一个结果值类型为 &lt;code>RpcResponse&amp;lt;Object&amp;gt;&lt;/code> 的 &lt;code>CompletableFuture&lt;/code>，你可以把 &lt;code>resultFuture&lt;/code> 看作是异步运算结果的载体&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>CompletableFuture&lt;span style="color:#f92672">&amp;lt;&lt;/span>RpcResponse&lt;span style="color:#f92672">&amp;lt;&lt;/span>Object&lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> resultFuture &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> CompletableFuture&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>();&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>&lt;p>如果后面某个时刻，得到了最终结果，可以调用complete()方法传入结果，表示resultFuture已经被完成：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// complete() 方法只能调用一次，后续调用将被忽略。&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>resultFuture.&lt;span style="color:#a6e22e">complete&lt;/span>(rpcResponse);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>通过isDone()检查是否完成：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">boolean&lt;/span> &lt;span style="color:#a6e22e">isDone&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> result &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>获取异步结果，使用get() ，调用get()方法的线程会阻塞 直到CompletableFuture完成运算：
&lt;code>rpcResponse = completableFuture.get();&lt;/code>&lt;/p></description></item><item><title>ThreadLocal详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0310lythreadlocal/</link><pubDate>Mon, 05 Dec 2022 17:31:52 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0310lythreadlocal/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>
&lt;p>本文来自一枝花算不算浪漫投稿， 原文地址：
&lt;a target="_blank" href="https://juejin.cn/post/6844904151567040519">https://juejin.cn/post/6844904151567040519open in new window&lt;/a>。 感谢作者!&lt;/p>&lt;/blockquote>&lt;p>思维导图&lt;br>
&lt;img src="img/ly-20241212141945045.png" alt="img" />&lt;/p>
&lt;h1 id="目录">目录&lt;a class="anchor" href="#%e7%9b%ae%e5%bd%95">#&lt;/a>&lt;/h1>
&lt;h1 id="threadlocal代码演示">ThreadLocal代码演示&lt;a class="anchor" href="#threadlocal%e4%bb%a3%e7%a0%81%e6%bc%94%e7%a4%ba">#&lt;/a>&lt;/h1>
&lt;p>&lt;strong>简单使用&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ThreadLocalTest&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span> messages &lt;span style="color:#f92672">=&lt;/span> Lists.&lt;span style="color:#a6e22e">newArrayList&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> ThreadLocal&lt;span style="color:#f92672">&amp;lt;&lt;/span>ThreadLocalTest&lt;span style="color:#f92672">&amp;gt;&lt;/span> holder &lt;span style="color:#f92672">=&lt;/span> ThreadLocal.&lt;span style="color:#a6e22e">withInitial&lt;/span>(ThreadLocalTest::&lt;span style="color:#66d9ef">new&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">add&lt;/span>(String message) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> holder.&lt;span style="color:#a6e22e">get&lt;/span>().&lt;span style="color:#a6e22e">messages&lt;/span>.&lt;span style="color:#a6e22e">add&lt;/span>(message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#a6e22e">clear&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span> messages &lt;span style="color:#f92672">=&lt;/span> holder.&lt;span style="color:#a6e22e">get&lt;/span>().&lt;span style="color:#a6e22e">messages&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> holder.&lt;span style="color:#a6e22e">remove&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;size: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> holder.&lt;span style="color:#a6e22e">get&lt;/span>().&lt;span style="color:#a6e22e">messages&lt;/span>.&lt;span style="color:#a6e22e">size&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> messages;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ThreadLocalTest.&lt;span style="color:#a6e22e">add&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;一枝花算不算浪漫&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(holder.&lt;span style="color:#a6e22e">get&lt;/span>().&lt;span style="color:#a6e22e">messages&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ThreadLocalTest.&lt;span style="color:#a6e22e">clear&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/* 结果 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">[一枝花算不算浪漫]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">size: 0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>简单使用2&lt;/strong>&lt;/p></description></item><item><title>Atomic原子类介绍</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0309lyatomic-classes/</link><pubDate>Mon, 05 Dec 2022 09:24:36 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0309lyatomic-classes/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>文章开头先用例子介绍几种类型的api使用&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> com.aqs;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> lombok.*;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> java.util.concurrent.atomic.*;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Getter&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Setter&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@AllArgsConstructor&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@ToString&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">User&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> String name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//如果要为atomicReferenceFieldUpdater服务,必须加上volatile修饰&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">volatile&lt;/span> Integer age;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">AtomicTest&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;原子更新数值---------------&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AtomicInteger atomicInteger &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> AtomicInteger();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i1 &lt;span style="color:#f92672">=&lt;/span> atomicInteger.&lt;span style="color:#a6e22e">incrementAndGet&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;原子增加后为&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> i1);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;原子更新数组---------------&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">[]&lt;/span> a &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">[&lt;/span>3&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AtomicIntegerArray atomicIntegerArray &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> AtomicIntegerArray(a);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> atomicIntegerArray.&lt;span style="color:#a6e22e">addAndGet&lt;/span>(1, 3);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;数组元素[&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> 1 &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;]增加后为&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;数组为&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> atomicIntegerArray);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;原子更新对象---------------&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user1 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly1&amp;#34;&lt;/span>, 10);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user2 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly2&amp;#34;&lt;/span>, 20);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user3 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly3&amp;#34;&lt;/span>, 30);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AtomicReference&lt;span style="color:#f92672">&amp;lt;&lt;/span>User&lt;span style="color:#f92672">&amp;gt;&lt;/span> atomicReference &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> AtomicReference&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>(user1);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b &lt;span style="color:#f92672">=&lt;/span> atomicReference.&lt;span style="color:#a6e22e">compareAndSet&lt;/span>(user2, user3);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> (b &lt;span style="color:#f92672">?&lt;/span> &lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span> : &lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>atomicReference.&lt;span style="color:#a6e22e">get&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b1 &lt;span style="color:#f92672">=&lt;/span> atomicReference.&lt;span style="color:#a6e22e">compareAndSet&lt;/span>(user1, user3);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> (b1 &lt;span style="color:#f92672">?&lt;/span> &lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span> : &lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>atomicReference.&lt;span style="color:#a6e22e">get&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;原子更新对象属性---------------&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user4&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly4&amp;#34;&lt;/span>,40);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AtomicReferenceFieldUpdater&lt;span style="color:#f92672">&amp;lt;&lt;/span>User, Integer&lt;span style="color:#f92672">&amp;gt;&lt;/span> atomicReferenceFieldUpdater &lt;span style="color:#f92672">=&lt;/span> AtomicReferenceFieldUpdater.&lt;span style="color:#a6e22e">newUpdater&lt;/span>(User.&lt;span style="color:#a6e22e">class&lt;/span>, Integer.&lt;span style="color:#a6e22e">class&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;age&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b2 &lt;span style="color:#f92672">=&lt;/span> atomicReferenceFieldUpdater.&lt;span style="color:#a6e22e">compareAndSet&lt;/span>(user4, 41, 400);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b2&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里user4值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>atomicReferenceFieldUpdater.&lt;span style="color:#a6e22e">get&lt;/span>(user4));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b3 &lt;span style="color:#f92672">=&lt;/span> atomicReferenceFieldUpdater.&lt;span style="color:#a6e22e">compareAndSet&lt;/span>(user4, 40, 400);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b3&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里user4值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>atomicReferenceFieldUpdater.&lt;span style="color:#a6e22e">get&lt;/span>(user4));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;其他使用---------------&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user5&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly5&amp;#34;&lt;/span>,50);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user6&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly6&amp;#34;&lt;/span>,60);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user7&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly7&amp;#34;&lt;/span>,70);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AtomicMarkableReference&lt;span style="color:#f92672">&amp;lt;&lt;/span>User&lt;span style="color:#f92672">&amp;gt;&lt;/span> userAtomicMarkableReference&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> AtomicMarkableReference&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>(user5,&lt;span style="color:#66d9ef">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b4 &lt;span style="color:#f92672">=&lt;/span> userAtomicMarkableReference.&lt;span style="color:#a6e22e">weakCompareAndSet&lt;/span>(user6, user7, &lt;span style="color:#66d9ef">true&lt;/span>, &lt;span style="color:#66d9ef">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b4&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>userAtomicMarkableReference.&lt;span style="color:#a6e22e">getReference&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b5 &lt;span style="color:#f92672">=&lt;/span> userAtomicMarkableReference.&lt;span style="color:#a6e22e">weakCompareAndSet&lt;/span>(user5, user7, &lt;span style="color:#66d9ef">false&lt;/span>, &lt;span style="color:#66d9ef">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b5&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>userAtomicMarkableReference.&lt;span style="color:#a6e22e">getReference&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b6 &lt;span style="color:#f92672">=&lt;/span> userAtomicMarkableReference.&lt;span style="color:#a6e22e">weakCompareAndSet&lt;/span>(user5, user7, &lt;span style="color:#66d9ef">true&lt;/span>, &lt;span style="color:#66d9ef">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b6&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>userAtomicMarkableReference.&lt;span style="color:#a6e22e">getReference&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;AtomicStampedReference使用---------------&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user80&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly8&amp;#34;&lt;/span>,80);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user90&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly9&amp;#34;&lt;/span>,90);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User user100&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> User(&lt;span style="color:#e6db74">&amp;#34;ly10&amp;#34;&lt;/span>,100);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AtomicStampedReference&lt;span style="color:#f92672">&amp;lt;&lt;/span>User&lt;span style="color:#f92672">&amp;gt;&lt;/span> userAtomicStampedReference&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> AtomicStampedReference&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>(user80,80);&lt;span style="color:#75715e">//版本80&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//...每次更改stamp都加1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//这里假设中途被改成81了&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b7 &lt;span style="color:#f92672">=&lt;/span> userAtomicStampedReference.&lt;span style="color:#a6e22e">compareAndSet&lt;/span>(user80, user100,81,90);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b7&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>userAtomicStampedReference.&lt;span style="color:#a6e22e">getReference&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> b8 &lt;span style="color:#f92672">=&lt;/span> userAtomicStampedReference.&lt;span style="color:#a6e22e">compareAndSet&lt;/span>(user80, user100,80,90);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;更新&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>(b8&lt;span style="color:#f92672">?&lt;/span>&lt;span style="color:#e6db74">&amp;#34;成功&amp;#34;&lt;/span>:&lt;span style="color:#e6db74">&amp;#34;失败&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;引用里值为&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>userAtomicStampedReference.&lt;span style="color:#a6e22e">getReference&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">原子更新数值---------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">原子增加后为1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">原子更新数组---------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">数组元素[1]增加后为3
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">数组为[0, 3, 0]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">原子更新对象---------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly1, age=10)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新成功
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly3, age=30)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">原子更新对象属性---------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里user4值为40
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新成功
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里user4值为400
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">其他使用---------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly5, age=50)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly5, age=50)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新成功
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly7, age=70)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">AtomicStampedReference使用---------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly8, age=80)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">更新成功
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">引用里值为User(name=ly10, age=100)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">Process finished with exit code 0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="原子类介绍">原子类介绍&lt;a class="anchor" href="#%e5%8e%9f%e5%ad%90%e7%b1%bb%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>在化学上，原子是构成一般物质的最小单位，化学反应中是不可分割的，Atomic指&lt;strong>一个操作是不可中断的&lt;/strong>，即使在多个线程一起执行时，一个操作一旦开始就&lt;strong>不会被其他线程干扰&lt;/strong>&lt;/li>
&lt;li>原子类&amp;ndash;&amp;gt;具有原子/原子操作特征的类&lt;/li>
&lt;li>并发包java.util.concurrent 的原子类都放着&lt;code>java.util.concurrent.atomic&lt;/code>中
&lt;img src="img/ly-20241212141944757.png" alt="ly-20241212141944757" />&lt;/li>
&lt;li>根据操作的数据类型，可以将JUC包中的原子类分为4类（基本类型、数组类型、引用类型、对象的属性修改类型）
&lt;ul>
&lt;li>
&lt;p>基本类型
使用原子方式更新基本类型，包括&lt;strong>AtomicInteger 整型原子类&lt;/strong>，&lt;strong>AtomicLong 长整型原子类&lt;/strong>，AtomicBoolean 布尔型原子类&lt;/p></description></item><item><title>aqs详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0308lyaqs-details/</link><pubDate>Wed, 30 Nov 2022 14:48:01 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0308lyaqs-details/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>&lt;code>Semaphore [ˈseməfɔː(r)]&lt;/code>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ul>
&lt;li>何为 AQS？AQS 原理了解吗？&lt;/li>
&lt;li>&lt;code>CountDownLatch&lt;/code> 和 &lt;code>CyclicBarrier&lt;/code> 了解吗？两者的区别是什么？&lt;/li>
&lt;li>用过 &lt;code>Semaphore&lt;/code> 吗？应用场景了解吗？&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;h1 id="aqs简单介绍">AQS简单介绍&lt;a class="anchor" href="#aqs%e7%ae%80%e5%8d%95%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h1>
&lt;p>AQS,AbstractQueueSyschronizer，即抽象队列同步器，这个类在java.util.concurrent.locks包下面&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141944167.png" alt="ly-20241212141944167" />&lt;/p>
&lt;p>AQS是一个抽象类，主要用来构建&lt;strong>锁&lt;/strong>和&lt;strong>同步器&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">abstract&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">AbstractQueuedSynchronizer&lt;/span> &lt;span style="color:#66d9ef">extends&lt;/span> AbstractOwnableSynchronizer &lt;span style="color:#66d9ef">implements&lt;/span> java.&lt;span style="color:#a6e22e">io&lt;/span>.&lt;span style="color:#a6e22e">Serializable&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>AQS &lt;strong>为构建锁和同步器提供了一些通用功能&lt;/strong>的实现，因此，使用 AQS 能简单且高效地&lt;strong>构造出应用广泛的大量的同步器&lt;/strong>，比如我们提到的 &lt;strong>&lt;code>ReentrantLock&lt;/code>&lt;/strong>，&lt;strong>&lt;code>Semaphore&lt;/code>&lt;/strong>，其他的诸如 &lt;code>ReentrantReadWriteLock&lt;/code>，&lt;code>SynchronousQueue&lt;/code>，&lt;code>FutureTask&lt;/code>(jdk1.7) 等等皆是基于 AQS 的。&lt;/p>
&lt;h1 id="aqs原理">AQS原理&lt;a class="anchor" href="#aqs%e5%8e%9f%e7%90%86">#&lt;/a>&lt;/h1>
&lt;h2 id="aqs核心思想">AQS核心思想&lt;a class="anchor" href="#aqs%e6%a0%b8%e5%bf%83%e6%80%9d%e6%83%b3">#&lt;/a>&lt;/h2>
&lt;p>&lt;strong>面试不是背题，大家一定要加入自己的思想，即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来&lt;/strong>&lt;/p>
&lt;p>AQS 核心思想是，如果被请求的&lt;strong>共享资源（AQS内部）&lt;strong>空闲，则将&lt;/strong>当前请求资源的线程&lt;/strong>设置为&lt;strong>有效&lt;/strong>的工作线程，并且将&lt;strong>共享资源&lt;/strong>设置为&lt;strong>锁定&lt;/strong>状态。如果被请求的共享资源&lt;strong>被占用&lt;/strong>，那么就需要一套&lt;strong>线程阻塞等待&lt;/strong>以及&lt;strong>被唤醒时锁分配&lt;/strong>的机制，这个机制 AQS 是用 &lt;strong>CLH 队列锁&lt;/strong>实现的，即&lt;strong>将暂时获取不到锁的线程加入到队列&lt;/strong>中。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>CLH(Craig,Landin and Hagersten)队列是一个&lt;strong>虚拟的双向队列&lt;/strong>（虚拟的双向队列即&lt;strong>不存在队列实例&lt;/strong>，仅存在结点之间的关联关系）。AQS 是&lt;strong>将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点&lt;/strong>（Node）来实现锁的分配。&lt;br>
[ 搜索了一下，CLH好像是人名 ]
在 CLH 同步队列中，一个节点表示一个线程，它保存着&lt;strong>线程的引用&lt;/strong>（thread）、 当前节点在&lt;strong>队列中的状态&lt;/strong>（waitStatus）、&lt;strong>前驱节点&lt;/strong>（prev）、&lt;strong>后继节点&lt;/strong>（next）。&lt;br>
CLH队列结构&lt;br>
&lt;img src="img/ly-20241212141944445.png" alt="ly-20241212141944445" />&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>
&lt;p>AQS（AbstractQueuedSynchronized）原理图&lt;br>
&lt;img src="img/ly-20241212141944597.png" alt="ly-20241212141944597" />&lt;/p>
&lt;p>AQS使用一个&lt;strong>int成员变量来表示同步状态&lt;/strong>，通过内置的&lt;strong>线程等待队列&lt;/strong>来获取资源线程的排队工作。&lt;br>
&lt;code>state&lt;/code> 变量由 &lt;code>volatile&lt;/code> 修饰，用于展示&lt;strong>当前临界资源的获锁&lt;/strong>情况。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">volatile&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> state;&lt;span style="color:#f92672">//&lt;/span>共享变量&lt;span style="color:#960050;background-color:#1e0010">，&lt;/span>使用volatile修饰保证线程可见性&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>状态信息的操作&lt;/p></description></item><item><title>java常见并发容器</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0307lyconcurrent-collections/</link><pubDate>Tue, 29 Nov 2022 16:58:59 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0307lyconcurrent-collections/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>JDK提供的容器，大部分在java.util.concurrent包中&lt;/p>
&lt;ul>
&lt;li>ConcurrentHashMap：线程安全的&lt;strong>HashMap&lt;/strong>&lt;/li>
&lt;li>CopyOnWriteArrayList：线程安全的&lt;strong>List&lt;/strong>，在读多写少的场合性能非常好，远好于Vector&lt;/li>
&lt;li>&lt;strong>ConcurrentLinkedQueue&lt;/strong>：高效的&lt;strong>并发队列&lt;/strong>，使用&lt;strong>链表&lt;/strong>实现，可以看作一个&lt;strong>线程安全的LinkedList&lt;/strong>，是一个&lt;strong>非阻塞队列&lt;/strong>&lt;/li>
&lt;li>&lt;strong>BlockingQueue&lt;/strong>：这是一个接口，JDK内部通过链表、数组等方式实现了该接口。表示&lt;strong>阻塞队列&lt;/strong>，非常适合用于作为数据共享的通道&lt;/li>
&lt;li>ConcorrentSkipListMap：&lt;strong>跳表&lt;/strong>的实现，是一个Map，使用&lt;strong>跳表的数据结构进行快速查找&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h1 id="concurrenthashmap">ConcurrentHashMap&lt;a class="anchor" href="#concurrenthashmap">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>HashMap是线程不安全的，并发场景下要保证线程安全，可以使用Collections.synchronizedMap()方法来包装HashMap，但这是通过&lt;strong>使用一个全局的锁&lt;/strong>来&lt;strong>同步不同线程间的并发访问&lt;/strong>，因此会带来性能问题&lt;/li>
&lt;li>建议使用ConcurrentHashMap，&lt;strong>不论是读操作还是写操作&lt;/strong>都能保证高性能：读操作（几乎）不需要加锁，而写操作时通过&lt;strong>锁分段(这里说的是JDK1.7？)&lt;strong>技术，只对&lt;/strong>所操作的段加锁&lt;/strong>而不影响客户端对其他段的访问&lt;/li>
&lt;/ul>
&lt;h1 id="copyonwritearraylist">CopyOnWriteArrayList&lt;a class="anchor" href="#copyonwritearraylist">#&lt;/a>&lt;/h1>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//源码&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">CopyOnWriteArrayList&lt;/span>&lt;span style="color:#f92672">&amp;lt;&lt;/span>E&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">extends&lt;/span> Object
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">implements&lt;/span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>E&lt;span style="color:#f92672">&amp;gt;&lt;/span>, RandomAccess, Cloneable, Serializable
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>在很多应用场景中，&lt;strong>读操作可能会远远大于写操作&lt;/strong>&lt;/li>
&lt;li>我们应该允许&lt;strong>多个线程同时访问List内部数据&lt;/strong>（针对读）&lt;/li>
&lt;li>与ReentrantReadWriteLock读写锁思想非常类似，即&lt;strong>读读共享&lt;/strong>、&lt;strong>写写互斥&lt;/strong>、&lt;strong>读写互斥&lt;/strong>、&lt;strong>写读互斥&lt;/strong>&lt;/li>
&lt;li>不一样的是，CopyOnWriteArrayList&lt;strong>读取时完全不需要加锁&lt;/strong>，且&lt;strong>写入也不会阻塞读取操作&lt;/strong>，只有&lt;strong>写入和写入之间需要同步等待&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;h2 id="copyonwritearraylist是如何做到的">CopyOnWriteArrayList是如何做到的&lt;a class="anchor" href="#copyonwritearraylist%e6%98%af%e5%a6%82%e4%bd%95%e5%81%9a%e5%88%b0%e7%9a%84">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;code>CopyOnWriteArrayList&lt;/code> 类的&lt;strong>所有可变操作（add，set 等等）都是通过创建底层数组的新副本&lt;/strong>来实现的。当 List 需要被修改的时候，并不修改原有内容，而是&lt;strong>对原有数据进行一次复制，将修改的内容写入副本。写完之后，再将修改完的副本替换原来的数据&lt;/strong>，这样就可以保证写操作不会影响读操作了。&lt;/li>
&lt;li>从 &lt;code>CopyOnWriteArrayList&lt;/code> 的名字就能看出 &lt;code>CopyOnWriteArrayList&lt;/code> 是满足 &lt;strong>&lt;code>CopyOnWrite&lt;/code>&lt;/strong> 的&lt;/li>
&lt;li>在计算机，如果你想要对一块内存进行修改时，我们不在原有内存块中进行写操作，而是将内存拷贝一份，在新的内存中进行写操作，写完之后呢，就&lt;strong>将指向原来内存指针指向新的内存(注意，是指向，而不是重新拷贝★重要★)&lt;/strong>，原来的内存就可以被回收掉了&lt;/li>
&lt;/ul>
&lt;h2 id="copyonwritearraylist-读取和写入源码简单分析">CopyOnWriteArrayList 读取和写入源码简单分析&lt;a class="anchor" href="#copyonwritearraylist-%e8%af%bb%e5%8f%96%e5%92%8c%e5%86%99%e5%85%a5%e6%ba%90%e7%a0%81%e7%ae%80%e5%8d%95%e5%88%86%e6%9e%90">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>CopyOnWriteArrayList读取操作的实现
&lt;strong>读取操作没有任何同步控制&lt;/strong>和&lt;strong>锁&lt;/strong>操作，理由就是内部数组array不会发生修改，只会&lt;strong>被另一个array替换&lt;/strong>，因此可以保证数据安全&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/** The array, accessed only via getArray/setArray. */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">transient&lt;/span> &lt;span style="color:#66d9ef">volatile&lt;/span> Object&lt;span style="color:#f92672">[]&lt;/span> array;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> E &lt;span style="color:#a6e22e">get&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> index) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> get(getArray(), index);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@SuppressWarnings&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;unchecked&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> E &lt;span style="color:#a6e22e">get&lt;/span>(Object&lt;span style="color:#f92672">[]&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> index) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> (E) a&lt;span style="color:#f92672">[&lt;/span>index&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">final&lt;/span> Object&lt;span style="color:#f92672">[]&lt;/span> &lt;span style="color:#a6e22e">getArray&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> array;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>CopyOnWriteArrayList写入操作的实现
在添加集合的时候加了锁，保证同步，&lt;strong>避免多线程写的时候会copy出多个副本&lt;/strong>&lt;/p></description></item><item><title>线程池最佳实践</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0306lythread-pool-best/</link><pubDate>Tue, 29 Nov 2022 11:31:20 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0306lythread-pool-best/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="线程池知识回顾">线程池知识回顾&lt;a class="anchor" href="#%e7%ba%bf%e7%a8%8b%e6%b1%a0%e7%9f%a5%e8%af%86%e5%9b%9e%e9%a1%be">#&lt;/a>&lt;/h2>
&lt;h3 id="1-为什么要使用线程池">1. 为什么要使用线程池&lt;a class="anchor" href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e4%bd%bf%e7%94%a8%e7%ba%bf%e7%a8%8b%e6%b1%a0">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>池化技术的思想，主要是为了&lt;strong>减少每次获取资源（线程资源）的消耗&lt;/strong>，提高对资源的利用率&lt;/li>
&lt;li>线程池提供了一种&lt;strong>限制&lt;/strong>和&lt;strong>管理资源&lt;/strong>（包括执行一个任务）的方法，每个线程池还维护一些&lt;strong>基本统计&lt;/strong>信息，例如已完成任务的数量&lt;/li>
&lt;/ul>
&lt;p>好处：&lt;/p>
&lt;ol>
&lt;li>降低资源消耗&lt;/li>
&lt;li>提高响应速度&lt;/li>
&lt;li>提高线程的可管理性&lt;/li>
&lt;/ol>
&lt;h3 id="2-线程池在实际项目的使用场景">2. 线程池在实际项目的使用场景&lt;a class="anchor" href="#2-%e7%ba%bf%e7%a8%8b%e6%b1%a0%e5%9c%a8%e5%ae%9e%e9%99%85%e9%a1%b9%e7%9b%ae%e7%9a%84%e4%bd%bf%e7%94%a8%e5%9c%ba%e6%99%af">#&lt;/a>&lt;/h3>
&lt;p>线程池一般用于执行&lt;strong>多个不相关联的耗时任务&lt;/strong>，没有多线程的情况下，任务顺序执行，使用了线程池的话可让多个不相关联的任务&lt;strong>同时执行&lt;/strong>。&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141942375.png" alt="ly-20241212141942375" />&lt;/p>
&lt;h3 id="3-如何使用线程池">3. 如何使用线程池&lt;a class="anchor" href="#3-%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8%e7%ba%bf%e7%a8%8b%e6%b1%a0">#&lt;/a>&lt;/h3>
&lt;p>一般是通过 &lt;code>ThreadPoolExecutor&lt;/code> 的构造函数来创建线程池，然后提交任务给线程池执行就可以了。构造函数如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * 用给定的初始参数创建一个新的ThreadPoolExecutor。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">ThreadPoolExecutor&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> corePoolSize,&lt;span style="color:#75715e">//线程池的核心线程数量&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> maximumPoolSize,&lt;span style="color:#75715e">//线程池的最大线程数&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">long&lt;/span> keepAliveTime,&lt;span style="color:#75715e">//当线程数大于核心线程数时，多余的空闲线程存活的最长时间&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TimeUnit unit,&lt;span style="color:#75715e">//时间单位&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BlockingQueue&lt;span style="color:#f92672">&amp;lt;&lt;/span>Runnable&lt;span style="color:#f92672">&amp;gt;&lt;/span> workQueue,&lt;span style="color:#75715e">//任务队列，用来储存等待执行任务的队列&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ThreadFactory threadFactory,&lt;span style="color:#75715e">//线程工厂，用来创建线程，一般默认即可&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RejectedExecutionHandler handler&lt;span style="color:#75715e">//拒绝策略，当提交的任务过多而不能及时处理时，我们可以定制策略来处理任务&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (corePoolSize &lt;span style="color:#f92672">&amp;lt;&lt;/span> 0 &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> maximumPoolSize &lt;span style="color:#f92672">&amp;lt;=&lt;/span> 0 &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> maximumPoolSize &lt;span style="color:#f92672">&amp;lt;&lt;/span> corePoolSize &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> keepAliveTime &lt;span style="color:#f92672">&amp;lt;&lt;/span> 0)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> IllegalArgumentException();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (workQueue &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span> &lt;span style="color:#f92672">||&lt;/span> threadFactory &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span> &lt;span style="color:#f92672">||&lt;/span> handler &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> NullPointerException();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">corePoolSize&lt;/span> &lt;span style="color:#f92672">=&lt;/span> corePoolSize;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">maximumPoolSize&lt;/span> &lt;span style="color:#f92672">=&lt;/span> maximumPoolSize;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">workQueue&lt;/span> &lt;span style="color:#f92672">=&lt;/span> workQueue;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">keepAliveTime&lt;/span> &lt;span style="color:#f92672">=&lt;/span> unit.&lt;span style="color:#a6e22e">toNanos&lt;/span>(keepAliveTime);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">threadFactory&lt;/span> &lt;span style="color:#f92672">=&lt;/span> threadFactory;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">handler&lt;/span> &lt;span style="color:#f92672">=&lt;/span> handler;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用代码：&lt;/p></description></item><item><title>java线程池详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0305lyjava-thread-pool/</link><pubDate>Wed, 23 Nov 2022 14:40:41 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0305lyjava-thread-pool/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="一-使用线程池的好处">一 使用线程池的好处&lt;a class="anchor" href="#%e4%b8%80-%e4%bd%bf%e7%94%a8%e7%ba%bf%e7%a8%8b%e6%b1%a0%e7%9a%84%e5%a5%bd%e5%a4%84">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>池化技术：减少每次获取资源的消耗，提高对资源的利用率&lt;/li>
&lt;li>线程池提供一种&lt;strong>限制&lt;/strong>和&lt;strong>管理资源（包括执行一个任务）&lt;strong>的方式，每个线程池还维护一些基本统计信息，例如&lt;/strong>已完成任务&lt;/strong>的数量&lt;/li>
&lt;li>线程池的好处
&lt;ul>
&lt;li>降低&lt;strong>资源消耗&lt;/strong>（重复利用，降低&lt;strong>线程创建和销毁&lt;/strong>造成的消耗）&lt;/li>
&lt;li>提高&lt;strong>响应速度&lt;/strong>（任务到达直接执行，&lt;strong>无需等待线程创建&lt;/strong>）&lt;/li>
&lt;li>提高线程&lt;strong>可管理性&lt;/strong>（&lt;strong>避免无休止创建&lt;/strong>，使用线程池统一&lt;strong>分配&lt;/strong>、&lt;strong>调优&lt;/strong>、&lt;strong>监控&lt;/strong>）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="二-executor框架">二 Executor框架&lt;a class="anchor" href="#%e4%ba%8c-executor%e6%a1%86%e6%9e%b6">#&lt;/a>&lt;/h2>
&lt;p>Java5之后，通过Executor启动线程，比使用Thread的start方法更好，更&lt;strong>易于管理&lt;/strong>，&lt;strong>效率高&lt;/strong>，还能有助于避免this逃逸的问题&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>this逃逸，指的是&lt;strong>构造函数返回之前&lt;/strong>，&lt;strong>其他线程就持有该对象的引用&lt;/strong>，会导致调用尚未构造完全的对象&lt;br>
例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ThisEscape&lt;/span> { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">ThisEscape&lt;/span>() { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(&lt;span style="color:#66d9ef">new&lt;/span> EscapeRunnable()).&lt;span style="color:#a6e22e">start&lt;/span>(); 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// ... &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">EscapeRunnable&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> Runnable { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Override&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>() { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 通过ThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸 &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>处理办法 &lt;strong>//不要在构造函数中运行线程&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ThisEscape&lt;/span> { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Thread t; 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">ThisEscape&lt;/span>() { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(&lt;span style="color:#66d9ef">new&lt;/span> EscapeRunnable()); 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// ... &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">init&lt;/span>() { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//也就是说对象没有构造完成前，不要调用ThisEscape.this即可&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#a6e22e">start&lt;/span>(); 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">EscapeRunnable&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> Runnable { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Override&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>() { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 通过ThisEscape.this就可以引用外围类对象, 此时可以保证外围类对象已经构造完成 &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>&lt;p>Executor框架不仅包括&lt;strong>线程池的管理&lt;/strong>，提供&lt;strong>线程工厂&lt;/strong>、&lt;strong>队列&lt;/strong>以及&lt;strong>拒绝策略&lt;/strong>。&lt;/p></description></item><item><title>java内存模型</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0304lyjmm/</link><pubDate>Mon, 21 Nov 2022 10:57:24 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0304lyjmm/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>引用自https://github.com/Snailclimb/JavaGuide&lt;/p>&lt;/blockquote>&lt;h2 id="从cpu缓存模型说起">从CPU缓存模型说起&lt;a class="anchor" href="#%e4%bb%8ecpu%e7%bc%93%e5%ad%98%e6%a8%a1%e5%9e%8b%e8%af%b4%e8%b5%b7">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>redis是为了解决&lt;strong>程序处理速度和访问常规关系型数据库速度不对等&lt;/strong>的问题，&lt;strong>CPU缓存则是为了解决CPU处理速度和内存处理速度不对等的问题&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>我们把内存看作外存的高速缓存，程序运行时把外存的数据复制到内存，由于&lt;strong>内存的处理速度远高于外存&lt;/strong>，这样提高了处理速度&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>总结，&lt;strong>CPU Cache缓存的是内存数据&lt;/strong>，用于解决&lt;strong>CPU处理速度和内存不匹配&lt;/strong>的问题，&lt;strong>内存缓存的是硬盘数据&lt;/strong>用于解决硬盘访问速度过慢的问题
CPU Cache示意图：&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141939038.png" alt="ly-20241212141939038" />
CPU Cache通常分为三层，分别叫L1，L2，L3 Cache
工作方式： &lt;strong>先复制一份数据到CPUCache中，当CPU需要用的时候就可以从CPUCache中读取数据，运算完成后，将运算得到的数据，写回MainMemory中&lt;/strong>，此时，会出现&lt;strong>内存缓存不一致的问题&lt;/strong>，例子：执行了i++，如果两个线程同时执行，假设两个线程从CPUCach中读取的i=1，两个线程做了1++运算完之后再写回MainMemory，此时i=2 而正确结果为3&lt;/p>
&lt;/li>
&lt;li>
&lt;p>CPU为了解决&lt;strong>内存缓存不一致&lt;/strong>问题，可以通过制定&lt;strong>缓存一致协议（比如MESI协议）或其他手段&lt;/strong>。这个&lt;strong>缓存一致协议&lt;/strong>，指的是在 &lt;strong>CPU 高速缓存与主内存交互的时候需要遵守的原则和规范&lt;/strong>
&lt;img src="img/ly-20241212141939314.png" alt="ly-20241212141939314" />
操作系统，通过&lt;strong>内存模型MemoryModel&lt;/strong>定义一系列规范来解决这个问题&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="java内存模型">Java内存模型&lt;a class="anchor" href="#java%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212141939466.png" alt="ly-20241212141939466" />&lt;/p>
&lt;h2 id="指令重排序">指令重排序&lt;a class="anchor" href="#%e6%8c%87%e4%bb%a4%e9%87%8d%e6%8e%92%e5%ba%8f">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>什么是指令重排序？&lt;/strong> 简单来说就是系统在&lt;strong>执行代码的时候并不一定是按照你写的代码的顺序&lt;/strong>依次执行&lt;/p>
&lt;/li>
&lt;li>
&lt;p>指令重排有下面2种&lt;/p>
&lt;ul>
&lt;li>&lt;strong>编译器优化重排&lt;/strong>：编译器（包括 JVM、JIT 编译器等）在不改变&lt;strong>单线程程序语义&lt;/strong>的前提下，重新安排语句的执行顺序。&lt;/li>
&lt;li>&lt;strong>指令并行重排&lt;/strong>：现代处理器采用了&lt;strong>指令级并行技术(Instruction-Level Parallelism，ILP)来将多条指令重叠执行&lt;/strong>。如果不存在数据依赖性，处理器可以改变语句对应机器指令的执行顺序&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>另外，&lt;strong>内存系统&lt;/strong>也会有“重排序”，但又不是真正意义上的重排序。在 JMM 里表现为&lt;strong>主存和本地内存的内容可能不一致&lt;/strong>，进而&lt;strong>导致程序在多线程下执行可能出现问题&lt;/strong>。&lt;/p>&lt;/blockquote>&lt;p>即Java源代码会经历 &lt;strong>编译器优化重排&lt;/strong>&amp;mdash;&amp;gt;&lt;strong>指令并行重排&lt;/strong>&amp;mdash;&amp;gt;&lt;strong>内存系统重排&lt;/strong>，最终&lt;strong>编程&lt;/strong>操作系统可执行的&lt;strong>指令序列&lt;/strong>&lt;/p>
&lt;p>极其重要★：指令重排序可以&lt;strong>保证串行语义一致&lt;/strong>，但是&lt;strong>没有义务保证多线程间的语义也一致&lt;/strong>，所以在多线程下&lt;strong>指令重排&lt;/strong>可能导致一些问题&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>编译器和处理器的指令重排序的处理方式不一样。对于&lt;strong>编译器&lt;/strong>，通过&lt;strong>禁止特定类型的编译器重排序&lt;/strong>的方式来禁止重排序。对于&lt;strong>处理器&lt;/strong>，通过&lt;strong>插入内存屏障（Memory Barrier，或有时叫做内存栅栏，Memory Fence）&lt;strong>的方式来禁止特定类型的处理器重排序。&lt;strong>指令并行重&lt;/strong>排和&lt;/strong>内存系统重排&lt;/strong>都属于是&lt;strong>处理器级别的指令重排序&lt;/strong>。&lt;/p>
&lt;p>内存屏障（Memory Barrier，或有时叫做内存栅栏，Memory Fence）是一种 &lt;strong>CPU 指令&lt;/strong>，用来&lt;strong>禁止处理器指令发生重排序&lt;/strong>（像屏障一样），从而保障指令执行的有序性。另外，为了达到屏障的效果，它也会使&lt;strong>处理器写入、读取值之前&lt;/strong>，将&lt;strong>主内存的值写入高速缓存&lt;/strong>，&lt;strong>清空无效队列&lt;/strong>，从而保障变量的可见性。&lt;/p>&lt;/blockquote>&lt;h2 id="jmmjavamemorymode">JMM（JavaMemoryMode）&lt;a class="anchor" href="#jmmjavamemorymode">#&lt;/a>&lt;/h2>
&lt;h3 id="什么是-jmm为什么需要-jmm">什么是 JMM？为什么需要 JMM？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-jmm%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-jmm">#&lt;/a>&lt;/h3>
&lt;blockquote class='book-hint '>
&lt;p>一般来说，编程语言也可以直接复用操作系统层面的内存模型。不过，不同的操作系统内存模型不同。&lt;strong>如果直接复用操作系统层面的内存模型，就可能会导致同样一套代码换了一个操作系统就无法执行了&lt;/strong>。&lt;strong>Java 语言是跨平台的，它需要自己提供一套内存模型以屏蔽系统差异。&lt;/strong>&lt;/p>&lt;/blockquote>&lt;p>实际上，对于Java来说，可以&lt;strong>把JMM看作是Java定义的并发编程相关的一组规范&lt;/strong>，除了抽象了线程和主内存之间的关系之外，还规定了&lt;strong>从Java源代码到CPU可执行指令的转化过程&lt;/strong>要遵守哪些和并发相关的原则和规范，主要目的是为了&lt;strong>简化多线程编程&lt;/strong>，&lt;strong>增强程序可移植性&lt;/strong>。&lt;/p>
&lt;p>为什么要遵守这些并发相关的原则和规范呢？因为在&lt;strong>并发编程&lt;/strong>下，CPU多级缓存和指令重排这类设计会导致程序运行出问题，比如&lt;strong>指令重排&lt;/strong>，为此JMM抽象了&lt;strong>happens-before&lt;/strong>原则&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>JMM 说白了就是定义了一些规范来解决这些问题，开发者可以利用这些规范更方便地开发多线程程序。对于 Java 开发者说，你&lt;strong>不需要了解底层原理&lt;/strong>，&lt;strong>直接使用并发相关的一些关键字&lt;/strong>和&lt;strong>类&lt;/strong>（比如 &lt;code>volatile&lt;/code>、&lt;code>synchronized&lt;/code>、各种 &lt;code>Lock&lt;/code>）即可开发出并发安全的程序。&lt;/p>&lt;/blockquote>&lt;h3 id="jmm-是如何抽象线程和主内存之间的关系">JMM 是如何抽象线程和主内存之间的关系？&lt;a class="anchor" href="#jmm-%e6%98%af%e5%a6%82%e4%bd%95%e6%8a%bd%e8%b1%a1%e7%ba%bf%e7%a8%8b%e5%92%8c%e4%b8%bb%e5%86%85%e5%ad%98%e4%b9%8b%e9%97%b4%e7%9a%84%e5%85%b3%e7%b3%bb">#&lt;/a>&lt;/h3>
&lt;p>&lt;strong>Java内存模型(JMM)&lt;/strong>，抽象了&lt;strong>线程和主内存之间的关系&lt;/strong>，比如线程之间的&lt;strong>共享变量必须存储在主内存&lt;/strong>中&lt;/p></description></item><item><title>并发03</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0303lyconcurrent-03/</link><pubDate>Mon, 07 Nov 2022 16:04:33 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0303lyconcurrent-03/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="线程池">线程池&lt;a class="anchor" href="#%e7%ba%bf%e7%a8%8b%e6%b1%a0">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>为什么要使用线程池&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ul>
&lt;li>池化技术：&lt;strong>线程池&lt;/strong>、&lt;strong>数据库连接池&lt;/strong>、&lt;strong>Http连接池&lt;/strong>&lt;/li>
&lt;li>池化技术思想意义：为了减少每次&lt;strong>获取资源&lt;/strong>的&lt;strong>消耗&lt;/strong>，提高对&lt;strong>资源的利用率&lt;/strong>&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;ul>
&lt;li>线程池提供了&lt;strong>限制&lt;/strong>和&lt;strong>管理&lt;/strong> &lt;strong>资源&lt;/strong>(包括执行一个任务)的方式&lt;/li>
&lt;li>每个线程池还维护&lt;strong>基本统计信息&lt;/strong>，例如&lt;strong>已完成&lt;/strong>任务的数量&lt;/li>
&lt;li>好处：
&lt;ol>
&lt;li>&lt;strong>降低资源消耗&lt;/strong> &lt;strong>重复利用已创建线程&lt;/strong>降低&lt;strong>线程创建&lt;/strong>和&lt;strong>销毁&lt;/strong>造成的消耗&lt;/li>
&lt;li>提高响应速度 任务到达时，任务可以&lt;strong>不需等到线程创建&lt;/strong>就能继续执行&lt;/li>
&lt;li>提高线程的&lt;strong>可管理性&lt;/strong> 线程是稀缺资源，如果无限制创建，不仅&lt;strong>消耗系统资源&lt;/strong>，还会&lt;strong>降低系统的稳定性&lt;/strong>，使用线程池统一&lt;strong>管理分配&lt;/strong>、&lt;strong>调优&lt;/strong>和&lt;strong>监控&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>实现Runnable接口和Callable接口的区别&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Callable的用法 &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">TestLy&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//如果加上volatile,就能保证可见性，线程1 才能停止&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> stop &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">false&lt;/span>;&lt;span style="color:#75715e">//对象属性&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) &lt;span style="color:#66d9ef">throws&lt;/span> InterruptedException, ExecutionException {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FutureTask&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span> futureTask&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> FutureTask&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>(&lt;span style="color:#66d9ef">new&lt;/span> Callable&lt;span style="color:#f92672">&amp;lt;&lt;/span>String&lt;span style="color:#f92672">&amp;gt;&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Override&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">call&lt;/span>() &lt;span style="color:#66d9ef">throws&lt;/span> Exception {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;等3s再把结果给你&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TimeUnit.&lt;span style="color:#a6e22e">SECONDS&lt;/span>.&lt;span style="color:#a6e22e">sleep&lt;/span>(3);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#e6db74">&amp;#34;hello world&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(futureTask).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> String s &lt;span style="color:#f92672">=&lt;/span> futureTask.&lt;span style="color:#a6e22e">get&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;3s后获取到了结果&amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>s);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(&lt;span style="color:#66d9ef">new&lt;/span> Runnable() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Override&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;abc&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">等3s再把结果给你
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">3s后获取到了结果hello world
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">abc
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>
&lt;p>Runnable接口不会返回结果或抛出检查异常，Callable接口可以&lt;/p></description></item><item><title>锁升级</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly03122lylock_escalation/</link><pubDate>Sun, 06 Nov 2022 12:31:02 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly03122lylock_escalation/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>以下内容均转自 
&lt;a target="_blank" href="https://www.cnblogs.com/wuqinglong/p/9945618.html">https://www.cnblogs.com/wuqinglong/p/9945618.html&lt;/a>，部分疑惑参考自另一作者 
&lt;a target="_blank" href="https://github.com/farmerjohngit/myblog/issues/12">https://github.com/farmerjohngit/myblog/issues/12&lt;/a> ，感谢原作者。&lt;/p>
&lt;p>【&lt;strong>目前还是存有部分疑虑（轻量级锁那块）&lt;/strong>，可能需要详细看源码才能释疑】&lt;/p>&lt;/blockquote>&lt;h2 id="概述">概述&lt;a class="anchor" href="#%e6%a6%82%e8%bf%b0">#&lt;/a>&lt;/h2>
&lt;p>传统的synchronized为重量级锁（&lt;strong>使用操作系统互斥量（&lt;code>mutex&lt;/code>）来实现的传统锁&lt;/strong>），但是随着JavaSE1.6对synchronized优化后，部分情况下他就没有那么重了。本文介绍了JavaSE1.6为了减少获得锁和释放锁带来的性能消耗而引入的&lt;strong>偏向锁&lt;/strong>和&lt;strong>轻量级锁&lt;/strong>，以及&lt;strong>锁结构&lt;/strong>、及&lt;strong>锁升级&lt;/strong>过程&lt;/p>
&lt;h2 id="实现同步的基础">实现同步的基础&lt;a class="anchor" href="#%e5%ae%9e%e7%8e%b0%e5%90%8c%e6%ad%a5%e7%9a%84%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h2>
&lt;p>Java中&lt;strong>每个对象&lt;/strong>都可以&lt;strong>作为锁&lt;/strong>，具体变现形式&lt;/p>
&lt;ol>
&lt;li>对于&lt;strong>普通同步&lt;/strong>方法，锁是&lt;strong>当前实例对象&lt;/strong>&lt;/li>
&lt;li>对于&lt;strong>静态同步&lt;/strong>方法，锁是&lt;strong>当前类的Class对象&lt;/strong>&lt;/li>
&lt;li>对于&lt;strong>同步方法块&lt;/strong>，锁是&lt;strong>synchronized括号里配置的对象&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>一个线程试图访问同步代码块时，必须&lt;strong>获取锁&lt;/strong>；在&lt;strong>退出&lt;/strong>或者&lt;strong>抛出异常&lt;/strong>时，必须&lt;strong>释放锁&lt;/strong>&lt;/p>
&lt;h2 id="实现方式">实现方式&lt;a class="anchor" href="#%e5%ae%9e%e7%8e%b0%e6%96%b9%e5%bc%8f">#&lt;/a>&lt;/h2>
&lt;p>JVM 基于&lt;strong>进入和退出 Monitor 对象&lt;/strong>来实现&lt;strong>方法同步&lt;/strong>和&lt;strong>代码块同步&lt;/strong>，但是两者的&lt;strong>实现细节不一样&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>代码块同步&lt;/strong>：通过使用 &lt;strong>monitorenter&lt;/strong> 和 &lt;strong>monitorexit&lt;/strong> 指令实现的&lt;/li>
&lt;li>同步方法：&lt;strong>ACC_SYNCHRONIZED&lt;/strong> 修饰&lt;/li>
&lt;/ol>
&lt;p>monitorenter 指令是在&lt;strong>编译后插入到同步代码块的开始位置&lt;/strong>，而 monitorexit 指令是在&lt;strong>编译后插入到同步代码块的结束处或异常处&lt;/strong>&lt;/p>
&lt;p>对于同步方法，&lt;strong>进入方法前&lt;/strong>添加一个 monitorenter 指令，&lt;strong>退出方法后&lt;/strong>添加一个 monitorexit 指令。&lt;/p>
&lt;p>demo：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Demo&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">f1&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">synchronized&lt;/span> (Demo.&lt;span style="color:#a6e22e">class&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Hello World.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">synchronized&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">f2&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Hello World.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>编译之后的字节码（使用 javap )&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">f1&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> descriptor: ()V
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flags: ACC_PUBLIC
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Code:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stack&lt;span style="color:#f92672">=&lt;/span>2, locals&lt;span style="color:#f92672">=&lt;/span>3, args_size&lt;span style="color:#f92672">=&lt;/span>1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 0: ldc &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>2 &lt;span style="color:#75715e">// class me/snail/base/Demo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 2: dup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 3: astore_1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 4: monitorenter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 5: getstatic &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>3 &lt;span style="color:#75715e">// Field java/lang/System.out:Ljava/io/PrintStream;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 8: ldc &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>4 &lt;span style="color:#75715e">// String Hello World.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 10: invokevirtual &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>5 &lt;span style="color:#75715e">// Method java/io/PrintStream.println:(Ljava/lang/String;)V&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 13: aload_1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 14: monitorexit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 15: &lt;span style="color:#66d9ef">goto&lt;/span> 23
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 18: astore_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 19: aload_1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 20: monitorexit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 21: aload_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 22: athrow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 23: &lt;span style="color:#66d9ef">return&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Exception table:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> from to target type
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 5 15 18 any
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 18 21 18 any
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LineNumberTable:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 6: 0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 7: 5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 8: 13
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 9: 23
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StackMapTable: number_of_entries &lt;span style="color:#f92672">=&lt;/span> 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> frame_type &lt;span style="color:#f92672">=&lt;/span> 255 &lt;span style="color:#75715e">/* full_frame */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset_delta &lt;span style="color:#f92672">=&lt;/span> 18
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> locals &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">me&lt;/span>&lt;span style="color:#f92672">/&lt;/span>snail&lt;span style="color:#f92672">/&lt;/span>base&lt;span style="color:#f92672">/&lt;/span>Demo, &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">java&lt;/span>&lt;span style="color:#f92672">/&lt;/span>lang&lt;span style="color:#f92672">/&lt;/span>Object &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stack &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">java&lt;/span>&lt;span style="color:#f92672">/&lt;/span>lang&lt;span style="color:#f92672">/&lt;/span>Throwable &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> frame_type &lt;span style="color:#f92672">=&lt;/span> 250 &lt;span style="color:#75715e">/* chop */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset_delta &lt;span style="color:#f92672">=&lt;/span> 4
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">synchronized&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">f2&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> descriptor: ()V
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flags: ACC_PUBLIC, ACC_SYNCHRONIZED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Code:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stack&lt;span style="color:#f92672">=&lt;/span>2, locals&lt;span style="color:#f92672">=&lt;/span>1, args_size&lt;span style="color:#f92672">=&lt;/span>1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 0: getstatic &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>3 &lt;span style="color:#75715e">// Field java/lang/System.out:Ljava/io/PrintStream;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 3: ldc &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>4 &lt;span style="color:#75715e">// String Hello World.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 5: invokevirtual &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>5 &lt;span style="color:#75715e">// Method java/io/PrintStream.println:(Ljava/lang/String;)V&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 8: &lt;span style="color:#66d9ef">return&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LineNumberTable:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 12: 0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 13: 8&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>先说 &lt;strong>f1() 方法&lt;/strong>，发现其中&lt;strong>一个 monitorenter 对应了两个 monitorexit&lt;/strong>，这是不对的。&lt;strong>但是&lt;/strong>仔细看 #15: goto 语句，直接跳转到了 #23: return 处，再看 #22: athrow 语句发现，原来&lt;strong>第二个 monitorexit&lt;/strong> 是&lt;strong>保证同步代码块抛出异常&lt;/strong>时锁能&lt;strong>得到正确的释放&lt;/strong>而存在的，这就理解了。&lt;/p></description></item><item><title>(该文弃用)锁升级</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/lock_escalation_deprecated2/</link><pubDate>Thu, 03 Nov 2022 11:08:59 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/lock_escalation_deprecated2/</guid><description>&lt;p>本文主要讲解synchronized原理和偏向锁、轻量级锁、重量级锁的升级过程，基本都转自&lt;/p>
&lt;p>
&lt;a target="_blank" href="https://blog.csdn.net/MariaOzawa/article/details/107665689">https://blog.csdn.net/MariaOzawa/article/details/107665689&lt;/a> 原作者:
&lt;a target="_blank" href="https://blog.csdn.net/MariaOzawa">MariaOzawa&lt;/a>&lt;/p>
&lt;h2 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>为什么需要锁&lt;br>
并发编程中，多个线程访问同一共享资源时，必须考虑如何维护数据的&lt;strong>原子性&lt;/strong>&lt;/li>
&lt;li>历史
&lt;ul>
&lt;li>JDK1.5之前，Java依靠Synchronized关键字实现锁功能，Synchronized是&lt;strong>Jvm&lt;/strong>实现的&lt;strong>内置锁&lt;/strong>，锁的&lt;strong>获取与释放&lt;/strong>由JVM隐式实现&lt;/li>
&lt;li>JDK1.5，并发包新增Lock接口实现锁功能，提供同步功能，使用时&lt;strong>显式获取和释放锁&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>区别
&lt;ul>
&lt;li>Lock同步锁基于Java实现，Synchronized基于底层操作系统的MutexLock实现
&lt;code>/ˈmjuːtɛks/ &lt;/code>，每次&lt;strong>获取和释放锁&lt;/strong>都会带来&lt;strong>用户态和内核态的切换&lt;/strong>，从而&lt;strong>增加系统性能开销&lt;/strong>，性能糟糕，又称&lt;strong>重量级锁&lt;/strong>&lt;/li>
&lt;li>JDK1.6之后，对&lt;strong>Synchronized同步锁&lt;/strong>做了&lt;strong>充分优化&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="synchronized同步锁实现原理">Synchronized同步锁实现原理&lt;a class="anchor" href="#synchronized%e5%90%8c%e6%ad%a5%e9%94%81%e5%ae%9e%e7%8e%b0%e5%8e%9f%e7%90%86">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>Synchronized实现同步锁的两种方式：修饰方法；修饰方法块&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 关键字在实例方法上，锁为当前实例&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">synchronized&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">method1&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// code&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 关键字在代码块上，锁为括号里面的对象&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">method2&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Object o &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Object();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">synchronized&lt;/span> (o) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// code&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里使用编译&amp;ndash;及javap 打印字节文件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>javac -encoding UTF-8 SyncTest.java //先运行编译class文件命令
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>javap -v SyncTest.class //再通过javap打印出字节文件&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>结果如下，Synchronized修饰代码块时，由monitorenter和monitorexist指令实现同步。进入monitorenter指令后线程持有Monitor对象；退出monitorenter指令后，线程释放该Monitor对象&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">method2&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> descriptor: ()V
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flags: ACC_PUBLIC
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Code:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stack&lt;span style="color:#f92672">=&lt;/span>2, locals&lt;span style="color:#f92672">=&lt;/span>4, args_size&lt;span style="color:#f92672">=&lt;/span>1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 0: &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>2 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 3: dup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 4: invokespecial &lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>1 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 7: astore_1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 8: aload_1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 9: dup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 10: astore_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 11: monitorenter &lt;span style="color:#75715e">//monitorenter 指令&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 12: aload_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 13: monitorexit &lt;span style="color:#75715e">//monitorexit 指令&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 14: &lt;span style="color:#66d9ef">goto&lt;/span> 22
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 17: astore_3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 18: aload_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 19: monitorexit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 20: aload_3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 21: athrow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 22: &lt;span style="color:#66d9ef">return&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Exception table:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> from to target type
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 12 14 17 any
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 17 20 17 any
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LineNumberTable:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 18: 0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 19: 8
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 21: 12
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> line 22: 22
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> StackMapTable: number_of_entries &lt;span style="color:#f92672">=&lt;/span> 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> frame_type &lt;span style="color:#f92672">=&lt;/span> 255 &lt;span style="color:#75715e">/* full_frame */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset_delta &lt;span style="color:#f92672">=&lt;/span> 17
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> locals &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">com&lt;/span>&lt;span style="color:#f92672">/&lt;/span>demo&lt;span style="color:#f92672">/&lt;/span>io&lt;span style="color:#f92672">/&lt;/span>SyncTest, &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">java&lt;/span>&lt;span style="color:#f92672">/&lt;/span>lang&lt;span style="color:#f92672">/&lt;/span>Object, &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">java&lt;/span>&lt;span style="color:#f92672">/&lt;/span>lang&lt;span style="color:#f92672">/&lt;/span>Object &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stack &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">java&lt;/span>&lt;span style="color:#f92672">/&lt;/span>lang&lt;span style="color:#f92672">/&lt;/span>Throwable &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> frame_type &lt;span style="color:#f92672">=&lt;/span> 250 &lt;span style="color:#75715e">/* chop */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset_delta &lt;span style="color:#f92672">=&lt;/span> 4&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果Synchronized修饰同步方法，代替monitorenter和monitorexit的是 &lt;code>ACC_SYNCHRONIZED&lt;/code>标志，即：JVM使用该访问标志区分方法是否为同步方法。方法调用时，调用指令检查是否设置ACC_SYNCHRONIZED标志，如有，则执行线程&lt;strong>先持有&lt;/strong>该Monitor对象，再执行该方法；&lt;strong>运行期间&lt;/strong>，&lt;strong>其他线程无法获取到该Monitor&lt;/strong>对象；方法&lt;strong>执行完成&lt;/strong>后，&lt;strong>释放该Monitor&lt;/strong>对象
javap -v xx.class 字节文件查看&lt;/p></description></item><item><title>(该文弃用)锁升级</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/lock_escalation_deprecated/</link><pubDate>Mon, 31 Oct 2022 11:08:59 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/lock_escalation_deprecated/</guid><description>&lt;h2 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;p>无锁 =&amp;gt; 偏向锁 =&amp;gt; 轻量锁 =&amp;gt; 重量锁&lt;/p>
&lt;p>复习Class类锁和实例对象锁，说明Class类锁和实例对象锁不是同一把锁，互相不影响&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) &lt;span style="color:#66d9ef">throws&lt;/span> InterruptedException { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Object object&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">new&lt;/span> Object();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(()&lt;span style="color:#f92672">-&amp;gt;&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">synchronized&lt;/span> (Customer.&lt;span style="color:#a6e22e">class&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>()&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Object.class类锁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TimeUnit.&lt;span style="color:#a6e22e">SECONDS&lt;/span>.&lt;span style="color:#a6e22e">sleep&lt;/span>(5);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (InterruptedException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.&lt;span style="color:#a6e22e">printStackTrace&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>()&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#34;结束并释放锁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },&lt;span style="color:#e6db74">&amp;#34;线程1&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//保证线程1已经获得类锁&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TimeUnit.&lt;span style="color:#a6e22e">SECONDS&lt;/span>.&lt;span style="color:#a6e22e">sleep&lt;/span>(2);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (InterruptedException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.&lt;span style="color:#a6e22e">printStackTrace&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(()&lt;span style="color:#f92672">-&amp;gt;&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">synchronized&lt;/span> (object){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>()&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#34;获得object实例对象锁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>()&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#34;结束并释放锁&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },&lt;span style="color:#e6db74">&amp;#34;线程2&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/* 输出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">线程1Object.class类锁
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">线程2获得object实例对象锁
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">线程2结束并释放锁
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">线程1结束并释放锁
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>总结图 , 00 , 01 , 10 ，没有11&lt;/p></description></item><item><title>对象内存布局和对象头</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly03121lyobject-concurrent/</link><pubDate>Sun, 30 Oct 2022 16:56:16 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly03121lyobject-concurrent/</guid><description>&lt;h2 id="对象布局">对象布局&lt;a class="anchor" href="#%e5%af%b9%e8%b1%a1%e5%b8%83%e5%b1%80">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>heap&lt;/strong> （&lt;strong>where&lt;/strong>）: &lt;strong>new (eden ,s0 ,s1) ,old, metaspace&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对象的构成元素（what）
HotSpot虚拟机里，对象在&lt;strong>堆内存中的存储布局&lt;/strong>分为三个部分
&lt;img src="img/ly-20241212141948626.png" alt="ly-20241212141948626" />&lt;/p>
&lt;ul>
&lt;li>对象头（Header）
&lt;ul>
&lt;li>&lt;strong>对象标记 MarkWord&lt;/strong>&lt;/li>
&lt;li>&lt;strong>类元信息&lt;/strong>（类型指针 Class Pointer，指向方法区的地址）&lt;/li>
&lt;li>对象头多大 &lt;strong>length&lt;/strong>（数组才有）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>实例数据（Instance Data）&lt;/li>
&lt;li>对其填充（Padding，保证整个对象大小，是8个字节的倍数）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="对象头">对象头&lt;a class="anchor" href="#%e5%af%b9%e8%b1%a1%e5%a4%b4">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>对象标记&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ol>
&lt;li>Object o= new Object(); //new一个对象，占内存多少&lt;/li>
&lt;li>o.hashCode() //hashCode存在对象哪个地方&lt;/li>
&lt;li>synchronized(o){ } //对象被锁了多少次（可重入锁）&lt;/li>
&lt;li>System.gc(); //躲过了几次gc（次数）&lt;/li>
&lt;/ol>&lt;/blockquote>&lt;p>上面这些，&lt;strong>哈希码&lt;/strong>、&lt;strong>gc标记&lt;/strong>、&lt;strong>gc次数&lt;/strong>、&lt;strong>同步锁标记&lt;/strong>、&lt;strong>偏向锁持有者&lt;/strong>，都保存在&lt;strong>对象标记&lt;/strong>里面
&lt;img src="img/ly-20241212141948895.png" alt="ly-20241212141948895" />&lt;/p>
&lt;ol>
&lt;li>如果在64位系统中，对象头中，**mark word（对象标记）**占用8个字节（64位）；**class pointer（类元信息）**占用8个字节，总共16字节（忽略压缩指针）&lt;/li>
&lt;li>无锁的时候，
&lt;img src="img/ly-20241212141949076.png" alt="ly-20241212141949076" />&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>类型指针
注意下图，指向方法区中（模板）的地址
&lt;img src="img/ly-20241212141949249.png" alt="ly-20241212141949249" />&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="实例数据和对齐填充">实例数据和对齐填充&lt;a class="anchor" href="#%e5%ae%9e%e4%be%8b%e6%95%b0%e6%8d%ae%e5%92%8c%e5%af%b9%e9%bd%90%e5%a1%ab%e5%85%85">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>实例数据&lt;/p>
&lt;/li>
&lt;li>
&lt;p>用来存放类的属性（Filed）数据信息，包括父类的属性信息&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对齐填充&lt;/p>
&lt;/li>
&lt;li>
&lt;p>填充到长度为8字节，因为虚拟机要求&lt;strong>对象起始地址必须是8字节的整数倍&lt;/strong>（对齐填充不一定存在）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>示例&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Customer&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> id;&lt;span style="color:#75715e">//4字节&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> flag&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">false&lt;/span>; &lt;span style="color:#75715e">//1字节&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Customer customer=new Customer();&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">//&lt;/span>该对象大小&lt;span style="color:#960050;background-color:#1e0010">：&lt;/span>对象头&lt;span style="color:#960050;background-color:#1e0010">（&lt;/span>对象标记8&lt;span style="color:#f92672">+&lt;/span>类型指针8&lt;span style="color:#960050;background-color:#1e0010">）&lt;/span>&lt;span style="color:#f92672">+&lt;/span>实例数据&lt;span style="color:#960050;background-color:#1e0010">（&lt;/span>4&lt;span style="color:#f92672">+&lt;/span>1&lt;span style="color:#960050;background-color:#1e0010">）&lt;/span>&lt;span style="color:#f92672">=&lt;/span>21字节 &lt;span style="color:#f92672">===&amp;gt;&lt;/span> 为了对齐填充&lt;span style="color:#960050;background-color:#1e0010">，&lt;/span>则为24字节&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h3 id="源码查看">源码查看&lt;a class="anchor" href="#%e6%ba%90%e7%a0%81%e6%9f%a5%e7%9c%8b">#&lt;/a>&lt;/h3>
&lt;p>&lt;img src="img/ly-20241212141949397.png" alt="ly-20241212141949397" />&lt;/p>
&lt;h2 id="具体的64位虚拟机为主">具体的（64位虚拟机为主）&lt;a class="anchor" href="#%e5%85%b7%e4%bd%93%e7%9a%8464%e4%bd%8d%e8%99%9a%e6%8b%9f%e6%9c%ba%e4%b8%ba%e4%b8%bb">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212141949552.png" alt="ly-20241212141949552" />&lt;/p></description></item><item><title>并发02</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0302lyconcurrent-02/</link><pubDate>Fri, 28 Oct 2022 14:15:06 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0302lyconcurrent-02/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="jmmjavamemorymodel">JMM（JavaMemoryModel)&lt;a class="anchor" href="#jmmjavamemorymodel">#&lt;/a>&lt;/h2>
&lt;p>
&lt;a target="_blank" href="https://blog.liuyi2026.asia/2022/11/21/review/java_guide/java/concurrent/jmm">详见-知识点&lt;/a>
&lt;img src="img/ly-20241212141935173.png" alt="Java内存模型" />&lt;/p>
&lt;h2 id="volatile关键字">volatile关键字&lt;a class="anchor" href="#volatile%e5%85%b3%e9%94%ae%e5%ad%97">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>保证变量可见性&lt;/p>
&lt;ul>
&lt;li>
&lt;p>使用volatile关键字保证变量可见性，如果将变量声明为volatile则&lt;strong>指示JVM该变量是共享且不稳定&lt;/strong>的，每次使用它都到&lt;strong>主存&lt;/strong>中读取&lt;br>
&lt;img src="img/ly-20241212141935463.png" alt="ly-20241212141935463" />&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>volatile关键字并非Java语言特有，在C语言里也有，它最原始的意义就是&lt;strong>禁用CPU缓存&lt;/strong>。&lt;/p>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>volatile关键字只能&lt;strong>保证数据可见性&lt;/strong>，&lt;strong>不能保证数据原子性&lt;/strong>。&lt;strong>synchronized&lt;/strong>关键字两者都能保证&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不可见的例子&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> com.concurrent; 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> java.util.concurrent.TimeUnit;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">TestLy&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//如果加上volatile,就能保证可见性，线程1 才能停止&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">boolean&lt;/span> stop &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">false&lt;/span>;&lt;span style="color:#75715e">//对象属性&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) &lt;span style="color:#66d9ef">throws&lt;/span> InterruptedException {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TestLy atomicTest &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> TestLy();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(() &lt;span style="color:#f92672">-&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>atomicTest.&lt;span style="color:#a6e22e">stop&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//这里不能加System.out.println ,因为这个方法内部用了synchronized修饰,会导致获取主内存的值，&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//就没法展示效果了&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*System.out.println(&amp;#34;1还没有停止&amp;#34;);*/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>()&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#34;停止了&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },&lt;span style="color:#e6db74">&amp;#34;线程1&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> Thread(() &lt;span style="color:#f92672">-&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TimeUnit.&lt;span style="color:#a6e22e">SECONDS&lt;/span>.&lt;span style="color:#a6e22e">sleep&lt;/span>(1);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (InterruptedException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.&lt;span style="color:#a6e22e">printStackTrace&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> atomicTest.&lt;span style="color:#a6e22e">stop&lt;/span>&lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(Thread.&lt;span style="color:#a6e22e">currentThread&lt;/span>().&lt;span style="color:#a6e22e">getName&lt;/span>()&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#34;让线程1停止&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },&lt;span style="color:#e6db74">&amp;#34;线程2&amp;#34;&lt;/span>).&lt;span style="color:#a6e22e">start&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#66d9ef">true&lt;/span>){}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>如何禁止指令重排
使用&lt;strong>volatile&lt;/strong>关键字，除了可以保证&lt;strong>变量的可见性&lt;/strong>，还能&lt;strong>防止JVM指令重排&lt;/strong>。当我们对这个变量进行读写操作的时候，-会通过插入特定的&lt;strong>内存屏障&lt;/strong>来禁止指令重排&lt;/p></description></item><item><title>并发01</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0301lyconcurrent-01/</link><pubDate>Wed, 26 Oct 2022 16:46:32 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Concurrent/ly0301lyconcurrent-01/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>
&lt;p>什么是进程和线程&lt;/p>
&lt;ul>
&lt;li>
&lt;p>进程：是程序的&lt;strong>一次执行过程&lt;/strong>，是系统运行程序的&lt;strong>基本单位&lt;/strong>
系统运行一个程序，即一个进程从&lt;strong>创建、运行到消亡&lt;/strong>的过程&lt;/p>
&lt;ul>
&lt;li>
&lt;p>启动main函数则启动了一个JVM进程，&lt;strong>main函数所在线程&lt;/strong>为进程中的一个线程，也称&lt;strong>主线程&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>以下为一个个的进程&lt;br>
&lt;img src="img/ly-20241212141934200.png" alt="ly-20241212141934200" />&lt;/p>
&lt;ul>
&lt;li>
&lt;p>查看java进程&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>jps -l
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">32&lt;/span> org.jetbrains.jps.cmdline.Launcher
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">10084&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">16244&lt;/span> com.Test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">17400&lt;/span> sun.tools.jps.Jps&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>杀死进程&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span> taskkill /f /pid &lt;span style="color:#ae81ff">16244&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>何为线程&lt;/p>
&lt;ul>
&lt;li>
&lt;p>线程，比进程更小的执行单位&lt;/p>
&lt;/li>
&lt;li>
&lt;p>同类的&lt;strong>多个线程&lt;/strong>共享&lt;strong>进程&lt;/strong>的&lt;strong>堆和方法区&lt;/strong>资源，但每个线程有自己的&lt;strong>程序计数器、虚拟机栈、本地方法栈&lt;/strong>，又被称为&lt;strong>轻量级进程&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java天生就是多线程程序，如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">MultiThread&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 获取 Java 线程管理 MXBean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	ThreadMXBean threadMXBean &lt;span style="color:#f92672">=&lt;/span> ManagementFactory.&lt;span style="color:#a6e22e">getThreadMXBean&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 不需要获取同步的 monitor 和 synchronizer 信息，仅获取线程和线程堆栈信息&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		ThreadInfo&lt;span style="color:#f92672">[]&lt;/span> threadInfos &lt;span style="color:#f92672">=&lt;/span> threadMXBean.&lt;span style="color:#a6e22e">dumpAllThreads&lt;/span>(&lt;span style="color:#66d9ef">false&lt;/span>, &lt;span style="color:#66d9ef">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 遍历线程信息，仅打印线程 ID 和线程名称信息&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">for&lt;/span> (ThreadInfo threadInfo : threadInfos) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;[&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> threadInfo.&lt;span style="color:#a6e22e">getThreadId&lt;/span>() &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34;] &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> threadInfo.&lt;span style="color:#a6e22e">getThreadName&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//输出&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>5&lt;span style="color:#f92672">]&lt;/span> Attach Listener &lt;span style="color:#75715e">//添加事件&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>4&lt;span style="color:#f92672">]&lt;/span> Signal Dispatcher &lt;span style="color:#75715e">// 分发处理给 JVM 信号的线程&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>3&lt;span style="color:#f92672">]&lt;/span> Finalizer &lt;span style="color:#75715e">//调用对象 finalize 方法的线程&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>2&lt;span style="color:#f92672">]&lt;/span> Reference Handler &lt;span style="color:#75715e">//清除 reference 线程&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>1&lt;span style="color:#f92672">]&lt;/span> main &lt;span style="color:#f92672">//&lt;/span>main 线程,程序入口&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>也就是说，一个Java程序的运行，是main线程和多个其他线程同时运行&lt;/p></description></item><item><title>io模型</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/IO/ly0203lyio-model/</link><pubDate>Wed, 26 Oct 2022 14:17:58 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/IO/ly0203lyio-model/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;blockquote class='book-hint '>
&lt;p>
&lt;a target="_blank" href="https://zhuanlan.zhihu.com/p/360878783">https://zhuanlan.zhihu.com/p/360878783&lt;/a> IO多路复用讲解，这是一个与系统底层有关的知识点，需要一些操作系统调用代码才知道IO多路复用省的时间。&lt;/p>&lt;/blockquote>&lt;h2 id="io">I/O&lt;a class="anchor" href="#io">#&lt;/a>&lt;/h2>
&lt;h3 id="何为io">何为I/O&lt;a class="anchor" href="#%e4%bd%95%e4%b8%baio">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>I/O(&lt;strong>Input/Output&lt;/strong>)，即&lt;strong>输入/输出&lt;/strong>
从计算机结构的角度来解读一下I/O，根据冯诺依曼结构，计算机结构分为5大部分：&lt;strong>运算器&lt;/strong>、&lt;strong>控制器&lt;/strong>、&lt;strong>存储器&lt;/strong>、&lt;strong>输入设备&lt;/strong>、&lt;strong>输出设备&lt;/strong>
&lt;img src="img/ly-20241212141951603.png" alt="ly-20241212141951603" />
其中，输入设备：键盘；输出设备：显示器
网卡、硬盘既属于输入设备也属于输出设备&lt;/li>
&lt;li>输入设备&lt;strong>向计算机输入（内存）&lt;strong>数据，输出设备&lt;/strong>接收计算机（内存）&lt;strong>输出的数据，即I/O描述了&lt;/strong>计算机系统&lt;/strong>与&lt;strong>外部设备&lt;/strong>之间&lt;strong>通信&lt;/strong>的过程&lt;/li>
&lt;li>从应用程序的角度解读I/O
&lt;ul>
&lt;li>为了保证系统稳定性和安全性，一个进程的地址空间划分为&lt;strong>用户空间User space&lt;/strong>和&lt;strong>内核空间Kernel space&lt;/strong> &lt;code>kernel	英[ˈkɜːnl]&lt;/code>&lt;/li>
&lt;li>平常运行的应用程序都运行在用户空间，只有&lt;strong>内核空间才能进行系统态级别&lt;/strong>的&lt;strong>资源&lt;/strong>有关操作&amp;mdash;&lt;strong>文件管理、进程通信、内存管理&lt;/strong>&lt;/li>
&lt;li>如果要进行IO操作，就得依赖&lt;strong>内核空间&lt;/strong>的能力，&lt;strong>用户空间的程序&lt;/strong>不能直接访问&lt;strong>内核空间&lt;/strong>&lt;/li>
&lt;li>用户进程要想执行IO操作，必须通过&lt;strong>系统调用&lt;/strong>来间接访问内核空间&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>对于&lt;strong>磁盘IO（读写文件）&lt;strong>和&lt;/strong>网络IO（网络请求和响应）&lt;/strong>，从应用程序视角来看，&lt;strong>应用程序&lt;/strong>对操作系统的&lt;strong>内核&lt;/strong>发起&lt;strong>IO调用（系统调用）&lt;/strong>，操作系统负责的&lt;strong>内核&lt;/strong>执行具体&lt;strong>IO&lt;/strong>操作
&lt;ul>
&lt;li>&lt;strong>应用程序只是发起了IO操作调用&lt;/strong>，而具体的IO执行则由&lt;strong>操作系统内核&lt;/strong>完成&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>应用程序&lt;strong>发起I/O后&lt;/strong>，经历两个步骤
&lt;ul>
&lt;li>内核&lt;strong>等待I/O设备&lt;/strong>准备好数据&lt;/li>
&lt;li>内核将数据&lt;strong>从内核空间&lt;/strong>拷贝&lt;strong>到用户空间&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="有哪些常见的io模型">有哪些常见的IO模型&lt;a class="anchor" href="#%e6%9c%89%e5%93%aa%e4%ba%9b%e5%b8%b8%e8%a7%81%e7%9a%84io%e6%a8%a1%e5%9e%8b">#&lt;/a>&lt;/h3>
&lt;p>UNIX系统下，包括5种：&lt;strong>同步阻塞I/O&lt;/strong>，&lt;strong>同步非阻塞I/O&lt;/strong>，&lt;strong>I/O多路复用&lt;/strong>、&lt;strong>信号驱动I/O&lt;/strong>和&lt;strong>异步I/O&lt;/strong>&lt;/p>
&lt;h2 id="java中3中常见io模型">Java中3中常见I/O模型&lt;a class="anchor" href="#java%e4%b8%ad3%e4%b8%ad%e5%b8%b8%e8%a7%81io%e6%a8%a1%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;h3 id="bio-blocking-io-">BIO (Blocking I/O )&lt;a class="anchor" href="#bio-blocking-io-">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>应用程序发起read调用后，会一直阻塞，&lt;strong>直到内核把数据拷贝到用户空间&lt;/strong>
&lt;img src="img/ly-20241212141951883.png" alt="ly-20241212141951883" />&lt;/li>
&lt;/ul>
&lt;h3 id="nio-non-blockingnew-io">NIO (Non-blocking/New I/O)&lt;a class="anchor" href="#nio-non-blockingnew-io">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>对于java.nio包，提供了&lt;strong>Channel&lt;/strong>、&lt;strong>Selector&lt;/strong>、&lt;strong>Buffer&lt;/strong>等抽象概念，对于&lt;strong>高负载高并发&lt;/strong>，应使用NIO&lt;/li>
&lt;li>NIO是I/O多路复用模型，属于&lt;strong>同步非阻塞IO&lt;/strong>模型
&lt;ul>
&lt;li>
&lt;p>&lt;strong>一般的同步非阻塞 IO 模型&lt;/strong>中，应用程序会&lt;strong>一直发起 read&lt;/strong> 调用。&lt;br>
等待&lt;strong>数据从内核空间拷贝到用户空&lt;/strong>间的这段时间里，&lt;strong>线程依然是阻塞&lt;/strong>的**，**直到在内核把数据拷贝到用户空间。&lt;/p>
&lt;p>相比于同步阻塞 IO 模型，同步非阻塞 IO 模型确实有了很大改进。通过轮询操作，避免了一直阻塞。&lt;/p>
&lt;p>但是，这种 IO 模型同样存在问题：&lt;strong>应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。&lt;/strong>&lt;/p>
&lt;p>★★ 也就是说，【准备数据，数据就绪】是&lt;strong>不阻塞&lt;/strong>的。而【拷贝数据】是&lt;strong>阻塞&lt;/strong>的
&lt;img src="img/ly-20241212141952028.image" alt="图源：《深入拆解Tomcat &amp;amp; Jetty》" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>I/O多路复用
线程首先&lt;strong>发起select调用，询问内核数据是否准备就绪&lt;/strong>，等准备好了，&lt;strong>用户线程再发起read调用&lt;/strong>，r**ead调用的过程（数据从内核空间&amp;ndash;&amp;gt;用户空间）**还是阻塞的&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>IO 多路复用模型，通过&lt;strong>减少无效的系统调用&lt;/strong>，&lt;strong>减少了对 CPU 资源的消耗&lt;/strong>。&lt;/p></description></item><item><title>io设计模式</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/IO/ly0202lyio-design-patterns/</link><pubDate>Mon, 24 Oct 2022 23:40:53 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/IO/ly0202lyio-design-patterns/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="装饰器模式">装饰器模式&lt;a class="anchor" href="#%e8%a3%85%e9%a5%b0%e5%99%a8%e6%a8%a1%e5%bc%8f">#&lt;/a>&lt;/h2>
&lt;p>​	类图：&lt;br>
​	&lt;img src="img/ly-20241212141951023.png" alt="ly-20241212141951023" />&lt;/p>
&lt;ul>
&lt;li>
&lt;p>装饰器，Decorator，装饰器模式可以在&lt;strong>不改变原有对象的情况下拓展其功能&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>★装饰器模式，&lt;strong>通过组合替代继承&lt;/strong>来扩展原始类功能，在一些&lt;strong>继承关系较复杂&lt;/strong>的场景（IO这一场景各种类的继承关系就比较复杂）下更加实用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对于字节流，&lt;strong>FilterInputStream（对应输入流）和FilterOutputStream（对应输出流）&lt;strong>是&lt;/strong>装饰器模式的核心&lt;/strong>，分别用于&lt;strong>增强（继承了）InputStream&lt;/strong>和&lt;strong>OutputStream&lt;/strong>子类对象的功能
Filter （过滤的意思），中间（Closeable）下面这两条&lt;strong>虚线代表实现&lt;/strong>；最下面的&lt;strong>实线代表继承&lt;/strong>
&lt;img src="img/ly-20241212141951298.png" alt="ly-20241212141951298" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>其中BufferedInputStream（字节缓冲输入流）、DataInputStream等等都是FilterInputStream的子类，对应的BufferedOutputStream和DataOutputStream都是FilterOutputStream的子类&lt;/p>
&lt;/li>
&lt;li>
&lt;p>例子，使用BufferedInputStream（字节缓冲输入流）来增强FileInputStream功能&lt;/p>
&lt;ul>
&lt;li>
&lt;p>BufferedInputStream源码（构造函数）&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> DEFAULT_BUFFER_SIZE &lt;span style="color:#f92672">=&lt;/span> 8192;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">BufferedInputStream&lt;/span>(InputStream in) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>(in, DEFAULT_BUFFER_SIZE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">BufferedInputStream&lt;/span>(InputStream in, &lt;span style="color:#66d9ef">int&lt;/span> size) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">super&lt;/span>(in);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (size &lt;span style="color:#f92672">&amp;lt;=&lt;/span> 0) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> IllegalArgumentException(&lt;span style="color:#e6db74">&amp;#34;Buffer size &amp;lt;= 0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#66d9ef">byte&lt;/span>&lt;span style="color:#f92672">[&lt;/span>size&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>使用&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">try&lt;/span> (BufferedInputStream bis &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> BufferedInputStream(&lt;span style="color:#66d9ef">new&lt;/span> FileInputStream(&lt;span style="color:#e6db74">&amp;#34;input.txt&amp;#34;&lt;/span>))) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> content;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">long&lt;/span> skip &lt;span style="color:#f92672">=&lt;/span> bis.&lt;span style="color:#a6e22e">skip&lt;/span>(2);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> ((content &lt;span style="color:#f92672">=&lt;/span> bis.&lt;span style="color:#a6e22e">read&lt;/span>()) &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>1) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">print&lt;/span>((&lt;span style="color:#66d9ef">char&lt;/span>) content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;span style="color:#66d9ef">catch&lt;/span> (IOException e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> e.&lt;span style="color:#a6e22e">printStackTrace&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>ZipInputStream和ZipOutputStream还可以用来增强BufferedInputStream和BufferedOutputStream的能力&lt;/p></description></item><item><title>io基础</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/IO/ly0201lyio/</link><pubDate>Sun, 23 Oct 2022 12:21:12 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/IO/ly0201lyio/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>IO，即Input/Output，输入和输出，输入就是&lt;strong>数据输入到计算机内存&lt;/strong>；输出则是&lt;strong>输出到外部存储&lt;/strong>（如&lt;strong>数据库&lt;/strong>、&lt;strong>文件&lt;/strong>、&lt;strong>远程主机&lt;/strong>）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>根据数据处理方式，又分为&lt;strong>字节流&lt;/strong>和&lt;strong>字符流&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>基类&lt;/p>
&lt;ul>
&lt;li>字节输入流 &lt;strong>InputStream&lt;/strong>，字符输入流 &lt;strong>Reader&lt;/strong>&lt;/li>
&lt;li>字节输出流 &lt;strong>OutputStream&lt;/strong>, 字符输出流 &lt;strong>Writer&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="字节流">字节流&lt;a class="anchor" href="#%e5%ad%97%e8%8a%82%e6%b5%81">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>字节输入流 InputStream
InputStream用于从源头（通常是文件）&lt;strong>读取数据（字节信息）到内存&lt;/strong>中，java.io.InputStream抽象类是&lt;strong>所有字节输入流的父类&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>常用方法&lt;/p>
&lt;blockquote class='book-hint '>
&lt;ul>
&lt;li>&lt;code>read()&lt;/code> ：返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节，则代码返回 &lt;code>-1&lt;/code> ，表示文件结束。&lt;/li>
&lt;li>&lt;code>read(byte b[ ])&lt;/code> : 从输入流中读取一些字节存储到数组 &lt;code>b&lt;/code> 中。如果数组 &lt;code>b&lt;/code> 的长度为零，则不读取。如果没有可用字节读取，返回 &lt;code>-1&lt;/code>。如果有可用字节读取，则最多读取的字节数最多等于 &lt;code>b.length&lt;/code> ， 返回读取的字节数。这个方法等价于 &lt;code>read(b, 0, b.length)&lt;/code>。&lt;/li>
&lt;li>&lt;code>read(byte b[], int off, int len)&lt;/code> ：在&lt;code>read(byte b[ ])&lt;/code> 方法的基础上增加了 &lt;code>off&lt;/code> 参数（偏移量）和 &lt;code>len&lt;/code> 参数（要读取的最大字节数）。&lt;/li>
&lt;li>&lt;code>skip(long n)&lt;/code> ：忽略输入流中的 n 个字节 ,返回实际忽略的字节数。&lt;/li>
&lt;li>&lt;code>available()&lt;/code> ：返回输入流中可以读取的字节数。&lt;/li>
&lt;li>&lt;code>close()&lt;/code> ：关闭输入流释放相关的系统资源。&lt;/li>
&lt;/ul>&lt;/blockquote>&lt;/li>
&lt;li>
&lt;p>Java9 新增了多个实用方法&lt;/p></description></item><item><title>ConcurrentHashMap源码</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0105lysource-code-concurrenthashmap/</link><pubDate>Sat, 22 Oct 2022 18:26:52 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0105lysource-code-concurrenthashmap/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="总结">总结&lt;a class="anchor" href="#%e6%80%bb%e7%bb%93">#&lt;/a>&lt;/h2>
&lt;p>Java7 中 &lt;code>ConcurrentHashMap&lt;/code> 使用的&lt;strong>分段锁&lt;/strong>，也就是&lt;strong>每一个 Segment&lt;/strong> 上同时只有一个线程可以操作，&lt;strong>每一个 &lt;code>Segment&lt;/code>&lt;/strong> 都是一个类似 &lt;code>HashMap&lt;/code> 数组的结构，每一个HashMap&lt;strong>可以扩容&lt;/strong>，它的&lt;strong>冲突会转化为链表&lt;/strong>。但是 &lt;code>Segment&lt;/code> 的个数一但初始化就不能改变。&lt;/p>
&lt;p>Java8 中的 &lt;code>ConcurrentHashMap&lt;/code> 使用的 &lt;code>Synchronized&lt;/code> 锁加 CAS 的机制。结构也由 Java7 中的 &lt;strong>&lt;code>Segment&lt;/code> 数组 + &lt;code>HashEntry&lt;/code> 数组 + 链表&lt;/strong> 进化成了 &lt;strong>Node 数组 + 链表 / 红黑树&lt;/strong>，&lt;strong>Node 是类似于一个 HashEntry 的结构&lt;/strong>。它的冲突再达到一定大小时会转化成红黑树，在冲突小于一定数量时又退回链表。&lt;/p>
&lt;h2 id="源码-略过">源码 （略过）&lt;a class="anchor" href="#%e6%ba%90%e7%a0%81-%e7%95%a5%e8%bf%87">#&lt;/a>&lt;/h2>
&lt;h2 id="concurrenthashmap17">ConcurrentHashMap1.7&lt;a class="anchor" href="#concurrenthashmap17">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>存储结构
&lt;ul>
&lt;li>Segment数组（该数组用来加锁，每个数组元素是一个HashEntry数组（该数组可能包含链表）&lt;/li>
&lt;li>如图，ConcurrentHashMap由多个Segment组合，每一个Segment是一个类似HashMap的结构，每一个HashMap内部可以扩容，但是Segment个数初始化后不能改变，默认16个（即默认支持16个线程并发）
&lt;img src="img/ly-20241212141930985.png" alt="ly-20241212141930985" />&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="concurrenthashmap18">ConcurrentHashMap1.8&lt;a class="anchor" href="#concurrenthashmap18">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>存储结构
&lt;img src="img/ly-20241212141931187.png" alt="ly-20241212141931187" />
可以发现 Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大，不再是之前的 &lt;strong>Segment 数组 + HashEntry 数组 + 链表&lt;/strong>，而是 &lt;strong>Node 数组 + 链表 / 红黑树&lt;/strong>。当冲突链表达到一定长度时，链表会转换成红黑树。&lt;/p></description></item><item><title>HashMap源码</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0106lysource-code-hashmap/</link><pubDate>Fri, 21 Oct 2022 15:30:09 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0106lysource-code-hashmap/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="hashmap简介">HashMap简介&lt;a class="anchor" href="#hashmap%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>HashMap用来存放&lt;strong>键值对&lt;/strong>，基于哈希表的Map接口实现，是&lt;strong>非线程安全&lt;/strong>的&lt;/li>
&lt;li>可以存储null的key和value，但null作为键只能有一个&lt;/li>
&lt;li>JDK8之前，HashMap由&lt;strong>数组和链表&lt;/strong>组成，链表是为了解决哈希冲突而存在；JDK8之后，当链表大于阈值（默认8），则会选择&lt;strong>转为红黑树&lt;/strong>（当数组长度大于64则进行转换，否则只是扩容），以&lt;strong>减少搜索时间&lt;/strong>&lt;/li>
&lt;li>HashMap默认初始化大小为16，每次&lt;strong>扩容为原容量2倍&lt;/strong>，且总是使用2的幂作为哈希表的大小&lt;/li>
&lt;/ul>
&lt;h2 id="底层数据结构分析">底层数据结构分析&lt;a class="anchor" href="#%e5%ba%95%e5%b1%82%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%90">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>JDK8之前，HashMap底层是&lt;strong>数组和链表&lt;/strong>，即&lt;strong>链表散列&lt;/strong>；通过key的hashCode，经过扰动函数，获得hash值，然后再通过(n-1) &amp;amp; hash 判断当前元素存放位置（n指的是数组长度），如果当前位置存在元素，就判断元素与要存入的元素的hash值以及key是否相同，相同则覆盖，否则通过&lt;strong>拉链法&lt;/strong>解决&lt;br>
&lt;img src="img/ly-20241212141931350.png" alt="ly-20241212141931350" />&lt;/p>
&lt;ul>
&lt;li>
&lt;p>扰动函数，即hash(Object key)方法&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//JDK1.8 &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">hash&lt;/span>(Object key) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> h;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// key.hashCode()：返回散列值也就是hashcode&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// ^ ：按位异或&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// &amp;gt;&amp;gt;&amp;gt;:无符号右移，忽略符号位，空位都以0补齐&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> (key &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>) &lt;span style="color:#f92672">?&lt;/span> 0 : (h &lt;span style="color:#f92672">=&lt;/span> key.&lt;span style="color:#a6e22e">hashCode&lt;/span>()) &lt;span style="color:#f92672">^&lt;/span> (h &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 16);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>JDK1.7&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//JDK1.7 , 则扰动了4次，性能较差&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">hash&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> h) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// This function ensures that hashCodes that differ only by&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// constant multiples at each bit position have a bounded&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// number of collisions (approximately 8 at default load factor).&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> h &lt;span style="color:#f92672">^=&lt;/span> (h &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 20) &lt;span style="color:#f92672">^&lt;/span> (h &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 12);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> h &lt;span style="color:#f92672">^&lt;/span> (h &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 7) &lt;span style="color:#f92672">^&lt;/span> (h &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 4);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>JDK1.8之后，当链表长度大于阈值（默认为 8）时，会首先调用 &lt;code>treeifyBin()&lt;/code>方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有&lt;strong>当数组长度大于或者等于 64&lt;/strong> 的情况下，才会执行转换红黑树操作，以减少搜索时间。&lt;strong>否则，就是只是执行 &lt;code>resize()&lt;/code> 方法对数组扩容&lt;/strong>。相关源码这里就不贴了，重点关注 &lt;code>treeifyBin()&lt;/code>方法即可！&lt;/p></description></item><item><title>ArrayList源码</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0104lysource-code-ArrayList/</link><pubDate>Thu, 20 Oct 2022 17:01:47 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0104lysource-code-ArrayList/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>底层是数组队列，相当于&lt;strong>动态数组&lt;/strong>，能&lt;strong>动态增长&lt;/strong>，可以在添加大量元素前先使用&lt;strong>ensureCapacity&lt;/strong>来增加ArrayList容量，&lt;strong>减少递增式再分配的数量&lt;/strong>
源码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ArrayList&lt;/span>&lt;span style="color:#f92672">&amp;lt;&lt;/span>E&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">extends&lt;/span> AbstractList&lt;span style="color:#f92672">&amp;lt;&lt;/span>E&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">implements&lt;/span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>E&lt;span style="color:#f92672">&amp;gt;&lt;/span>, RandomAccess, Cloneable, java.&lt;span style="color:#a6e22e">io&lt;/span>.&lt;span style="color:#a6e22e">Serializable&lt;/span>{ }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>Random Access，标志接口，表明这个接口的List集合支持&lt;strong>快速随机访问&lt;/strong>，这里是指可&lt;strong>通过元素序号快速访问&lt;/strong>&lt;/li>
&lt;li>实现Cloneable接口，能&lt;strong>被克隆&lt;/strong>&lt;/li>
&lt;li>实现java.io.Serializable，&lt;strong>支持序列化&lt;/strong>&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>ArrayList和Vector区别&lt;/p>
&lt;ul>
&lt;li>ArrayList和Vector都是List的实现类，Vector出现的比较早，底层都是Object[] 存储&lt;/li>
&lt;li>ArrayList线程不安全（适合频繁查找，线程不安全 ）&lt;/li>
&lt;li>Vector 线程安全的&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>ArrayList与LinkedList区别&lt;/p>
&lt;ul>
&lt;li>
&lt;p>都是&lt;strong>不同步&lt;/strong>的，即不保证线程安全&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ArrayList底层为Object数组；LinkedList底层使用&lt;strong>双向链表数据结构&lt;/strong>(1.6之前为循环链表，1.7取消了循环)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>插入和删除是否受元素位置影响&lt;/p>
&lt;ul>
&lt;li>
&lt;p>ArrayList采用数组存储，所以插入和删除元素的时间复杂度受元素位置影响[ 默认增加到末尾，O(1) ; 在指定位置，则O(n) , 要往后移动]&lt;/p>
&lt;/li>
&lt;li>
&lt;p>LinkedList采用链表存储，所以对于add(E e)方法，还是O(1)；如果是在指定位置插入和删除，则为O(n) 因为需要遍历将指针移动到指定位置&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//LinkedList默认添加到最后&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">boolean&lt;/span> &lt;span style="color:#a6e22e">add&lt;/span>(E e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> linkLast(e);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>LinkedList&lt;strong>不支持高效随机元素访问&lt;/strong>，而ArrayList支持（通过get(int index))&lt;/p>
&lt;/li>
&lt;li>
&lt;p>内存空间占用
ArrayList的空间浪费主要体现在list列表的结尾会预留一定的容量空间，而LinkedList的空间花费在，每个元素都需要比ArrayList更多空间（要存放直接前驱和直接后继以及(当前)数据)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="3-扩容机制分析--jdk8-">3. 扩容机制分析 ( JDK8 )&lt;a class="anchor" href="#3-%e6%89%a9%e5%ae%b9%e6%9c%ba%e5%88%b6%e5%88%86%e6%9e%90--jdk8-">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>ArrayList的构造函数&lt;/p>
&lt;ul>
&lt;li>三种方式初始化，构造方法源码&lt;/li>
&lt;li>空参，指定大小，指定集合 （如果集合类型非Object[].class，则使用Arrays.copyOf转为Object[].class)&lt;/li>
&lt;li>以无参构造方式创建ArrayList时，实际上初始化赋值的是空数组；当真正操作时才分配容量，即添加第一个元素时扩容为10&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * 默认初始容量大小
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> DEFAULT_CAPACITY &lt;span style="color:#f92672">=&lt;/span> 10;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> Object&lt;span style="color:#f92672">[]&lt;/span> DEFAULTCAPACITY_EMPTY_ELEMENTDATA &lt;span style="color:#f92672">=&lt;/span> {};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> *默认构造函数，使用初始容量10构造一个空列表(无参数构造)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">ArrayList&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">elementData&lt;/span> &lt;span style="color:#f92672">=&lt;/span> DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * 带初始容量参数的构造函数。（用户自己指定容量）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">ArrayList&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> initialCapacity) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (initialCapacity &lt;span style="color:#f92672">&amp;gt;&lt;/span> 0) {&lt;span style="color:#75715e">//初始容量大于0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//创建initialCapacity大小的数组&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">elementData&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> Object&lt;span style="color:#f92672">[&lt;/span>initialCapacity&lt;span style="color:#f92672">]&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (initialCapacity &lt;span style="color:#f92672">==&lt;/span> 0) {&lt;span style="color:#75715e">//初始容量等于0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//创建空数组&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">elementData&lt;/span> &lt;span style="color:#f92672">=&lt;/span> EMPTY_ELEMENTDATA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {&lt;span style="color:#75715e">//初始容量小于0，抛出异常&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> IllegalArgumentException(&lt;span style="color:#e6db74">&amp;#34;Illegal Capacity: &amp;#34;&lt;/span>&lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> initialCapacity);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> *构造包含指定collection元素的列表，这些元素利用该集合的迭代器按顺序返回
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> *如果指定的集合为null，throws NullPointerException。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">ArrayList&lt;/span>(Collection&lt;span style="color:#f92672">&amp;lt;?&lt;/span> &lt;span style="color:#66d9ef">extends&lt;/span> E&lt;span style="color:#f92672">&amp;gt;&lt;/span> c) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> elementData &lt;span style="color:#f92672">=&lt;/span> c.&lt;span style="color:#a6e22e">toArray&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> ((size &lt;span style="color:#f92672">=&lt;/span> elementData.&lt;span style="color:#a6e22e">length&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> 0) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// c.toArray might (incorrectly) not return Object[] (see 6260652)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (elementData.&lt;span style="color:#a6e22e">getClass&lt;/span>() &lt;span style="color:#f92672">!=&lt;/span> Object&lt;span style="color:#f92672">[]&lt;/span>.&lt;span style="color:#a6e22e">class&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> elementData &lt;span style="color:#f92672">=&lt;/span> Arrays.&lt;span style="color:#a6e22e">copyOf&lt;/span>(elementData, size, Object&lt;span style="color:#f92672">[]&lt;/span>.&lt;span style="color:#a6e22e">class&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// replace with empty array.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">elementData&lt;/span> &lt;span style="color:#f92672">=&lt;/span> EMPTY_ELEMENTDATA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>以无参构造参数函数为例
&lt;strong>先看下面的 add()方法扩容&lt;/strong>&lt;/p></description></item><item><title>集合使用注意事项</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0103lycollections-precautions-for-use/</link><pubDate>Wed, 19 Oct 2022 17:26:07 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0103lycollections-precautions-for-use/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="集合判空">集合判空&lt;a class="anchor" href="#%e9%9b%86%e5%90%88%e5%88%a4%e7%a9%ba">#&lt;/a>&lt;/h2>
&lt;blockquote class='book-hint '>
&lt;p>//阿里巴巴开发手册&lt;/p>
&lt;p>&lt;strong>判断所有集合内部的元素是否为空，使用 &lt;code>isEmpty()&lt;/code> 方法，而不是 &lt;code>size()==0&lt;/code> 的方式。&lt;/strong>&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>
&lt;p>isEmpty()可读性更好，且&lt;strong>绝大部分情况下时间复杂度为O(1)&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有例外：ConcurrentHashMap的size()和isEmpty() 时间复杂度均不是O(1)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">size&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">long&lt;/span> n &lt;span style="color:#f92672">=&lt;/span> sumCount();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> ((n &lt;span style="color:#f92672">&amp;lt;&lt;/span> 0L) &lt;span style="color:#f92672">?&lt;/span> 0 :
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (n &lt;span style="color:#f92672">&amp;gt;&lt;/span> (&lt;span style="color:#66d9ef">long&lt;/span>)Integer.&lt;span style="color:#a6e22e">MAX_VALUE&lt;/span>) &lt;span style="color:#f92672">?&lt;/span> Integer.&lt;span style="color:#a6e22e">MAX_VALUE&lt;/span> :
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">int&lt;/span>)n);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> &lt;span style="color:#a6e22e">sumCount&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CounterCell&lt;span style="color:#f92672">[]&lt;/span> as &lt;span style="color:#f92672">=&lt;/span> counterCells; CounterCell a;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">long&lt;/span> sum &lt;span style="color:#f92672">=&lt;/span> baseCount;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (as &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> 0; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> as.&lt;span style="color:#a6e22e">length&lt;/span>; &lt;span style="color:#f92672">++&lt;/span>i) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> ((a &lt;span style="color:#f92672">=&lt;/span> as&lt;span style="color:#f92672">[&lt;/span>i&lt;span style="color:#f92672">]&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sum &lt;span style="color:#f92672">+=&lt;/span> a.&lt;span style="color:#a6e22e">value&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> sum;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">boolean&lt;/span> &lt;span style="color:#a6e22e">isEmpty&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> sumCount() &lt;span style="color:#f92672">&amp;lt;=&lt;/span> 0L; &lt;span style="color:#75715e">// ignore transient negative values&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h2 id="集合转map">集合转Map&lt;a class="anchor" href="#%e9%9b%86%e5%90%88%e8%bd%acmap">#&lt;/a>&lt;/h2>
&lt;blockquote class='book-hint '>
&lt;p>//阿里巴巴开发手册&lt;/p></description></item><item><title>集合_2</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0102lycollection_2/</link><pubDate>Tue, 18 Oct 2022 08:54:49 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0102lycollection_2/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="map">Map&lt;a class="anchor" href="#map">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>HashMap和Hashtable的区别&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>HashMap是非线程安全&lt;/strong>的，&lt;strong>Hashtable是线程安全&lt;/strong>的，因为Hashtable内部方法&lt;strong>都经过synchronized&lt;/strong>修饰（不过要保证线程安全&lt;strong>一般用ConcurrentHashMap&lt;/strong>）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>由于加了synchronized修饰，HashTable&lt;strong>效率&lt;/strong>没有HashMap高&lt;/p>
&lt;/li>
&lt;li>
&lt;p>HashMap&lt;strong>可以存储null的key和value&lt;/strong>，但&lt;strong>null作为键&lt;/strong>只能有一个**；&lt;strong>HashTable&lt;/strong>不允许有null键和null值**&lt;/p>
&lt;/li>
&lt;li>
&lt;p>初始容量及每次扩容&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hashtable默认初始大小11&lt;/strong>，之后&lt;strong>扩容为2n+1&lt;/strong>;HashMap&lt;strong>初始大小16&lt;/strong>，之后&lt;strong>扩容变为原来的2倍&lt;/strong>&lt;/li>
&lt;li>如果指定初始大小，HashTable直接使用初始大小&lt;br>
而&lt;code>HashMap&lt;/code> 会将其&lt;strong>扩充为 2 的幂次方&lt;/strong>大小（&lt;code>HashMap&lt;/code> 中的**&lt;code>tableSizeFor()&lt;/code>**方法保证，下面给出了源代码）。也就是说 &lt;code>HashMap&lt;/code> 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>底层数据结构&lt;/p>
&lt;ul>
&lt;li>JDK1.8之后&lt;strong>HashMap&lt;/strong>解决哈希冲突时，当&lt;strong>链表大于阈值（默认8）&lt;strong>时，将链表&lt;/strong>转为红黑树&lt;/strong>（转换&lt;strong>前&lt;/strong>判断，如果当前&lt;strong>数组长度小于64&lt;/strong>，则&lt;strong>先进行数组扩容&lt;/strong>，而不转成红黑树），以减少搜索时间。&lt;/li>
&lt;li>Hashtable&lt;strong>没有&lt;/strong>上面的机制&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">HashMap 中带有初始容量的构造函数：
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">*/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">HashMap&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> initialCapacity, &lt;span style="color:#66d9ef">float&lt;/span> loadFactor) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (initialCapacity &lt;span style="color:#f92672">&amp;lt;&lt;/span> 0)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> IllegalArgumentException(&lt;span style="color:#e6db74">&amp;#34;Illegal initial capacity: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> initialCapacity);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (initialCapacity &lt;span style="color:#f92672">&amp;gt;&lt;/span> MAXIMUM_CAPACITY)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> initialCapacity &lt;span style="color:#f92672">=&lt;/span> MAXIMUM_CAPACITY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (loadFactor &lt;span style="color:#f92672">&amp;lt;=&lt;/span> 0 &lt;span style="color:#f92672">||&lt;/span> Float.&lt;span style="color:#a6e22e">isNaN&lt;/span>(loadFactor))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> IllegalArgumentException(&lt;span style="color:#e6db74">&amp;#34;Illegal load factor: &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loadFactor);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">loadFactor&lt;/span> &lt;span style="color:#f92672">=&lt;/span> loadFactor;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">threshold&lt;/span> &lt;span style="color:#f92672">=&lt;/span> tableSizeFor(initialCapacity);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">HashMap&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> initialCapacity) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>(initialCapacity, DEFAULT_LOAD_FACTOR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*下面这个方法保证了 HashMap 总是使用 2 的幂作为哈希表的大小。*/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * Returns a power of two size for the given target capacity.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">tableSizeFor&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> cap) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> n &lt;span style="color:#f92672">=&lt;/span> cap &lt;span style="color:#f92672">-&lt;/span> 1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">|=&lt;/span> n &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">|=&lt;/span> n &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 2;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">|=&lt;/span> n &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 4;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">|=&lt;/span> n &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 8;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">|=&lt;/span> n &lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> 16;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> (n &lt;span style="color:#f92672">&amp;lt;&lt;/span> 0) &lt;span style="color:#f92672">?&lt;/span> 1 : (n &lt;span style="color:#f92672">&amp;gt;=&lt;/span> MAXIMUM_CAPACITY) &lt;span style="color:#f92672">?&lt;/span> MAXIMUM_CAPACITY : n &lt;span style="color:#f92672">+&lt;/span> 1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>HashMap和hashSet区别&lt;/p></description></item><item><title>集合_1</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0101lycollection_1/</link><pubDate>Mon, 17 Oct 2022 08:55:24 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Collection/ly0101lycollection_1/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="集合包括collection和mapcollection-存放单一元素map-存放键值对">集合包括&lt;code>Collection&lt;/code>和&lt;code>Map&lt;/code>，Collection 存放单一元素。Map 存放键值对&lt;a class="anchor" href="#%e9%9b%86%e5%90%88%e5%8c%85%e6%8b%accollection%e5%92%8cmapcollection-%e5%ad%98%e6%94%be%e5%8d%95%e4%b8%80%e5%85%83%e7%b4%a0map-%e5%ad%98%e6%94%be%e9%94%ae%e5%80%bc%e5%af%b9">#&lt;/a>&lt;/h2>
&lt;p>&lt;img src="img/ly-20241212141928129.png" alt="ly-20241212141928129" />&lt;/p>
&lt;h2 id="listsetqueuemap区别">List，Set，Queue，Map区别&lt;a class="anchor" href="#listsetqueuemap%e5%8c%ba%e5%88%ab">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;code>List&lt;/code>(对付顺序的好帮手): 存储的元素是有序的、可重复的。&lt;/li>
&lt;li>&lt;code>Set&lt;/code>(注重独一无二的性质): 存储的元素是无序的、不可重复的。&lt;/li>
&lt;li>&lt;code>Queue&lt;/code>(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序，存储的元素是有序的、可重复的。&lt;/li>
&lt;li>&lt;code>Map&lt;/code>(用 key 来搜索的专家): 使用键值对（key-value）存储，类似于数学上的函数 y=f(x)，&amp;ldquo;x&amp;rdquo; 代表 key，&amp;ldquo;y&amp;rdquo; 代表 value，key 是无序的、不可重复的，value 是无序的、可重复的，每个键最多映射到一个值。&lt;/li>
&lt;/ul>
&lt;h2 id="各种集合框架底层数据结构">各种集合框架&amp;ndash;底层数据结构&lt;a class="anchor" href="#%e5%90%84%e7%a7%8d%e9%9b%86%e5%90%88%e6%a1%86%e6%9e%b6%e5%ba%95%e5%b1%82%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>List
&lt;ul>
&lt;li>ArrayList、Vector &amp;mdash;-&amp;gt; Object[] 数组&lt;/li>
&lt;li>LinkedList 双向链表 (jdk 1.6 之前为循环链表, 1.7 取消了循环)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Set
&lt;ul>
&lt;li>HashSet （无序，唯一），且&lt;strong>基于HashMap&lt;/strong>&lt;/li>
&lt;li>LinkedHashSet 是HashSet的子类，基于&lt;strong>LinkedHashMap&lt;/strong>
(LinkedHashMap内部基于HashMap实现)&lt;/li>
&lt;li>TreeSet(有序，唯一) ：红黑树（自平衡的排序二叉树）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Queue (队列)
&lt;ul>
&lt;li>PriorityQueue：Object[] 数组来实现二叉堆&lt;/li>
&lt;li>ArrayQueue：Object[] 数组+ 双指针&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Map
&lt;ul>
&lt;li>
&lt;p>&lt;code>HashMap&lt;/code>： JDK1.8 之前 &lt;code>HashMap&lt;/code> 由&lt;strong>数组+链表&lt;/strong>组成的，数组是 &lt;code>HashMap&lt;/code> 的主体，链表则是主要为了解决哈希冲突而存在的（“拉链法”解决冲突）。JDK1.8 以后在解决哈希冲突时有了较大的变化，当&lt;strong>链表长度大于阈值&lt;/strong>（&lt;strong>默认为 8&lt;/strong>）（将&lt;strong>链表转换成红黑树前&lt;/strong>会判断，如果&lt;strong>当前数组的长度小于 64&lt;/strong>，那么会选择&lt;strong>先进行数组扩容&lt;/strong>，而不是转换为红黑树）时，将链表转化为红黑树，以减少搜索时间&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>LinkedHashMap&lt;/code>： &lt;code>LinkedHashMap&lt;/code> 继承自 &lt;code>HashMap&lt;/code>，所以它的底层仍然是基于拉链式散列结构 即由&lt;strong>数组和链表或红黑树&lt;/strong>组成。另外，&lt;code>LinkedHashMap&lt;/code> 在上面结构的基础上，增加了一条双向链表，使得上面的结构可以&lt;strong>保持键值对的插入顺序&lt;/strong>。同时通过对链表进行相应的操作，实现了&lt;strong>访问顺序&lt;/strong>相关逻辑。&lt;/p></description></item><item><title>语法糖</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0011lysyntactic_sugar/</link><pubDate>Wed, 12 Oct 2022 17:36:26 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0011lysyntactic_sugar/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;p>语法糖（Syntactic Sugar）也称&lt;strong>糖衣语法&lt;/strong>，指的是在计算机语言中添加的某种语法，这种语法对语言的功能并没有影响，但是更方便程序员使用，简而言之，让程序更加&lt;strong>简洁&lt;/strong>，有更高的&lt;strong>可读性&lt;/strong>&lt;/p>
&lt;h2 id="java中有哪些语法糖">Java中有哪些语法糖&lt;a class="anchor" href="#java%e4%b8%ad%e6%9c%89%e5%93%aa%e4%ba%9b%e8%af%ad%e6%b3%95%e7%b3%96">#&lt;/a>&lt;/h2>
&lt;p>Java&lt;strong>虚拟机并不支持&lt;/strong>这些语法糖，这些语法糖在&lt;strong>编译阶段就会被还原成简单的基础语法&lt;/strong>结构，这个过程就是解语法糖&lt;/p>
&lt;ul>
&lt;li>&lt;code>javac&lt;/code>命令可以将后缀为&lt;code>.java&lt;/code>的源文件编译为后缀名为&lt;code>.class&lt;/code>的&lt;strong>可以运行于Java虚拟机的字节码&lt;/strong>。其中，&lt;code>com.sun.tools.javac.main.JavaCompiler&lt;/code>的源码中，&lt;code>compile()&lt;/code>中有一个步骤就是调用&lt;code>desugar()&lt;/code>，这个方法就是负责&lt;strong>解语法糖&lt;/strong>的实现的&lt;/li>
&lt;li>Java中的语法糖，包括 &lt;strong>泛型&lt;/strong>、&lt;strong>变长参数&lt;/strong>、&lt;strong>条件编译&lt;/strong>、&lt;strong>自动拆装箱&lt;/strong>、&lt;strong>内部类&lt;/strong>等&lt;/li>
&lt;/ul>
&lt;h3 id="switch支持string与枚举">switch支持String与枚举&lt;a class="anchor" href="#switch%e6%94%af%e6%8c%81string%e4%b8%8e%e6%9e%9a%e4%b8%be">#&lt;/a>&lt;/h3>
&lt;p>switch本身原本只支持基本类型，如int、char&lt;br>
&lt;img src="img/ly-20241212141927344.png" alt="ly-20241212141927344" />&lt;/p>
&lt;p>int是比较数值，而char则是比较其ascii码，所以其实对于编译器来说，都是int类型(整型)，比如&lt;code>byte&lt;/code>。&lt;code>short&lt;/code>，&lt;code>char&lt;/code>(ackii 码是整型)以及&lt;code>int&lt;/code>。
&lt;img src="img/ly-20241212141927646.png" alt="ly-20241212141927646" />&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141927801.png" alt="ly-20241212141927801" />
而对于enum类型，&lt;br>
&lt;img src="img/ly-20241212141927960.png" alt="ly-20241212141927960" />&lt;/p>
&lt;p>对于switch中使用String，则：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">switchDemoString&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> String str &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;world&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">switch&lt;/span> (str) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#34;hello&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;hello&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#34;world&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;world&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//反编译之后&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">switchDemoString&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">switchDemoString&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String args&lt;span style="color:#f92672">[]&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> String str &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;world&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> String s;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">switch&lt;/span>((s &lt;span style="color:#f92672">=&lt;/span> str).&lt;span style="color:#a6e22e">hashCode&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> 99162322:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(s.&lt;span style="color:#a6e22e">equals&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;hello&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;hello&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> 113318802:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(s.&lt;span style="color:#a6e22e">equals&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;world&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;world&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>即switch判断是通过**equals()&lt;strong>和&lt;/strong>hashCode()**方法来实现的&lt;/p></description></item><item><title>java_spi</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0010lyjava_spi/</link><pubDate>Wed, 12 Oct 2022 10:12:52 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0010lyjava_spi/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="简介">简介&lt;a class="anchor" href="#%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h3>
&lt;p>为了实现在&lt;strong>模块装配&lt;/strong>的时候&lt;strong>不用再程序里面动态指明&lt;/strong>，这就需要一种&lt;strong>服务发现&lt;/strong>机制。JavaSPI就是提供了这样的一个机制：&lt;strong>为某个接口寻找服务实现&lt;/strong>的机制。有点类似IoC的思想，将装配的控制权交到了程序之外&lt;/p>
&lt;h3 id="spi介绍">SPI介绍&lt;a class="anchor" href="#spi%e4%bb%8b%e7%bb%8d">#&lt;/a>&lt;/h3>
&lt;p>SPI，ServiceProviderInterface
使用SPI：Spring框架、数据库加载驱动、日志接口、以及Dubbo的扩展实现&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141926732.png" alt="ly-20241212141926732" />&lt;/p>
&lt;p>感觉下面这个图不太对，被调用方应该
一般&lt;strong>模块之间&lt;/strong>都是&lt;strong>通过接口&lt;/strong>进行通讯，&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>当&lt;strong>实现方&lt;/strong>提供了&lt;strong>接口和实现&lt;/strong>，我们可以通过&lt;strong>调用实现方的接口&lt;/strong>从而拥有&lt;strong>实现方给我们提供的能力&lt;/strong>，这就是 API ，这种&lt;strong>接口和实现&lt;/strong>都是放在实现方的。&lt;/p>
&lt;p>当接口存在于&lt;strong>调用方&lt;/strong>这边时，就是 SPI ，由接口调用方确定接口规则，然后由不同的厂商去根据这个规则对这个接口进行实现，从而提供服务。[&lt;strong>可以理解成业务方，或者说使用方。它使用了这个接口，而且制定了接口规范，但是具体实现，由被调用方实现&lt;/strong>]&lt;/p>
&lt;p>我的理解：被调用方（提供接口的人），调用方（使用接口的人），但是其实这里只把调用方&amp;ndash;&amp;gt;使用接口的人 这个关系是对的。&lt;/p>
&lt;p>也就是说，正常情况下由被调用方自己提供接口和实现，即API。而现在，由调用方（这里的调用方其实可以理解成上面的被调用方），提供了接口还使用了接口，而由被调用方进行接口实现&lt;/p>&lt;/blockquote>&lt;h3 id="实战演示">实战演示&lt;a class="anchor" href="#%e5%ae%9e%e6%88%98%e6%bc%94%e7%a4%ba">#&lt;/a>&lt;/h3>
&lt;p>SLF4J只是一个日志门面（接口），但是SLF4J的具体实现可以有多种，如：Logback/Log4j/Log4j2等等&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141927020.png" alt="ly-20241212141927020" />&lt;/p>
&lt;h4 id="简易版本">简易版本&lt;a class="anchor" href="#%e7%ae%80%e6%98%93%e7%89%88%e6%9c%ac">#&lt;/a>&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>ServiceProviderInterface&lt;/p>
&lt;/li>
&lt;li>
&lt;p>目录结构&lt;/p>
&lt;pre tabindex="0">&lt;code>│ service-provider-interface.iml
│
├─.idea
│ │ .gitignore
│ │ misc.xml
│ │ modules.xml
│ └─ workspace.xml
│
└─src
 └─edu
 └─jiangxuan
 └─up
 └─spi
 Logger.java
 LoggerService.java
 Main.class&lt;/code>&lt;/pre>&lt;ul>
&lt;li>
&lt;p>Logger接口，即SPI 服务提供者接口，后面的服务提供者要针对这个接口进行实现&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> edu.jiangxuan.up.spi;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">interface&lt;/span> &lt;span style="color:#a6e22e">Logger&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">info&lt;/span>(String msg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">debug&lt;/span>(String msg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>LoggerService类，主要是为服务使用者（调用方）提供特定功能，这个类是实现JavaSPI机制的关键所在&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> edu.jiangxuan.up.spi;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> java.util.ArrayList;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> java.util.List;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> java.util.ServiceLoader;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">LoggerService&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> LoggerService SERVICE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> LoggerService();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> Logger logger;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>Logger&lt;span style="color:#f92672">&amp;gt;&lt;/span> loggerList;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#a6e22e">LoggerService&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ServiceLoader&lt;span style="color:#f92672">&amp;lt;&lt;/span>Logger&lt;span style="color:#f92672">&amp;gt;&lt;/span> loader &lt;span style="color:#f92672">=&lt;/span> ServiceLoader.&lt;span style="color:#a6e22e">load&lt;/span>(Logger.&lt;span style="color:#a6e22e">class&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> List&lt;span style="color:#f92672">&amp;lt;&lt;/span>Logger&lt;span style="color:#f92672">&amp;gt;&lt;/span> list &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> ArrayList&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (Logger log : loader) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list.&lt;span style="color:#a6e22e">add&lt;/span>(log);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// LoggerList 是所有 ServiceProvider&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loggerList &lt;span style="color:#f92672">=&lt;/span> list;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>list.&lt;span style="color:#a6e22e">isEmpty&lt;/span>()) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Logger 只取一个&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger &lt;span style="color:#f92672">=&lt;/span> list.&lt;span style="color:#a6e22e">get&lt;/span>(0);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//简单单例&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> LoggerService &lt;span style="color:#a6e22e">getService&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> SERVICE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">info&lt;/span>(String msg) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (logger &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;info 中没有发现 Logger 服务提供者&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.&lt;span style="color:#a6e22e">info&lt;/span>(msg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">debug&lt;/span>(String msg) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (loggerList.&lt;span style="color:#a6e22e">isEmpty&lt;/span>()) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;debug 中没有发现 Logger 服务提供者&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loggerList.&lt;span style="color:#a6e22e">forEach&lt;/span>(log &lt;span style="color:#f92672">-&amp;gt;&lt;/span> log.&lt;span style="color:#a6e22e">debug&lt;/span>(msg));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>Main类（服务使用者，调用方）&lt;/p></description></item><item><title>unsafe类</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0009lyunsafe_class/</link><pubDate>Mon, 10 Oct 2022 17:10:27 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0009lyunsafe_class/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;p>&lt;code>sun.misc.Unsafe&lt;/code>&lt;/p>
&lt;p>提供&lt;strong>执行低级别&lt;/strong>、&lt;strong>不安全操作&lt;/strong>的方法，如&lt;strong>直接访问系统内存资源&lt;/strong>、&lt;strong>自主管理内存资源&lt;/strong>等，效率快，但由于有了操作内存空间的能力，会&lt;strong>增加指针问题风险&lt;/strong>。且这些功能的实现依赖于本地方法，Java代码中只是声明方法头，具体实现规则交给本地代码
&lt;img src="img/ly-20241212141925562.png" alt="ly-20241212141925562" />&lt;/p>
&lt;h3 id="为什么要使用本地方法">为什么要使用本地方法&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e4%bd%bf%e7%94%a8%e6%9c%ac%e5%9c%b0%e6%96%b9%e6%b3%95">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>需要用到Java中不具备的&lt;strong>依赖于操作系统&lt;/strong>的特性，跨平台的同时要实现&lt;strong>对底层控制&lt;/strong>&lt;/li>
&lt;li>对于其他语言已经完成的现成功能，可以使用Java调用&lt;/li>
&lt;li>对&lt;strong>时间敏感/性能要求&lt;/strong>非常高，有必要使用更为底层的语言&lt;/li>
&lt;/ul>
&lt;p>对于同一本地方法，不同的操作系统可能通过不同的方式来实现的&lt;/p>
&lt;h3 id="unsafe创建">Unsafe创建&lt;a class="anchor" href="#unsafe%e5%88%9b%e5%bb%ba">#&lt;/a>&lt;/h3>
&lt;p>sun.misc.Unsafe部分源码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Unsafe&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 单例对象&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> Unsafe theUnsafe;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ......
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#a6e22e">Unsafe&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//Sensitive : 敏感的 英[ˈsensətɪv]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@CallerSensitive&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> Unsafe &lt;span style="color:#a6e22e">getUnsafe&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Class var0 &lt;span style="color:#f92672">=&lt;/span> Reflection.&lt;span style="color:#a6e22e">getCallerClass&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 仅在引导类加载器`BootstrapClassLoader`加载时才合法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(&lt;span style="color:#f92672">!&lt;/span>VM.&lt;span style="color:#a6e22e">isSystemDomainLoader&lt;/span>(var0.&lt;span style="color:#a6e22e">getClassLoader&lt;/span>())) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> SecurityException(&lt;span style="color:#e6db74">&amp;#34;Unsafe&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> theUnsafe;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>会先判断当前类是否由&lt;strong>Bootstrap classloader&lt;/strong>加载。即只有&lt;strong>启动类加载器加载的类&lt;/strong>才能够调用Unsafe类中的方法&lt;/p>
&lt;p>如何使用&lt;code>Unsafe&lt;/code>这个类&lt;/p>
&lt;ol>
&lt;li>
&lt;p>利用反射获得Unsafe类中已经实例化完成的单例对象&lt;code>theUnsafe&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> Unsafe &lt;span style="color:#a6e22e">reflectGetUnsafe&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Field field &lt;span style="color:#f92672">=&lt;/span> Unsafe.&lt;span style="color:#a6e22e">class&lt;/span>.&lt;span style="color:#a6e22e">getDeclaredField&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;theUnsafe&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> field.&lt;span style="color:#a6e22e">setAccessible&lt;/span>(&lt;span style="color:#66d9ef">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> (Unsafe) field.&lt;span style="color:#a6e22e">get&lt;/span>(&lt;span style="color:#66d9ef">null&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">catch&lt;/span> (Exception e) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> log.&lt;span style="color:#a6e22e">error&lt;/span>(e.&lt;span style="color:#a6e22e">getMessage&lt;/span>(), e);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>通过Java命令行命令&lt;code>-Xbootclasspath/a&lt;/code>把&lt;strong>调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中&lt;/strong>，使得&lt;strong>A被引导类加载器加载&lt;/strong>&lt;/p></description></item><item><title>big_decimal</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0008lybig_decimal/</link><pubDate>Mon, 10 Oct 2022 14:56:26 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0008lybig_decimal/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="精度的丢失">精度的丢失&lt;a class="anchor" href="#%e7%b2%be%e5%ba%a6%e7%9a%84%e4%b8%a2%e5%a4%b1">#&lt;/a>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> a &lt;span style="color:#f92672">=&lt;/span> 2.&lt;span style="color:#a6e22e">0f&lt;/span> &lt;span style="color:#f92672">-&lt;/span> 1.&lt;span style="color:#a6e22e">9f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> b &lt;span style="color:#f92672">=&lt;/span> 1.&lt;span style="color:#a6e22e">8f&lt;/span> &lt;span style="color:#f92672">-&lt;/span> 1.&lt;span style="color:#a6e22e">7f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a);&lt;span style="color:#75715e">// 0.100000024&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(b);&lt;span style="color:#75715e">// 0.099999905&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a &lt;span style="color:#f92672">==&lt;/span> b);&lt;span style="color:#f92672">//&lt;/span> &lt;span style="color:#66d9ef">false&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote class='book-hint '>
&lt;p>为什么会有精度丢失的风险&lt;/p>
&lt;p>这个和计算机保存浮点数的机制有很大关系。我们知道计算机是&lt;strong>二进制&lt;/strong>的，而且计算机在&lt;strong>表示一个数字时，宽度是有限&lt;/strong>的，无限循环的小数存储在计算机时，&lt;strong>只能被截断&lt;/strong>，所以就会导致&lt;strong>小数精度发生损失&lt;/strong>的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示&lt;/p>&lt;/blockquote>&lt;p>使用BigDecimal来定义浮点数的值，然后再进行浮点数的运算操作即可&lt;/p>
&lt;h3 id="bigdecimal常见方法">BigDecimal常见方法&lt;a class="anchor" href="#bigdecimal%e5%b8%b8%e8%a7%81%e6%96%b9%e6%b3%95">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>我们在使用 &lt;code>BigDecimal&lt;/code> 时，为了防止精度丢失，推荐使用它的&lt;code>BigDecimal(String val)&lt;/code>构造方法或者 &lt;code>BigDecimal.valueOf(double val)&lt;/code> 静态方法来创建对象&lt;/p>
&lt;/li>
&lt;li>
&lt;p>加减乘除&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>BigDecimal a &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> BigDecimal(&lt;span style="color:#e6db74">&amp;#34;1.0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>BigDecimal b &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> BigDecimal(&lt;span style="color:#e6db74">&amp;#34;0.9&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a.&lt;span style="color:#a6e22e">add&lt;/span>(b));&lt;span style="color:#75715e">// 1.9&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a.&lt;span style="color:#a6e22e">subtract&lt;/span>(b));&lt;span style="color:#75715e">// 0.1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a.&lt;span style="color:#a6e22e">multiply&lt;/span>(b));&lt;span style="color:#75715e">// 0.90&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a.&lt;span style="color:#a6e22e">divide&lt;/span>(b));&lt;span style="color:#75715e">// 无法除尽，抛出 ArithmeticException 异常&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(a.&lt;span style="color:#a6e22e">divide&lt;/span>(b, 2, RoundingMode.&lt;span style="color:#a6e22e">HALF_UP&lt;/span>));&lt;span style="color:#f92672">//&lt;/span> 1.&lt;span style="color:#a6e22e">11&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用divide方法的时候，尽量使用3个参数版本（roundingMode.oldMode)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>保留规则&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">enum&lt;/span> RoundingMode {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 2.5 -&amp;gt; 3 , 1.6 -&amp;gt; 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// -1.6 -&amp;gt; -2 , -2.5 -&amp;gt; -3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 UP(BigDecimal.&lt;span style="color:#a6e22e">ROUND_UP&lt;/span>), &lt;span style="color:#75715e">//数轴上靠近哪个取哪个&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 2.5 -&amp;gt; 2 , 1.6 -&amp;gt; 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// -1.6 -&amp;gt; -1 , -2.5 -&amp;gt; -2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 DOWN(BigDecimal.&lt;span style="color:#a6e22e">ROUND_DOWN&lt;/span>), &lt;span style="color:#75715e">//数轴上离哪个远取哪个&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 &lt;span style="color:#75715e">// 2.5 -&amp;gt; 3 , 1.6 -&amp;gt; 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// -1.6 -&amp;gt; -1 , -2.5 -&amp;gt; -2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 CEILING(BigDecimal.&lt;span style="color:#a6e22e">ROUND_CEILING&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 &lt;span style="color:#75715e">// 2.5 -&amp;gt; 2 , 1.6 -&amp;gt; 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// -1.6 -&amp;gt; -2 , -2.5 -&amp;gt; -3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 FLOOR(BigDecimal.&lt;span style="color:#a6e22e">ROUND_FLOOR&lt;/span>), &lt;span style="color:#75715e">////数轴上 正数：远离哪个取哪个 负数：靠近哪个取哪个&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 	&lt;span style="color:#75715e">// 2.5 -&amp;gt; 3 , 1.6 -&amp;gt; 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// -1.6 -&amp;gt; -2 , -2.5 -&amp;gt; -3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 HALF_UP(BigDecimal.&lt;span style="color:#a6e22e">ROUND_HALF_UP&lt;/span>),&lt;span style="color:#75715e">// 数轴上 正数：靠近哪个取哪个 负数：远离哪个取哪个&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//......&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>大小比较&lt;br>
使用compareTo&lt;/p></description></item><item><title>Java代理模式</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0007lyproxy_pattern/</link><pubDate>Mon, 10 Oct 2022 14:30:02 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0007lyproxy_pattern/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="代理模式">代理模式&lt;a class="anchor" href="#%e4%bb%a3%e7%90%86%e6%a8%a1%e5%bc%8f">#&lt;/a>&lt;/h3>
&lt;p>使用代理对象来代替对真实对象的访问，就可以在&lt;strong>不修改原目标对象的前提下提供额外的功能操作&lt;/strong>，&lt;strong>扩展目标对象的功能&lt;/strong>，即在目标对象的&lt;strong>某个方法执行前后可以增加一些自定义的操作&lt;/strong>&lt;/p>
&lt;h3 id="静态代理">静态代理&lt;a class="anchor" href="#%e9%9d%99%e6%80%81%e4%bb%a3%e7%90%86">#&lt;/a>&lt;/h3>
&lt;blockquote class='book-hint '>
&lt;p>&lt;strong>静态代理中，我们对目标对象的每个方法的增强都是手动完成的（*后面会具体演示代码*），非常不灵活（*比如接口一旦新增加方法，目标对象和代理对象都要进行修改*）且麻烦(*需要对每个目标类都单独写一个代理类*）。&lt;/strong> 实际应用场景非常非常少，日常开发几乎看不到使用静态代理的场景。&lt;/p>
&lt;p>上面我们是从实现和应用角度来说的静态代理，从 JVM 层面来说， &lt;strong>静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。&lt;/strong>&lt;/p>&lt;/blockquote>&lt;ol>
&lt;li>定义一个接口及其实现类；&lt;/li>
&lt;li>创建一个代理类同样实现这个接口&lt;/li>
&lt;li>将目标对象注入进代理类，然后在代理类的对应方法调用目标类中的对应方法。这样的话，我们就可以通过代理类屏蔽对目标对象的访问，并且可以在目标方法执行前后做一些自己想做的事情。&lt;/li>
&lt;/ol>
&lt;p>代码:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//定义发送短信的接口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">interface&lt;/span> &lt;span style="color:#a6e22e">SmsService&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> String &lt;span style="color:#a6e22e">send&lt;/span>(String message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//实现发送短信的接口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">SmsServiceImpl&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> SmsService {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">send&lt;/span>(String message) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;send message:&amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> message;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//创建代理类并同样实现发送短信的接口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">SmsProxy&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> SmsService {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> SmsService smsService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">SmsProxy&lt;/span>(SmsService smsService) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">smsService&lt;/span> &lt;span style="color:#f92672">=&lt;/span> smsService;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">@Override&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">send&lt;/span>(String message) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//调用方法之前，我们可以添加自己的操作&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;before method send()&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> smsService.&lt;span style="color:#a6e22e">send&lt;/span>(message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//调用方法之后，我们同样可以添加自己的操作&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;after method send()&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//实际使用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Main&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SmsService smsService &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> SmsServiceImpl();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SmsProxy smsProxy &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> SmsProxy(smsService);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> smsProxy.&lt;span style="color:#a6e22e">send&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;java&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//打印结果&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>before method &lt;span style="color:#a6e22e">send&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>send message:java
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>after method &lt;span style="color:#a6e22e">send&lt;/span>()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="动态代理">动态代理&lt;a class="anchor" href="#%e5%8a%a8%e6%80%81%e4%bb%a3%e7%90%86">#&lt;/a>&lt;/h3>
&lt;p>从JVM角度来说，动态代理是在&lt;strong>运行时动态生成类字节码&lt;/strong>，并&lt;strong>加载到JVM中的&lt;/strong>。 SpringAOP和RPC等框架都实现了动态代理&lt;/p></description></item><item><title>java-reflex</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0006lyreflex/</link><pubDate>Mon, 10 Oct 2022 11:27:04 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0006lyreflex/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="何为反射">何为反射&lt;a class="anchor" href="#%e4%bd%95%e4%b8%ba%e5%8f%8d%e5%b0%84">#&lt;/a>&lt;/h3>
&lt;p>赋予了我们在&lt;strong>运行时分析类&lt;/strong>以及&lt;strong>执行类中方法&lt;/strong>的能力；运行中&lt;strong>获取任意一个类的所有属性&lt;/strong>和&lt;strong>方法&lt;/strong>，以及&lt;strong>调用这些方法&lt;/strong>和&lt;strong>属性&lt;/strong>&lt;/p>
&lt;h3 id="应用场景">应用场景&lt;a class="anchor" href="#%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af">#&lt;/a>&lt;/h3>
&lt;p>Spring/Spring Boot 、MyBatis等框架都用了大量反射机制，以下为&lt;/p>
&lt;ul>
&lt;li>
&lt;p>JDK动态代理&lt;/p>
&lt;ul>
&lt;li>
&lt;p>接口及实现类&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">package&lt;/span> proxy;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">interface&lt;/span> &lt;span style="color:#a6e22e">Car&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//实现类&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">package&lt;/span> proxy;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">CarImpl&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> Car{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;car running&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>代理类 及main方法使用
&lt;code>[ˌɪnvəˈkeɪʃn] 祈祷&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> proxy;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> java.lang.reflect.InvocationHandler;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">import&lt;/span> java.lang.reflect.Method;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//JDK动态代理代理类 &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">CarHandler&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> InvocationHandler{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//真实类的对象&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Object car;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//构造方法赋值给真实的类&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#a6e22e">CarHandler&lt;/span>(Object obj){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">car&lt;/span> &lt;span style="color:#f92672">=&lt;/span> obj;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//代理类执行方法时，调用的是这个方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> Object &lt;span style="color:#a6e22e">invoke&lt;/span>(Object proxy, Method method, Object&lt;span style="color:#f92672">[]&lt;/span> args) &lt;span style="color:#66d9ef">throws&lt;/span> Throwable {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;before&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Object res &lt;span style="color:#f92672">=&lt;/span> method.&lt;span style="color:#a6e22e">invoke&lt;/span>(car, args);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;after&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> res;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//main方法使用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> proxy;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> java.lang.reflect.Proxy;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CarImpl carImpl &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> CarImpl();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CarHandler carHandler &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> CarHandler(carImpl);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Car proxy &lt;span style="color:#f92672">=&lt;/span> (Car)Proxy.&lt;span style="color:#a6e22e">newProxyInstance&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main.&lt;span style="color:#a6e22e">class&lt;/span>.&lt;span style="color:#a6e22e">getClassLoader&lt;/span>(), &lt;span style="color:#75715e">//第一个参数，获取ClassLoader&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> carImpl.&lt;span style="color:#a6e22e">getClass&lt;/span>().&lt;span style="color:#a6e22e">getInterfaces&lt;/span>(), &lt;span style="color:#75715e">//第二个参数，获取被代理类的接口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> carHandler);&lt;span style="color:#75715e">//第三个参数，一个InvocationHandler对象，表示的是当我这个动态代理对象在调用方法的时候，会关联到哪一个InvocationHandler对象上&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> proxy.&lt;span style="color:#a6e22e">run&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//输出&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>before
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>car running
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>after&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Cglib动态代理（没有实现接口的Car&lt;/p></description></item><item><title>Java序列化详解</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0005lyserialize/</link><pubDate>Mon, 10 Oct 2022 10:39:01 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0005lyserialize/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h3 id="什么是序列化什么是反序列化">什么是序列化？什么是反序列化&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%ba%8f%e5%88%97%e5%8c%96%e4%bb%80%e4%b9%88%e6%98%af%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96">#&lt;/a>&lt;/h3>
&lt;p>当需要&lt;strong>持久化Java对象&lt;/strong>，比如&lt;strong>将Java对象保存在文件&lt;/strong>中、或者&lt;strong>在网络中传输Java对象&lt;/strong>，这些场景都需要用到序列化&lt;/p>
&lt;p>即：&lt;/p>
&lt;ul>
&lt;li>序列化：将&lt;strong>数据结构/对象&lt;/strong>，&lt;strong>转换成二进制字节流&lt;/strong>&lt;/li>
&lt;li>反序列化：将在&lt;strong>序列化过程中所生成的二进制字节流&lt;/strong>，&lt;strong>转换成数据结构或者对象&lt;/strong>的过程&lt;/li>
&lt;/ul>
&lt;p>对于Java，序列化的是对象(Object)，也就是实例化后的类(Class)&lt;/p>
&lt;p>序列化的目的，是通过网络传输对象，或者说是将对象存储到文件系统、数据库、内存中，如图：
&lt;img src="img/ly-20241212141924954.png" alt="ly-20241212141924954" />&lt;/p>
&lt;h3 id="实际场景">实际场景&lt;a class="anchor" href="#%e5%ae%9e%e9%99%85%e5%9c%ba%e6%99%af">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>对象在&lt;strong>进行网络传输（比如远程方法调用 RPC 的时候）之前&lt;/strong>需要先被序列化，&lt;strong>接收到&lt;/strong>序列化的对象&lt;strong>之后&lt;/strong>需要再进行&lt;strong>反序列化&lt;/strong>；&lt;/li>
&lt;li>将对象&lt;strong>存储到文件中&lt;/strong>的时候需要进行序列化，将对象从文件中读取出来需要进行反序列化。&lt;/li>
&lt;li>将&lt;strong>对象存储到缓存数据库（如 Redis）时需要用到序列化&lt;/strong>，将对象&lt;strong>从缓存数据库中读取&lt;/strong>出来需要反序列化&lt;/li>
&lt;/ul>
&lt;h3 id="序列化协议对于tcpip-4层模型的哪一层">序列化协议对于TCP/IP 4层模型的哪一层&lt;a class="anchor" href="#%e5%ba%8f%e5%88%97%e5%8c%96%e5%8d%8f%e8%ae%ae%e5%af%b9%e4%ba%8etcpip-4%e5%b1%82%e6%a8%a1%e5%9e%8b%e7%9a%84%e5%93%aa%e4%b8%80%e5%b1%82">#&lt;/a>&lt;/h3>
&lt;p>4层包括，网络接口层，网络层，传输层，应用层
如下图所示：&lt;br>
&lt;img src="img/ly-20241212141925161.png" alt="ly-20241212141925161" />&lt;/p>
&lt;p>OSI七层协议模型中，表示层就是&lt;strong>对应用层的用户数据，进行处理转换成二进制流&lt;/strong>；反过来的话，就是&lt;strong>将二进制流转换成应用层的用户数据&lt;/strong>，即序列化和反序列化，&lt;br>
因为，OSI 七层协议模型中的&lt;strong>应用层、表示层和会话层&lt;/strong>对应的都是 &lt;strong>TCP/IP 四层模型&lt;/strong>中的&lt;strong>应用层&lt;/strong>，所以&lt;strong>序列化协议&lt;/strong>属于 &lt;strong>TCP/IP 协议应用层&lt;/strong>的一部分&lt;/p>
&lt;h3 id="常见序列化协议对比">常见序列化协议对比&lt;a class="anchor" href="#%e5%b8%b8%e8%a7%81%e5%ba%8f%e5%88%97%e5%8c%96%e5%8d%8f%e8%ae%ae%e5%af%b9%e6%af%94">#&lt;/a>&lt;/h3>
&lt;p>kryo 英音 [k&amp;rsquo;rɪəʊ] ，除了JDK自带的序列化，还有&lt;strong>hessian&lt;/strong>、&lt;strong>kryo&lt;/strong>、&lt;strong>protostuff&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>JDK自带的序列化，只需要实现java.io.Serializable接口即可&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@AllArgsConstructor&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@NoArgsConstructor&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Getter&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@Builder&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@ToString&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">RpcRequest&lt;/span> &lt;span style="color:#66d9ef">implements&lt;/span> Serializable {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">final&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> serialVersionUID &lt;span style="color:#f92672">=&lt;/span> 1905122041950251207L;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> String requestId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> String interfaceName;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> String methodName;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Object&lt;span style="color:#f92672">[]&lt;/span> parameters;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Class&lt;span style="color:#f92672">&amp;lt;?&amp;gt;[]&lt;/span> paramTypes;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> RpcMessageTypeEnum rpcMessageTypeEnum;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>serialVersionUID用于版本控制，会被写入二进制序列，反序列化如果发现和当前类不一致则会抛出InvalidClassException异常。一般不使用JDK自带序列化，1 不支持跨语言调用 2 性能差，序列化之后字节数组体积过大&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Kryo
由于变长存储特性并使用了字节码生成机制，拥有较高的运行速度和较小字节码体积，代码：&lt;/p></description></item><item><title>为什么Java中只有值传递</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0004lypassbyvalue/</link><pubDate>Sun, 09 Oct 2022 11:30:05 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0004lypassbyvalue/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;ul>
&lt;li>
&lt;p>形参&amp;amp;&amp;amp;实参&lt;/p>
&lt;ul>
&lt;li>
&lt;p>实参(&lt;strong>实际参数，Arguments&lt;/strong>)，用于传递给函数/方法的参数，必须有确定的值&lt;/p>
&lt;/li>
&lt;li>
&lt;p>形参(&lt;strong>形式参数，Parameters&lt;/strong>)，用于定义函数/方法，接收实参，不需要有确定的值&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>String hello &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Hello!&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// hello 为实参&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sayHello(hello);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// str 为形参&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">sayHello&lt;/span>(String str) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(str);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>值传递&amp;amp;&amp;amp;引用传递&lt;/p>
&lt;ul>
&lt;li>程序设计将实参传递给方法的方式分为两种，值传递：方法&lt;strong>接收实参值的拷&lt;/strong>贝，会&lt;strong>创建副本&lt;/strong>；引用传递：&lt;strong>方法接受的是实参所引用的对象在堆中的地址&lt;/strong>，不会创建副本，对形参的修改将影响到实参&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Java中只有值传递&lt;/strong>，原因：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>传递基本类型参数&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(String&lt;span style="color:#f92672">[]&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> num1 &lt;span style="color:#f92672">=&lt;/span> 10;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> num2 &lt;span style="color:#f92672">=&lt;/span> 20;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> swap(num1, num2);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;num1 = &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> num1);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;num2 = &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> num2);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">swap&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> b) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> temp &lt;span style="color:#f92672">=&lt;/span> a;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b &lt;span style="color:#f92672">=&lt;/span> temp;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;a = &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> a);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> System.&lt;span style="color:#a6e22e">out&lt;/span>.&lt;span style="color:#a6e22e">println&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;b = &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> b);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//输出&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>a &lt;span style="color:#f92672">=&lt;/span> 20
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>b &lt;span style="color:#f92672">=&lt;/span> 10
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>num1 &lt;span style="color:#f92672">=&lt;/span> 10
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>num2 &lt;span style="color:#f92672">=&lt;/span> 20&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>传递引用类型参数 1&lt;/p></description></item><item><title>javaGuide基础3</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0003lyjava_guide_basic_3/</link><pubDate>Sat, 08 Oct 2022 15:23:15 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0003lyjava_guide_basic_3/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="异常">异常&lt;a class="anchor" href="#%e5%bc%82%e5%b8%b8">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>unchecked exceptions (运行时异常)&lt;br>
checked exceptions (非运行时异常，编译异常）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java异常类层次结构图
&lt;img src="img/ly-20241212141923567.png" alt="ly-20241212141923567" />&lt;/p>
&lt;p>&lt;img src="img/ly-20241212141923848.png" alt="ly-20241212141923848" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Exception和Error有什么区别&lt;/p>
&lt;ul>
&lt;li>
&lt;p>除了RuntimeException及其子类以外，其他的Exception类及其子类都属于受检查异常&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Exception : 程序本身可以处理的异常（可通过catch捕获）&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Checked Exception ，受检查异常，必须处理(&lt;strong>catch 或者 throws ，否则编译器通过不了&lt;/strong>)
IOException，ClassNotFoundException，SQLException，FileNotFoundException&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Unchecked Exception ， 不受检查异常 ， 可以不处理&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>（算数异常，类型转换异常，不合法的线程状态异常，下标超出异常，空指针异常，参数类型异常，数字格式异常，不支持操作异常）
ArithmeticException，ClassCastException，IllegalThreadStateException，IndexOutOfBoundsException&lt;/p>
&lt;pre>&lt;code> NullPointerException，IllegalArgumentException，NumberFormatException，SecurityException，UnsupportedOperationException 


 ```illegal 英[ɪˈliːɡl] 非法的``` 
 ```Arithmetic 英[əˈrɪθmətɪk] 算术```
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>
&lt;p>Error： &lt;strong>程序无法处理&lt;/strong>的错误 ，不建议通过catch 捕获，已办错误发生时JVM会选择线程终止&lt;br>
OutOfMemoryError （堆，Java heap space），VirtualMachineError，StackOverFlowError，AssertionError （断言），IOError&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Throwable类常用方法&lt;/p>
&lt;ul>
&lt;li>String getMessage() //简要描述&lt;/li>
&lt;li>String toString() //详细&lt;/li>
&lt;li>String getLocalizedMessage() //本地化信息，如果子类(Throwable的子类)没有覆盖该方法，则与gtMessage() 结果一样&lt;/li>
&lt;li>void printStackTrace() //打印Throwable对象封装的异常信息&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>try-catch-finally如何使用
try后面必须要有catch或者finally；无论是否捕获异常，finally都会执行；当在 &lt;code>try&lt;/code> 块或 &lt;code>catch&lt;/code> 块中遇到 &lt;code>return&lt;/code> 语句时，&lt;code>finally&lt;/code> 语句块将在方法返回之前被执行。&lt;/p></description></item><item><title>javaGuide基础2</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0002lyjava_guide_basic_2/</link><pubDate>Thu, 29 Sep 2022 10:16:13 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0002lyjava_guide_basic_2/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="面向对象基础">面向对象基础&lt;a class="anchor" href="#%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>区别&lt;/p>
&lt;ul>
&lt;li>面向&lt;strong>过程&lt;/strong>把解决问题的&lt;strong>过程拆成一个个方法&lt;/strong>，通过一个个方法的执行解决问题。&lt;/li>
&lt;li>面向对象会&lt;strong>先抽象出对象&lt;/strong>，然后&lt;strong>用对象执行方法&lt;/strong>的方式解决问题。&lt;/li>
&lt;li>面向对象编程 &lt;strong>易维护&lt;/strong>、&lt;strong>易复用&lt;/strong>、&lt;strong>易扩展&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>对象实例与对象引用的不同&lt;br>
new 运算符，new 创建对象实例（&lt;strong>对象实例在堆&lt;/strong>内存中），对象引用指向对象实例（&lt;strong>对象引用存放在栈&lt;/strong>内存中）。&lt;/p>
&lt;p>一个对象引用可以指向 0 个或 1 个对象（一根绳子可以不系气球，也可以系一个气球）;一个对象可以有 n 个引用指向它（可以用 n 条绳子系住一个气球）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对象的相等一般比较的是&lt;strong>内存中存放的内容&lt;/strong>是否相等；引用相等一般比较的是他们指向的&lt;strong>内存地址是否相等&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果一个类没有声明构造方法，该程序能正确执行吗?
如果我们自己添加了类的构造方法（无论是否有参），Java 就不会再添加默认的无参数的构造方法了&lt;/p>
&lt;ul>
&lt;li>构造方法特点：名字与类名相同；没有返回值但不能用void声明构造函数；生成类的对象时自动执行&lt;/li>
&lt;li>构造方法&lt;strong>不能重写&lt;/strong>(override)，但&lt;strong>能重载&lt;/strong> (overload)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>面向对象三大特征&lt;/p>
&lt;ul>
&lt;li>
&lt;p>封装&lt;br>
把一个对象的状态信息(属性)&lt;strong>隐藏在对象内部&lt;/strong>，&lt;strong>不允许直接访问&lt;/strong>，但提供可以被外界访问的方法来操作属性&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Student&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> id;&lt;span style="color:#75715e">//id属性私有化&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> String name;&lt;span style="color:#75715e">//name属性私有化&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//获取id的方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">getId&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//设置id的方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">setId&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> id) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">id&lt;/span> &lt;span style="color:#f92672">=&lt;/span> id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//获取name的方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> String &lt;span style="color:#a6e22e">getName&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//设置name的方法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">setName&lt;/span>(String name) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">name&lt;/span> &lt;span style="color:#f92672">=&lt;/span> name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>继承&lt;br>
不通类型的对象，相互之间有一定数量的共同点，同时每个对象定义了额外的特性使得他们与众不同。继承是&lt;strong>使用已存在的类的定义作为基础&lt;/strong>建立新类的技术&lt;/p></description></item><item><title>javaGuide基础1</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0001lyjava_guide_basic_1/</link><pubDate>Wed, 28 Sep 2022 10:51:00 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Review/java_guide/java/Basic/ly0001lyjava_guide_basic_1/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>转载自https://github.com/Snailclimb/JavaGuide （添加小部分笔记）感谢作者!&lt;/p>&lt;/blockquote>&lt;h2 id="基础概念及常识">基础概念及常识&lt;a class="anchor" href="#%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5%e5%8f%8a%e5%b8%b8%e8%af%86">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>Java语言特点&lt;/p>
&lt;ul>
&lt;li>面向对象（封装、继承、多态）&lt;/li>
&lt;li>平台无关性（Java虚拟机）&lt;/li>
&lt;li>等等&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>JVM并非只有一种，只要满足JVM规范，可以开发自己专属JVM&lt;/p>
&lt;/li>
&lt;li>
&lt;p>JDK与JRE&lt;/p>
&lt;ul>
&lt;li>JDK，JavaDevelopmentKit，包含JRE，还有编译器（javac）和工具（如javadoc、jdb）。能够创建和编译程序&lt;/li>
&lt;li>JRE，Java运行时环境，包括Java虚拟机、Java类库，及Java命令等。但是不能创建新程序&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>字节码，采用字节码的好处&lt;/p>
&lt;ul>
&lt;li>Java中，JVM可以理解的代码称为字节码（.class文件)，不面向任何处理器，只面向虚拟机&lt;/li>
&lt;li>Java程序从源代码到运行的过程
&lt;img src="img/ly-20241212141922068.png" alt="ly-20241212141922068" />
&lt;ul>
&lt;li>java代码必须先编译为字节码，之后呢，.class&amp;ndash;&amp;gt;机器码，这里JVM类加载器先加载字节码文件，然后通过解释器进行解释执行（也就是字节码需要由Java解释器来解释执行）&lt;/li>
&lt;li>Java解释器是JVM的一部分&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>编译与解释并存&lt;/p>
&lt;ul>
&lt;li>编译型：通过编译器将源代码一次性翻译成可被该平台执行的机器码，执行快、开发效率低&lt;/li>
&lt;li>解释型：通过解释器一句一句的将代码解释成机器代码后执行，执行慢，开发效率高&lt;/li>
&lt;li>如图
&lt;img src="img/ly-20241212141922280.png" alt="ly-20241212141922280" />&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>为什么说 Java 语言“编译与解释并存”？&lt;/strong>&lt;/p>
&lt;p>这是因为 Java 语言既具有编译型语言的特征，也具有解释型语言的特征。因为 Java 程序要经过&lt;strong>先编译，后解释&lt;/strong>两个步骤，由 Java 编写的程序需要先经过编译步骤，生成字节码（&lt;code>.class&lt;/code> 文件），&lt;strong>这种字节码必须由 Java 解释器来解释执行&lt;/strong>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java与C++区别&lt;/p>
&lt;ul>
&lt;li>没学过C++，Java不提供指针直接访问内存&lt;/li>
&lt;li>Java为单继承；但是Java支持继承多接口&lt;/li>
&lt;li>Java有自动内存管理垃圾回收机制（GC），不需要程序员手动释放无用内存&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>注释分为 单行注释、多行注释、文档注释
&lt;img src="img/ly-20241212141922440.png" alt="ly-20241212141922440" />&lt;/p>
&lt;/li>
&lt;li>
&lt;p>标识符与关键字
标识符即名字，关键字则是被赋予特殊含义的标识符&lt;/p>
&lt;/li>
&lt;li>
&lt;p>自增自减运算符
当 &lt;code>b = ++a&lt;/code> 时，先自增（自己增加 1），再赋值（赋值给 b）；当 &lt;code>b = a++&lt;/code> 时，先赋值(赋值给 b)，再自增（自己增加 1）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>continue/break/return&lt;/p>
&lt;ul>
&lt;li>&lt;code>continue&lt;/code> ：指跳出当前的这一次循环，继续下一次循环。&lt;/li>
&lt;li>&lt;code>break&lt;/code> ：指跳出整个循环体，继续执行循环下面的语句。&lt;/li>
&lt;li>&lt;code>return&lt;/code> 用于跳出所在方法，结束该方法的运行。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>变量&lt;/p>
&lt;ul>
&lt;li>成员变量和局部变量
&lt;ul>
&lt;li>成员变量可以被 &lt;code>public&lt;/code>,&lt;code>private&lt;/code>,&lt;code>static&lt;/code> 等修饰符所修饰，而局部变量不能被访问控制修饰符及 &lt;code>static&lt;/code> 所修饰；但是，成员变量和局部变量都能被 &lt;code>final&lt;/code> 所修饰&lt;/li>
&lt;li>从变量在内存中的存储方式来看,如果成员变量是使用 &lt;code>static&lt;/code> 修饰的，那么这个成员变量是属于类的，如果没有使用 &lt;code>static&lt;/code> 修饰，这个成员变量是属于实例的。而对象存在于堆内存，局部变量则存在于栈内存。&lt;/li>
&lt;li>从变量在内存中的生存时间上看，成员变量是对象的一部分，它随着对象的创建而存在，而局部变量随着方法的调用而自动生成，随着方法的调用结束而消亡（即方法栈弹出后消亡）。&lt;/li>
&lt;li>final必须显示赋初始值，其他都自动以类型默认值赋值&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>静态变量：被类所有实例共享&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>字符型常量与字符串常量区别&lt;/p></description></item></channel></rss>